-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathSpecReusability.pier
392 lines (287 loc) · 12.3 KB
/
SpecReusability.pier
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
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
!! Defining and Reusing UIs with ''Spec''
@sec_reuse_spec
This section introduces an example of how to define and reuse ''Spec'' user interfaces. The example UI that is built will serve to browse the public API of all the basic widgets offered by ''Spec''.
This API is further documented in *sec_where_to_find_what_I_want*.
In this section we do not detail the different parts of ''Spec'' yet, for a more in-depth discussion on the heart of ''Spec'' we refer to *sec_heart_of_spec*.
The example is structured in four parts.
First, a list UI named ""ModelList"" that is dedicated to render the subclasses of the ''AbstractWidgetModel'' class is created.
Second, a UI composed of a list and a label is defined and named ""ProtocolList"".
Third, a protocol viewer is defined by combining a ""ModelList"" with two ""ProtocolList"" to browse the ''protocol'' and ''protocol-events'' methods.
Finally a protocol browser is made by reusing a protocol viewer and adding a text zone.
!!! The ModelList
Creating a specific UI always starts with the subclassing of ""ComposableModel"".
Each sub widget is stored into an instance variable of the newly created class.
The snippet *ex_model_list* shows the definition of this ModelList class.
[[[label=ex_model_list|caption=ModelList definition|language=Smalltalk
ComposableModel subclass: #ModelList
instanceVariableNames: 'list'
classVariableNames: ''
category: 'Spec-Examples'
]]]
The first required step then is to instantiate and define the sub widgets.
This step is done in the method ==initializeWidgets== as shown in the code *ex_modelList_initializeWidgets*. It creates the list and populates it with the required classes, in alphabetical order.
More details on the use of the ==initializeWidgets== method are given in *subsec_initializeWidgets*.
[[[label=ex_modelList_initializeWidgets|caption=Implementation of ModelList>>#initializeWidgets|language=Smalltalk
initializeWidgets
list := self newList.
list items: (AbstractWidgetModel allSubclasses
sorted: [:a :b | a name < b name ]).
self focusOrder add: list
]]]
The second required step is to define a layout, which is done on the class side.
Since there is here only one sub widget, the layout is quite simple, as shown in the code in *ex_modelList_layout*.
It simply returns a layout that contains only the list.
More details on the use of this method are given in *subsec_layout*.
[[[label=ex_modelList_layout|caption=ModelList layout|language=Smalltalk
ModelList class>>#defaultSpec
<spec: #default>
^ SpecLayout composed
add: #list;
yourself
]]]
The three last methods to define on ModelList are a getter, a method to display the UI title and a method to register to list selection changes.
The code *ex_modelList_others* shows the implementation of these three methods and their protocols.
[[[label=ex_modelList_others|caption=ModelList other methods|language=Smalltalk
"accessing"
list
^ list
"protocol"
title
^ 'Widgets'
"protocol-events"
whenSelectedItemChanged: aBlock
list whenSelectedItemChanged: aBlock
]]]
The first UI is now done.
The result can be seen by executing the following snippet of code: ==ModelList new openWithSpec==.
!!! The ProtocolList
The next user interface is the protocol list.
This UI combines two sub widgets: a list and a label.
The class definition is similar to the code above, as can be seen in *ex_protocolList_definition*.
[[[label=ex_protocolList_definition|caption=ProtocolList definition|language=Smalltalk
ComposableModel subclass: #ProtocolList
instanceVariableNames: 'label protocols'
classVariableNames: ''
category: 'Spec-Examples'
]]]
The ==initializeWidgets== method for this UI is quite similar to the method in ModelList, as the code in *ex_protocolList_init* shows.
[[[label=ex_protocolList_init|caption=ProtocolList implementation of initializeWidgets|language=Smalltalk
initializeWidgets
protocols := self newList.
label := self newLabel.
label text: 'Protocol'.
protocols displayBlock: [ :m | m selector ].
self focusOrder add: protocols
]]]
The layout method is quite different though.
Now the sub widgets need to be placed more specifically than in the previous example.
The code *ex_protocolList_layout* shows how to build a column with the label on top and the list taking all the space that is left.
[[[label=ex_protocolList_layout|caption=ProtocolList layout|language=Smalltalk
defaultSpec
<spec: #default>
^ SpecLayout composed
newColumn: [ :column |
column
add: #label
height: self toolbarHeight;
add: #protocols ];
yourself
]]]
The remaining methods are getters, sub widget delegation methods, a method to display the title, and a method to register to list selection changes.
The code *ex_protocolList_others* shows the implementations of these methods as well as their protocol.
[[[label=ex_protocolList_others|caption=ProtocolList other methods|language=Smalltalk
"accessing"
label
^ label
"accessing"
protocols
^ protocols
"protocol"
items: aCollection
protocols items: aCollection
"protocol"
label: aText
label text: aText
"protocol"
resetSelection
protocols resetSelection
"protocol"
title
^ 'Protocol widget'
"protocol-events"
whenSelectedItemChanged: aBlock
protocols whenSelectedItemChanged: aBlock
]]]
The ""ProtocolList"" UI can be seen by evaluating ==ProtocolList new openWithSpec==.
!!! The ProtocolViewer
The third user interface is a composition of the two previous user interfaces.
It is composed of a ""ModelList"" and two ""ProtocolList"".
When a model class is selected, the methods in the protocol ''protocol'' and in the protocol ''protocol-events'' are listed.
The class has now three instance variables: ==models== to store the ""ModelList"", ==protocols== to store the ""ProtocolList"" for the protocol ''protocol'', and ==events== to store the ""ProtocolList"" for protocol ''protocol-events''.
The code in *ex_viewer_definition* shows the definition of the class ""ProtocolViewer"".
[[[label=ex_viewer_definition|caption=ProtocolViewer definition|language=Smalltalk
ComposableModel subclass: #ProtocolViewer
instanceVariableNames: 'models protocols events'
classVariableNames: ''
category: 'Spec-Examples'
]]]
The ==initializeWidgets== method now uses a different way to initialize the sub-widgets of the UI.
This is because it does not use basic widgets but instead reuses the user interfaces we defines previously.
The remainder of the method is quite similar to the previous implementation, as shown in the code in *ex_viewer_initializeWidgets*.
[[[label=ex_viewer_initializeWidgets|caption=Implementation of ProtocolViewer>>#initializeWidgets|language=Smalltalk
initializeWidgets
models := self instantiate: ModelList.
protocols := self instantiate: ProtocolList.
events := self instantiate: ProtocolList.
protocols label: 'protocol'.
events label: 'protocol-events'.
self focusOrder
add: models;
add: protocols;
add: events
]]]
The layout puts the sub widgets in one column, with all sub widgets taking the same amount of space.
The code in *ex_viewer_layout* shows the implementation of this layout.
[[[label=ex_viewer_layout|caption=ProtocolViewer column layout|language=Smalltalk
defaultSpec
<spec: #default>
^ SpecLayout composed
newColumn: [ :column |
column
add: #models;
add: #protocols;
add: #events ];
yourself
]]]
To describe the interactions between the sub widgets, the method ==initializePresenter== needs to be defined.
Here, it specifies that when a class is selected, the selections in the protocol list are reset and both protocol lists are populated.
Additionally, when a method is selected in one protocol list, the selection in the other list is reset.
The implementation of this method is exposed in code *ex_viewer_presenter*.
More details on the ==initializePresenter== method are given in *subsec_initializePresenter*.
[[[label=ex_viewer_presenter|caption=ProtocolViewer sub widget interactions|language=Smalltalk
initializePresenter
models whenSelectedItemChanged: [ :class |
self resetProtocolSelection.
self resetEventSelection.
class
ifNil: [
protocols items: #().
events items: #() ]
ifNotNil: [
protocols items: (self methodsIn: class for: 'protocol').
events items: (self methodsIn: class for: 'protocol-events') ] ].
protocols whenSelectedItemChanged: [ :method | method ifNotNil: [ self resetEventSelection ] ].
events whenSelectedItemChanged: [ :method | method ifNotNil: [ self resetProtocolSelection ] ].
]]]
The remaining methods are getters, methods to delegate to sub widgets, one method to compute the methods in a specific class for a specific protocol, and methods to register to sub widget events.
Those methods are given in the code in *ex_viewer_others*.
[[[label=ex_viewer_others|caption=ProtocolViewer other methods|language=Smalltalk
"accessing"
events
^ events
"accessing"
models
^ models
"accessing"
protocols
^ protocols
"private"
methodsIn: class for: protocol
^ (class methodsInProtocol: protocol)
sorted: [ :a :b | a selector < b selector ]
"protocol"
resetEventSelection
events resetSelection
"protocol"
resetProtocolSelection
protocols resetSelection
"protocol"
title
^ 'Protocol viewer'
"protocol-events"
whenClassChanged: aBlock
models whenSelectedItemChanged: aBlock
"protocol-events"
whenEventChangedDo: aBlock
events whenSelectedItemChanged: aBlock
"protocol-events"
whenProtocolChangedDo: aBlock
protocols whenSelectedItemChanged: aBlock
]]]
As previously, the result can be seen by executing the following snippet of code: ==ProtocolViewer new openWithSpec==.
!!! Protocol Editor
@subsec_protocol_editor
The last user interface reuses a ""ProtocolViewer"" with a different layout and adds a text zone to edit the source code of the selected method.
The class definition can be seen in code in *ex_browser_definition*.
[[[label=ex_browser_definition|caption=ProtocolBrowser definition|language=Smalltalk
ComposableModel subclass: #ProtocolEditor
instanceVariableNames: 'viewer text'
classVariableNames: ''
category: 'Spec-Examples'
]]]
The ==initializeWidgets== implementation is shown in the code in *ex_browser_initializeWidgets*.
[[[label=ex_browser_initializeWidgets|caption=ProtocolEditor>>#initializeWidgets|language=Smalltalk
initializeWidgets
text := self newText.
viewer := self instantiate: ProtocolViewer.
text aboutToStyle: true.
self focusOrder
add: viewer;
add: text
]]]
The layout is more complex than the previous layouts.
Now the user interface mainly lays out widgets that are contained in its ==viewer== sub widget (the list of models and the two protocol browsers).
The layout is based on a column whose first row is divided in columns.
The implementation of this method is shown in code in *ex_browser_layout*.
[[[label=ex_browser_layout|caption=ProtocolBrowser layout|language=Smalltalk
defaultSpec
<spec: #default>
^ SpecLayout composed
newColumn: [ :col |
col
newRow: [ :r |
r
add: #(viewer models);
newColumn: [ :c |
c
add: #(viewer protocols);
add: #(viewer events) ] ];
add: #text
];
yourself
]]]
The ==initalizePresenter== method is used to make the text zone react to a selection in the lists.
When a method is seleted, the text zone updates its contents to show the source code of the selected method.
The implementation of this method is detailled in the code in *ex_browser_presenter*.
[[[label=ex_browser_presenter|caption=ProtocolBrowser interactions|language=Smalltalk
initializePresenter
viewer whenClassChanged: [ :class | text behavior: class ].
viewer whenProtocolChangedDo: [ :item |
item
ifNil: [ text text: '' ]
ifNotNil: [ text text: item sourceCode ] ].
viewer whenEventChangedDo: [ :item |
item
ifNil: [ text text: '' ]
ifNotNil: [ text text: item sourceCode ] ]
]]]
The other methods are two getters, a method to set the default size, and a method to set the UI title.
Their implemenations are detailled in code *ex_browser_others*.
[[[label=ex_browser_others|caption=ProtocolBrowser remaining methods|language=Smalltalk
"accessing"
text
^ text
"accessing"
viewer
^ viewer
"protocol"
initialExtent
^ 750@600
"protocol"
title
^ 'Protocols browser'
]]]
This finishes the protocol browser.
The final user interface can be opened with the following snippet: ==ProtocolBrowser new openWithSpec==.
The result can be seen in figure *fig_protocol_browser*.
+Protocol Browser>file://figures/Protocol_Browser.png|width=50|label=fig_protocol_browser+