Drawing area widgets
The drawing area (CwDrawingArea) widget provides an application with an area in which application-defined graphics can be drawn using Common Graphics operations such as fillRectangle:, drawArc:, and drawString:. Consult the Common Graphics chapter for an explanation of drawing and other graphics operations.
Drawing is actually done on the CgWindow associated with the CwDrawingArea. Every CwWidget has a corresponding CgWindow, obtained by sending window to a widget, that can be used for drawing. Although any widget can be drawn on in this manner, CwDrawingArea widgets are typically used because they provide additional drawing-related functionality. Create CwDrawingArea widgets using the createDrawingArea:argBlock: convenience method.
A CwDrawingArea can be told to notify the application with an expose callback whenever a part of the drawing area needs to be redrawn. The expose callback contains an expose event with a rectangle describing the damaged area of the widget's CgWindow.
The following example is a simple application that draws a mandala. (A mandala is a drawing of lines connecting each of a given number of points on the circumference of a circle to every other such point.) Four callbacks that are often used in conjunction with drawing areas are illustrated: exposeCallback (described above), resizeCallback, inputCallback, and destroyCallback.
The resize callback is called when the drawing area changes size, usually due to a change in the size of a parent widget. If an expose callback is triggered as a result of a resize, the resize callback is always sent before the expose callback. It is possible for the resize callback to be run before the window has been realized. The resize callback handler should handle the case where the window message returns nil.
The input callback is called when a mouse button is pressed or released inside the widget or a key on the keyboard has been pressed or released. The destroy callback, run when the widget is about to be destroyed, is a good place to free any graphics resources that have been allocated for drawing.
Object subclass: #DrawingAreaExample
instanceVariableNames: 'gc radius segments '
classVariableNames: ''
poolDictionaries: 'CwConstants CgConstants '
example1
"Open the drawing area example."
| diameter shell draw |
"Initialize the radius instance variable and calculate the diameter
of the mandala."
radius := 150.
diameter := radius * 2.
shell := CwTopLevelShell
createApplicationShell: 'shell'
argBlock: [:w | w title: 'Drawing Area Example'].
draw := shell
"Create a drawing area widget, with its width and height set to the
diameter of the mandala"
createDrawingArea: 'draw'
argBlock: [:w |
w
width: diameter;
height: diameter].
draw manageChild.
draw
"Add an expose callback that is run when a portion of the drawing area
needs redrawing."
addCallback: XmNexposeCallback
receiver: self
selector: #expose:clientData:callData:
clientData: nil;
"Add a resize callback that is run when the drawing area is resized
as a result of the user resizing the shell."
addCallback: XmNresizeCallback
receiver: self
selector: #resize:clientData:callData:
clientData: nil;
"Add an input callback that is run when the drawing area receives a
key or mouse button event."
addCallback: XmNinputCallback
receiver: self
selector: #input:clientData:callData:
clientData: nil;
"Add a destroy callback that is run when the drawing area is destroyed.
The destroy callback is a good place to free any allocated graphics
resources."
addCallback: XmNdestroyCallback
receiver: self
selector: #destroy:clientData:callData:
clientData: nil.
"Realize the widgets".
shell realizeWidget.
expose: widget clientData: clientData callData: callData
"Redraw the contents of the drawing area."
gc isNil ifTrue: [
gc := widget window
"On the first expose, create a graphics context with a foreground color
of black."
createGC: None
values: nil.
gc setForeground: widget window blackPixel].
callData event count = 0
ifTrue: [
segments isNil
ifTrue: [self recalculateSegments: widget].
widget window
"Draw the line segments."
drawSegments: gc
segments: segments].
recalculateSegments: widget
"Recalculate the coordinates of the mandala's line segments."
| n points x y |
n := 20.
points := OrderedCollection new.
"Calculate the points of the mandala."
0 to: Float pi * 2 by: Float pi * 2 / n do: [:angle |
x := (angle cos * radius) rounded + (widget width // 2).
y := (angle sin * radius) rounded + (widget height // 2).
points add: x@y].
segments := OrderedCollection new.
"Calculate the line segments of the mandala.
Each point is connected to every other point."
1 to: points size - 1 do: [:i |
i + 1 to: points size do: [:j |
segments add:
(CgSegment
point1: (points at: i)
point2: (points at: j))]].
resize: widget clientData: clientData callData: callData
"The drawing area has been resized."
widget window notNil
ifTrue: [
"Recalculate the radius of the mandala. Force a recalculation of
segments on expose."
radius := (widget width min: widget height) // 2.
segments := nil].
input: widget clientData: clientData callData: callData
"The drawing area has received an input callback (button or key event).
Explicitly destroy the widget if one of three things has happened:
- the user typed 'Q' or 'q'.
- the user typed 'control-DownArrow'.
- the user did a 'shift-click' (shift key pressed, click left
mouse button)."
| event quit |
quit := false.
event := callData event.
"$Q, $q, or control-End typed"
event type = KeyPress
ifTrue: [
quit := ('Qq' includes: event character)
or: (event state & ControlMask) = ControlMask
and: event keysym = XKdownarrow]]].
"Shift-click"
(event type = ButtonPress and: event button = 1])
ifTrue: [
quit := (event state & ShiftMask) = ShiftMask].
quit ifTrue: [widget shell destroyWidget].
destroy: widget clientData: clientData callData: callData
"The drawing area has been destroyed.
Free any allocated graphics resources."
gc notNil ifTrue: [
"Free the graphics context."
gc freeGC].