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

How to evolve Eevee in Pokemon GO
By now, almost everyone should be hip to how to evolve Pokemon in Pokemon GO (and if not, there's a guide for that). Just gather enough candy of the appropriate type, feed them all to the Pokemon, and evolution happens. It's a miracle that would... | Read more »
CSR Racing 2: Guide to all game modes
It might not seem like there are all that many ways to go fast in a straight line, but CSR Racing 2 begs to differ. [Read more] | Read more »
Bulb Boy (Games)
Bulb Boy 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: Multi-award winning 2D point & click horror adventure about a boy with a glowing head. | Read more »
5 top free emoji keyboard apps
If we're not at peak emoji yet as a society, it feels like we definitely should be. The emoji concept has gone far beyond what anyone in Japan could have envisioned when the people there unleashed it on an unsuspecting world, but the West has... | Read more »
How to unlock more characters in Disney...
One of the big charms of Disney Emoji Blitz is seeing a wide variety of beloved Disney and Pixar characters transformed into smiling emojis. Even someone like the sneaky Randall from Monsters Inc., who probably never cracked a smile on film, is... | Read more »
Cubway (Games)
Cubway 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Cubway is a journey with an abstract story of lifecycle of rebirth, called Samsara. Guide the cube through the long way full of dangers... | Read more »
Colorcube (Games)
Colorcube 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Turn pieces and blend colours in this minimal yet visually stunning puzzler.Over 200 handcrafted and challenging levels. Features... | Read more »
Doodle God Griddlers (Games)
Doodle God Griddlers 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: | Read more »
Crusader Kings: Chronicles (Games)
Crusader Kings: Chronicles 1.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0 (iTunes) Description: Crusader Kings: Chronicles is an interactive text based game that puts you in the shoes of Guy de Rose as you make... | Read more »
Roads of Rome: New Generation (Games)
Roads of Rome: New Generation 1.0 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0 (iTunes) Description: | Read more »

Price Scanner via MacPrices.net

Charitybuzz Set to Auction Unique Apple-1 Com...
Offering an opportunity to own the computer that sparked a revolution, on Monday, July 25, leading online charity auction platform Charitybuzz will auction what is claimed to be the world’s most... Read more
MacBook Airs on sale for up to $150 off MSRP
Amazon has 11″ and 13″ MacBook Airs on sale for up to $150 off MSRP for a limited time. Shipping is free: - 13″ 1.6GHz/128GB MacBook Air (sku MMGF2LL/A): $899.99 $100 off MSRP - 13″ 1.6GHz/256GB... Read more
Apple refurbished 13-inch Retina MacBook Pros...
Apple has Certified Refurbished 13″ Retina MacBook Pros available for up to $270 off the cost of new models. An Apple one-year warranty is included with each model, and shipping is free: - 13″ 2.7GHz... Read more
Apple refurbished 11-inch MacBook Airs availa...
Apple has Certified Refurbished 11″ MacBook Airs (the latest models), available for up to $170 off the cost of new models. An Apple one-year warranty is included with each MacBook, and shipping is... Read more
Apple iPad Pro Sales Far Outpacing Microsoft...
A report on Appleinsider notes that despite Microsoft Surface tablet PC sales growing by 9 percent year over year, revenues remained below $1 billion, and are down sequentially from the $1.1 billion... Read more
DEVONthink 2.9 Features Ultra-fast, Robust, A...
DEVONthink 2.9 allows users to keep databases synchronized using many means of transport. It transmits them between Macs on the local network or stores them in a syncable form on removable hard... Read more
12-inch WiFi Apple iPad Pros on sale for up t...
B&H Photo has 12″ WiFi iPad Pros on sale for up to $100 off MSRP, each including free shipping. B&H charges sales tax in NY only: - 12″ Space Gray 32GB WiFi iPad Pro: $749 $50 off MSRP - 12″... Read more
Apple refurbished 15-inch Retina MacBook Pros...
Apple has Certified Refurbished 2015 15″ Retina MacBook Pros available for up to $380 off the cost of new models. An Apple one-year warranty is included with each model, and shipping is free: - 15″ 2... Read more
Apple refurbished Mac minis available for up...
Apple has Certified Refurbished Mac minis available starting at $419. Apple’s one-year warranty is included with each mini, and shipping is free: - 1.4GHz Mac mini: $419 $80 off MSRP - 2.6GHz Mac... Read more
Apple Mac Sales Finally Colliding With ‘Innov...
After successfully swimming against a tide of diminishing PC sales through 2014 and 2015, it appears that Apple has finally stopped defying gravity and is running up against the unwritten “innovate... Read more

Jobs Board

*Apple* Solutions Consultant - APPLE (United...
Job Summary As an Apple Solutions Consultant, you'll be the link between our future customers and our products. You'll showcase your entrepreneurial spirit as you Read more
*Apple* Professional Learning Specialist - A...
Job Summary The Apple Professional Learning Specialist is a full-time position for one year with Apple in the Phoenix, AZ area. This position requires a high Read more
*Apple* Picker - Apple Hill Orchard (United...
Apple Hill Orchard, Co. Rte. 21,Whitehall, NY 9/7/16-10/228/16. Pick fresh market or processing apples Productivity of 60 boxes and 80 boxes processing fruit per Read more
*Apple* Solutions Consultant - APPLE (United...
Job Summary As an Apple Solutions Consultant, you'll be the link between our future customers and our products. You'll showcase your entrepreneurial spirit as you Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description:SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.