actionPart is already an instance variable in ActionDisplayArtist... we use it here to hold on to our button. Then, we give our button a Frame, in this case, a FractionalFrame. We do this because our button is inside our Calendar pane, and no matter how big we make the outer pane, we want the button part to lay itself out inside itself. Notice that we gave the left fraction a value of 1. That butts it up against the right hand side. We follow that up with a value for the left offset. Here, we use a constant, so we'll put it in an method:
CalendarArtist>>buttonWidth
^19
How do we know that the button should be 19 wide? We've done some research on various Guidelines, and have found that for Windows 9x/ME/2k, the suggested height of a drop down widget is 23 pixels high. The button in the drop down is square when that default size is used. So, why didn't I use the value 23? Well, that's the whole height of the Calendar. There is an interior decoration, in this case the lowered border we are using, and if we subtract the top and bottom area that takes up, 2 pixels each, we end up with 19. So, when/if the Calendar is set to be it's default height, 23, then our button part will be 19x19, and a perfect square! For future reference, the default height of a MacOSX drop down like widget is 20. Motif doesn't specify a default height, so when we get to it, we'll use 23.
Display
Now we just have to write the code to display the button. We do this in the displayOn: method.
The two blue lines above show the changes. You see we lazy create the button (action part). This is a convention used in all ActionDisplay widgets. We don't have to do it that way... we could have created an initialize method and put the call to #createActionPart in there instead.
Finally we put the call to have the action part display itself inside the "displayable rectangle" block. This ensures that our button will only display inside our interior decoration. So let's try it out. Before we do, let's modify our #openWindowWithCalendar method, to put the origin of our pane at 10 @ 10. Also, let's make the extent 23, so it will display with what we now know is the default height. These changes will make it easier to see what's going on when we open it:
And... Hmmmm. The button is there, but instead of being inside the bounds of our interior decoration, it overlaps it. The reason for this is a detail I left out last time. When you add an interior decoration, you have to tell the world about the extra area you have carved out of the inside displayable area of the pane. We do this in our #interiorDecoration: method:
There are two steps in this. First, we tell the interior decoration that the pane it should use as the "owner" pane, is the Calendar pane itself. This facilitates the double dispatch displaying I talked about last time. Also, unlike first class panes, the artist for an interior decoration doesn't automatically point back to the decoration itself. This is also to help with that double dispatching. It's a detail you normally don't have to worry about, just do it.
Next we tell the frame to clip the displayable bounds. This method takes what in Pollock is called an "Edge Rectangle." In fact, it's just a rectangle, but instead of describing an area, the top, left, bottom and right values are used to say how big an edge is. In this case, we have a Pollock framework method all ready to use that takes our decoration (#edgeRectangleIncludingBorderEdge), and supply us with an edge rectangle just right for sending to clipDisplayableBounds.
Get Fancy
Now, we execute the test method again and ... OOooohhh... Nice!... The button is now inside the interior decoration just like it should be. Let's have some fun and make it even cooler! Vassili Bykov, our Tools maven took the time to make a small icon that we can put inside our button. Here's his code for the icon:
Before you say "I'm Not Going To Enter That!", I know, I know... But, don't forget... I publish all of this on the Cincom Public Repository. So you can pretend to write this part yourself, and this time, load it instead.
So, now we have a basic image thing, but to use it, we need to create a Pollock DisplayImage to hold it, and give it a frame. Here's the code for that:
CalendarArtist>>calendarButtonImage
| displayImage |
displayImage := #{Pollock.DisplayImage} value image: self class calendarImage.
displayImage frame: (AlignmentFrame new).
^displayImage
Then we add this image to our action button when we create it:
Now, we execute it again and ... wow, we're really doing a nice job!
Press The Button
But, if you are like me, and you moved the mouse and clicked on the button we added, you probably saw something not so nice. An error saying "Message not understood: #quietlyRequestActivationFor:"
When a pane is added as a subpane of another pane, there is a mechanism of double dispatching that allows the subpane to configure itself, depending on it's needs. This is called the "setup for becoming a subpane" mechanism. By default, the AbstractPane has a method called #setupForBecomingSubpane: which does nothing. However, if we look at several other implementors, in particular, DropDownList, we see that it does several things. Most important for our issue is that it sets up the keyboard processor for the pane. In Pollock and Wrapper, the keyboard processor, besides forwarding keystrokes to panes that want to relate to them, also keeps a collection of all panes that are allowed to get focus. Well, actually, it keeps track of their controllers, but that's a detail we won't have to deal with.
When this method is called, the caller sends itself as a the parameter. The bottom line is that in order for a widget to get focus, it needs to have it's keyboard processor set up for it. Luckily, all we have to do is call another built in Pollock framework method to get that to happen. So, we create our own #setupForBecomingSubpane: like so:
Last line first, all panes always send #addedAsSubpane at the end of being set up. This facilitates any other panes on your window or form to listen when a pane is added, and possibly react. We'll actually use this later. For now, we can just call it part of the boiler plate.
The first part first asks if the pane is open. A pane is only open when it has been added to a window or other pane, and the window has been opened. It doesn't matter if that window is "iconized" or not. Just if it's been put on the screen. There is a reason we care about this. Pollock has a rule that you can send any pane augmentation message to any widget at any time, without regard to if the window that hosts the pane is open or not. There are some messages, like in our case, getting the keyboard processor, that aren't actually available until after the window is open!
That would put Pollock in a bind... if it didn't have a mechanism to deal with it. The mechanism is #configure:for:. What the #configure:for: messages do is capture a "message" that needs to be sent before a window is open, and then replay it just after the window has actually opened. As you see, it looks sort of like a perform method. The first parameter is the message to send. The second parameter is the eventual executor. In our case, we have to send an argument, so we use the #configure:for:argument: form, and give the last parameter the parameter that the eventual #setupKeyboardFor: needs. #setupKeyboardFor: is a Pollock framework method that takes care of making sure our widget gets properly registered with the keyboard processor so that it can get focus.
Where Pollock says "Any Message, Any Time", that is a responsibility of Pollock's widgets. Since we are creating a new widget, it becomes our responsibility. This is just the first of many that we'll come across in this series. Fortunately, as you see, Pollock makes it E-Z and not a burden. None the less: TANSTAAFL.
Ok, we'll open our test window again, and now press the button,... and ... Darn! Another error message! "Message not understood: #showMouseIndicator:"
This is a part of the ActionDisplay framework that was put in early, but will be refactored out early in 2005. Basically, when you click and hold the mouse over a Radio or CheckBox, in some looks, the circle or box respectively changes the interior color until you release. This turns out not to be needed for our Calendar, nor for other drop down widgets. No matter, we can clear that up easily because the Calendar itself never needs to track mouse up or mouse down events!:
Now if we press on the button, nothing bad happens. We'll leave it at that for now. We can run all of our tests and everything works.
The above is published as version 1.6 in the Package named "Pollock-Calendar" on the Cincom public repository.
You may notice, that while nothing bad happens, nothing good happens either. The Button in our Calendar doesn't react like a button, instead it just sits there when we press it. Next time we'll discuss the above track mouse methods, as well as make our button act like a button.