Example

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 the page 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 Instantiating sub widgets and Defining the layout.

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 following snippet shows the definition of this ModelList class.

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 following code. 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 here.

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 following code. It simply returns a layout that contains only the list. More details on the use of this method are given here.

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. This code shows the implementation of these three methods and their protocols:

"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:

ComposableModel subclass: #ProtocolList
    instanceVariableNames: 'label protocols'
    classVariableNames: ''
    category: 'Spec-Examples'

The initializeWidgets method for this UI is quite similar to the method in ModelList:

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 following code shows how to build a column with the label on top and the list taking all the space that is left.

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 following code shows the implementations of these methods as well as their protocol.

"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 following code in shows the definition of the class ProtocolViewer.

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:

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 implementation of this layout is:

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 the following code. More details on the initializePresenter method are given in Sub widgets interaction.

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 this code:

"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

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 is:

ComposableModel subclass: #ProtocolEditor
    instanceVariableNames: 'viewer text'
    classVariableNames: ''
    category: 'Spec-Examples'

The initializeWidgets implementation is shown in the code:

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 the following code:

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 following code.

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:

"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 1.1.

fig_protocol_browser