home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Crawly Crypt Collection 1
/
crawlyvol1.bin
/
program
/
books
/
progem
/
menus.7
< prev
next >
Wrap
Text File
|
1986-11-01
|
24KB
|
532 lines
Permission to reprint or excerpt is granted only if the following line
appears at the top of the article:
ANTIC PUBLISHING INC., COPYRIGHT 1986. REPRINTED BY PERMISSION.
Professional GEM by Tim Oren
Column #7 - Menu Structures
HAPPY NEW YEAR!
This is article number seven in the ST PRO GEM series, and the
first for 1986. In this installment, I will be discussing GEM menu
structures and how to use them in your application. There is also a
short Feedback response section. You will find the download file
containing the code for this column in the file GEMCL7.C in DL3 of the
ATARI16 SIG (PCS-58).
MENU BASICS
In ST GEM, the menu consists of a bar across the top of the screen
which displays several sub-menu titles. Touching one of the titles
causes it to highlight, and an associated "drop-down" to be drawn
directly below on the screen. This drop-down may be dismissed by
moving to another title, or by clicking the mouse off of the
drop-down.
To make a selection, the mouse is moved over the drop-down. Each
valid selection is highlighted when the mouse touches it. Clicking
the mouse while over one of these selections picks that item. GEM
then undraws the drop-down, and sends a message to your application
giving the object number of the title bar entry, and the object number
of the drop-down item which were selected by the user. The selected
title entry is left highlighted while your code processes the
request.
MENU STRUCTURES
The data structure which defines a GEM menu is (surprise!) an
object tree, just like the dialogs and panels which we have discussed
before. However, the operations of the GEM menu manager are quite
different from those of the form manager, so the internal design of
the menu tree has some curious constraints.
The best way to understand these constraints is to look at an
example. The first item in the download is the object structure
(only) of the menu tree from the GEM Doodle/Demo sample application.
The ROOT of a menu tree is sized to fit the entire screen. To
satisfy the visual hierarchy principle (see article #5), the screen is
divided into two parts: THE BAR, containing the menu titles, and THE
SCREEN, while contains the drop-downs when they are drawn. Each of
these areas is defined by an object of the same name, which are the
only two objects linked directly below the ROOT of a menu tree. You
will notice an important implication of this structure: the menu
titles and their associated drop-downs are stored in entirely
different subtrees of the menu!
While examining THE BAR in the example listing, you may notice
that its OB_HEIGHT is very large (513). In hexadecimal this is
0x0201. This defines a height for THE BAR of one character plus two
pixels used for spacing. THE BAR and its subtree are the only objects
which are drawn on the screen in the menu's quiescent state.
The only offspring object of THE BAR is THE ACTIVE. This object
defines the part of THE BAR which is covered by menu titles. The
screen rectangle belonging to THE ACTIVE is used by the GEM screen
manager when it waits for the mouse to enter an active menu title.
Notice that THE ACTIVE and its offspring also have OB_HEIGHTs with
pixel residues.
The actual menu titles are linked left to right in order below THE
ACTIVE. Their OB_Xs and OB_WIDTHs are arranged so that they
completely cover THE ACTIVE. Normally, the title objects are typed
G_TITLE, a special type which assures that the title bar margins are
correctly drawn.
THE SCREEN is the parent object of the drop-down boxes themselves.
They are linked left to right in an order identical with their titles,
so that the menu manager can make the correct correspondence at
run-time. The OB_X of each drop-down is set so that it is positioned
below its title on the screen.
Notice that it is safe to overlap the drop-downs within a menu,
since only one of them will be displayed at any time. There is one
constraint on the boxes however: they must be no greater than a
quarter screen in total size. This is the size of the off-screen blit
buffer which is used by GEM to store the screen contents when the
drop-down is drawn. If you exceed this size, not all the screen under
the drop-down will be restored, or the ST may crash!
The entries within a drop-down are usually G_STRINGs, which are
optimized for drawing speed. The rectangles of these entries must
completely cover the drop-down, or the entire drop-down will be
inverted when the mouse touches an uncovered area! Techniques for
using objects other than G_STRINGs are discussed later in this column.
The first title and its corresponding drop-down are special. The
title name, by custom, is set to DESK. The drop-down must contain
exactly eight G_STRING objects. The first (again by custom) is the
INFO entry, which usually leads to a dialog displaying author and
copyright information for your application. The next is a separator
string of dashes with the DISABLED flag set. The following six
objects are dummy strings which GEM fills in with the names of desk
accessories when your menu is loaded.
The purpose of this description of menu trees is to give you an
understanding of what lies "behind the scenes" in the next section,
which describes the run-time menu library calls. In practice, the
Resource Construction Set provides "blank menus" which include all of
the required elements, and it also enforces the constraints on
internal structure. You only need to worry about these if you modify
the menu tree "on-the-fly".
USING THE MENU
Once you have loaded the application's resource, you can ask the
AES to install your menu. You must first get the address of the menu
tree within the resource using:
rsrc_gaddr(R_TREE, MENUTREE, &ad_menu);
assuming that MENUTREE is the name you gave the menu in the RCS, and
that ad_menu is a LONG which will receive the address. Then you call
the AES to establish the menu:
menu_bar(ad_menu, TRUE);
At this point, the AES draws your menu bar on the screen and animates
it when the user moves the mouse into the title area.
The AES indicates that the user has made a menu selection by
sending your application a message. The message type is MN_SELECTED,
which will be stored in msg[0], the first location in the message
returned by evnt_multi().
The AES also stores the object number of the selected menu's title
in msg[3], and the object number of the selected menu item in msg[4].
Generally, your application will process menu messages with nested C
switch statements. The outer switch will have one case for each menu
title, and the inner switch statements will have a case for each entry
within the selected menu. (This implies that you must give a name to
each title and to each menu entry when you create the menu in the
RCS.)
After the user has made a menu selection, the AES leaves the title
of the chosen menu in reverse video to indicate that your application
is busy processing the message. When you are done with whatever
action is indicated, you need to return the title to a normal state.
This is done with
menu_tnormal(ad_menu, msg[3], TRUE);
(Remember that msg[3] is the title's object number.)
When your application is ready to terminate, it should delete its
menu bar. Do this with the call:
menu_bar(ad_menu, FALSE);
GETTING FANCY
The techniques above represent the bare minimum to handle menus.
In most cases, however, you will want your menus to be more
"intelligent" in displaying the user's options. For instance, you can
prevent many user errors by disabling inappropriate choices, or you
can save space on drop-downs by showing only one line for a toggle and
altering its text or placing and removing a check mark when the state
is changed. This section discusses these and other advanced
techniques.
It is a truism of user interface design that the best way to deal
with an error is not to let it happen in the first place. It many
cases, you can apply this principle to GEM menus by disabling choices
which should not be used. If your application uses a "selection
precedes action" type of interface, the type of object selected may
give the information needed to do this. Alternately, the state of the
underlying program may render certain menu choices illegal.
GEM provides a call to disable and re-enable menu options. The
call is:
menu_ienable(ad_menu, ENTRY, FALSE);
to disable a selection. The entry will be grayed out when it is
drawn, and will not invert under the mouse and will not be selected by
the user. Substituting TRUE for FALSE re-enables the option. ENTRY is
the name of the object which is being affected, as assigned in the
RCS.
Note that menu_ienable() will not normally affect the appearance
or operation of menu TITLE entries. However, there is an undocumented
feature which allows this. If ENTRY is replaced by the object number
of a title bar entry with its top bit set, then the entire associated
drop-down will be disabled or re-enabled as requested, and the title's
appearance will be changed. But, be warned that this feature did not
work reliably in some early versions of GEM. Test it on your copy of
ST GEM, and use it with caution when you cannot control the version
under which your application may run.
It is also possible to disable menu entries by directly altering
the DISABLED attribute within the OB_STATE word. The routines
enab_obj() and disab_obj() in the download show how this is done.
They are also used in set_menu(), which follows them immediately.
Set_menu() is a utility which is useful when you wish to
simultaneously enable or disable many entries in the menu when the
program's state changes or a new object is selected by the user. It
is called with
set_menu(ad_menu, vector);
where vector is a pointer to an array of WORDs. The first word of the
array determines the default state of menu entries. If it is TRUE,
then set_menu() enables all entries in every drop-down of the menu
tree, except that the DESK drop-down is unaffected. If it is FALSE,
then every menu entry is disabled.
The following entries in the array are the numbers of menu entries
which are to be toggled to the reverse of the default state. This list
is terminated by a zero entry.
The advantage of set_menu() is that it allows you to build a
collection of menu state arrays, and associate one with each type of
user-selected object, program state, and so on. Changing the status
of the menu tree may then be accomplished with a single call.
CHECK, PLEASE?
One type of state indicator which may appear within a drop-down is
a checkmark next to an entry. You can add the checkmark with the
call:
menu_icheck(ad_menu, ENTRY, TRUE);
and remove it by replacing the TRUE with FALSE. As above, ENTRY is
the name of the menu entry of interest. The checkmark appears inside
the left boundary of the entry object, so leave some space for it.
The menu_icheck() call is actually changing the state of the
CHECKED flag within the entry object's OB_STATE word. If necessary,
you may alter the flag directly using do_obj() and undo_obj() from the
download.
NOW YOU SEE IT, NOW YOU DON'T
You can also alter the text which appears in a particular menu
entry (assuming that the entry is a G_STRING object). The call
menu_text(ad_menu, ENTRY, ADDR(text));
will substitute the null-terminated string pointed to by text for
whatever is currently in ENTRY. Remember to make the drop-down wide
enough to handle the largest text string which you may substitute. In
the interests of speed, G_STRINGs drawn within drop-downs are not
clipped, so you may get garbage characters on the desktop if you do
not size the drop-down properly!
The menu_text() call actually alters the OB_SPEC field of the menu
entry object to point to the string which you specify. Since the menu
tree is a static data structure which may be directly accessed by the
AES at any time, be sure that the string is also statically allocated
and that it is not modified without first being delinked from the menu
tree. Failure to do this may result in random crashes when the user
accesses the drop-down!
LUNCH AND DINNER MENUS
Some applications may have such a wide range of operations that
they need more than one menu bar at different times. There is no
problem with having more than one menu tree in a resource, but the AES
can only keep track of one at a time. Therefore, to switch menus you
need to use menu_bar(ad_menu1, FALSE); to release the first menu, then
use menu_bar(ad_menu2, TRUE); to load the second menu tree.
Changing the entire menu is a drastic action. Out of
consideration for your user, it should be associated with some equally
obvious change in the application which has just been manually
requested. An example might be changing from spreadsheet to data
graphing mode in a multi-function program.
DO IT YOURSELF
In a future column, I will discuss how to set up user-defined
drawing objects. If you have already discovered them on your own, you
can use them within a drop-down or as a title entry.
If the user-defined object is within a drop-down, its associated
drawing code will be called once when the drop-down is first drawn.
It will then be called in "state-change" mode when the entry is
highlighted (inverted). This allows you to use non-standard methods
to show selection, such as outlines.
If you try to insert a user-defined object within the menu title
area, remember that the G_TITLE object which you are replacing
includes part of the dark margin of the bar. You will need to
experiment with your object drawing code to replicate this effect.
MAKE PRETTY
There are a number of menu formatting conventions which have
become standard practice. Using these gives your application a
recognizable "look-and-feel" and helps users learn it. The following
section reviews these conventions, and supplies a few hints and tricks
to obtain a better appearance for you menus.
The second drop-down is customarily used as the FILE menu. It
contains options related to loading and saving the files used by the
application, as well as entries for clearing the workspace and
terminating the program.
You should avoid crowding the menu bar. Leave a couple of spaces
between each entry, and try not to use more than 70% of the bar. Not
only does this look better, but you will have space for longer words
if you translate your application to a foreign language.
Similarly, avoid cluttering menu drop-downs. Try to keep the
number of options to no more than ten unless they are clearly related,
such as colors. Separate off dissimilar entries with the standard
disabled dashes line. (If you are using set_menu(), remember to
consider the separators when setting up the state vectors.)
If the number of options grows beyond this bound, it may be time
to move them to a dialog box. If so, it is a convention to put three
dots following each menu entry which leads to a dialog. Also, allow a
margin on the menu entries. Two leading blanks and a minimum of one
trailing blank is standard, and allows room for checkmarks if they are
used.
Dangerous menu options should be far away from common used
entries, and are best separated with dashed lines. Such options
should either lead to a confirming go/no-go alert, or should have
associated "undo" options.
After you have finished defining a menu drop-down with the RCS, be
sure that its entries cover the entire box. Then use ctrl-click to
select the drop-down itself, and SORT the entries top to bottom. This
way the drop-down draws in smoothly top to bottom.
Finally, it is possible to put entries other than G_STRINGs into
drop-downs. In the RCS, you will need to import them via the
clipboard from the Dialog mode.
Some non-string object, such as icons and images, will look odd
when they are inverted under the mouse. There is a standard trick for
dealing with this problem. Insert the icon or whatever in the
drop-down first. Then get a G_IBOX object and position and size it so
that it covers the first object as well as the extra area you would
like to be inverted.
Edit the G_IBOX to remove its border, and assign the entry name to
it. Since the menu manager uses objc_find(), it will detect and
invert this second object when the mouse moves into the drop-down.
(To see why, refer to article #5.) Finally, DO NOT SORT a drop-down
which has been set up this way!
THAT'S IT FOR NOW!
The next column will discuss some of the principles of designing
GEM interfaces for applications. This topic is irreverantly known as
GEM mythology or interface religion. The subject for the following
column is undecided. I am considering mouse and keyboard messages,
VDI drawing primitives, and the file selector as topics. Let me know
your preferences in the Feedback!
>>>>>>>>>>>>>>>>>>>>>>>>>>>> Sample Menu Tree <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-1, 1, 6, G_IBOX, NONE, NORMAL, 0x0L, 0,0, 80,25, /* ROOT */
6, 2, 2, G_BOX, NONE, NORMAL, 0x1100L, 0,0, 80,513, /* THE BAR */
1, 3, 5, G_IBOX, NONE, NORMAL, 0x0L, 2,0, 20,769, /* THE ACTIVE */
4, -1, -1, G_TITLE, NONE, NORMAL, 0x0L, 0,0, 6,769, /* Title #1 */
5, -1, -1, G_TITLE, NONE, NORMAL, 0x1L, 6,0, 6,769, /* Title #2 */
2, -1, -1, G_TITLE, NONE, NORMAL, 0x2L, 12,0, 8,769, /* Title #3 */
0, 7, 22, G_IBOX, NONE, NORMAL, 0x0L, 0,769, 80,19, /* THE SCREEN */
16, 8, 15, G_BOX, NONE, NORMAL, 0xFF1100L, 2,0, 20,8, /* Drop-down #1 */
9, -1, -1, G_STRING, NONE, NORMAL, 0x3L, 0,0, 19,1, /* About... entry */
10, -1, -1, G_STRING, NONE, DISABLED, 0x4L, 0,1, 20,1,
11, -1, -1, G_STRING, NONE, NORMAL, 0x5L, 0,2, 20,1, /* Desk acc entries */
12, -1, -1, G_STRING, NONE, NORMAL, 0x6L, 0,3, 20,1,
13, -1, -1, G_STRING, NONE, NORMAL, 0x7L, 0,4, 20,1,
14, -1, -1, G_STRING, NONE, NORMAL, 0x8L, 0,5, 20,1,
15, -1, -1, G_STRING, NONE, NORMAL, 0x9L, 0,6, 20,1,
7, -1, -1, G_STRING, NONE, NORMAL, 0xAL, 0,7, 20,1,
22, 17, 21, G_BOX, NONE, NORMAL, 0xFF1100L, 8,0, 13,5, /* Drop-down #2 */
18, -1, -1, G_STRING, NONE, NORMAL, 0xBL, 0,0, 13,1,
19, -1, -1, G_STRING, NONE, DISABLED, 0xCL, 0,1, 13,1,
20, -1, -1, G_STRING, NONE, NORMAL, 0xDL, 0,4, 13,1,
21, -1, -1, G_STRING, NONE, NORMAL, 0xEL, 0,2, 13,1,
16, -1, -1, G_STRING, NONE, DISABLED, 0xFL, 0,3, 13,1,
6, 23, 25, G_BOX, NONE, NORMAL, 0xFF1100L, 14,0, 26,3, /* Drop down #3 */
24, -1, -1, G_STRING, NONE, NORMAL, 0x10L, 0,2, 26,1,
25, -1, -1, G_STRING, NONE, NORMAL, 0x11L, 0,0, 26,1,
22, -1, -1, G_STRING, LASTOB, DISABLED, 0x12L, 0,1, 26,1
>>>>>>>>>>>>>>>>>>>>>>>> Menu enable/disable utility <<<<<<<<<<<<<<<<<<<<<<
/*------------------------------*/
/* undo_obj */
/*------------------------------*/
VOID
undo_obj(tree, which, bit)
LONG tree;
WORD which;
UWORD bit;
{
WORD state;
state = LWGET(OB_STATE(which));
LWSET(OB_STATE(which), state & ~bit);
}
/*------------------------------*/
/* enab_obj */
/*------------------------------*/
WORD
enab_obj(tree, which)
LONG tree;
WORD which;
{
undo_obj(tree, which, (UWORD) DISABLED);
return (TRUE);
}
/*------------------------------*/
/* do_obj */
/*------------------------------*/
VOID
do_obj(tree, which, bit)
LONG tree;
WORD which;
UWORD bit;
{
WORD state;
state = LWGET(OB_STATE(which));
LWSET(OB_STATE(which), state | bit);
}
/*------------------------------*/
/* disab_obj */
/*------------------------------*/
WORD
disab_obj(tree, which)
LONG tree;
WORD which;
{
do_obj(tree, which, (UWORD) DISABLED);
return (TRUE);
}
/*------------------------------*/
/* set_menu */
/*------------------------------*/
VOID
set_menu(tree, change) /* change[0] TRUE selects all entries*/
LONG tree; /* FALSE deselects all. Change list */
WORD *change; /* of items is then toggled. */
{
WORD dflt, screen, drop, obj;
dflt = *change++; /* What is default? */
screen = LWGET(OB_TAIL(ROOT)); /* Get SCREEN */
drop = LWGET(OB_HEAD(screen)); /* Get DESK drop-down */
/* and skip it */
for (; (drop = LWGET(OB_NEXT(drop))) != screen; )
{
obj = LWGET(OB_HEAD(drop));
if (obj != NIL)
if (dflt)
map_tree(tree, obj, drop, enab_obj);
else
map_tree(tree, obj, drop, disab_obj);
}
for (; *change; change++)
if (dflt)
disab_obj(tree, *change);
else
enab_obj(tree, *change);
}
>>>>>>>>>>>>>>>>>>>>> Definitions used in this article <<<<<<<<<<<<<<<<<<<<<<
#define ROOT 0
#define G_IBOX 25
#define G_STRING 28
#define G_TITLE 32
#define R_TREE 0
#define MN_SELECTED 10
#define CHECKED 0x4
#define DISABLED 0x8
#define OB_NEXT(x) (tree + (x) * sizeof(OBJECT) + 0)
#define OB_HEAD(x) (tree + (x) * sizeof(OBJECT) + 2)
#define OB_TAIL(x) (tree + (x) * sizeof(OBJECT) + 4)
#define OB_TYPE(x) (tree + (x) * sizeof(OBJECT) + 6)
#define OB_FLAGS(x) (tree + (x) * sizeof(OBJECT) + 8)
#define OB_STATE(x) (tree + (x) * sizeof(OBJECT) + 10)
#define OB_SPEC(x) (tree + (x) * sizeof(OBJECT) + 12)
#define OB_X(x) (tree + (x) * sizeof(OBJECT) + 16)
#define OB_Y(x) (tree + (x) * sizeof(OBJECT) + 18)
#define OB_WIDTH(x) (tree + (x) * sizeof(OBJECT) + 20)
#define OB_HEIGHT(x) (tree + (x) * sizeof(OBJECT) + 22)
#define M_OFF 256
#define M_ON 257