POP-UP MENU MODULE DOCUMENTATION

Introduction

It has been perceived by some that Intuition's requirement that menus be
attached to the very top of screens is a flaw.  Many windowing systems
provide "pop-up" menus which can appear anywhere on the screen, typically
for context-specific input of a multiple-choice selection.  In the
interests of experimenting with the Amiga's user interface (in this case
extending it to include pop-up menus), the code bundled with this document
is placed in the public domain for use, alteration, discussion, and
enhancement.  I hope someone somewhere finds it useful.

Derek Zahn  (my Amiga hat -- Gambit Software, Madison WI)
USENET:         ...!{allegra,heurikon,ihnp4,seismo,ucbvax}!uwvax!derek

Files

makefile: constructs the demonstration program 'demo' with 'make'.
  The command 'make pop.o' will just make the pop-up menu object module
pop.c: source file for the pop-up menu module
popmenu.h: include file containing essential constant definitions;
  should be included by any program using pop-up menus
demo.c: example program making use of the pop-up menu module

1. Features

This small chunk of code provides pop-up menu functionality that utilizes
data structures compatible with the existing methods for specifying
conventional Intuition menus.  As will be described in sections 3 and 4 of
this document, some flag values have been added to provide control
useful for pop-up menus, some flag values are not meaningful here, some
are unsupported, and some fields of the Menu structure are used in ways
different from Intuition's.  A potential user of the code should read and
understand this document and examine the simple sample cases provided in
the demonstration program.

The interface, as will be seen in section 2, is a very simple blocking
function call that will retrieve a pop-up menu selection from the specified
menu.  This interface differs considerably from the normal Intuition menu
interface which sends messages to the user's IDCMP message port.  The reason
for this difference is as follows:  The standard Intuition menu strip
associated with a particular window is available for use any time the
window is active (unless specifically shut off).  The physical mechanism 
allowing the user to communicate her desire to make a menu choice, and then 
to do so, is built into Intuition at a fundamental level.  This is not the 
case for an application-level pop-up menu facility.  There is no clear 
universally acceptable method for removing from the application program the 
responsibility for recognizing the need to invoke the pop-up menu function.

I had considered writing another level of interface to this code that would
spawn off a Task which would be responsible for rendering and handling the
pop-up menus in response to recognized human interaction, and then notify
the program via the IDCMP port with a new set of IntuiMessage Classes (such
as POPMENUVERIFY, POPMENUHIT, etc).  However, I became bogged down in trying
to devise a good scheme for registering pop-up menus and then detecting an 
appropriate time to bring up them up.  I would welcome discussion on this
point.

Despite the lack of procedural alignment with Intuition menus (these pop-up 
menus are much more like AutoRequests), clearly there are instances where the
interface is reasonably natural anyway.  For instance, pop-up menus might be
very handy for providing a quick way, in a word processing program, to change
the font of a selected bunch of text -- using a pop-up menu saves the mouse
movement required to make an Intuition menu selection, allows conservation
of space that would be taken up by window gadgets to perform similar
operations, and requires less memorization than keyboard-command equivalents
(although Power Users may very well migrate there as they become familiar
with the program).

These pop-up menus are constructed with standard Intuition Menu and MenuItem
structures.  Most of the features provided by Intuition menus are supported
in nearly identical fashion, as is described in sections 3 and 4.

There are several notable exceptions, however; I do feel that this code is
incomplete.  The facility for providing sub-items is not supported, nor
are multiple menus in a single pop-up menu.  There is also no scheme for
supporting extended selection.  See the Appendix for a discussion of these 
lacks and other qualms I have about the code in its current form.

2. PopChoose

The user interface:

#include <intuition/intuition.h>
#include "popmenu.h"

LONG
PopChoose(menu, win)
struct Menu *menu;
struct Window *win;

The argument 'win' specifies the window that is to act as the "parent"
of the the pop-up menu to be rendered.  If this value is NULL, the currently
active window is used.  The argument 'menu' is a specification for the 
pop-up menu, as described in sections 3 and 4 below.

If an error is detected or no valid selection is made by the user, -1 is
returned.  If a selection is made, a non-negative value corresponding to
the choice is returned, and is computed as follows:  The first MenuItem
in the Menu is numbered zero; thereafter, each is numbered one higher than
the last as the list of MenuItems is traversed.  This is the same scheme
Intuition uses for the Code field of the class MENUHIT IntuiMessages.

PopChoose() creates a new window in which it renders the menu.  It is up to
the caller to be sure that nobody is scribbling indiscriminately on the
screen where the menu is to go.

IMPORTANT POINT:  In order to link the pop.o module into an application
program, somewhere in that application the following global variables
MUST be declared:

struct IntuitionBase *IntuitionBase;
struct GfxBase *GfxBase;

Then, before PopChoose is called, IntuitionBase MUST be set to the result of
successfully opening the intuition.library, and GfxBase MUST be set to the
result of successfully opening the graphics.library.  The potential for
disaster if these steps are not properly taken is great indeed.

If you do not understand exactly what is required, examine the code in the
supplied demonstration program, 'demo.c'.

3. The Menu structure

This section describes how the fields of the Menu structure are interpreted
by PopChoose().  Some of the fields are interpreted differently than they
are by Intuition; understand the differences before using the code.

LeftEdge, TopEdge

  These fields contain the upper left corner values for the menu.  Their
  meaning (and perhaps even value) is modified by the Flags POPPOINTREL, 
  POPWINREL, POPTIDY, and POPREMEMBER, as described below.

Width, Height

  The use of these fields differs considerably from Intuition.  Here, they
  give the TOTAL size of the menu -- all of the MenuItems (and the title,
  if present) must fit inside the rectangle defined by these variables.

MenuNext

  This field is ignored.

MenuName

  Use of this field is confusing, as it affects the positioning of the
  MenuItems in the menu.  If this field is NULL, there is no title present
  and the offsets in the MenuItem structures represent true offsets from
  the UL corner of the menu.  If the field is non-null, it is assumed to
  point to a null-terminated character string which will be rendered in the
  top of the menu as the title.  In this case, POPTITLEHEIGHT (defined in
  "popmenu.h") will be silently added to the the TopEdge of each of the 
  MenuItems to take the title into account. This confusing behavior is the 
  result of providing optional menu titles and still retaining compatibility
  with Intuition's notions about what the TopEdge field in a MenuItem means.

FirstItem

  As with Intuition, this points to the list of MenuItem structures that make
  up the choices in the menu.  This list may be empty.

Flags

  Flags for Menus mentioned in the Intuition Reference Manual:

  MENUENABLED
    If this flag is not set in the menu passed to PopChoose(), -1 will be
    returned immediately.
  
  MIDRAWN
    This flag is unused.

  Menu flags (defined in "popmenu.h") added for use by the pop-up menus:

  By default, the values in LeftEdge and TopEdge are absolute offsets from
  the upper left corner of the screen.  The following flags alter this.

  POPPOINTREL
    This flag, if set, specifies that the LeftEdge and TopEdge fields are
    to represent offsets from the pointer position when PopChoose() was
    called.

  POPWINREL
    This flag, if set, specifies that the LeftEdge and TopEdge fields
    represent offsets from the upper left corner of the "parent"
    window. The POPPOINTREL flag takes priority over POPWINREL if both 
    are set.

  POPREMEMBER
    This flag, if set, takes effect if the menu passed to PopChoose() has
    previously been used to make a successful selection.  Once this has
    happened and if the POPREMEMBER flag is set, the TopEdge and LeftEdge
    fields will be altered so that on the next call, the menu will appear
    relative to the pointer in such a manner that the last-selected item
    will be pre-selected.

  POPTIDY
    This flag, if set, makes sure that the whole menu appears on the screen
    by adjusting its location if part of it would not appear on the screen.
    It should be noted that this process can frustrate the intentions of
    the POPPOINTREL, POPWINREL, and POPREMEMBER flags by making the menu
    appear in an "unexpected" place; however, it does assure that the
    menu will be rendered and that all its items are on-screen.
  
  POPUSED
    This flag is for internal use by PopChoose() -- it indicates
    whether the next call to PopChoose() should treat the POPREMEMBER
    flag as being in effect.

  These next flags determine the conditions under which a menu selection is
  considered made.  Note that it is ABSOLUTELY REQUIRED that at least one
  of { POPLEFTBUTTON, POPRIGHTBUTTON } be set AND at least one of
  { POPTRIGGERUP, POPTRIGGERDOWN } be set -- if these conditions are not met,
  the call to PopChoose() will never return!

  POPLEFTBUTTON
    Treat the left mouse button as "important" in determining when the user
    is through with the menu.  Both this and POPRIGHTBUTTON may be set, in
    which case, both buttons are important.

  POPRIGHTBUTTON
    Treat the right mouse button as "important" in determining when the user
    is through with the menu.

  POPTRIGGERUP
    Send the menu away, making a selection if the pointer is over an item,
    and not if not, when the "important" button or buttons is/are released.
    Both this and POPTRIGGERDOWN may be set, in which case both are in
    effect.

  POPTRIGGERDOWN
    Send the menu away, making a selection if the pointer is over an item,
    and not if not, when the "important" button or buttons is/are depressed.

4. The MenuItem structure

This section describes how the fields of the MenuItem structure are 
interpreted by PopChoose().  Their use is nearly identical to their use 
under Intuition.

NextItem

  The next MenuItem in the list

LeftEdge, TopEdge
  
  These values hold the coordinates of the upper left hand corner of the
  select box for the item, relative to the upper left hand corner of the
  menu itself.  If the menu has a title, TopEdge will be silently
  incremented by POPTITLEHEIGHT for rendering purposes to make room for the 
  title.  This will be transparent to you (except when specifying the
  Height field of the Menu structure, as noted above).

Width, Height

  These fields are the height and width of the select box for this item.

MutualExclude

  Items with the the CHECKIT flag set (see below) may form mutual exclusion
  groups.  If this item is not CHECKED and is selected, all items designated
  with the MutualExclude field will be un-CHECKED.  This field is a bitmask
  where each bit refers to a single MenuItem in the list attached to the
  Menu.  The first MenuItem corresponds to bit zero, the second to bit one,
  and so on.  Setting a bit in this mask puts the corresponding MenuItem
  into a mutual exclusion group with this item.  If you place an item in
  its own mutual exclusion group, it will never be CHECKED.  In most cases
  this is not what you want, but who knows?

ItemFill, SelectFill

  Exactly as described in the Intuition Reference Manual.

Command

  This field is ignored - there are no keyboard equivalents for pop-up menus.

SubItem

  In the current implementation, this field is ignored; menu sub-items are
  unsupported.

NextSelect

  This field is unused -- extended selections are not supported.

Flags

  There are no pop-up-menu-specific flags for the MenuItem structure.  The
  following are the flags defined in the Intuition Reference Manual along
  with descriptions of how their use relates to pop-up menus.

  CHECKIT, CHECKED
    These flags provide checkmarking functionality identical to that of 
    Intuition's menus.  If the user tries to select a MenuItem that is
    CHECKED, -1 will ALWAYS be returned; that is the nature of checkmarking.

  HIGHCOMP, HIGHBOX, HIGHIMAGE, HIGHNONE
    Just as with Intuition, these specify the highlighting mode desired.  If
    none of these are set, HIGHIMAGE is the default (because of the screwy
    way Intuition defines the flag values); if no alternate imagery is
    supplied in the SelectFill field, the effect will be the same as if
    HIGHNONE was chosen.

  ITEMTEXT
    This flag's function is identical to that explained in the Intuition 
    Reference Manual.

  ITEMENABLED
    As in Intuition, if this flag is not set, the item is "ghosted" and
    unavailable for selection.  An attempt by the user to select an item
    that is not ITEMENABLED will result in a return of -1.

  ISDRAWN, HIGHITEM, COMMSEQ
    These flags are unused.

Appendix:  Problems

This code is not nearly perfect, although I confess my inability to provide 
either clearly better solutions to the problems that make me fidget or 
reasonably simple implementation schemes for the missing features.  This 
Appendix details to some extent the concerns with which I send this code 
into the Amiga community.

First, the inconsistencies in user interface.  Although it still seems to me
that the consistent way to do these menus would have involved the delivery
of IntuiMessages, the current scheme seems workable in nearly all scenarios
I have imagined; in fact, PopChoose() seems simpler to use the way it is now.
Suggestions or enhancements for the interface would be welcome.

Second, the POPREMEMBER business makes me a little nervous; altering the
LeftEdge and TopEdge fields seems like a kludge.

Third, the way the optional menu title is handled seems too strange.  If
the offsets of the MenuItems are going to be increased, it seems like
the size of the window should be as well, in the interest of "transparency".
But I couldn't bring myself to go around altering the window size....

Fourth, the code could probably be improved in a couple of ways:  there is
altogether too much looping through MenuItem lists, and as always there may
be bugs lurking in the code somewhere.  (clipping???)

Fifth, there are several crucial features missing, with no clue how they
might be added: subitems, extended select.  In addition, the keyboard
equivalents for Intuition menu items would be nice to support, but it is
not at all clear to me how this would even be possible.

Sixth, bugs.  There are two that I know of.  1:  although the case where
the POPTIDY flag is not set and the menu extends past the bottom and/or
right edge of the screen is supported (the window is altered in size), the
case where the screen's top and/or left border(s) is/are violated is not
handled; the menu will not be placed if this happens, and -1 returned
(at least under 1.2 this is what happens -- I am not sure if the
OpenWindow() call behaves the same under 1.1). Speaking of which, I just 
examined how the X window system (or at least the 'xterm' application) 
handles pop-up menus that fly off the screen.  It appears to use the 
equivalent of my POPTIDY flag, except when the menu would violate the bottom
edge of the screen.  In this case, the menu is moved up as expected and THE 
POINTER IS MOVED so that it is where it should be in relation to the menu.
A little voice in my head gasped and said, "Never, Ever, move the user's
pointer!!!", but is this wrong or what?  Should I add a POPMOVEPOINTER flag
to work in conjunction with POPTIDY?
2:  it is possible for the user to do the mouse operation that would
end the menu request before the window is rendered.  In this case, the
event is not noticed and must be repeated.  This could be remedied if I
could figure out how to query the state of the mouse buttons just after
the window is opened.
