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].