To illustrate the use of multiple Canvasses, the application can be
elaborated a bit: Some users may find it annoying to remember to hold
down the Shift modifier to be able to create rails. Except for the
Shift-modification, the application as described above is
'mode-less', i.e., a given user action always results in the
same action in the application. As an alternative, the application can
be changed, so that it may be put into different modes. To control
what is the current mode, a Palette can be
put into a separate window. This Palette could look like the one shown
to the right. The item with the arrow, indicates
'Move-mode',
where all that can be done is moving the
Stations around. The second item contains a Station, and denotes
'Station-mode', where all that can
be done is creation of Stations. The third item contains a Rail, and
denotes 'Rail-Mode', where all that
can be done is creation of Rails. Finally the last item is used to
quit the application, using the mouse. In the Bifrost library, a
pattern exists, which is intended to make such palettes out of
graphical objects. The pattern is in ~beta/bifrost/Palette, which the
application thus needs to INCLUDE. Then, in subway7.bet,
the Palette may be specified in the following
way:
(* Constants corresponding to palette selections *) MoveMode: (# exit 1 #); StationMode: (# exit 2 #); RailMode: (# exit 3 #); Quit: (# exit 4 #); PaletteWindow: @window (# Mode: @Palette (# changed:: (# do (if selection=Quit then Terminate if); #); open:: (# G: @GraphicalObject; T: @GraphicText; L: @myCanvas.Rail; S: @myCanvas.Station; do G.init; ( 0, 32)->G.theShape.open; ( 0, 10)->G.theShape.lineto; ( 5, 15)->G.theShape.lineto; ( 11, 0)->G.theShape.lineto; ( 15, 2)->G.theShape.lineto; ( 9, 16)->G.theShape.lineto; ( 16, 16)->G.theShape.lineto; G.theShape.close; blackpaint[]->G.setpaint; G[]->append; ((0,0), 'S')->S.init; S[]->append; L.init; ((0,0), (50,30))->L.coordinates; L[]->append; T.init; ((100,20),Helvetica,Italic,20,false,'Quit') ->T.inittext; blackpaint[]->T.setpaint; T[]->append; StationMode->selection; (size, (4,4)) -> AddPoints -> palettewindow.size; #); #) (* Mode *); open:: (* PaletteWindow *) (# s: @point; do hide (* initially invisible *); false->paletteOpen; (NONE, 80, 50, true)->Mode.open; 'Mode'->title; myCanvas.size->s; (myCanvas.position, (s.x, 0)) ->AddPoints -> position; #); #);
First a standard Lidskjalv window called PaletteWindow is created
to contain the Palette. In the Palette, which is called Mode, open
sets up the contents of the Palette. Mode.open is called from
PaletteWindow.open. It enters a position - (10, 10) -,
the size of each field in the Palette - 80 times 50, and finally a
boolean spcifying if the sequence of graphical
objects should be placed next to each other
(a horizontal Palette),
or below each other
(a vertical Palette),
which is chosen here. The window is given a
title 'Mode', and is placed next to the window containing
the subway system.
In Mode.open the four graphical objects are specified and appended: First the arrow is made as an instance of GraphicalObject. In a GraphicalObject, the Shape may be directly manipulated. Here the shape definition language is used to add the line Segments, that define the outline of the arrow. Then blackpaint, which is a local attribute of Palette, is specified as the Paint, and finally the GraphicalObject is appended to the Palette. Likewise instances of Station, Rail, and GraphicText are initialized and appended. Finally the initial selection of the Palette is set. The selection attribute of a Palette is an integer holding the number of the currently selected item of the Palette. The number of an item is set to the current number of items after the item has been appended to the Palette. To improve the readability of the program four constant patterns have been defined, exiting the integers corresponding to the selection of the Palette.
The Palette must then be opened. An appropriate place to do this is in when opening the main window:
open:: (# do (* Initialize colors for Stations and Rails *) color.init; IndianRed->color.name; fill.init; PaleGreen->fill.name; (* Open the BifrostCanvas *) myCanvas.open; (* Open the Palette *) palettewindow.open; 'Type ''P'' to open the Palette'->putline; #)
Notice, that because hide is called in PaletteWindow.open, the window is not immediately shown when opened: It should be possible to open and close the Palette when the user wants to. The application should behave like before when the Palette is not shown, i.e., Rails are created by Shift-clicking a Station and there are no other modes. But when the Palette is shown, it should define the current mode, as described above. The state of the Palette can be toggled when the user types a 'P':
paletteOpen: @boolean; onKeyDown:: (# do (if ch //'Q' then Terminate //'P' then (if not paletteOpen then palettewindow.show; else palettewindow.hide; if); not paletteopen -> paletteopen; if) #);
The current state of the Palette, i.e., shown or hidden, is determined by the boolean paletteOpen. This boolean is then used in onMouseDown to determine what to do.
onMouseDown:: (# (* Actions for Stations *) StationAction: (# s: ^Station enter s[] do INNER #); MoveIt: StationAction (# do (s[],mousepos,NoModifier)->interactivemove #); MakeRail: StationAction (# do s[]->interactiveCreateRail #); (* Control pattern for finding a station and * performing an action on it. *) findStation: (# s: ^Station; action: ##StationAction; enter action## do (* Find out what was hit - if any *) scan: thePicture.ScanGOsReverse (# do (if (myCanvas[], mousepos)->go.containspoint then (if go[] // map[] then (* ignore *) else (if go##=Station## then (* We hit a station *) go[]->s[]; (if action##<>NONE then s[]->action if); leave scan if); if); if); #); exit s[] #); hitstation: ^Station; do mousepos->devicetocanvas->mousepos; (if paletteOpen then (* Palette determines mode *) (if palettewindow.mode.selection // MoveMode then MoveIt##->findStation; // StationMode then mousepos->makeStation; // RailMode then MakeRail##->findStation; if); else (* Mode-less *) (if findStation->hitstation[] // NONE then mousepos->makeStation; else (* We hit a station *) (if shiftmodified then hitstation[]->MakeRail; else hitstation[]->MoveIt; if); if); if); #);
If the Palette is open, as mentioned, Mode.selection determines the current mode and otherwise the behavior should be as before. Because of the more complex control structure, a slightly different approach is taken: There are several places in the code, where it should be known whether a Station was hit or not. A general control pattern findStation is defined. This will search for a Station that is hit, in the same way as before. If a hit Station is found, an action can be performed on it. This action is specified to findStation by using a pattern reference. Using findStation, the control structure becomes much shorter:
If the Palette is not open, the behavior should be as before. As can be seen the old behavior can also be specified using findStation and the action patterns.
The use of pattern variables for the action in findStation could just as well be changed to normal object references (qualified by StationAction). This is mostly a matter of taste.
| The Bifrost Graphics System - Tutorial | © 1991-2002 Mjølner Informatics |
[Modified: Tuesday October 24th 2000 at 15:07]
|