We present a typical Polymorph application featuring Window, menu, toolbar and drawing canvas.The example is a painter application we present step by step.
The complete example is at http://www.squeaksource.com/CollaborActiveBook. The example exposes several aspects in UI programming: Morph layout, Polymorph, Traits, graphic and form display manipulations, mouse handling, announcement.

The application is divided in two classes. A top level class Painter and a canvas class PaintCanvas, a kind of Morph. In the following section we explain in detail those implementations.
Painter
Our application comes with a main window, a menubar, a toolbar and a drawing area. Naturally we have an instance for each one.
Object subclass: #Painter instanceVariableNames: 'window menubar toolbar drawingArea' classVariableNames: '' poolDictionaries: '' category: 'PharoCollaborActiveBook-GUI'
Installing the components
At the initialization time we install all the application components:
initialize super initialize. self installMenubar. self installToolbar. self installDrawingArea. self window openInWorld. self window extent: 300@250
The layout component use the technic explained in the previous section about Layout. For example to install the menu it looks like:
installMenubar
self window
addMorph: self menubar
fullFrame: (LayoutFrame
fractions: (0@0 corner: 1@0)
offsets: (0@0
corner: 0@self menubar minExtent y))
At first, we write the menu should occupy 0% vertically, but we statically shift its bottom position to its y extent.
The toolbar installation is a bit more complex as we need to shift its vertical position according to the menu y extent. As for the menu, we write the toolbar should occupy 0% vertically, then we shift statically both its top and bottom positions:
installToolbar
self window
addMorph: self toolbar
fullFrame: (LayoutFrame
fractions: (0@0 corner: 1@0)
offsets: (0@self menubar minExtent y
corner: 0@ self menubar minExtent y +
self toolbar minExtent y))
Instantiating the components
To create the menu, we use #newMenu and #newToolDockingBar, there are controls provided by the windows.
menubar
^ menubar ifNil: [ | menu |
menu := self window newMenu.
menu
addToggle: 'Load'
target: self
selector: #load.
menu
addToggle: 'Save'
target: self
selector: #save.
menubar := self window newToolDockingBar.
menubar
add: 'File'
font: self window theme menuBarFont
icon: nil
help: 'File operations'
subMenu: menu.
menubar
]
To understand from where are coming from these controls, we need to look at the windows instantiation:
window
^ window ifNil: [
window := StandardWindow labelled: 'Painter'.
window announcer
on: WindowResizing
do: [:ann |
self drawingArea extent: ann newSize].
window
]
Our window is a StandardWindow, it comes with the TEasilyThemed traits, all the controls are coming from this traits:
SystemWindow subclass: #StandardWindow
uses: TEasilyThemed - {#theme}
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Polymorph-Widgets-Windows'
At the window instantiation we are defining an announcement: it says we should send #extent: to the drawingArea when the window is resized.
The drawing area instantiation itself is straight full:
drawingArea
^ drawingArea ifNil: [
drawingArea := PaintCanvas new]
It is time now to look at the PaintCanvas class itself in the next section.
Paint Canvas
Our paint canvas is a Morh instance. As we can not draw directly into a Morph, we use a FormCanvas object to draw into it. When the Moprh needs to be updated, the FormCanvas content is copied in the Morph canvas. Our paint canvas comes with two instances, one is a FormCanvas, the other a Color:
Initialization
Morph subclass: #PaintCanvas instanceVariableNames: 'area paintingColor' classVariableNames: '' poolDictionaries: '' category: 'PharoCollaborActiveBook-GUI'
The area instance is initialized as a FormCanvas, with a default size identical to the Morph extent:
area
^ area ifNil: [
area := FormCanvas extent: self extent.
self clear.
area]
It is filled with white color:
clear self area fillColor: Color white. self changed
Update operation
The #changed message informs the morph something changed and redrawing is necessary. The drawing itself only consists to copy the area content in the rendering Morph canvas:
drawOn: aCanvas
aCanvas
drawImage: self area form
at: self position
Now you should remember the announcement we added in our main application window (if no, read the previous section about the Painter). This announcement states the message #extent: is sent to our Paint Canvas at WindowResize announcement. Now we need to override the #extent: method here:
extent: aPoint
| newArea |
super extent: aPoint.
newArea := FormCanvas extent: aPoint.
newArea
fillColor: Color white;
drawImage: self area form at: 0@0.
area := newArea
Here, we just create a FormCanvas matching the new window extent, then we copy the content of the old FormCanvas in the new one. Mouse handling and drawing operation
To effectively paint we need to handle mouse operations. First we inform we want to handle the mouse down event whatever the clicked button:
handlesMouseDown: evt ^ true
Then when the mouse is moved, we paint in our FormCanvas:
mouseMove: anEvent
self area
fillOval: (Rectangle
center: anEvent cursorPoint - self position
extent: 4@4)
color: self paintingColor.
self changed.


