Chapter 25: Standard Window Menus
We’ve now covered two special types of menu – the iconbar menu and pop-up menu – but have yet to look at the simplest type: one that opens over a window following a click with the Menu button.
It’s now time for us to correct that omission, but first we will use some of our new-found knowledge to lay the foundations for our small application to do something useful. With the ability to select from a range of shapes, we could perhaps use it to do some simple geometric calculations.
Updating the templates
The first thing that we need to do is to update the window templates to include some new icons. Load !ExamplApp.Templates into your template editor, then open the “Main” window. As ever, we will be using WinEd in the description that follows.
We are going to add two new display fields, as seen in Figure 25.1. Expand the work area downwards, then drag a couple of display fields and labels across from the Icon picker into the positions shown.
If the work is done in WinEd, then the icons will be numbered 4 and 5 for the Sides display field and label respectively, then 6 and 7 for the Internal angles display field and label. These will be the numbers that we will use in our code below.
Both display field icons use the R validation command that we have met before, in Section 15.7 and Chapter 19, to create the 3D effect. Entering a default string of “000” into the Sides field ensures that WinEd configures a Max text length of 4 – allowing up to three digits and a terminator. In a similar way, the “00000000” in the Internal angles field ensures a length of 8 digits plus a terminator, or a total length of 9.
Close the window and save the changes to the templates file.
Adding a calculator
In order to be able to complete our two new fields, we will need some way to identify the number of sides for each of our shapes, and to calculate their internal angles. It’s usually a good idea to separate the code which does the actual work from the code which handles the user interface, so we will add another source file called c.calc and its associated header file h.calc. It will be necessary to update the OBJS list in the Makefile to include a reference to it.
OBJS = calc main menu ibar win
The new file will be a self-contained module for calculating simple geometry for the shapes that we are using. The h.calc header file can be seen in Listing 25.1, whilst the main code from c.calc can be seen in Listing 25.2. The new enum calc_shape will replace the enum win_shape which was previously defined in c.win.
The code starts with some global constants and variables, which between them provide storage for the current shape and formatting choices. There are two buffers: one to hold the most recently generated output text, and the other to hold a printf() format string.
The calc_initialise() function needs to be called from main_initialise() in order to set up the variables and buffers to some sensible defaults, after which calc_set_shape() and calc_set_format() can be used to choose the active shape and set the number of decimal places used when displaying numbers. The configured number of decimal places can be read back using the calc_get_places() function.
The other two functions are calc_get_sides() and calc_get_internal_angle(), which return a pointer to the output buffer having written a suitable string into it given the current shape. To avoid getting into geometric arguments, this will be “n/a” for the circle, and the calculated values for the triangle and square.
We can now modify the win_set_shape() function in c.win as shown in Listing 25.3, so that it calls a new win_update_all_calculations() function to make use of the new facilities.
The function now takes a parameter of the new enum calc_shape type, so the calls made to it will need to be updated accordingly.
When compiled and run, our application window should look similar to that shown in Figure 25.2. Choosing different shapes from the pop-up menu should result in the Sides and Internal angles fields updating to show appropriate values.
The full code of the changes can be found in Download 25.1.
A window menu
When adding our calculation facility, we included support for setting the number of decimal places to which values will be displayed. This doesn’t actually make a lot of difference for the shapes and data that we are able to show – although it would become relevant if we extended the shapes to include a heptagon, where the internal angle isn’s a round number of degrees. What we can’t do at present is change this value without recompiling our code.
An obvious solution would be to provide a menu which contains these options, so we will do just that. It’s possible to imagine further ‘behavioural’ parameters that we might wish to adjust in the future, so we will create a general menu for the window to hold them all. We can start by adding some constants to c.win which define the menu entries as seen in Listing 25.4 and a new global variable for the wimp_menu block pointer as in Listing 25.5.
The menu itself can then be defined in the win_initialise() function as shown in Listing 25.6, in a way which should now be familiar.
Now that we have the new menu defined, we can open it when the user clicks Menu over the window. So far, we have created functions to open menus over the iconbar and from pop-up menu fields, but do not yet have a way to open a menu at the mouse pointer. To rectify this, we can add another function to c.menu which will open a menu in the standard position, as shown in Listing 25.7.
As before, this simply defers to SFLib’s menus_create_standard_menu() function to display the menu. When opening a menu at the mouse pointer as a result of a Menu click, the process is a lot easier that the other situations that we have seen so far. The Style Guide requires that it is opened at the y coordinate of the click, and 64 OS units to the left of the x coordinate; the code that SFLib uses to achieve this can be seen in Listing 25.8.
Armed with the new menu_open() function, we can update the win_mouse_click() event handler as shown in Listing 25.9, so that Menu clicks anywhere over the window will cause the main menu to be opened.
Before the menu is opened, the win_set_menu() function in Listing 25.10 is called to place a tick against the currently selected choice of decimal places. This reads the configured number of places back from the calc module first.
The final piece of the jigsaw is the Menu_Selection event handler in win_menu_selection(), which can be seen in Listing 25.11. This sets the required number of decimal places in the calc module, before calling win_update_all_calculations() to update the display fields with the new settings. Finally win_set_menu() is called, so that the tick can be updated before the menu is reopened if Adjust was clicked.
If the code is compiled and the application run, the menu should appear as seen in Figure 25.3. Selecting different numbers of places from the menu should update the number in the Internal angles field, assuming that shape isn’t a circle!
The full code and templates can be found in Download 25.2.
Updating menus
Now that we have two menus in our window, both of which are opened following clicks handled in win_mouse_click(), observant readers might have noticed a difference between the way that the two are treated.
static void win_mouse_click(wimp_pointer *pointer) { if (pointer->i == WIN_ICON_SHAPE_POPUP && pointer->buttons == wimp_CLICK_SELECT) { menu_open_popup(win_shape_menu, pointer, win_shape_menu_selection); } else if (pointer->buttons == wimp_CLICK_MENU) { win_set_menu(); menu_open(win_menu, pointer, win_menu_selection); } }
Both menus currently contain three entries, of which one can be ticked at any given time. However, whilst both of the two Menu_Selection handlers – in win_menu_selection() and win_shape_menu_selection() – call their respective routines to update the ticks in case the menu is reopened by an Adjust click, this isn’t true of when the menus are initially opened.
Before the main menu, whose pointer is in win_menu is opened with a call to menu_open(), there is a call made to win_set_menu() first to ensure that the tick is in the correct place. However, when the pop-up menu whose handle is in win_shape_menu is opened, there is no set-up done before menu_open_popup() is called. Instead the code relies on the fact that win_set_shape_menu() was called by the win_initialise() function, and will be left up-to-date each time a selection is made.
There is actually a good reason for this in our example, because there is no way for the pop-up menu’s set routine to read the shape that is currently active. When the window is initialised or a menu selection made, the shape and the ticks are set to match each other and the calc module is informed of the change through the calc_set_shape() function. Since we haven’t defined a corresponding calc_get_shape() function, it wouldn’t be possible for win_set_shape_menu() to read the current shape back in the way that win_set_menu() does for the decimal places.
Which approach to use is largely down to the developer, and different situations may well suggest different solutions. In general, your author would probably recommend using the approach taken by win_set_menu() and setting all of the ticks, shadings and other configurable aspects of a menu each time it is opened and reopened on screen. This keeps all of the associated code in one place, and can be much easier to maintain than if each separate routine updates the parts of the menu that it uses directly.
In addition, as we will see in the next chapter, this is the approach that SFLib supports with its own menu handling.