Adding drawing methods
To accomplish the actual drawing use drawing operations from the Common Graphics subsystem. By restricting yourself to these drawing techniques, you ensure that the code you write is portable across all the operating systems that VA Smalltalk supports. To learn more about the Common Graphics class hierarchy and the drawing process, refer to the Programmer Reference.
To draw the circle, write three new instance methods in MyCwShape. The first of these methods is drawFilledCircle.
drawFilledCircle
"Draw a filled arc with an outline around it."
| gc border |
gc := self createGC.
self clear: gc.
border := self borderWidth.
self window
fillArc: gc
x: (border // 2)
y: (border // 2)
width: (self width - border)
height: (self height - border)
angle1: 0
angle2: (360 * 64).
border > 0 ifTrue: [
self backgroundColor isNil ifFalse: [
gc setForeground:
(self window getPalette nearestPixelValue:
(self backgroundColor))].
self window
drawArc: gc
x: (border // 2)
y: (border // 2)
width: (self width - border)
height: (self height - border)
angle1: 0
angle2: (360 * 64).
].
gc freeGC.
Before drawing the circle, the preceding method clears the shape's drawing area, using the clear method:
clear: gc
"Clear the drawing area"
| values |
values := CgGCValues new.
gc getGCValues: GCForeground valuesReturn: values.
self parent backgroundColor isNil ifFalse: [
gc setForeground:
(self window getPalette nearestPixelValue:
(self parent backgroundColor))].
self window
fillRectangle: gc
x: 0
y: 0
width: (self width)
height: (self height).
gc setForeground: (values foreground).
These methods call createGC which is defined as follows:
createGC
"Initialize the graphics resources."
| gc |
gc := self window createGC: None values: nil.
self foregroundColor isNil ifFalse: [
gc setForeground:
(self window getPalette nearestPixelValue:
(self foregroundColor))].
gc setLineAttributes: (self borderWidth)
lineStyle: LineSolid
capStyle: CapButt
joinStyle: JoinMiter.
^gc
Together, these methods create a graphics context (an object that holds information about colors, line width, line style, and so on) and then draw a filled circle with an outline around it. The circle is filled with the widget's foreground color (inherited from CwDrawingArea) and the outline is drawn in the widget's background color.
After drawing, the graphics context is freed because it is no longer needed.
Default edit size
To make the shape part visible when it is dropped into the Composition Editor, you must set its default size by implementing the class method defaultEditSize in MyShape. This method returns a point value, indicating the width and height of the part.
defaultEditSize
"answer the part's default size when dropped"
^125@125
Along with the defaultEditSize method, you also implement attachmentSpecAt: and positionSpecAt:, unless you already inherit a suitable implementation of these two methods.
attachmentSpecAt: point
"answer the part's attachment spec"
^self attachmentSpecFromRect:
(point extent: self defaultEditSize)
positionSpecAt: point
"answer the part's position spec"
^self positionSpecFromRect:
(point extent: self defaultEditSize)
To specify an icon representing the part, add the following class method to MyShape:
abtInstanceGraphicsDescriptor
"Answers my icon descriptor"
^(AbtIconDescriptor new
moduleName: 'abtico50';
id: 9903)
Move the above class methods to your edit-time application, MyEditSamplePartsApp.
Callbacks
Next, you must ensure that the draw method is called whenever the part needs to be redrawn, such as when the part is first displayed, or when it is covered by another window and then uncovered. To do this, implement a special callback method for the part. Callbacks are methods that respond to events from the operating system. The callback that is called when the part needs to be redrawn is known as the expose callback. If you are familiar with Presentation Manager or Windows programming, this is similar to handling a WM_PAINT message.
To use a callback method, the part must register its interest in a specific event, and give VA Smalltalk the name of a method to call whenever the event occurs. The shape's superclass (AbtDrawingAreaView) has already registered its interest in the expose callback, so all you need to do is override the callback handler method itself. To register a callback, you would use an instance method like this one. In this example, you do not need to write this method because the superclass already implements it:
postCreationInitialization
"Initialize anything that is not a resource."
super postCreationInitialization.
widget
addCallback: XmNresizeCallback
receiver: self
selector: #resized:clientData:callData:
clientData: nil;
addCallback: XmNexposeCallback
receiver: self
selector: #exposed:clientData:callData:
clientData: nil
Before registering any callbacks, check your superclasses to see which callbacks they already register. If you register a callback more than once, you might find that the callback handlers are called more often than you intended. For example, if you registered the expose callback again, your shape part would be drawn twice each time it needed to be redrawn.
Now create the following instance method in MyShape, which is the expose callback handler:
exposed: aWidget clientData: clientData callData: callData
"hHandle the exposed event"
super exposed: aWidget clientData: clientData callData: callData.
self widget isNil ifFalse: self draw].
To learn more about the different callbacks you can implement, refer to the Programmer Reference.
Reviewing your work
Before you begin testing your work it is probably worthwhile to review the classes and methods you have implemented in your runtime application.
The MyShape class should have the following instance methods:
• circle
• draw
• exposed:clientData:callData:
• shape
• shape:
The MyShape class should have the following class methods:
• attachmentSpecAt:
• cwWidgetClass
• defaultEditSize
• isAttributeTranslatable:
• positionSpecAt:
The MyCwShape class should have the following instance methods:
• clear:
• createGC
• drawFilledCircle
Testing your work
Now you're ready to try testing your shape part.
Reuse the MyTestSamplePartsApp Application to test your work and create a new part named MyShapeTestView for testing.
Note:
If you have not already
created the test application, do so now.
Open the Composition Editor on MyShapeTestView and drop a shape part, MyShape, inside a Window part.
You should see a filled black circle. If not, go back and check each of the methods you wrote.
Now try two other tests:
1. Move another window on your desktop so that part of the shape is covered, and then move the window away to uncover the shape. What happened?
2. Try dropping a push button inside the shape. What happened to the shape?
In the first test, you should have seen that the shape was redrawn correctly after it was uncovered.
In the second test, you should have seen that the shape's size was changed so the shape just fit around the push button. In the next section, you will learn how to change this behavior.