Today we start making our Calendar deal with Motif look issues. But first, a bit of overview of how the Dynamic Look Change system works
The Look Change Mechanism
Whenever VisualWorks detects, on startup, that the platform for the image is different than the one on which the image was saved, it basically looks up the UILookPreferences class for that platform, and then sends #installLookPreferences to it.
That in turn sends #updateWidgetPolicy to every open GraphicsDevice. Graphics devices in VisualWorks are the Screen and any Printers. Once it gets to sending #updateWidgetPolicy to the Screen, it in turn sends #newGraphicsDevice: to each open window, passing the Screen as a parameter.
When a Pollock window gets this message, the Dynamic Look Change framework takes over. When any Pollock Pane or Window gets this message, it is it's responsibility to check if the look policy has changed, and if so, change the artist or agent to the appropriate artist or agent for that Pane or Window. Finally, it is it's responsibility to pass on the #newGraphicsDevice: to any subpanes or sub components, so that they can update themselves.
When you change the look in VisualWorks, on of the methods that is sent is the same #installLookPreferences method to the newly selected look's UILookPolicy.
Policies
In Pollock, three policies play a roll in the various looks. The UILookPolicy for a specific look, the WidgetPolicy for that look, the BorderPolicy for that look. The first two are from Wrapper, but the last, the BorderPolicy is new for Pollock.
The default Screen has an instance variable which holds onto the current look's WidgetPolicy. The WidgetPolicy, in Pollock, is the nexus for getting other policy objects and classes. As in Wrapper, it continues to be the place where look colors and fonts are initialized for a specific look.
In Pollock, the UILookPolicies have class side methods that answer look specific classes for a widget's agent and artist. When a Pane needs an agent or artist that is look specific, instead of having a hard coded class name in the #agentClass or #artistClass method, it instead asks the current look policy for that class.
The BorderPolicy works to supply any look specific BorderDecoration objects. This is where, for instance, the yellow 7 wide border comes from for a Motif button comes from, and where the more simple raised border for the Win9x/2K button comes from.
With these two mechanisms, the newGraphicsDevice and the policies, we can write code that allows our Calendar to dynamically change it's look.
Work Already Done
We've already done some of the work for making sure our Calendar, when initially created, to assume some look values. In our #createActionPart method, we used the following:
Here, we made sure that the action part, the Button, used an artist that is look specific... in this case, we're using the same button artist that is used for a DropDownList's button. Also, we are telling it to use a look specific interior decoration.
However, none of it takes care of dynamic look changing. For instance, let's open our look switcher window:
PollockWorkspaceWork new openLookSwitcherWindow
Now, with the Windows look selected, open our tester:
CalendarTest new openWindowWithCalendar
Everything looks fine. Now let's click on the MacOSX look button. As before, we get a gray and white, but still Windows looking widget. Now close the tester, and re-open our tester (we are still in MacOSX look mode).... Notice now that while the button is now blue jelly, the rest of the pane is still not right. In fact, if we click in the InputField part, the blue haze that should be around the whole widget, is somehow inside the gray border. Not good. Similarly if we start in Motif, the look doesn't change.
We'll start with doing some work on the pane's interior decoration for the Motif look
.
The Calendar Pane Decoration
Our Calendar pane uses the method #getInteriorDecoration to, well, get it's interior decoration object. However, that method is currently hard coded. We want to change that so that it instead gets its interior decoration object from the border policy.
For Motif, the pane as a whole, does not even have a border. Instead, the action and display parts are displayed side by side, each with their own decorations. So we'll add a method to both the BorderPolicy and MotifBorderPolicy:
BorderPolicy class>>calendarDecoration
^TwoToneRaisedDecoration on: TwoTonedRaisedDecorationArtist new
MotifBorderPolicy class>>calendarDecoration
^nil
Yes, a decoration can be nil. A nil decoration simply means... none.
Now we change our #getInteriorDecoration method to read:
Now, if we open our tester with the Windows look selected, it has a border around both panes, and if we open a window with the Motif look selected, it, well, it looks like a single simple text out there, and a button. If we switch from Windows to Motif and so on, whatever decoration was there at first stays there when we change.
Dynamically Changing The Artist
The owner of a decoration is the Artist for that pane. What we want to do now is when the look changes, we change the Artist, and then we'll have the Artist change the decoration for us.
As we see, it subclasses from the standard artist. Next we need to tell our WidgetPolicy where to find the artist classes (they're all in the associated LookPolicy):
CalendarTest>>testInitialArtistMotifLook
self shouldnt: [MotifLookPolicy installLookPreferences] raise: Error.
self openWindowWithCalendar.
self should: [calendar artist class == MotifCalendarArtist]
CalendarTest>>testInitialArtistWindowsLook
self shouldnt: [Win98LookPolicy installLookPreferences] raise: Error.
self openWindowWithCalendar.
self should: [calendar artist class == CalendarArtist]
Running these tests shows that we did what we wanted for the startup of a new Calendar. Let's do a little Test Driven Development, and write two tests that show what we want to happen when the look changes after the window is open:
After all this, we now get to the meat of the matter. We write our own #newGraphicsDevice: method, and start doing some work:
Calendar>>newGraphicsDevice: aGraphicsDevice
| widgetPolicy |
(self artist class == (widgetPolicy := aGraphicsDevice defaultWidgetPolicy) calendarArtistClass) ifFalse:
[self replaceArtistWithThatFrom: widgetPolicy].
super newGraphicsDevice: aGraphicsDevice
Here, we ask the passed in GraphicsDevice what for it's widget policy, and then the calendar artist class. If it's not the same as the artist class we current have, we call a method to replace it, which we typically call #replaceArtistFromThatFrom: and pass in the new device's widget policy.
This is how that method looks:
Calendar>>replaceArtistWithThatFrom: aWidgetPolicy
| newArtist oldArtist |
newArtist := aWidgetPolicy calendarArtistClass on: self.
self artist giveAttributesTo: newArtist.
oldArtist := artist.
artist := newArtist.
oldArtist release.
We create a new artist based on the widget policy passed in, then we ask the current artist to give it's attributes to the new artist. We swap around the artists, and then to be friendly, we release the old artist after our new one has been set to the artist instance variable.
Now if we run out tests, they all pass. So we have successfully changed the artist when the look changed. However, we haven't yet dealt with the pane's decoration. Again, a little TDD (Test Driven Development):
By default, the Pollock Framework does nothing in the base #giveAttributesTo: method. For now, all we want to do is have it change the interiorDecoration. Here's how that looks:
And that, is that. If we run our tests, all is well. Indeed, if we open our tester, and change the look from Motif to Windows, we see the border isn't there when in Motif look, and it is in Windows.
Discussion
It may seem that all that indirection is a bit much. It isn't. One of the goals of Pollock was to reuse as much of the underlying GUI core as we could. While unlike Wrapper, we don't turn our building of the widgets over to a Builder and a Specification and then toss the whole thing to horrid and convoluted build pane method in the UILookPolicy, we do use the underlying indirection that the Look and Widget policies supply.
Also we haven't really done anything with the MotifCalendarArtist except to use it as a clue that our look is changing. Even if that is the only use, it is a pretty good one. None the less, as we move along, or look at other widgets, we'll see that these artists will supply information and behavior that is useful.
The above is published as version 1.17 in the Package named "Pollock-Calendar" on the Cincom public repository.
Next time we'll tackle the displayPart (InputField) and make it change it's interior decoration and placement so that it looks more Motif like.