RISCOS.com

www.riscos.com Technical Support:
Toolbox

 


Building an application


This chapter describes how an application (!Hyper, which can be found in the Examples directory) was designed with Acorn C/C++. In particular it demonstrates how using !ResEd and !ResTest can lead to very short design times. The first section describes how to use !Hyper, and the second section is a description of how it was designed and implemented.

Guide To Hyper

!Hyper is a multi-document viewer for HCL files (see HyperCard Control Language for the syntax). HCL files define stacks of cards allowing multiple Draw objects to be linked such that a user may click on active areas (called hot spots) of a viewer to navigate between different cards. Only one card from a stack is visible at any time in a viewer, although being multi-document, !Hyper may display several views onto the same stack, each of which may be displaying a different card.

!Hyper is started by double-clicking on its application icon or by double clicking on an HCL file (but only after !Hyper has been seen by the Filer).

Application icon menu

Clicking Menu over the application icon will display the following menu:

TOOLBOXEXAMPLES-2.PNG

Info leads to a standard program information dialogue box.

Show stack allows any closed viewers to be reopened or brings to the top an already opened one.

Delete stack will remove it from memory.

Note that if no stacks have been loaded then the show stack/delete stack will be greyed out.

Quit will exit the application.

Once a stack has been loaded, !Hyper will open a viewer displaying the 'Home Card' of that stack. For example:

TOOLBOXEXAMPLES-3.PNG

The user can move from one card to another by clicking on hotspots. Hot Spots will usually be identifiable in some way, though !Hyper will change the pointer shape whilst it is over one. It is also possible to jump to the Home Card or back to the previous card by clicking on the action buttons in the status area at the bottom of the window.

Pressing menu over a viewer window will display the following menu:

TOOLBOXEXAMPLES-4.PNG

This allows various operations to be performed on the stack being displayed:

File Info displays information about the file.

Scale View leads to a standard scale dialogue box which lets the user zoom in and out on a card.

Find Keyword allows searching for keywords that are stored in the stack. This allows an index type search to be applied.

Print... allows the current card to be printed.

Status Line controls whether or not the status area is to be displayed at the bottom of the viewer window.

Keyboard Short-cuts

Clicking in a viewer gives it the keyboard input focus. This then allows various keyboard short-cuts to work. The standard keys for Find Keyword, Scale View, File Info and Print... all work (as can be seen from the menu, pictured above) as well as p and h for previous and home.

How !Hyper was designed

It is worth having !Hyper at hand whilst reading this section. Loading its resource file into !ResEd and !ResTest will make it easier to see the various linkages between objects and observe the events that are raised when interacting with the user interface. The chapters later in this manual give full information on each of the classes involved.

Requirements

Before designing the structure of !Hyper we had to decide what it must be able to do. We wanted to design a HyperCard-type application with the following features:

  • multi-document capability
  • navigation between cards (based around Draw files) using hotspots
  • home/previous facility
  • keyboard driven option
  • suitable for range of screen modes/scalable output
  • easily extendible
  • easy to make a demo version
  • find capability
  • ability to print a card
  • maintain history of all loaded cards.
Design decisions

From the required features, we made the following design decisions.

Shared objects and client handles

The multi-document support suggested the use of shared objects and the use of client handles for maintaining what file the viewer was showing. By doing this we would reduce memory usage (by just having one copy of the shared menus and dialogues) without complicating the association between events on a menu and the viewer that it was opened from.

Event driven interface

Given that we wanted to extend and modify the interface easily, we decided to make it event driven as opposed to object driven. In other words when registering event handlers, we register for specific event numbers, rather than a generic event (e.g. ActionButton_Selected) on a specific component of an object. In this way we are able to modify the interface (e.g. reorder a menu or even move menu entries off onto a submenu) without having to change the code.

AboutToBeShown events

We also decided to take advantage of a number of features offered by the toolbox such as the 'About To Be Shown' events. These made it possible to set up dialogue boxes as they were being shown, and not have to update them constantly as other parts of the application altered data. A less obvious benefit of this mechanism is that since the toolbox tells us the object id of what is being shown, we do not have to remember this ourselves, and in fact it is possible to let the toolbox automatically create such objects.

A good example of this is the Program Information box. This is created by the toolbox as a side effect of creating the iconbar (which is created on initialisation due to it having its AutoCreate bit set). We then just need to register for the ProgInfo_AboutToBeShownEvent and in our handler set the version string from our message file.

Standard objects

To be Style Guide compliant (and to make less work for ourselves) we can use the standard PrintDbox, Scale, ProgInfo and FileInfo object templates supplied by the Toolbox.

Keyboard short-cuts

As we want !Hyper to be keyboard drivable, we can make use of the Toolbox's keyboard short-cuts facility.

How !Hyper was implemented

The rest of this chapter takes you through the stages involved in implementing !Hyper. It breaks down into the following sections:

Creating and testing a simple resource file for !Hyper

The first stage in implementing !Hyper was to create and test a very simple resource file consisting of an IconBar object template, a Menu object template for the iconbar icon, and a ProgInfo object template.

Creating a basic resource file
  1. We began by starting the resource file editor (ResEd - described in the chapter ResEd), and then opened a new resource file display. Next we opened an object prototypes window and dragged an IconBar object template, menu and ProgInfo object template to our empty resource file:

    TOOLBOXEXAMPLES-5.GIF

  2. Next we double-clicked on the ProgInfo object template in the resource file display. This opened its properties box and we entered the information we wanted to appear in this box. We also switched on Deliver event Before showing:

    TOOLBOXEXAMPLES-6.PNG

  3. Then we edited the Menu object template in the resource file display and renamed it to IbarMenu. Next we double-clicked on IbarMenu and created two menu entries. The first entry we named Info, and the second entry Quit.

    The Info entry we edited to include a submenu option to display the ProgInfo object template:

    TOOLBOXEXAMPLES-7.GIF

    The Quit entry was edited to return a particular event:

    TOOLBOXEXAMPLES-8.PNG

    As we could choose our own events, the choice of 82a91 may seem strange. However, this is the same event that is generated by the Quit dialogue class, hence if we added editor features and required a quit confirmation, we could still use the same handles.

  4. Finally we edited the Iconbar object template. We set up the sprite name, inserted some Help text, and dragged IbarMenu to the Menu button option:

    TOOLBOXEXAMPLES-9.GIF

Using ResTest to check the resource file

To test out this initial design we dragged the resource file from !ResEd to !ResTest's iconbar icon (ResTest is described on ResTest). As we had set the AutoCreate and AutoShow options for the iconbar object template, it appeared immediately on the iconbar. Pressing Menu over the icon opened our menu (IbarMenu) with the Quit and Info options. Sliding the mouse pointer over the submenu arrow opened the ProgInfo box:

TOOLBOXEXAMPLES-10.PNG

Clicking on !ResTest's iconbar icon opened its Event Log window. We could now see what events were being raised when we tested the interface:

TOOLBOXEXAMPLES-11.PNG

Coding

We could now start writing some code. Being event driven, we decided to use eventlib. Our initial code merely consisted of initialising the Toolbox and eventlib and then registering our handlers. At this point we just needed some quit handlers (for the event generated by the Quit menu option and for the Wimp messages) and a handler to fill in the version string on the ProgInfo box.

Note the use of wimplib to provide easy access to the Wimp SWIs.

(from main.c)

static void app_init(void)
{
  /* initialise as a toolbox task */
  _kernel_oserror *e;

  if ((e = toolbox_initialise(0, 310, messages, tbcodes, "<Hyper$Dir>", &mbl,
                              &idblk, 0, 0, 0)) != NULL) {
    wimp_report_error(e, 0, 0, 0, 0, 0);
    exit(1);
  }

  /* initialise event lib */

  event_initialise(&idblk);

  /* not interested in nulls or keypresses- the toolbox handles all our
     keyboard shortcuts */

  event_set_mask(1 + 256);

  /* register events */

  event_register_message_handler(Wimp_MQuit, quit_handler, 0);
  
  event_register_toolbox_handler(-1, Quit_Quit, tbquit_handler, NULL);
}

(from handler.c)

int tbquit_handler(int event_code, ToolboxEvent *event, IdBlock *id_block,
                   void *handle)
{
  IGNORE(event);
  IGNORE(event_code);
  IGNORE(handle);
  IGNORE(id_block);
  
  quit = 1;
  return 1;
}

int quit_handler(WimpMessage *message, void *handle)
{
  IGNORE(message);
  IGNORE(handle);
  
  quit = 1;
  return 1;
}

int proginfo_show(int event_code, ToolboxEvent *event, IdBlock *id_block,
                  void *handle)
{
  IGNORE(handle);
  IGNORE(event);
  IGNORE(event_code);

  proginfo_set_version(0, id_block->self_id, lookup_token("Version"));
  return 1;
}

File loading

Next we turned our attention to file loading. This involved coping with DataOpen messages on HCL files and files that are dragged to the iconbar icon. To do this we registered some more Wimp message handlers.

(from main.c)
  event_register_message_handler(Wimp_MDataOpen, file_loader, 0);
  event_register_message_handler(Wimp_MDataLoad, file_loader, 0);

(from file.c)
int file_loader(WimpMessage *message, void *handle)
{
  
  WimpMessage msg;
  IGNORE(handle);

  /* only interested in HCL files */
  if (message->data.data_open.file_type != 0xFAC)
    return 0;

  msg = *message;
  msg.hdr.your_ref = msg.hdr.my_ref;
  load_hcl_file(msg.data.data_load_ack.leaf_name);
  if (message->hdr.action_code == Wimp_MDataLoad)
    msg.hdr.action_code = Wimp_MDataLoadAck;
  wimp_send_message(Wimp_EUserMessage,&msg, msg.hdr.sender,0,0);

  return 1;
}

Handling views

Now it was time to open a viewer onto a file. This involved going back to our resource file and adding some more object templates:

  • a window object template to view the files in, which we called HyperViewer
  • a menu to be shown on the viewer, which we called ViewerMenu
  • attached to this menu a FileInfo box, a Scale box and a PrintDbox object template.

The dialogue box for FileInfo we filled in as follows (note that we switched on Deliver event Before showing):

TOOLBOXEXAMPLES-12.PNG

The dialogue box for Print we filled in as follows:

TOOLBOXEXAMPLES-13.PNG

We changed the default values in the dialogue box for Scale as follows:

TOOLBOXEXAMPLES-14.PNG

We then edited ViewerMenu, dragging the above three object templates to the Show object options in the appropriate Menu entry properties boxes.

For example, the Scale View Menu entry properties box:

TOOLBOXEXAMPLES-15.PNG

Having filled in all three menu entries, we then edited the HyperViewer window object template. We dragged ViewerMenu to the Show menu field, and filled in the other window properties boxes as appropriate.

Note that, to receive redraw events, we switched off the Auto-redraw flag in the Other properties dialogue in the HyperViewer window. This will affect the appearance in !ResTest and so, for the purposes of this demonstration, is left on.

Our resource file display now looked like this:

TOOLBOXEXAMPLES-16.PNG

After connecting them we dragged the resource file to !ResTest. Our icon appeared on the iconbar as before, but now when we pressed Menu over !ResTest's icon and looked at the Create submenu, we saw all the new object templates that we added.

TOOLBOXEXAMPLES-17.PNG

We then clicked on HyperViewer to create a viewer. This also unfaded the Show option and allowed us to go into the Show submenu and see all the object ids that had been created:

TOOLBOXEXAMPLES-18.PNG

The Show submenu has three columns:

  • the first indicates (via a tick) whether the object is showing
  • the second is the unique identifier for a particular object - called the object id
  • the third is the name of the template from which it was created.

When we clicked on the HyperViewer entry in the Show submenu the viewer was displayed on the screen. As a side effect of the creation the menu tree for the viewer was created as well. Pressing Menu over the viewer displayed the menu as one would expect:

TOOLBOXEXAMPLES-19.PNG

Moving the pointer over the submenu arrows displayed the File Info and Scale View dialogue boxes:

TOOLBOXEXAMPLES-20A.PNG

TOOLBOXEXAMPLES-20B.PNG

Clicking on Print ... displayed the Print dialogue persistently:

TOOLBOXEXAMPLES-21.PNG

The code to support these new features can be found in the C files under the !Hyper directory of the examples. As with the code fragments above, they take the form of registering a handler for a specific event in app_init (e.g. FileInfo_AboutToBeShown) and then handling the event elsewhere. Note that the print code is an essentially standard print job/render loop, differing only in that it uses the DrawFile module to do the rendering. See print.c for more information on this.

For the viewer (see view.c) we create a window object from a template (called HyperView, as seen in the !ResTest menu) and attach various handlers to cope with RedrawRequests and CloseWindow requests. Note that there is no need to register for OpenWindow requests as this is done on our behalf by the toolbox (as we set the AutoOpen bit of the window's template). We also register for mouse click events on the window. The relevant handler (click_viewer) sets input focus to the window and if applicable jumps to a new card.

Redraw handler

The redraw handler (in draw.c) is a standard Wimp redraw handler that uses the DrawFile module to render into the window. Note that the DrawFile module is a generic renderer (i.e. not Wimp specific) and so needs absolute coordinates and a transformation matrix. We use the latter in the simplest sense - just as a way of scaling the Draw files.

Scaling

The scaling is set whenever the user clicks scale on the Scale box. If you have the !ResTest Event log window open with the Resource file loaded, you will see that a 'Scale_ApplyFactor' event is generated. We use this in a handler (in draw.c) to adjust the transformation matrix.

TOOLBOXEXAMPLES-22.GIF

The object id for the ancestor of the Scale_ApplyFactor event in this example is &187CEF0. This equates to the object id of HyperViewer (as shown in the Show submenu). This is because the viewer is the ancestor of this menu. The usefulness of this becomes apparent when more than one viewer object is shown.

Implementing hotspots

To implement the hotspots on a view, we add gadgets (components of a Window Object) to our viewer window. We use the simplest gadget type, a button gadget, which is quite close in functionality to a Wimp icon (see button.c). Rather than hard code the definition of the gadget into the code, Window_ExtractGadgetInfo is used to get the basic gadget definition from a window template called 'Properties'.

Linking the data structures

Not surprisingly, we link all the data structures for the loaded files together on a linked list. However, we do not need to search down this list every time an event happens: by using client handles (see view.c) we can attach the address of the relevant structure to an object. In this way, when we get a redraw event, we just find out the client handle of the viewer on which it happened and can determine what Draw files are to be rendered.

This also works for the menu tree; even though we are sharing the menu tree amongst all the open views, the IdBlock that initialised the toolbox is filled in with the ancestor of the tree. In Hyper, that will be a viewer (we set the Ancestor bit of the HyperView template). So, for example, when we receive a Scale_ApplyFactor event (as in Scaling), the ancestor is the viewer that leads to the scale object being shown. This also applies to PrintDboxes, even though they are shown persistently.

Showing and hiding views

As we want a history of all views, we build a 'Views' submenu which will be off the icon bar menu. In common with other applications we want the ability to show a view and remove one from memory. In both cases the list of views is the same. This allows us to take advantage of shared objects again. We just need one menu that we build up entry by entry and make this a submenu of the 'Remove View' and 'Show View' entries that are added to the iconbar menu. When an event happens on this menu, we just need to find out the parent component (from the IdBlock) to determine whether we are removing or showing a view. We can also use another useful toolbox feature, in that it is the client that chooses the component ids. This means we can choose the address of the structure that defines a view as its component id - allowing very easy association between the menu entry and the view it refers to. Note that by having an about to be shown event enabled for the iconbar menu, it was possible to fade or unfade the 'Show view' and 'Remove view' entries as required (simply by checking whether our linked list was NULL).

Adding keyboard short-cuts

With the interface beginning to stabilise, it was possible to start adding some of the keyboard short-cuts. These were generally decided by the Style Guide (e.g. F11 for scale), though some aspects of the interface required keys specific to Hyper (e.g. previous and home) to generate events. All this was handled through !ResEd (using the keyboard short-cuts option from the window object template menu) without any additional code requirement.

TOOLBOXEXAMPLES-23.PNG

Adding a status bar

A status bar was also provided by creating a Toolbar containing a button gadget:

TOOLBOXEXAMPLES-24.PNG

This Toolbar object template was then dragged to the Toolbars dialogue box from the HyperViewer window:

TOOLBOXEXAMPLES-25.PNG

By using an internal bottom left toolbar, the parent window could be resized whilst still allowing the status to be visible. Previous and home action buttons were added (generating the same event codes as the keyboard short-cuts, so no additional code was required) as well.

TOOLBOXEXAMPLES-26.PNG

To control the visibility of the status bar, a menu entry (and appropriate keyboard short-cut) was added that would tick according to whether the status was showing. The handler for this is in handler.c. Note that since the status is on a per-viewer basis, we need to know when the viewer menu is opened (and over what viewer) to determine whether the option should be ticked or not.

Adding a find capability

Finally, to provide a find capability, a custom dialogue was designed using !ResEd starting from a basic Window and adding gadgets from the gadgets window:

TOOLBOXEXAMPLES-27.GIF

The properties dialogues for the two action buttons were:

TOOLBOXEXAMPLES-28.GIF

The Next action button was made the default and assigned a specific event code.

The Home Card radio button properties dialogue was filled in as follows (this radio button was specified as the selected radio button):

TOOLBOXEXAMPLES-29.PNG

The Current Card radio button properties dialogue was edited to be similar to the Home Card radio button, except that it was not specified as the selected radio button.

The Keyword writable field properties dialogue was filled in as follows:

TOOLBOXEXAMPLES-30.PNG

After choosing suitable components and event codes, the handler code can be written in a self contained unit.

Modifying the interface

One of the original requirements was that it should be easy to modify the interface to !Hyper. By taking an event driven approach, it is possible to make significant changes to the User Interface, without altering the code. Alternatively, when adding new functionality, this can be done in a modular fashion by adding the required handlers and registering them when required.

Adding an export DrawFile facility

As an example, consider adding an export DrawFile facility. This would allow saving away the Draw files that make up the card on show in the viewer. The best way to implement this would be:

  • add a new submenu to the main menu, and call this new submenu File
  • create two menu entries in this submenu; the first entry will replace the FileInfo menu entry currently on the main menu; the second entry would provide an export facility (implemented using a simple SaveAs dialogue).

This can be achieved easily by some very simple editing of the resource file:

  1. Drag a Menu object template from the Object prototype window to the resource file, and rename the object template to FileMenu.
  2. Edit ViewerMenu and add a new menu entry to it:

TOOLBOXEXAMPLES-31A.PNG

TOOLBOXEXAMPLES-31B.PNG

Now edit the new menu entry and rename it to File. Then drag the new menu object template FileMenu to the Show object option:

TOOLBOXEXAMPLES-32.PNG

  1. Next double-click on the FileMenu object template. Rename the title File, and then Shift-drag the File Info menu entry from ViewerMenu to it. To make the copied menu entry Style Guide compliant rename it to Info:

TOOLBOXEXAMPLES-33.GIF

Moving the File Info menu entry from ViewerMenu to the new File submenu is a very simple way of relocating this menu option from one menu to another. As we rely on the FileInfo_AboutToBeShown event, it doesn't matter where it is in the interface; it will still work.

  1. Now drag a SaveAs object template from the Object prototype window to the resource file. Edit this object template to specify that the filetype should be DrawFile:

TOOLBOXEXAMPLES-34.PNG

  1. Finally return to the File menu and create an Export menu entry (by renaming the default entry title Menu Entry to Export). Edit this entry and drag the SaveAs object template to the Show object option:

TOOLBOXEXAMPLES-35.PNG

The final submenu should now appear as follows:

TOOLBOXEXAMPLES-36.PNG

The code for the export facility would consist of registering for the various toolbox events and then handling them in a separate area of the code.

If you now dragged the resource file to ResTest, you would see:

TOOLBOXEXAMPLES-37A.PNG

TOOLBOXEXAMPLES-37B.PNG

Other possible modifications

By this time the viewer menu could begin to get cluttered. It would then be very easy to drag off some of the entries to a separate 'Utilities' submenu. Again, being event driven and remembering that the handlers operate on the Ancestor of the menu tree, they will continue to work without code alteration.

Making a demo version of Hyper could be achieved by removing or fading parts of the interface with !ResEd.

Client Events

A number of events were used in Hyper that were 'Client specified'. These are listed here to help understand properties and output in !ResEd and !ResTest.

Event number Usage
&101 Go to Home card
&103 Go to previous card
&150 Start find operation
&151 Iconbar menu is about to be shown
&900 Viewer menu is about to be shown
&901 Toggle status bar

Other standard events were enabled for dialogues being shown, Print etc.

Summary

This chapter has demonstrated the following features of the toolbox:

Toolbox feature see section/file
shared objects and client handles Shared objects and client handles
About to be shown events AboutToBeShown events
adding and removing gadgets at run-time button.c (see Implementing hotspots)
creating objects from a template view.c (see Redraw handler)
auto creation AboutToBeShown events
the Draw file renderer draw.c (see Redraw handler)
event handling with eventlib Coding
Menu handling Creating a basic resource file
keyboard short-cuts Adding keyboard short-cuts
client specified events and component ids Showing and hiding views

HyperCard Control Language

HyperCard Control Language (HCL) is used by !Hyper to control which draw files are displayed to the user and when jumps should be made to new cards. It is beyond the scope of this example to describe an editor, so the following section is provided to describe the commands that are used.

HCL commands

All card definitions are enclosed within start and end directives:

!!start 
name

...
!!end

where name is cardXXXXXXXX, XXXXXXXX being an 8 digit hex number.

Other commands are as follows:

Command Action
button bbox name sets up a hotspot at the given position and sets its behaviour to go to the named card when clicked on
clear removes all buttons and Draw files from the viewer window
colour n sets the background colour to the given decimal value
gosub name allows 'inclusion' of common functionality
goto name allows common ending of cards
keyword string sets keyword(s) for this card - allows searching with the find dialogue box
load file loads a file into the bottom layer - overlay will do this if it follows a clear
overlay file loads a draw file into the next available layer
stack string sets the name of this stack to the given string. This will appear in the iconbar menu
status string changes the status line to the given string
title string sets the title bar to the given string

There are also a number of commands that are only used by an editor. These are not described here as they are not required by !Hyper.

This edition Copyright © 3QD Developments Ltd 2015
Last Edit: Tue,03 Nov 2015