-
Notifications
You must be signed in to change notification settings - Fork 68
/
Copy pathSpExecutableLayout.class.st
363 lines (276 loc) · 8.67 KB
/
SpExecutableLayout.class.st
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
"
I'm the common root of layout.
My subclasses are responsible to define the logic and the API developer will use.
Since the layouts require different specifications most of my code is more internal than to be used directly.
From my methods, the ones that may interested Spec application developers are:
- `add:withConstraints:` to add a presenter as subcomponents
- `remove` to remove an existing presenter from a layout.
Note that adding and removing presenters are raising corresponding announcement `SpChildrenAdded` and `SpChildrenRemoved`.
"
Class {
#name : 'SpExecutableLayout',
#superclass : 'Object',
#instVars : [
'children',
'selector',
'adapter',
'announcer'
],
#category : 'Spec2-Layout-Base',
#package : 'Spec2-Layout',
#tag : 'Base'
}
{ #category : 'documentation' }
SpExecutableLayout class >> addDocumentSection: aBuilder label: label methods: methods [
methods ifEmpty: [ ^ self ].
aBuilder newLine.
aBuilder header: [ :builder | builder text: label ] withLevel: 2.
aBuilder unorderedListDuring: [
(methods sorted: #selector ascending) do: [ :each |
aBuilder item: [
aBuilder monospace: (each methodClass name, '>>#', each selector) ] ] ]
]
{ #category : 'documentation' }
SpExecutableLayout class >> addDocumentSectionHierarchy: aBuilder [
aBuilder newLine.
aBuilder
header: [ :builder | builder text: 'Hierarchy' ]
withLevel: 2.
SpDocumentHierarchyBuilder new
fromClass: Object;
builder: aBuilder;
filter: [ :eachClass | eachClass package name beginsWith: 'Spec2-' ];
buildFor: self
]
{ #category : 'documentation' }
SpExecutableLayout class >> documentSections [
^ OrderedDictionary newFromPairs: {
'Examples'.
(self class methods select: [ :method | method protocolName = '*Spec2-Examples' ]).
'Adding Methods'.
(self methods select: [ :method | method protocolName = #'api - adding' ]).
'API Methods'.
(self methods select: [ :method | method protocolName = #api ]) }
]
{ #category : 'accessing' }
SpExecutableLayout >> adapter [
^ adapter
]
{ #category : 'private' }
SpExecutableLayout >> adapterName [
^ self subclassResponsibility
]
{ #category : 'accessing' }
SpExecutableLayout >> add: aName withConstraints: aBlock [
| constraints |
constraints := self constraintsClass new.
aBlock value: constraints.
children at: aName put: constraints
]
{ #category : 'accessing' }
SpExecutableLayout >> allPresenters [
"answer all presenters in the layout hierarchy"
^ self presenters
inject: #()
into: [ :all :each |
all, (each isSpLayout
ifTrue: [ each allPresenters ]
ifFalse: [ { each } ]) ]
]
{ #category : 'announcing' }
SpExecutableLayout >> announce: anAnnouncement [
announcer ifNil: [ ^ self ].
announcer announce: anAnnouncement
]
{ #category : 'private' }
SpExecutableLayout >> announceChildAdded: aChildPresenter [
self presenter ifNotNil: [
self announcer announce: (SpChildrenAdded new
parent: self presenter;
child: aChildPresenter;
yourself) ]
]
{ #category : 'private' }
SpExecutableLayout >> announceChildRemoved: aChildPresenter [
self presenter ifNotNil: [
self announcer announce: (SpChildrenRemoved new
parent: self presenter;
child: aChildPresenter;
yourself)].
]
{ #category : 'announcing' }
SpExecutableLayout >> announcer [
^ announcer ifNil: [ announcer := Announcer new ]
]
{ #category : 'converting' }
SpExecutableLayout >> asArray [
self error: 'Should not arrive here. This layout is executable then it will not be interpreted.'
]
{ #category : 'converting' }
SpExecutableLayout >> asSpLayout [
^ self
]
{ #category : 'building' }
SpExecutableLayout >> buildAdapterFor: aPresenter bindings: bindings [
| oldAdapter |
oldAdapter := adapter.
adapter := (aPresenter needRebuild or: [ aPresenter adapter isNil ])
ifTrue: [
(bindings adapterClass: self adapterName) adapt: aPresenter ]
ifFalse: [
aPresenter adapter
removeSubWidgets;
yourself ].
"Ensure we remove the flag `no need of rebuild` if it was set."
aPresenter needRebuild: true.
adapter layout: self.
children keysAndValuesDo: [ :presenterNameOrLayout :constraints |
"Since a layout can be composed (by other layouts), we can include a
presenter or a layout"
adapter
add: (self
resolvePresenter: presenterNameOrLayout
presenter: aPresenter
bindings: bindings)
constraints: constraints ].
adapter adapterWasBuilt.
oldAdapter ifNotNil: [ oldAdapter unsubscribe ].
^ adapter
]
{ #category : 'accessing' }
SpExecutableLayout >> childLayouts [
^ children keys select: [ :child |
child isSpLayout ]
]
{ #category : 'accessing' }
SpExecutableLayout >> children [
^ children keys
]
{ #category : 'private' }
SpExecutableLayout >> constraintsClass [
^ self subclassResponsibility
]
{ #category : 'accessing' }
SpExecutableLayout >> constraintsFor: aPresenter [
^ children at: aPresenter
]
{ #category : 'initialization' }
SpExecutableLayout >> initialize [
super initialize.
children := OrderedDictionary new
]
{ #category : 'testing' }
SpExecutableLayout >> isEmpty [
^ children isEmpty
]
{ #category : 'testing' }
SpExecutableLayout >> isExecutable [
^ true
]
{ #category : 'testing' }
SpExecutableLayout >> isSpLayout [
^ true
]
{ #category : 'accessing' }
SpExecutableLayout >> parentLayoutOf: aKey [
"Find a child layout that contains a presenter of given key. Useful if you need to find
a parent layout of a presenter that you need to replace."
(self children anySatisfy: [ :each | each = aKey ])
ifTrue: [ ^ self ].
self children do: [ :each |
each isSpLayout ifTrue: [
| found |
found := each parentLayoutOf: aKey.
found ifNotNil: [ ^ found ] ] ].
^ nil
]
{ #category : 'accessing' }
SpExecutableLayout >> presenter [
^ adapter ifNotNil: [ adapter presenter ]
]
{ #category : 'accessing' }
SpExecutableLayout >> presenters [
^ children keys
]
{ #category : 'removing' }
SpExecutableLayout >> remove: aPresenter [
self
remove: aPresenter
ifAbsent: [ self removeBySymbol: aPresenter ].
self announceChildRemoved: aPresenter
]
{ #category : 'private' }
SpExecutableLayout >> remove: aPresenter ifAbsent: aBlock [
^ (children includesKey: aPresenter)
ifTrue: [ children removeKey: aPresenter ]
ifFalse: [ aBlock value ]
]
{ #category : 'private' }
SpExecutableLayout >> removeBySymbol: aPresenter [
"I will look for the symbol that represents the presenter (in case of layouts defined by symbolic
representation of presenter) and then I will remove it"
children keys do: [ :each |
each isPresenter ifFalse: [
(adapter presenter presenterAt: each) = aPresenter ifTrue: [
self remove: each ifAbsent: [ NotFound signal: 'Presenter not found.' ] ] ] ]
]
{ #category : 'private' }
SpExecutableLayout >> resolvePresenter: aPresenterOrLayout presenter: aPresenter bindings: bindings [
"most common case: I receive a symbol that I need to convert into a presenter"
aPresenterOrLayout isSymbol ifTrue: [
^ (self subpresenterOrLayoutNamed: aPresenterOrLayout of: aPresenter)
ifNil: [ self error: 'You presenter named "', aPresenterOrLayout , '" from ', aPresenter printString ,' was not initialized.' ] ].
"I receive a layout: dig inside."
aPresenterOrLayout isSpLayout ifTrue: [
^ aPresenterOrLayout
buildAdapterFor: aPresenter
bindings: bindings ].
"I receive an arbitrary object (needs to understand #asPresenter)"
^ aPresenterOrLayout asPresenter
owner: aPresenter;
yourself
]
{ #category : 'accessing' }
SpExecutableLayout >> selector [
^ selector
]
{ #category : 'accessing' }
SpExecutableLayout >> selector: aSelector [
selector := aSelector
]
{ #category : 'private' }
SpExecutableLayout >> subpresenterOrLayoutNamed: presenterNameOrLayout of: aPresenter [
^ aPresenter additionalSubpresentersMap
at: presenterNameOrLayout
ifAbsent: [
aPresenter
presenterAt: presenterNameOrLayout
ifAbsent: [
self error: 'No presenter named "' , presenterNameOrLayout , '" was found in ' , aPresenter printString ] ]
]
{ #category : 'releasing' }
SpExecutableLayout >> unsubscribe [
self announcer unsubscribe: adapter.
self childLayouts do: [ :childLayout |
childLayout adapter unsubscribe.
childLayout unsubscribe ]
]
{ #category : 'events' }
SpExecutableLayout >> whenChildrenAddedDo: aBlock [
self announcer
when: SpChildrenAdded
do: aBlock
for: aBlock receiver
]
{ #category : 'events' }
SpExecutableLayout >> whenChildrenRemovedDo: aBlock [
self announcer
when: SpChildrenRemoved
do: aBlock
for: aBlock receiver
]
{ #category : 'private' }
SpExecutableLayout >> withAdapterDo: aValuable [
"a convenience method to avoid verify by nil all the time"
^ adapter ifNotNil: aValuable
]