-
- What is a transmission?
- Input ports
- Model Input Port (SpModelPort)
- Text Input Port (SpTextPort)
- List Items Input Port (SpListItemsPort)
- Action Input Port (SpActionPort)
- Output ports
- Selection Output Port (SpSelectionPort)
- Activation Output Port (SpActivationPort)
- Text Changed Output Port (SpTextChangedPort)
- Drop List Selection Output Port (SpDropListSelectionPort)
A SpApplication is a class that handles many aspects of an Spec Application (hence it's name) in a convenient fashion. SpApplication handles your application initialization, configuration and resources. It also keeps the windows you have currently opened.
Initialization of an application includes (non mandatory): configure the backend you want to use, add useful resources and define a start method that will call your initial window.
Spec2 includes several backends (for the moment, Morphic and Gtk). A SpApplication configures a Morphic backend by default, but you can change it using #useBackend:
or #useBackend:with:
and sending the backend identifier and optionally a configuration (you may want to do specific backend things to configure your application behavior).
See this example:
"This example shows how to change the backend of an application"
| app |
"You want to subclass SpApplication to create your app"
app := SpApplication new.
app useBackend: #Gtk.
app run
see also: SpApplicationConfiguration
During initialization, you may want to add special resources (like icons, themes, etc.).
While you can add your own way to access resources, SpApplication provides a property registration mechanism (a simple Dictionary and accessors), you may find useful to search at accessing properties
protocol.
This is useful to give your application a starting window (in general, this is what you want). Example:
MyApplication>>start
(self new: MyMainPresenter) openWithSpec
Typically, each Spec application will implement one or several configurations (for example, to run on Morphic or Gtk) by extending this class or one of its children. A configuration takes the responsibility to prepare an application to run properly. This preparation can be different depending on the platform where it is running, that's why you have several extension points you can extend/override:
configure:
a generic configuration point that normally will dispatch the configuration to a plarform specific method.configureOSX:
/configureUnix:
/configureWindows:
platform specific entry points.
TODO:: Examples of configurations. see also: SpMorphicConfiguration, SpGtkConfiguration
Morphic configurations will prepare your application to run in a Morphic backend. Typically, you will not change much of what is already provided on a Pharo system, but there are several entry points you may want to extend/override:
Define the default styleSheet to use in your application. You can override this and add your own application dependent styles (and you can compose them, see ).
^ SpStyle defaultStyleSheet
A style is a property container to "style" morphic components, and define (in a certain degree) its behaviour within the different layouts implemented. There are two kinds of style elements: style classes and style properties.
To easy the definition and storage outside an image, a stylesheet may be defined as a STON file, string or stream, that you can later read using SpStyleSTONReader.
A defined stylesheet has to have always a root element, and this root element needs to be called .application
.
See this small example:
SpStyleSTONReader fromString: '
.application [
.myButton [
Geometry { #width: 150 }
]
]'
As a more complex example, see [SpStyle class>>#createDefaultStyleSheet](#SpStyle class_createDefaultStyleSheet) which defines the default behaviour of all elements of a Morphic Spec backend.
You can add styles to your presenters easily by using class:SpAbstractWidgetPresenter TODO: more examples See SpStyleClass, SpStyleProperty, SpMorphStyle
Add a style-class to a presenter. Styles are defined in the application stylesheet and will affect presenters by applying the properties the user adds to the class.
- Styles can just be added to widget presenters (the ones that inherits of ).
- Styles can be added and removed dynamically (see also SpAbstractWidgetPresenter
button := self newButton
label: 'Example of style';
addStyle: 'myButton';
yourself.
A style class define a set of properties grouped by a common name. You can think a style class of morphic a little bit as a style class of CSS, but it has several differences.
You can nest classes to refine some properties. For example, if you have this definition:
.application [
.button [
Geometry { #height: 25, #width: 100 }
.smallButton {
Geometry { #width: 150 }
}
]
]
the result style for a button with "smallButton" style will have a Geometry with the form: Geometry { #width: 150, #height: 25 }
, which is the result of the merge of all properties, with the deepest nested property taking precedence.
You can compose class styles (stacking them to form a new style). This is an useful practice to add your own styles to the default definition.
myStyle := SpStyle defaultStyleSheet, myOwnStyleDefinition
Style properties define different kind of properties a morphic component can have. There are several types of properties, defined as:
A container property can be applied to container elements (buttonbar, toolbar, actionbar), and define several properties:
- borderColor: The color of the border (in case borderWidth > 0).
- borderWidth: The width of the border.
- padding: The space between elements.
See SpStyleContainer>>#borderColor
The identifier of container in the stylesheet is Container
.
Container {
#borderColor: #blue,
#borderWidth: 2,
#padding: 5
}
This property can be expressed as
- a STON map:
Color { #red : 1., #green : 0, #blue : 0, #alpha : 1 }
- a named selector:
#red
- an hex string:
'FF0000'
Draw properties control how the component (morph) will be draw. I keep this properties:
- color: foreground color for the morph if it applies (if the morph understands #color:).
- backgroundColor: background color if it applies (if the morph understands #backgroundColor:).
See SpStyleDraw>>#color and SpStyleDraw>>#backgroundColor
The identifier of draw in the stylesheet is Draw
.
Draw {
#color: #red,
#backgroundColor: '00FF00'
}
Font properties control how a component (morph) with font will draw the text. I keep this properties:
- name: The font name (it needs to be available in the list of fonts, e.g. "Source Code Pro")
- size: The font point size.
- bold: Font is bold? (boolean, default false)
- italic: Font is italic? (boolean, default false)
The identifier of font in the stylesheet is Font
.
Font {
#name: "Source Sans Pro",
#size: 12,
#bold: false,
#italic: false
}
Geometry properties controls how the component (morph) will be arranged within its layout.
- hResizing: the component can be resized horizontally? (boolean, default depends on how the morph behaves outside spec)
- vResizing: the component can be resized vertically? (boolean, default depends on how the morph behaves outside spec)
- width: fixed width of the component.
- height: fixed height of the component.
- minWidth: minimum width of the component (to use when
hResizing=true
) - minHeight: minimum height of the component (to use when
vResizing=true
) - maxWidth: maximum width of the component (to use when
hResizing=true
) - maxHeight: maximum height of the component (to use when
vResizing=true
)
The identifier of geometry in the stylesheet is Geometry
.
Geometry {
#hResizing: false,
#vResizing: false,
#width: 100,
#height: 25,
#minWidth: 50,
#minHeight: 25,
#maxWidth: 150,
#maxHeight: 25
}
This is for internal use of the framework, but it is interesting to notice how it works since it may give some insigts on how to declare things.
At creation of a component, an instance of SpMorphStyle
is created and by taking the stylesheet it collects styles from most external to more internal. So, for example, this stylesheet:
.application [
Geometry { #height: 100 },
.button [
Geometry { #width: 100 }
]
]
Will collect, for a button (SpButtonPresenter, who has a style name button
), the styles
application
application.button
This collection will be used to get all properties defined and perform a merge between them (SpStyleProperty>>#mergeWith:), to get all one single property for each type of them. Which means at the end it will apply a property Geometry { #width: 100, #height: 25 }
.
Transmissions are a way to connect presenters, thinking on the "flow" of information more than the way it is displayed. For example, think on a master-detail (A->B) relationship, when you navigate the elements in master A, you want to see the detail B. This is tipically solved showing a list with master elements and a form with the detail of each master. In Spec, this will be declared more or less like this:
layout := SpBoxLayout newHorizontal
add: (list := self newList);
add: (detail := self newText);
yourself.
But this does not says how list
and detail
are linked.
The transmission sub-framework solves this in an elegant way: Each presenter defines output ports (ports to send information) and input ports (ports to receive information). Each presenter defines also a default input port and a default output port.
A transmission connects a presenter's output port with a presenter's input port. See this example:
list transmitTo: detail.
This will connect the list
presenter default output port with the detail
presenter default input port. This line is equivallent (but a lot simpler) to this one:
list defaultOutputPort transmitTo: detail defaultInputPort
Is important to remark that a transmission does not connects two components, it connect two component ports. The distinction is important because there can be many ports!
Take for example SpListPresenter, it defines two output ports (selection and activation), this means it is possible to define also this transmission:
list outputActivationPort transmitTo: detail defaultInputPort
The object transmitted from a presenter output port can be inadequate for the input port. To solve this problem a transmission allow transformations.
This is as simple as using the #transform:
protocol:
list
transmitTo: detail
transform: [ :aValue | aValue asString ].
list defaultOutputPort
transmitTo: detail defaultInputPort
transform: [ :aValue | aValue asString ].
Transmitting from an output port to an arbitrary input receiver (#transmitDo:, #transmitDo:transform:)
It is possible that the user requires to listen an output port, but instead transmitting the value to another presenter, other operation is needed.
There is the #transmitDo:
protocol to handle this situation:
list transmitDo: [ :aValue | aValue crTrace ].
Sometimes after a transmission happens, the user needs to react to modify something given the new status achieved by the presenter (like, pre-selecting something).
The #postTransmission:
protocol allows you to handle that situation.
list
transmitTo: detail
postTransmission: [ :fromPresenter :toPresenter :value |
"something to do here"
toPresenter enabled: value isEmptyOrNil not ].
See SpTransmission>>#postTransmission:
Input ports define the transmission destination points of a presenter. They handle an incoming transmissions and transmit them properly to the target presenter. This transmission happens in SpInputPort>>#incomingTransmission:from:, that concrete implementations of input ports needs to define to populate the destination presenter. See: SpLabelPort,SpListItemsPort, SpModelPort, SpTextPort, SpActionPort
When a transmission happens, the origin transmission has is triggers a value anObject
(already transformed if #transform: is specified) from the outPort SpOutputPort.
This method uses the incoming value to populate the destination presenter, according
with the concrete port definition.
Yes, this sounds abstract, you can check concrete implementations for a better
understanding of it.
self destinationPresenter setModel: anObject
A model input port define an incomming transmission to modify the model of a presenter.
The presenter exposing a model input port need to define also the method #setModel:
.
Presenters exposing this port: SpPresenterSelectorPresenter and SpPresenterWithModel
A text input port define an incomming transmission to modify the text of a text presenter (input fields or text areas).
The presenter exposing a text input port need to define also the method #text:
.
Presenters exposing this port: SpAbstractTextPresenter subclasses.
A list items input port define an incomming transmission to modify items of a list, tree or table presenter.
The presenter exposing a list items input port need to define also the method #items:
.
Presenters exposing this port: SpAbstractListPresenter subclasses and: SpDropListPresenter.
An action input port s a generic action to handle the result of a transmission: Instead transmit to a destination presenter, it will execute an action block.
aPresenter
transmitDo: [ :aValue | ... ]
transform: #asString
Important: This port is not meant to be used in the same way other input ports, instead, you will use the #transmitDo:
mechanism.
An output port defines origin actions (and the possible data associated to such action) to transmit to an destination (input) port. It also defines the trasformations to apply to the output data before give them to the input port. Finally, it can also define some operation to do after the transmission is completed. Transmission are attached to a presenter (each concrete output port will override SpOutputPort>>#attachTransmission:).
A transmission is no more than a definition of what to transmit to who.
By overriding this method, concrete output ports can listen the event they need to and
execute the transmission (sending #transmitTo:value:
).
self destinationPresenter whenSelectionChangedDo: [ :selection |
self
transmitWith: aTransmission
value: selection transmission ]
A selection output port will handle the transmission when a presenter can be selected (e.g. lists).
The presenter exposing a selection output port need to define also the event #whenSelectionChangedDo:
.
Presenters exposing this port: SpAbstractListPresenter subclasses and SpTreeTablePresenter
A selection output port will handle the transmission when a presenter can be activated (e.g. lists).
The presenter exposing a selection output port need to define also the event #whenActivatedDo:
.
Presenters exposing this port: SpAbstractListPresenter subclasses and SpTreeTablePresenter
A text changed output port will handle the transmission when an input presenter content changes.
The presenter exposing a selection output port need to define also the event #whenTextChangedDo:
.
Presenters exposing this port: SpAbstractTextPresenter subclasses.
A drop list selection output port is the same as SpSelectionPort, but to be used exclusively by SpDropListPresenter.
The presenter exposing a selection output port need to define also the event #whenSelectionChangedDo:
.
Presenters exposing this port: SpDropListPresenter
NOTE: Maybe this needs to be merged with SpSelectionPort?
This is the starting point of every Spec application. A subclass of SpApplication who defines how your application will behave and start.
You will need to override initialize
and start
methods as a begining.
The configuration point for this application can be defined here. Sending #useBackend:with: you will declare your application to use a morphic application and use a instance as configuration.
self
useBackend: #Morphic
with: SpSimpleExampleConfiguration new.
You will declare your initial window here (spec is, after all, a framework to create desktop applications).
(self new: SpSimpleExamplePresenter) openWithSpec.
# A (simple) sample configuration
A configuration is needed to define different elements for each different backend. You do this by extending SpApplicationConfiguration or one of its more specific children (SpMorphicConfiguration and SpGtkConfiguration). See SpSimpleExampleConfiguration>>#styleSheet
This method will answer the default stylesheed (provided by calling super styleSheed
) and
it will add a class called title
to be used by labels.
^ super styleSheet, (SpStyleSTONReader fromString: '
.application [
.label [
.title [
Font { #name: ""Source Code Pro"", #size: 24 },
Draw { #color: #red }
]
]
]')
A simple presenter to show how I can construct and show a window in the context of a Spec application. It will show a simple title, a message and a button.
A defaultSpec (@TODO change name for defaultLayout) will define the layout to arrange the different elements of this presenter. See .
^ SpBoxLayout newVertical
add: #titleLabel;
add: #messageLabel expand: false;
add: (SpBoxLayout newHorizontal
hAlignCenter;
add: #actionButton;
yourself)
expand: false;
yourself
Initialize presenters: you will add your presenters here. BEWARE! all your presenters names need to match the name defined in the SpSimpleExamplePresenter class method.
titleLabel := self newLabel
label: 'A Simple Title';
addStyle: 'title';
yourself.
messageLabel := self newLabel
label: 'Some message';
yourself.
actionButton := self newButton
label: 'A button';
action: [ ];
yourself
A presenter can be composed as part of another presenter. But it can also be shown as a window. Define here the window properties you need (see )
aWindowPresenter
title: 'Simple Application';
initialExtent: 400@400