TweetFollow Us on Twitter

Contextual Menu Basics

Volume Number: 14 (1998)
Issue Number: 1
Column Tag: Toolbox Techniques

Contextual Menu Basics

by Steve Sheets

Adding Support for the new Mac OS 8 Contextual Menu API; even for applications running without Apple's Contextual Menu Manager

A New OS, A New API, A New Logical Interface

The original thought that went into the Macintosh design was "ease of use". This would be the computer for "the rest of us" who were computer novices or worse. After awhile, novices become power users who want quicker ways to do things they already know how to do. You can see this in the menus of the Macintosh. The user has menu items that he can select with the mouse, but he also has Command-key short cuts. You also can see it in drawing palettes that allow simple commands, yet can be double clicked for more in depth configuration.

The contextual menu feature introduced with Mac OS 8 continues this idea. While holding down the Control key, a user can click the mouse while above some information he wants to manipulate. The information could be selected text, a picture, some database tables or anything we traditionally think of as exportable data. A popup menu appears to provide him with all the commands related to this data. Even if no data is selected, the user can see commands related to the window, document or application.

Contextual menus provide no new commands that are not already available. Rather it groups the commands in a quick table to be read and selected. Instead of selecting the data and then selecting the command, with one quick click, the user can see what can be accomplish. Searching for familiar commands becomes much easier. (For example, "is Find under Edit, View or Text?") Even if the user knows exactly where the command is in the normal menu, a Control-click on the selected data is faster. Power users will quickly adopt Contextual Menus to get their work done.

Whenever Apple introduced a new "logical interface", they provide a new API to developers so that we can provide this new feature to users. Luckily, the API for contextual menu is very small and involves concepts similar to those in the Drag Manager or even the Scrap Manager. This article explains what a developer must know to simply add contextual menus to his application. As an additional benefit, the article will show how easily Contextual-Menu-like features can be added to an application runing an older version of Mac OS, where Apple has not provided the Contextual Menu Manager.

Extension Modules

There is one exception to the idea that Contextual Menus should not provide commands not listed anywhere else. On a PowerPC Mac with the Contextual Menu API installed, extension modules can add additional functions. These code fragments must be installed in a special folder in the System Folder before startup. They are then loaded and called whenever a contextual menu is created. By looking at the data selected (text, picture, file, folder or even no data), the extensions can add commands to the contextual menu. These plug-ins have only read access to the selected data, so they can not modify the data, but they can return results in the Clipboard or by sending an Apple event. The hosting application need not concern itself with an extension module; all of a plug-in's functions are done without the application's knowledge. The Contextual Menu Extension Module API will be explained in an upcoming article. Until then, just be aware that it is available, but only supported on PowerPC machines.

Adding Commands

The key to determining what to put into a contextual menu is the emphasis -- contextual menus. Ignore that the feature is a popup menu. There are many locations to place commands, or allow short cuts to global settings. The user has selected some content, which he wants to modify. Add commands that are based on the content of what is clicked. In a drawing program, clicking drawing elements (circles, rect, lines) should show the commands that affect the element (rotate, change line thickness, change color). In a word processor, commands that change the style of the text or the arrangement of the document are good choices. Window commands can be available, even if the user clicks a portion of the window that has no data. Think twice before adding an application command. For example, Quit is a poor choice; it already has a short cut in the menu. The API provides a way for sub menus also to be added to the contextual menu. If too many commands need to be added, this is a good way to subdivide the list.

The API

There are only 3 new calls in the Contextual Menu API. In their easiest explanation, the calls initialize the API, check if the contextual menu should appear, and display the Contextual Menu. These functions should be called only if the API is installed on the Mac. Use Gestalt to check if this is the case. The gestaltContextualMenuPresent bit of the gestaltContextual- MenuAttr Gestalt attribute indicates if the Contextual Menu Manager is installed.

If the Contextual Menu Manager is installed, call InitContextualMenus(). This will register the application as a client of the Contextual Menu Manager. The routine returns noErr (0) if the initialization succeeds. This check & initialization should occur immediately after the standard toolbox initialization.

Listing 1: ContextualMenuFun.cp

Check if Manager Available, if so Init It

   long a_result;

   if (Gestalt(gestaltContextualMenuAttr, &a_result) == noErr)
      g_have_contextual_menus = BitTst(&a_result, 
                                       31-gestaltContextualMenuPresent);
   else
      g_have_contextual_menus = false;

   if (g_have_contextual_menus)
      g_have_contextual_menus = (InitContextualMenus()==noErr);

To indicate he wants the contextual menu, the user does a special click on some selected data. For Mac OS 8, this is done by holding down the Control key while doing a standard mouse click. However, there is no reason that in the future this special click could not be done some other way (such as a right mouse click on a two-button mouse). For this reason, the Contextual Menu Manager provides a call that checks if the special click is being done. IsShowContextualMenuClick() should be called after the application is passed any non-NullEvent event. The function is passed the event record and returns true if the special click occured. If the function returns false, the program should continue normally.

Listing 2: ContextualMenuFun.cp

Parse Event, checking if the event is Contextual Menu special click. If so, call routine to handle this.

      a_flag = WaitNextEvent(everyEvent, &g_record, 10, NULL);
      if (g_have_contextual_menus) {

//   if Contextual Menus event,

         if (IsShowContextualMenuClick(&g_record)) {
//   Handle Contextual Menus

            G_ContextualMenu(g_record);
            a_flag = false;            
         }
      }
      
//   Handle Normal Events if flag true

The most important routine of the Contextual Menu API is ContextualMenuSelect(). It should be called only when IsShowContextualMenuClick() has returned true. Not only does this routine have several parameters, the application must do several preparation tasks before it can be called.

Assuming the application knows which window was chosen, the application should check if this window is the currently selected window. If it is not, it should immediately be made the current selected window, and redrawn. Next, if some content data was selected, some sort of visual feedback should be drawn to indicate this. Inverting, either the data or the outline of the data is a common method to show what was chosen.

Next, a menu handle should be created and inserted into the menu bar just as if the PopUpMenuSelect() call was going to be made. At this point, the various menu items can be added to the menu handle. Sub-menus also can be added. If the Contextual Menu API adds any items to the list (Help or plug-ins), the Contextual Menu Manager will handle parsing these items; your application need not worry about them.

Now call ContextualMenuSelect(). Pass it the created menu handle, the point that was clicked (in global coordinates), a flag that must always be set to false (originally intended to indicate if the clicked area has a Balloon Help item, but now reserved for Apple's use), the Help type needed, the Help string, a pointer to the Apple event descriptor handling the selected data (named inSelection), a pointer to a long integer (resulting outUserSelectionType to explain what happened), and two pointers to short integers (resulting menu ID & menu item).

The Help type parameter can be one of three values: kCMHelpItemNoHelp, kCMHelpItemAppleGuide, kCMHelpItemOtherHelp. If it is kCMHelpItemNoHelp, then there is no help associated with this contextual menu. The Contextual Menu Manager will put the word "Help" in the front of the menu, but disable it. If the parameter is kCMHelpItemAppleGuide, the Contextual Menu Manager will put the name of the main Apple Guide file into the front of the menu. If the parameter is kCMHelpItemOtherHelp, the Contextual Menu Manager will put the name passed in the Help string into the front of the menu.

If there is no selected data (or if the application does not want to pass the data out), the inSelection parameter can be NULL. Otherwise, it should be a valid Apple event descriptor. After the ContextualMenuSelect() call, the application is responsible for disposing of this descriptor if needed. For simple data structures, the descriptor has a handle to the data (be it text or a picture) as well as the data type. Applications that support scriptability can pass an object specifier pointing to the selected data as the descriptor. This allows some Contextual Menu extensions to send an Apple event back to the current application to change the selected data. Refer to Inside Mac: Interapplication Communication for more information about Apple event descriptors and object specifiers.

If the ContextualMenuSelect() call returns successfully, its result is noErr (0). In this case, outUserSelectionType will indicate what happened. If the user fails to select a menu or a plug-in, the constant kCMNothingSelected is returned. If the user selected Show Balloon Help, the Contextual Menu Manager will handle this call, and the constant kShowBalloonSelected is returned (the application should do nothing). If the user selected the Help menu item, kCMShowHelpSelected is returned. The Application should call the appropriate code, Apple Guide or another help system, to display the appropriate help window. If the user selected a menu item, kCMMenuItemSelected is returned, and the menu ID & menu item are also returned.

Before the application parses the result, it should remove any hiliting it placed on the data. The commands the user selected might change this data, so inverting it later may give the wrong results. The application should delete and dispose of the menu handle. Lastly, if the Apple event descriptor was created using Apple event calls, it should be disposed. Listing 3 shows an example of how to setup the basic ContextualMenuSelect() call:

Listing 3: ContextualMenuFun.cp

Sample ContextualMenuSelect call to handle creating & displaying menu. 

      UInt32 a_type;
      SInt16 a_menu_id;
      UInt16 a_menu_item;
      Boolean a_balloon_available = false;
      Str255 a_help_str = "\pContextualMenuFun Help...";
      UInt32 a_help_type = kCMHelpItemOtherHelp;
      AEDesc a_descr;
      AEDesc* a_descr_ptr = NULL;
//   Create Menu
      MenuHandle a_menu_handle = NewMenu(255, "\pX");
      if (a_menu_handle) {
         SetPort(a_window_ptr);
//   If Window not front most, bring it front and draw on it
         if (FrontWindow()!=a_window_ptr) {
            SelectWindow(a_window_ptr);
            G_Draw(a_window_ptr);
         }
         
//   If Color Window, hilite picture (invert frame)
         if (a_window_ptr!=g_window_about) {
            SetRect(&g_rect, 0, 0, 200, 200);
            PenMode(srcXor);
            FrameRect(&g_rect);
            PenNormal();
         }
      
//   Insert Menu into Menu list
         InsertMenu(a_menu_handle, -1);
//   Fill Item on menu base on Front Window
         G_ContextualMenu_Fill(a_menu_handle, a_window_ptr);      
//   Fill Descriptor with Pic, if possible
         a_descr_ptr = &a_descr;
         a_descr.descriptorType = typePict;
         a_descr.dataHandle = (Handle) g_pic_1;
//   Call to Handle Popup
         OSStatus a_status = ContextualMenuSelect(a_menu_handle, 
            p_event_record.where, a_balloon_available,
            a_help_type, a_help_str, a_descr_ptr, &a_type,
            &a_menu_id, &a_menu_item);
//   Unhilite
         if (a_window_ptr!=g_window_about) {
            SetRect(&g_rect, 0, 0, 200, 200);
            PenMode(srcXor);
            FrameRect(&g_rect);
            PenNormal();
         }
//   If call was ok,
         if (a_status==noErr) {
//      If normal item selected, handle it
            if (a_type==kCMMenuItemSelected) {
               G_ContextualMenu_Action(a_menu_id, a_menu_item, 
                     a_window_ptr);
            }
//      If help selected, handle it
            else if (a_type==kCMShowHelpSelected) {
               if (g_window_about) {
                  ShowWindow(g_window_about);
                  SelectWindow(g_window_about);
               } 
            }
         }

//   Delete Menu
         DeleteMenu(255);
         DisposeMenu(a_menu_handle);
      }

Poor Man's Contextual Menu

Contextual menus were introduced with Mac OS 8. The Contextual Menu Manager also can be installed seperately in any Mac OS 7.6 and later system; although, this is uncommon and is not approved by Apple. While it is always nice to support the latest API, what about the majority of your users who will not be using Mac OS 8 for a time (if ever)? That is where the code G_PoorMan_ContextualMenu provides similar contextual menu functions for these older machines.

G_PoorMan_ContextualMenu uses the PopupMenuSelect() function to create it's own contextual menu. The extension modules will not work with Poor Man's Contextual Menu. Remember, plug-ins are not supported under Mac OS 8 running on a 68040 machine. The code calls the same functions as the normal contextual menu handler, so the developer only has to modify G_ContextualMenu_Fill & G_ContextualMenu_Action for it to work under Contextual Menu Manager API or the G_PoorMan_ContextualMenu code.

G_PoorMan_ContextualMenu must be called whenever the user has clicked the mouse within a window (such as inContent, inDrag, and inGoAway regions). The routine is passed the window the user selected and returns True if this action invokes a Poor Man's Contextual Menu. In that case, the program should not continue to process the event because it has been dealt with.

The routine first checks to make sure that the Contextual Menu API is not installed, and that the current event is a mouse-down in a window. Then it creates a menu handle, to be used by the PopUpMenuSelect() routine. Just like the Contextual Menu API, the Poor Man version has to bringing the window to the top and giving feedback to the selected item. Then the Help item is added to the menu. This is different from the Contextual Menu API, which handles the Help menu. Since this menu may appear on the Help window, the code accounts for this by disabling the Help item in that case. The last bit of setup has G_ContextualMenu_Fill being called for the menu items to be added.

Once the menu is fully created, PopUpMenuSelect() is called to process the popup menu. If an item is selected, the menu item number is examined. If it is one, then the Help menu item was selected, and the Help window should appear. Any other number would cause G_ContextualMenu_Action to be called with the correct number. Finally the temporary popup-menu handle is deleted and disposed.

Listing 4: G_PoorMan_ContextualMenu

Poor Man Contextual Menu (ie. no Manager), Return true if event handled

Boolean G_PoorMan_ContextualMenu(WindowPtr p_window_ptr)
{
   Boolean a_flag = false;
//   No Manager, event mouse down in Window & Control Key down
   if (!g_have_contextual_menus) {
      if ((g_record.what==mouseDown) && p_window_ptr) {
         if ((g_record.modifiers & controlKey)!=0) {
//   Event handled here!
            a_flag = true;
            Str255 a_temp_str = "\p?";
            MenuHandle a_menu_handle = NewMenu(255, a_temp_str);
            if (a_menu_handle) {
               SetPort(p_window_ptr);

//   If Window not front most, bring it front and draw on it
               if (FrontWindow()!=p_window_ptr) {
                  SelectWindow(p_window_ptr);
                  G_Draw(p_window_ptr);
               }
               
//   If Color Window, hilite picture (invert frame) 
               if (p_window_ptr!=g_window_about) {
                  SetRect(&g_rect, 0, 0, 200, 200);
                  PenMode(srcXor);
                  FrameRect(&g_rect);
                  PenNormal();
               }
            
//   Insert Menu into Menu list
               InsertMenu(a_menu_handle, -1);
               
//   Add About (Dim if About Window on top) 
               if (p_window_ptr==g_window_about)
                  AppendMenu(a_menu_handle, 
                     "\p(About ContextualMenuFun...;(-");
               else
                  AppendMenu(a_menu_handle, 
                     "\pAbout ContextualMenuFun...;(-");

//   Fill Item on menu base on Front Window
               G_ContextualMenu_Fill(
                        a_menu_handle, p_window_ptr);
//   Call to Handle Popup
               long a_result = PopUpMenuSelect(a_menu_handle,
                  g_record.where.v, g_record.where.h, 1);
//   Unhilite
               if (p_window_ptr!=g_window_about) {
                  SetRect(&g_rect, 0, 0, 200, 200);
                  PenMode(srcXor);
                  FrameRect(&g_rect);
                  PenNormal();
               }
//   If call was ok, 
               if (a_result) {
                  short a_menu_id = HiWord(a_result);
                  short a_menu_item = LoWord(a_result);
                  if (a_menu_id==255) {
//      If help selected, handle it
                     if (a_menu_item==1) {
                        if (g_window_about) {
                           ShowWindow(g_window_about);
                           SelectWindow(g_window_about);
                        } 
                     }
                     else {
//      If normal item selected, handle it (Adjust for Help Menu Item) 
                        a_menu_item -= 2;
                        G_ContextualMenu_Action(a_menu_id,
                           a_menu_item, p_window_ptr);
                     }
                  }
               }
//   Delete Menu
               DeleteMenu(255);
               DisposeMenu(a_menu_handle);
            }
         }
      }
   }
   return a_flag;
}

Sample Code

ContextualMenuFun was created to show how easy it is to provide contextual menus to the user. Most Macintosh developers will recognize their way around the standard portions of the program. All the code to handle details of a Contextual Menu is inside of the G_ContextualMenu routine. The Non-Contextual Menu Manager version of the code is inside G_PoorMan_ContextualMenu. Both routines call G_ContextualMenu_Fill & G_ContextualMenu_Action. G_ContextualMenu_Fill is passed the menu handle & selected window pointer, and fills the menu with items. Assuming the user has chosen a command, G_ContextualMenu_Action is passed the clicked window, the selected menu ID & menu item. G_ContextualMenu_Action parses this information, and takes action. The code was written to be expanded, which is why menu ID is passed. Remember, sub menus can be added so the menu ID is not always the same. The selected window is passed so commands can be more window and content sensitive.

The new "logical interface" begs for an object oriented approach to handling the contextual menus. A clicked visual object calls the standard routine to set up the contextual menu, using the API or not, which calls a virtual method to fill in the menu handle with commands. The standard routine would then display the menu. Assuming the user selects an item, another virtual method would be called to take action. Controls and field objects could even put their commands into the menu and then call the owning window for the window to put in its own commands, and so on up the command chain. Commercial or in-house frameworks easily could be modified this way to support the Contextual Menu API.


Steve Sheets has been happily programming the Macintosh since 1983, which makes him older than he wishes, but not as young as he acts. Being a native Californian, his developer wanderings have led him to Northern Virginia. For those interested, his non-computer interests involve his family (wife & 2 daughters), Society for Creative Anachronism (Medieval Reenactment) and Martial Arts (Fencing & Tai Chi). He is currently an independent developer, taking on any and all projects and can be reached by sending mail to MageSteve@aol.com.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

The Legend of Heroes: Trails of Cold Ste...
I adore game series that have connecting lore and stories, which of course means the Legend of Heroes is very dear to me, Trails lore has been building for two decades. Excitedly, the next stage is upon us as Userjoy has announced the upcoming... | Read more »
Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasn’t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »

Price Scanner via MacPrices.net

Apple is offering significant discounts on 16...
Apple has a full line of 16″ M3 Pro and M3 Max MacBook Pros available, Certified Refurbished, starting at $2119 and ranging up to $600 off MSRP. Each model features a new outer case, shipping is free... Read more
Apple HomePods on sale for $30-$50 off MSRP t...
Best Buy is offering a $30-$50 discount on Apple HomePods this weekend on their online store. The HomePod mini is on sale for $69.99, $30 off MSRP, while Best Buy has the full-size HomePod on sale... Read more
Limited-time sale: 13-inch M3 MacBook Airs fo...
Amazon has the base 13″ M3 MacBook Air (8GB/256GB) in stock and on sale for a limited time for $989 shipped. That’s $110 off MSRP, and it’s the lowest price we’ve seen so far for an M3-powered... Read more
13-inch M2 MacBook Airs in stock today at App...
Apple has 13″ M2 MacBook Airs available for only $849 today in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty is included,... Read more
New today at Apple: Series 9 Watches availabl...
Apple is now offering Certified Refurbished Apple Watch Series 9 models on their online store for up to $80 off MSRP, starting at $339. Each Watch includes Apple’s standard one-year warranty, a new... Read more
The latest Apple iPhone deals from wireless c...
We’ve updated our iPhone Price Tracker with the latest carrier deals on Apple’s iPhone 15 family of smartphones as well as previous models including the iPhone 14, 13, 12, 11, and SE. Use our price... Read more
Boost Mobile will sell you an iPhone 11 for $...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering an iPhone 11 for $149.99 when purchased with their $40 Unlimited service plan (12GB of premium data). No trade-in is required... Read more
Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more

Jobs Board

DMR Technician - *Apple* /iOS Systems - Haml...
…relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Operating Room Assistant - *Apple* Hill Sur...
Operating Room Assistant - Apple Hill Surgical Center - Day Location: WellSpan Health, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Read more
Solutions Engineer - *Apple* - SHI (United...
**Job Summary** An Apple Solution Engineer's primary role is tosupport SHI customers in their efforts to select, deploy, and manage Apple operating systems and Read more
DMR Technician - *Apple* /iOS Systems - Haml...
…relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.