8 Adding a Palette

[2kb 66x192 GIF] 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]