[ First ]  [ Previous ]  [ Next ]  [ Last ]  [ Manuals ]

 

Chapter 10.

 

Commanders and Menus



Now that you have an application set up and ready to run, the next step is to make it responsive. As the user makes menu choices, your application should respond appropriately. That responsiveness comes from the LCommander class.

This chapter has two main sections:

There are several important classes in PowerPlant that inherit from LCommander. Let's start there.


Introduction to Commands

In most of our class hierarchy diagrams, we use LCommander as a mix-in class. This is appropriate, because classes in several different inheritance chains also inherit from LCommander.

Figure 10.1 shows what the class hierarchy looks like with LCommander as the principal base class. This diagram puts LCommander at the center of attention and illustrates all the classes that are also commanders.

Like the distinction between the LView class hierarchy and the application's visual hierarchy, there is a distinction between the LCommander class hierarchy (which simply illustrates inheritance) and the command hierarchy within a running application. We will call the flow of commands within an application the "command chain."

LCommander hierarchy:

Remember that the subclasses in this diagram belong to various class hierarchies in PowerPlant, and many inherit from other classes besides LCommander, such as LPane or LView.

The LCommander class has functions devoted to:

Every commander-each object that derives directly or indirectly from LCommander-has these features.


Command Chain

In an application, the application object is the topmost commander. The LApplication constructor sets the application object as the top commander. The application object has no supercommander.

Every other commander has one supercommander. The LCommander class stores a pointer to an LCommander object-the supercommander-in the mSuperCommander data member. You use member functions to access this data.

Every commander may have an arbitrary number of subcommanders. The mSubCommanders data member is an LArray object. Table 10.1 contains the LCommander functions for command chain maintenance.

Command chain maintenance functions:

 

Function
Purpose
GetTopCommander()  
return pointer to top commander  
SetSuperCommander()  
set the supercommander (replaces existing supercommander)  
AddSubCommander()  
add a subcommander to list  
RemoveSubCommander()  
remove a subcommander from list  
AllowSubRemoval()  
return whether to allow removal of subcommanders  
AttemptQuit()  
ask all subcommanders whether it is OK to quit  
AttemptQuitSelf()  
return whether it is OK to quit  
GetDefaultCommander()  
return pointer to default commander  
SetDefaultCommander()  
set default commander  

GetTopCommander() is a static member function, so you can use LCommander::GetTopCommander() at any time to get a pointer to the application object.

The concept of the default commander deserves special attention. The default commander will be the supercommander for a commander created with a class creator function. PowerPlant uses the default commander when creating objects of various pane classes that are also derived from LCommander. These classes are LEditField, LListBox, LTextEditView, LWindow, LDialogBox, and LGrafPortView. Before creating one of these objects on the fly with a class creator function, you should ensure that the default commander is set appropriately.

In a subclass you may wish to override AttemptQuitSelf(). This function should handle any duties the commander must perform before quitting. LDocument is the only PowerPlant commander class that overrides this function. It does so to ensure that the user has an opportunity to save a changed document before quitting. We'll discuss saving documents in Chapter 13, "File I/O."

With that exception, the default functions for command chain maintenance are usually sufficient for most commanders. However, you may have noticed that there is no command dispatch mechanism. That's what the concept of the target object is all about.


Target Handling

At any moment there is one and only one target object. The target object is stored in a static LCommander data member-sTarget. Because it is a static class variable, there is only one instance of sTarget. When the application receives a command, it dispatches the command directly to the target commander, bypassing the command chain completely. Table 10.2 lists the target handling functions.

Some LCommander target handling functions:

 

Function
Purpose
GetTarget()  
return pointer to current target  
SwitchTarget()  
change target  
AllowTargetSwitch()  
return whether to allow change in target  
IsTarget()  
return if the specified commander is the target  
BeTarget()  
called when commander becomes the target  
DontBeTarget()  
called when commander stops being target  

GetTarget() and SwitchTarget() are both static member functions. You can call them from outside the class by using the class specifier-LCommander::GetTarget(), and LCommander:: SwitchTarget().

You use SwitchTarget() to make a new object the target object. For example, the LEditField pane is also a commander. When it receives a click, it calls SwitchTarget(this) to make itself the target.

In some cases, you may not want to allow the target object to relinquish its position unless some conditions are satisfied. For example, you may want to verify a text entry before you allow the user to switch to another field. If AllowTargetSwitch() returns true, a switch is allowed. Otherwise, the target object cannot change.

If you want to take some action when a commander becomes the target object or stops being the target object, override the member functions BeTarget() and DontBeTarget(). For example, you may want to highlight the target object, enable other objects, or perform setup duties when an object becomes a target. Another example is LListBox and its use of LFocusBox.


Duty Handling

An application may have multiple branches in the command hierarchy. Only one branch is active or "on duty" at any given moment, and that's the branch that ends at the target. When the target object changes, the new target's chain of command (from the target object back up to the application) returns to duty. The previous target object's chain goes off duty. Figure 10.2 illustrates the duty concept.

The duty property of commanders:

The target is not necessarily the lowest item in the command hierarchy. When a commander becomes the target object, its subcommanders (if any) are not automatically put on duty. Duty flows upward from the target object to the topmost commander. For example, if Window1 in Figure 10.2 were the target object, then all the commanders lower than it in the command hierarchy would be off duty.

If there are multiple command chains, the user can switch chains. In fact, if the application moves to the background all chains go off duty and the target object is set to nil. A background application cannot receive menu commands or keystrokes, so there can be no target object.

What happens when the application becomes active again? For example, look at Figure 10.2. Imagine the application has been suspended. When it resumes, LEditField2 should be restored as the target object.

To make that possible, PowerPlant allows each commander to have one of three states: on duty, off duty, or latent. If a commander is latent, it means that if the chain as a whole were on duty, the particular commander would also be on duty. Figure 10.3 shows the same command hierarchy as in Figure 10.2, but with the application suspended. As a result, certain commanders are now latent rather than on duty.

The duty property while suspended:

When an application resumes or a chain within the application is activated (for example, by switching windows), PowerPlant searches through the command hierarchy for latent subcommanders and puts them back on duty. The deepest latent subcommander becomes the target object.

Now that you have the duty concept in hand, let's look at the functions each commander has for managing its duty status.

LCommander duty handling functions:

 

Function
Purpose
IsOnDuty()  
return duty state  
GetLatentSub()  
return the latent subcommander  
SetLatentSub()  
specify the latent subcommander  
PutChainOnDuty()  
put command chain on duty  
PutOnDuty()  
called when this commander is going on duty  
TakeChainOffDuty()  
take command chain off duty  
TakeOffDuty()  
called when this commander is going off duty  

When you create a window, and you want one of its commander panes to be the "default" pane that becomes active when that window becomes active, use SetLatentSub(). A commander may have no more than one latent subcommander.

If you want something to happen when a commander goes on or off duty, override the PutOnDuty() and TakeOffDuty() functions. For example, you might want to outline a frame when on duty, and hide a frame when off duty.


TIP

Being on duty is not the same as being the target object. The target object is always on duty. An on-duty commander is not always the target object. If you want something to happen when a commander becomes the target object, override BeTarget() and DontBeTarget().



Command and Keystroke Handling

Fundamental to any commander's behavior is its ability to handle commands and keystrokes. A command is a menu selection or command-key equivalent. A keystroke occurs when the user types a key. Whenever one of these two events occurs, control passes to the target commander.

Each commander has three principal functions to handle the command or keystroke, as listed in Table 10.4.

Some LCommander command and key handling functions:

 

Function
Purpose
ObeyCommand()  
respond to a menu command  
FindCommandStatus()  
enable, disable, or mark a menu item  
HandleKeyPress()  
respond to a key event  

You override these three functions regularly in PowerPlant applications. In fact, it is your definition of these functions that is responsible for much of the unique behavior of your own application.

You set up your own menus, your own menu commands, and define how your application or its component parts respond to each command. When the user chooses a menu item, the target commander's ObeyCommand() function receives an identifying command number that corresponds to a menu item. You respond accordingly.

You override the FindCommandStatus() function to enable, disable, and/or mark menu items as appropriate for your target object.

When the user types a key, the target commander's HandleKeyPress() function receives the event and responds accordingly.

Because the flow of duty is upward in the command hierarchy, the target object gets first crack at responding to commands and keystrokes, and at setting up the menus appropriately. If the target object changes, the new target object can do things differently. We discuss how all this works in the next section on menus.


Making and Managing Menus

To make your application work, you will need menus. In this section we discuss:

Let's start with an overview of PowerPlant's menu strategy.


Menu Strategy

In traditional Macintosh programming, when the user chooses a menu item you dispatch control based on the menu ID and the item number for that particular item and menu. The Macintosh Toolbox routine MenuSelect() returns a number that contains both pieces of information.

As a result, traditional Macintosh menu dispatch code is heavily dependent upon item position. If you change the position of a command in a menu, or you move a command from one menu to another, you must rewrite the routine that dispatches menu choices.

To eliminate this problem, PowerPlant associates each menu item with a unique command number. A PowerPlant application maintains a "map" that says, for example, "The fifth item of the Edit menu has a command number of 14." The map contains a command number for every item of every menu in the application. (Actually, there's an exception to this that we'll discuss in just a bit.)

The "map" is really a series of Mcmd resources, one for each MENU resource in the application.

When the user chooses a menu item, PowerPlant looks up the associated command number in the Mcmd resource, then sends the command number to the target commander's ObeyCommand() function. Because you know what each command number represents, you know the identity of the menu item the user chose.

If you decide to reorganize your application's menus, you do not have to modify your menu parsing and dispatch code. The command number remains unchanged. You must, however, modify your Mcmd resources so the "map" remains consistent with the location of menu items. If you use Constructor to build and modify your MENU resources, it takes care of this for you.


NOTE

What's really happening here is that the designers of PowerPlant know that developers change their menus. So PowerPlant moves responsibility for tracking position changes out of code and into a resource. You must still modify something if you reorganize a menu. With PowerPlant you modify the Mcmd resource rather than your source code.



Menu-Related Resources

PowerPlant uses three resource types to manage menus:

You can use Constructor to create all three resources.

The MBAR and MENU resources are standard Mac OS resources for defining the contents of the menu bar, and an individual menu respectively. PowerPlant uses them in exactly the same way that any Macintosh program would.

The Mcmd resource is the "map" that tells PowerPlant what command number to associate with each menu item. There is one Mcmd resource for each MENU resource. The resource ID number for the Mcmd resource must be the same as the associated MENU resource. If you use Constructor, it takes care of the Mcmd numbering for you. Figure 10.4 illustrates the relationships between the MBAR, MENU, and Mcmd resources.

Example of Menu-related resources:

If you use PowerPlant stationery to build a new project, the file <PP Starter.ppob> contains the MENU and Mcmd resources necessary to support the standard Apple, File, and Edit menus. The file <PP Starter.rsrc> contains the MBAR resource. You can use these as a starting point for further development.


See also

"Installing Resource Templates" for information on installing Mcmd resource templates.


Command Numbers

Each menu command to which you respond must have a unique number. PowerPlant reserves command numbers -999 to 999 for its own use.

For example, command number 0 (zero) is reserved for a command that does nothing. The standard menu commands for the Apple, File, Edit, and font-related menu items are defined in PP_Messages.h. Several of the enumerated constants are listed in Figure 10.4.

When you create an Mcmd resource, you assign a command number to each menu item. You are free to use any unique number as long as it isn't in the reserved range. However, using a systematic approach of some sort when assigning command numbers can help you avoid errors and maintain consistency.

In an Mcmd resource, assign zero as the command number for a separator bar in the associated menu. When using other PowerPlant-defined values, refer to PP_Messages.h for the value to use.


Negative command numbers

Under typical circumstances, you should use positive numbers for your menu commands. PowerPlant has facilities for enabling, disabling, marking, and changing the text for menu items. You cannot use regular PowerPlant techniques to do any of these things if the command has a negative number.

Some negative command numbers have special meaning in PowerPlant, including synthetic commands. We will revisit negative command numbers when we discuss dialogs in Chapter 12.


Synthetic command numbers

So far we have overlooked one significant problem with the PowerPlant menu strategy. What do you do for menus whose contents are defined at runtime, such as the Apple menu or the Font menu? You cannot know what items will be in these menus, so you cannot put entries in an Mcmd resource for them.

In a PowerPlant application, you build such a menu at runtime in the traditional way. We will use the standard Apple menu and a hypothetical Font menu as examples in this discussion.

The MBAR resource for the application has the resource ID numbers of all the MENU resources. The MENU resource for the Apple menu has the menu title (the Apple symbol) and the first About item. The MENU resource for a Font menu would have the menu title and no items.

The PowerPlant LMenuBar constructor builds the Apple menu using traditional techniques, as shown in Listing 10.1

Building the Apple menu:


MenuHandle macAppleMenuH = ::GetMenuHandle(MENU_Apple);
if (macAppleMenuH != nil) {
  ::AppendResMenu(macAppleMenuH, 'DRVR');
}

The Apple menu has an Mcmd resource that contains an entry for the About item, because that is a standard item in the Apple menu. You cannot have an Mcmd entry for other items in the Apple menu. Similarly, you cannot have entries in an Mcmd resource for items in a font menu.

When the user chooses a menu item that does not have an associated entry in an Mcmd resource, PowerPlant generates a synthetic command number for that item.

A synthetic command number is a 32-bit number. The high 16 bits contain the menu ID, the low 16 bits contain the menu item. This is just like the return value from the traditional MenuSelect() Toolbox function, with one exception. The synthetic command number is negative.

After creating the synthetic command number, PowerPlant passes that command number to you, just like it would for a regular command number retrieved from an Mcmd resource.


Using synthetic command numbers

When you receive a menu command, you don't know if it is from an Mcmd resource. It might be a synthetic command. You call LCommander::IsSyntheticCommand() to determine if the menu command is synthetic or not. If the command is a synthetic command, the function returns true and supplies the menu ID and item number for the chosen menu item.

You can then process the command properly, because you have the required information: menu ID and menu item.

For example, Listing 10.2 shows how LApplication::ObeyCommand() responds to synthetic commands from the Apple menu.

Handling synthetic command numbers:


Boolean
LApplication::ObeyCommand(CommandT inCommand, void *ioParam)
{  
  ResIDT        menuID;
  SInt16        menuItem;
  // check for synthetic command
  if(IsSyntheticCommand(inCommand, menuID, menuItem))
  {
    if (menuID == MENU_Apple)
    {
      Str255 appleItem;
      ::GetItem(GetMHandle(MENU_Apple), menuItem, appleItem);
      ::OpenDeskAcc(appleItem);
    } else {
      cmdHandled = LCommander::ObeyCommand( inCommand, ioParam)
    }
  } else { // non-synthetic commands
    switch (inCommand) { 
    
      case cmd_About:
        ShowAboutBox();
        break;
  ...

Note that the About Box has a non-synthetic command number. If the command number is not synthetic, then you simply test for the command number directly-typically in a switch statement. There is no need to use the menu ID or menu item number for a regular command. In fact, that data is not readily available.

Now that you understand the strategy and details behind PowerPlant menu management, let's talk about how you accomplish real menu-related tasks.


Adding Menus

Adding a menu to your application is simple.

1. Create a MENU resource.

2. Create an Mcmd resource with the same ID number as the associated MENU resource. Enter command numbers for each item in the menu.

3. Modify the MBAR resource by adding the ID number of the new MENU resource to the MBAR resource.

If you use Constructor, this process is even simpler. You simply create a menu bar resource, add the necessary menus, and specify the menu items and command numbers. Constructor creates and manages the resources automatically. If you move menu items around, Constructor keeps the Mcmd resource updated. See the Constructor manual for details of menu editing in Constructor.


WARNING!

If you don't use Constructor and you modify the position of menu commands in the MENU resources, you must modify the associated Mcmd resources as well. Failure to do so will cause unpredictable results.


Menus are created as part of the default LApplication::Run() function, which creates the menu bar (and all menus on the menu bar as specified in the application resources). However, there are times when you must specify a menu at runtime, and cannot do so in advance. For example, you have to set the contents of the Font menu at runtime because you cannot know in advance what fonts are available on any particular machine.

Use the Initialize() method of your application class to accomplish this work. The default function in the LApplication class is empty. Override this function in your own LApplication class to do any additional menu setup work that's required for your application. The default LApplication::Run() function calls Initialize() as part of the application setup process, so your function will be called before you have to start handling events. The code exercise for this chapter demonstrates the technique. You'll see it used in some other exercises as well.


Responding to Menu Commands

Under most circumstances, the default PowerPlant behavior is all you need for menu dispatch. PowerPlant identifies the menu choice, retrieves the associated menu command, and passes the command to the target commander. It is the target object's responsibility to handle the command or not.

The calling chain for this dispatch is as follows. When a click occurs in the menu, LEventDispatcher::ClickMenuBar() (in the application object) gets the command number. It then calls the target commander's ProcessCommand() function. The ProcessCommand() function passes the command on to attachments, and then calls the target commander's ObeyCommand() function.

ObeyCommand() is where command identification and response occurs. It is also the only function in this entire dispatch series that you are likely to override.

A typical ObeyCommand() function will test for synthetic commands, if they are used in your application and are of significance to the particular commander. For example, a text-related object would be very interested in a synthetic command from a Font menu. It calls IsSyntheticCommand() to determine if the command is synthetic, and to get the menu ID and item number. After you identify menu and item, you respond appropriately. Our hypothetical text commander might change the font it uses when displaying its contents, for example.

The typical ObeyCommand() function has a switch statement with a case for each command of interest to the object. In response to the command, you do whatever is appropriate for your application.

Finally, the typical ObeyCommand() function calls its inherited ObeyCommand() function for any commands it does not handle. Otherwise you won't get the benefit of command testing and response in the base class.

Although we discuss windows in the next chapter, they make a great example here. LWindow is a commander. If you derive your own window class from LWindow, your class's ObeyCommand() function should call LWindow::ObeyCommand() for any command it does not handle directly. This way you get to take advantage of LWindow's code.

LWindow, in turn, inherits directly from LCommander. If LWindow::ObeyCommand() does not handle the command, it calls LCommander::ObeyCommand(). This call gets the supercommander and calls the supercommander's ProcessCommand() function. This gives the supercommander the opportunity to act or pass on the command.

This approach means that a commander is not required to handle every possible command. Each commander handles the commands for which it is responsible. If it cannot handle the command, it passes responsibility back up the command chain to a higher commander. This is the code-level implementation of the bottom-up command chain hierarchy we talked about in the chapters on application framework design and PowerPlant design.

If the user issues a quit command, for example, it is likely that the command would filter up through the command chain all the way back to the application. Remember, the application is itself a commander, so it has an ObeyCommand() function. Because quitting is really an application-level chore, in LApplication::ObeyCommand() you'll find this code:


case cmd_Quit:
  SendAEQuit();
  break;

No other object needs to know how to handle a quit command, the application object takes care of it.


When To Update Menus

As a Macintosh programmer you know that you must update the appearance of menu items as well as respond to menu choices. Before we discuss how to update menu items, let's talk about when PowerPlant updates menu items, and how to control when a menu updates.

There are two common strategies for updating menus: update before displaying a menu, and update whenever an event occurs that might change the state of a menu. You can think of these approaches as "update before display" and "update as needed."


Update before display

In the first strategy, you update menus only when the user clicks in the menu bar or types a command key. Before displaying the menu, you update it so the menu's contents reflect the current state of the program. The state of a menu while it is not displayed is really unimportant. The only time it is important that the menu's contents accurately reflect the state of the program is when you actually look at the menu.

The difficulty with this strategy is that one part of a menu is always visible-the menu title in the menu bar. If all of a menu's items are disabled, the menu title should also be dimmed. Assume that as the user works with a program, such a situation arises-that is, the state of the program is such that all of a menu's items would be disabled. The menu title does not reflect that situation, because the menu hasn't been updated. The user clicks in the menu bar, and menus update. The user suddenly finds that a menu that appeared enabled turns out to be disabled! This is bad human interface design.

One solution would be to have a secondary mechanism for updating the menu bar whenever an event occurs that changes a menu's title. To do that reliably, you have to track every event that might cause a change in any menu item's state. If you do that, you will find yourself implementing PowerPlant's menu updating strategy.


Update as needed

PowerPlant takes the second approach to menu updating. PowerPlant keeps menu items current at all times. As a result, the menu bar accurately reflects the state of the application.

PowerPlant maintains a flag that reflects the state of menus. If the flag is set, menus are considered "dirty" and in need of updating. After processing each event, if the flag is set PowerPlant updates menus. Figure 10.5 illustrates the process.

PowerPlant menu updating logic:

PowerPlant retrieves an event. It clears the menu update flag and processes the event. It is important to note that the event might be a click in the menu bar, in which case a menu is displayed without additional updating. PowerPlant relies on the fact that menus are always current.

When an event occurs that might change a menu (such as a click in the window content), PowerPlant sets the menu update flag. After the event is fully processed, if the update flag is set then PowerPlant updates menus. It calls the target commander's FindCommandStatus() function to do this. We'll discuss what to do in this function in just a bit.

The events that cause PowerPlant to update menus are:

Note that a click in the menu bar causes a menu update only after the menu is displayed, the user makes a choice, and the resulting command is fully processed.

An occasion may arise when you want to force a menu update as a result of some other occurrence. For example, you may want to enable the Save item in the File menu in a text processor only when there is text in the window. To force a menu update, call LCommander::SetUpdateCommandStatus(). Pass true as the only parameter. This call sets the menu update flag. When control returns to the main event loop, PowerPlant will update the menus.


Updating Menu Items

When it is time to update menu items, PowerPlant goes through each item in each menu and gets a command number. What happens depends upon the command number. There are five possible results:


The command number is positive

We call positive menu commands non-synthetic commands to distinguish them from synthetic commands. These are the most common command numbers for menu items.

The target commander's FindCommandStatus() function will be called once for each menu item that has a positive, non-zero menu command. Like the ObeyCommand() function, FindCommandStatus() receives the command number that identifies the menu item involved.

In response, your commander determines what the status of that menu item should be (based on application context), and provides the necessary information to the caller. FindCommandStatus() has a series of parameters, as listed in Table 10.5.

FindCommandStatus() parameters:

 

Data Type
Name
Purpose
CommandT  
inCommand  
command number  
Boolean&  
outEnabled  
provide true if enabled  
Boolean&  
outUsesMark  
provide true to use a mark  
Char16&  
outMark  
provide the mark to use  
Str255  
outName  
provide the menu item text  

Each of the output parameters has a default value set before entry into FindCommandStatus(). By default, a menu item is disabled, unmarked, and the text remains unchanged.

If you want the item enabled, set outEnabled to true.

If you want a mark to appear before the menu item (like a check mark, dash, diamond, or blank space to clear a mark), set outUsesMark to true. If you use a mark, you must also specify the mark you want to use in outMark.

To clear a mark, set outUsesMark to true and outMark to zero. Setting outUsesMark to false does not clear an existing mark.

If you want to modify the menu item text, set outName to the text you want to use. If you do not want to modify the item text, do not modify this parameter.

Each commander's FindCommandStatus() function handles those menu items for which the commander is responsible. If the commander's FindCommandStatus() function receives a command it does not recognize, it should pass the request on to its inherited FindComandStatus() function. This process works just like the ObeyCommand() design.

Call the FindCommandStatus() function inherited from your base class to get the benefit of the code in the base class. If the base commander from which you inherited is LCommander, then call LCommander::FindStatus(). This passes the request up to the supercommander.


The command number is zero

If the command number is zero, PowerPlant does nothing. The number zero indicates a menu item that is always disabled, like a separator bar item in the menu. There is no need for you to deal with such an item. It is and always remains disabled.


The command number is -1

The -1 number is a special command number. When PowerPlant encounters a command number -1 (cmd_UseMenuItem), it generates a synthetic menu command for this item, and then calls the target commander's FindCommandStatus() function, just like it does for positive menu commands.

The FindCommandStatus() function should deal with this item in exactly the same way that it handles non-synthetic items. However, to identify the menu item you must call IsSyntheticCommand() to get the menu ID and menu item number. Then you can determine how to update the item based on application context.


The command number is negative

PowerPlant does not update items with negative command numbers (except for the -1 command number). Items with a non-synthetic, negative command number remain in the same state, typically enabled. The target commander's FindCommandStatus() function is not called for these items.

However, there is a loophole that allows you to modify menu items with negative command numbers, as you'll see when we discuss updating items with synthetic command numbers.


The command number is synthetic

When PowerPlant searches for a menu item's command number, there may be none at all. If there is no command number, PowerPlant generates a synthetic command number.

PowerPlant does not update these items. The target commander's FindCommandStatus() function is not called for items with synthetic command numbers.

Clearly this presents a problem. You may need to update a menu item that has a synthetic command. For example, you may want to put a check mark in front of the current font in a Font menu.

PowerPlant provides a mechanism for you to accomplish that task, and other updates of synthetic menu items.

In addition to calling FindCommandStatus() once for each positive menu command, PowerPlant also calls the target commander's FindCommandStatus() function once for each menu as a whole. Here's how it works.

After calling FindCommandStatus() for each eligible menu item, PowerPlant manufactures a synthetic command for item zero of each menu-that is, for the menu title. PowerPlant then calls FindCommandStatus() with this synthetic command number. This gives the target object an opportunity to do something to the menu as a whole.

Note that this call is made once for each menu, regardless of whether the menu has synthetic commands, so that you can operate on an entire menu. However, this is your only opportunity to modify synthetic menu items.

For example, assume you have a text-processing application with a Font menu. Assume also that it is menu update time. Your text target object's FindCommandStatus() function is not called for any item in the Font menu, because they all have synthetic (negative) command numbers.

However, the text object's FindCommandStatus() function is called once for the Font menu as a whole. The command number is a synthetic command number for item zero of the menu. Your FindCommandStatus() function calls IsSyntheticCommand(). This call returns true and provides the menu ID for the Font menu, and zero for the item number.

At this time you can act on the items in the Font menu. For example, you may enable all of them, disable them, or put a check mark in front of the item for the current font. You do this using standard Macintosh Toolbox calls. The details, of course, are dependent upon your own application.


Working With LMenuBar and LMenu

Finally, you may have noticed that there hasn't been much talk about these two classes. That's because most of their utility is designed for internal PowerPlant use. You won't have to use objects of either class directly very often.

The exception to this rule is when you are dealing with menus like the typical Font menu. In that case, you need to get the Mac OS MenuHandle so that you can perform traditional Mac OS menu-management tasks like putting a check mark in front of the item representing the current font. You also need to work with the Menu Manager if you dynamically alter menus at runtime.

An application has one LMenuBar object. You can always get a pointer to this object by calling the static function LMenuBar::GetCurrentMenuBar().

You can use LMenuBar to add or remove menus dynamically, although doing so is not a recommended feature of the human interface. Use LMenuBar::AddMenu() to add a new menu at runtime. Use LMenuBar::RemoveMenu() to remove a menu at runtime. If the menu has submenus, they are added or removed as well.

Each menu in a PowerPlant application has an associated LMenu object. To get a pointer to the menu object of choice, you call the LMenuBar's FetchMenu() function. You provide the menu ID.

After you have a pointer to the menu object, getting the Macintosh Toolbox MenuHandle is simple. Call the menu object's GetMacMenuH() function. You now have a Mac OS MenuHandle, and you can do what you want with the menu.

However, LMenu provides some functions that are more useful in the PowerPlant context than using the Mac OS Menu Manager directly. They are listed in Table 10.6.

Some LMenu functions:

 

Function
Purpose
GetMacMenuH()  
return Mac OS MenuHandle  
GetMenuID()  
return menu ID  
InsertCommand()  
insert an item with the specified text and command number into the menu  
RemoveCommand()  
remove the item (specified by command number) from the menu  
RemoveItem()  
remove the specified item from the menu  
SetCommand()  
set the specified item's command number  
ItemIsEnabled()  
return whether specified item is enabled  

Finally, the LMenuBar and LMenu combination is another independent PowerPlant design element that you can use independently of the rest of PowerPlant. Simply include the necessary files in your project, and you're on your way. The command dispatch and menu updating mechanisms are an integral part of PowerPlant, and separate from the menu classes.


Summary

LCommander objects maintain the chain of command and duty in an application. The application has one target commander that receives all commands and keystrokes directly.

The target object can respond to a command, or pass it up the command chain. The target object also tells PowerPlant the status of menu items during menu updates.

You build menus using MBAR, MENU, and Mcmd resources. PowerPlant associates a unique command number with each menu item, and uses that number when handling commands or menu updates. PowerPlant handles all menu dispatch. You override the ObeyCommand(), FindCommandStatus() and HandleKeyPress() functions in your commanders to implement your application's behavior.

As usual, now it's time to put all this to work in real code.


Code Exercise

This exercise introduces you to commanders, and to menus. You create a simple application named "Menus." This application displays a simple window, as shown below in Figure 10.6. You can create an arbitrary number of windows.

The Menus application:

Each window contains a caption. Unlike the standard LCaption object, however, this caption is dynamic and a commander. It responds to menu commands to change font, size, and style. You cannot change the text in this object using the Menus application. It is a caption, not an editable text item.

Feel free to examine the PPob resource for this window. The CDynamicCaptionCmdr class is a custom class. The class ID is DyCC. In addition to the usual caption information, this item stores the menu resource ID for the font and size menus, and the item numbers of the first and last size items.

Because this exercise concentrates on menus, in this exercise we won't explore the visual interface further. We will explore the menu resources. And then you write the code to identify and respond to commands, and to update menus.


The Menu Resources

You can use Constructor to examine the menu-related resources. They are in the file Menus.ppob. You won't modify any of these resources, but you should take a look at them so you understand what PowerPlant requires for menus.

Examine the MENU resources. In addition to the standard Apple, File, and Edit menus, there are Font, Size, and Style menus. Notice the MENU resource ID numbers for these menus. They are 250, 251, and 252 respectively. These menus are very common in Macintosh applications, and the PP_Messages.h file defines these constants:


const MessageT cmd_FontMenu = 250;
const MessageT cmd_SizeMenu = 251;
const MessageT cmd_StyleMenu = 252;

The Font menu has no items. The Size and Style menus have the standard items you typically see in these menus. Close the MENU resources when you're through with them.

Examine the MBAR resource. Notice that it contains an entry for each of the six menus. PowerPlant will build the menu bar containing each of these menus. The combination of MENU and MBAR resources is all that's necessary to make a menu appear in a PowerPlant application. To make that menu work, however, you typically use an Mcmd resource.

Constructor maintains the Mcmd resources automatically based on the command ID you set for each menu item. Figure 10.7 illustrates the relationship between the Size and Style menus and their respective commands.

Menu commands in the Menus app:

There is one command for each of the 13 items in the Size menu. The first 8 items have a command number of -1. This is the cmd_UseMenuItem value we discussed earlier in this chapter. The other items in the Size menu and all the items in the Style menu have positive or zero command numbers. The corresponding PowerPlant constants for these standard items are defined in PP_Messages.h.

There are similar standard commands defined in PowerPlant and used in the Apple, File, and Edit menus. Feel free to examine them as well.

Notice that there is no Mcmd resource for the Font menu. PowerPlant generates synthetic commands for these items at runtime.


Implementing Menus

In this part of the code exercise you write the code to populate the Font menu, establish a command hierarchy, recognize and respond to menu commands, and handle menu updating. In the process you will work with both kinds of commands, synthetic and non-synthetic. You also see the practical difference between a pure synthetic command and the cmd_UseMenuItem command.

Before we get started, a little context will help. Let's look at the dynamic caption object and how it is implemented. The code for the dynamic caption object is provided for you.

1. Examine CDynamicCaption.

class declaration CDynamicCaption.h

Take a look at the class declaration for CDynamicCaption. It inherits from LCaption, and therefore has all the features of a regular LCaption object. In addition, the CDynamicCaption object has a text traits record. Most of the public functions of CDynamicCaption are simple accessors for the text traits record or one of its members such as font, size, style, and justification.

The FinishCreateSelf() routine simply sets the mTextTraits member.

The DrawSelf() routine gets the current text traits, sets the port characteristics to match, and calls UTextDrawing::DrawWithJustification() to draw the contents.

Based on this design, when you respond to a selection that affects the text traits, all you have to do is modify the text traits information and refresh the pane. If you look at the Set...() functions among the accessors, you'll see that they do just that. They set the appropriate text traits field, and call Refresh(). As a result, the caption draws itself with the new settings.

Close the file when you are finished.


NOTE

A CDynamicCaption object is never instantiated in this application. CDynamicCaption is used as a base class for CDynamicCaptionCmdr, discussed in the next step.


2. Examine CDynamicCaptionCmdr.

class declaration CDynamicCaptionCmdr.h

Left to itself, the CDynamicCaption object cannot respond to menu commands, because it is not a commander. In a simple application like this, you could design the application object to keep track of the CDynamicCaption object, recognize and respond to each menu command, set the CDynamicCaption object text traits, and so forth.

However, it is usually wiser to distribute responsibility for commands to the objects that respond to the commands. In order to accomplish that design, the object must be a commander.

CDynamicCaptionCmdr solves the problem. It inherits from CDynamicCaption and LCommander. It is a dynamic caption object that can respond to commands. The PPob resource for the window in this application specifies a CDynamicCaptionCmdr object.

The only functions this class overrides are ObeyCommand() and FindCommandStatus(). You will write those functions later in this exercise. Before you get that far, you must do some setup work.

Close this header file when you are finished.

3. Register custom classes

CMenusApp() CMenusApp.cp

This application uses two custom pane classes. Although you only instantiate CDynamicCaptionCmdr, you should register both. The CDynamicCaptionCmdr object inherits from CDynamicCaption. PowerPlant will create the CDynamicCaption object as part of the CDynamicCaptionCmdr object.

The existing code sets up debugging and registers PowerPlant classes. Register both custom classes.


// Register required PowerPlant core class.
	RegisterClass_(LWindow);
// Register custom classes.
RegisterClass_( CDynamicCaption );
RegisterClass_( CDynamicCaptionCmdr );

4. Populate the Font menu.

Initialize() CMenusApp.cp

The content of the Font menu depends upon the runtime environment. You must fill in the items as the application starts up. You perform final setup work in the application's Initialize() function.

You should get the menu bar object, get the Font menu from it, get the Mac OS menu handle, and call ::AppendResMenu() to add resources of type FONT.


// Setup the font menu.
LMenuBar *theMenuBar = LMenuBar::GetCurrentMenuBar();
ThrowIfNil_( theMenuBar );
LMenu *theFontMenu = theMenuBar->FetchMenu( rMENU_Font );
ThrowIfNil_( theFontMenu );

::AppendResMenu( theFontMenu->GetMacMenuH(), 'FONT');

Unlike most of the other applications you have built so far, there is no code to make a window. This application supports multiple windows. At launch time, if an open-application Apple event is received, the Startup() function calls ObeyCommand() to create a new window. You write ObeyCommand() in the next step.

5. Create a window.

ObeyCommand() CMenusApp.cp

One command the application object recognizes is cmd_New. In response you should create a new window. The existing code identifies the message in a case statement. The defined constant for the PPob resource is rPPob_MenusWindow. Call the LWindow class creator function.


case cmd_New:
{
  // Create the window.
  LWindow *theWindow;
  theWindow = LWindow::CreateWindow(rPPob_MenusWindow, this );
  Assert_( theWindow != nil );

6. Establish the latent command hierarchy.

ObeyCommand() CMenusApp.cp

The command hierarchy for this simple application is (from top to bottom): application, window, caption. When you call the LWindow class creator function, you specify the application object as the commander. When the window activates, you want the caption object to become the target object. Therefore, you must make it the latent subcommander of the window. Then show the window.

To accomplish this task, now that you have the window, get a pointer to the caption object, and make it the latent subcommander of the window. This code goes right after the code you wrote in the previous step, as part of the response to new_Cmd.


  Assert_( theWindow != nil );
// Get the caption.
CDynamicCaptionCmdr *theCaption;
theCaption = dynamic_cast <CDynamicCaptionCmdr *>
       (theWindow->FindPaneByID( kDynamicCaptionCmdr ));
Assert_( theCaption != nil );

// Make it the latent subcommander of window.
theWindow->SetLatentSub( theCaption );

// Show the window.
theWindow->Show();

The last line that shows the window is redundant if the window's visible attribute is set.

Notice that the default case passes unhandled commands on to the inherited ObeyCommand() function.

7. Update the New item in the file menu.

FindCommandStatus() CMenusApp.cp

The application object is responsible for the New item, both to respond to the command and update the menu. This is a non-synthetic menu command. The existing code identifies the case. The New item should always be enabled. Enable the menu item.


case cmd_New:
{
  // Enable the New command.
  outEnabled = true;
}
break;

Save your work and close the file. You have completely implemented the application object's behavior. The responsibility for other commands rests with the caption. In the following steps you write the ObeyCommand() and FindCommandStatus() functions for the caption.

8. Respond to synthetic commands

ObeyCommand() CDynamicCaptionCmdr.cp

The caption may receive synthetic commands from either the Font menu or the Size menu. To complete this step you perform the following tasks.

a. Determine if the command is synthetic.

If the command is synthetic continue. Otherwise, you'll skip to the code that handles non-synthetic commands. You write that code in the next step.

b. Determine if it is a Font menu command.

If it is, continue. If it is not, you'll skip to the code that handles the Size menu. You write that code in substep d in this step.

c. Process the Font menu command.

Get the Font menu object, read the text of the item, and call SetFont() to update the text traits record. This completes handling of a Font menu command.

d. Determine if it is a Size menu command.

There are other synthetic commands besides the Font and Size menus. If this command is from the Size menu, continue. If it is not, you skip to the code that handles other synthetic commands. You write that code in substep f in this step.

e. Process the Size menu command.

Get the Size menu object, read the text of the item, convert it to a number, and call SetSize() to update the text traits record. This completes handling of a Size menu command.

f. Pass on other synthetic commands.

The caption object is not responsible for any other synthetic commands. If the command is synthetic, but not a Font or Size menu command, call the inherited ObeyCommand() function. In this case, that's LCommander::ObeyCommand().

The solution code for this long step is listed here for reference.


SInt16 theMenuItem;
// Is it a synthetic command.
if ( IsSyntheticCommand( inCommand, theMenuID, theMenuItem ) ) {
// Is it a Font menu command.
  if ( theMenuID == mFontMenuID ) {
    // Get the menu object.
    LMenu *theMenu;
    theMenu = LMenuBar::GetCurrentMenuBar()->
								FetchMenu( mFontMenuID );
    Assert_( theMenu != nil );

    // Get the menu item text - name of font.
    Str255 theFontName;
    ::GetMenuItemText( theMenu->GetMacMenuH(), theMenuItem,
                       theFontName );

    // Set the caption font.
    SetFont( theFontName );

  } else if ( theMenuID == mSizeMenuID ) {
  
    // Get the menu object.
    LMenu *theMenu;
    theMenu = LMenuBar::GetCurrentMenuBar()->
								FetchMenu( mSizeMenuID );
    Assert_( theMenu != nil );

    // Get the menu item text.
    Str255 theMenuText;
    ::GetMenuItemText( theMenu->GetMacMenuH(), theMenuItem,
                       theMenuText );

    // Get the size referred to by menu item.
    SInt32 theSize;
    ::StringToNum( theMenuText, &theSize );

    // Set the caption size.
    SetSize( theSize );

  } else { // Neither Font nor Size menu.

    // Call inherited.
    cmdHandled = LCommander::ObeyCommand( inCommand, ioParam );
	}
}

9. Respond to non-synthetic commands.

ObeyCommand() CDynamicCaptionCmdr.cp

The code in the previous step started with an if statement that called IsSyntheticCommand(). All the code in this step falls inside the else condition to that original if statement. In other words, if the command is synthetic, the code in the previous step handles it. Otherwise, the code in this step handles it.

We have given you most of the code in this step. The existing code sets up the else condition, switches based on the command number, and dispatches control to a variety of case statements. In this step, you complete two cases: cmd_Plain and the default case.

a. Respond to the Plain menu command.

Call the caption's SetStyle() function. Set the style to the Toolbox constant normal.


case cmd_Plain:
{
  // Set the caption style to normal (plain).
  SetStyle( normal );
}
break;

Existing code handles most other cases.

b. Pass on other menu commands.

The caption object is not responsible for other non-synthetic commands. Call the inherited ObeyCommand() function. In this case, that's LCommander::ObeyCommand().


default:
{
  // Call inherited.
  cmdHandled = LCommander::ObeyCommand( inCommand, ioParam );
}
break;

You have now completely implemented commands in this application. Your final task is to update menu items. You accomplish that in the next two steps.

10. Update synthetic menu items.

FindCommandStatus() CDynamicCaptionCmdr.cp

Remember that the Font menu has no Mcmd resource. The Size menu does have an Mcmd resource, and assigns the value -1-cmd_UseMenuItem-to each of the items for setting a particular point size. PowerPlant behaves differently with respect to menu updating for items with a -1 command number, as you are about to see first hand in the code for this step.

Existing code calls IsSyntheticCommand(). Because the Font menu is populated completely by items with no Mcmd resource, PowerPlant does not call FindCommandStatus() on an item-by-item basis. Instead, PowerPlant calls this function once for the Font menu title, with an item ID of zero. The existing code tests to see if the Font menu is involved. If it is, you should:

a. Enable the Font menu title.

b. Get the name of the font used by the caption.

Use the CDynamicCaptionCmdr GetFont() function.

c. Get the menu object.

Get the menu bar object, use it to get the Font menu object.


if ( theMenuID == mFontMenuID ) {
  // Enable the menu title.
  outEnabled = true;

  // Get the name of the font used by caption.
  Str255 theFontName;
  GetFont( theFontName );
  
  // Get the menu object.
  LMenu *theMenu;
  theMenu = LMenuBar::GetCurrentMenuBar()->
							FetchMenu( mFontMenuID );
  Assert_( theMenu != nil );
  
  // Get the number of items in the menu.

The existing code uses Toolbox functions to count the number of items in the menu and set the proper mark for each item.

For the Size menu items that have command number -1, PowerPlant also creates a synthetic menu command. However, the FindCommandStatus() function is called for each item in turn. As a result, you can handle them individually. FindCommandStatus() is called once for the menu title as well.

The existing code identifies a synthetic command involving the size menu, and switches on the item. You should:

d. Enable the Size menu title.

If the item is the menu title, enable the item.

e. Process each other item.

All other size items are treated identically, so this can be a default case. You should enable the item. Then get the Size menu object. Existing code does the rest of the work.


switch (theMenuItem)
{
  case 0: // Menu title
    
    // Enable the menu title.
    outEnabled = true;
    break;
  
  default: // Other size items with -1 command
  {
    // enable the individual item
    outEnabled = true;
    
    // Get the menu object.
    LMenu *theMenu;
    theMenu = static_cast<LMenu*> (LMenuBar::GetCurrentMenuBar()->
								FetchMenu( mSizeMenuID ));
    ThrowIfNil_( theMenu );
    
    // Get the menu item text.

Existing code determines the size for the caption text. It uses Toolbox calls to match the size represented by the menu item with the text size. If they match, it sets a check mark. If they do not, the code sets no mark (thus clearing any previous mark).

Notice that the code for the Size menu does not loop. The Font menu code executes once only-for item zero, the menu title. The Size menu code executes repeatedly, once for each item with the -1 command number, and again for item zero-the menu title. In either case-Font or Size menu-the actual command number received by FindCommandStatus() is a synthetic command.

f. Pass on other menu commands.

You must pass any command you don't handle to the inherited FindCommandStatus() function. In this case, that's LCommander.


} else { // other synthetic command
  // Call inherited.
  LCommander::FindCommandStatus( inCommand,
    outEnabled, outUsesMark, outMark, outName );
}

You have now completely handled updating menu items for which PowerPlant generates synthetic menu items. Let's do the non-synthetic items.

11. Update non-synthetic menu items.

FindCommandStatus() CDynamicCaptionCmdr.cp

The code in the previous step started with an if statement that called IsSyntheticCommand(). All the code in this step falls inside the else condition to that original if statement. In other words, if the command is synthetic, the code in the previous step handles it. Otherwise, the code in this step handles it.

We have given you most of the code in this step. The existing code sets up the else condition, switches based on the command number, and dispatches control to a variety of case statements. In this step, you complete two cases: cmd_Plain and the default case.

a. Update the Plain menu item.

Enable the item. The item uses a mark, so set that flag as well. Then determine if the caption's current style is normal. If it is, use a check mark. If not, use no mark.


case cmd_Plain:
{
  // Enable the item, set the uses mark flag,
  // and get the mark.
  outEnabled = true;
  outUsesMark = true;
  outMark = (GetStyle() == normal) ? checkMark : noMark;
}
break;

Existing code handles most other cases.

b. Pass on other menu commands.

The caption object is not responsible for other non-synthetic commands. Call the inherited FindCommandStatus() function. In this case, that's from LCommander.


default:
{
  // Call inherited.
  LCommander::FindCommandStatus( inCommand, outEnabled,
                                 outUsesMark, outMark, outName );
}
break;

Of all the cases for which code is provided for you, the one worthy of note is the case cmd_FontOther. It loops to see if the caption text size matches any of the size items in the menu. If it does not, it modifies the text of the Other item to include the actual font size.

You have now completely implemented commands and menu updating. Save your work and close the file.

12. Build and run the application.

This has been a long exercise, but now its time to see the fruits of your labor. Make the project and run it. When you do, a window should appear. See Figure 10.6.

Choose items in the Font, Size, and Style menus and watch how the caption responds. When you look at each menu, notice the check marks and how the menu updates properly. This is your code hard at work making sure the menu items are consistent with the state of the caption. The Other item in the Size menu is not supported, so it isn't checked even if the caption uses an "other" size.

Choose the New item in the File menu. A second window should appear, with another caption. Set its font, size, and style. The settings for this caption are independent of the caption in the first window. When you look in the menus, the correct item is checked for the active window and caption. Very cool.

Now, close both windows. What happens to the Font, Size, and Style menus?

Because there is no caption object to update these menus, they remain disabled (the PowerPlant default). In the design of this little application, that's appropriate behavior.

Continue exploring the command and menu updating process. If you would like to expand on this application, here are some suggestions.

Have a good time!

 

You have reached another milestone on the road to PowerPlant mastery. This exercise has given you practical experience working with all kinds of menu commands in PowerPlant.

We aren't completely through with menus just yet. In the next chapter you learn all about windows in PowerPlant. In the code exercise for that chapter, you'll build a Window menu that lists each open window. You'll do a lot more work with menus in that exercise.

 

 


[ First ]  [ Previous ]  [ Next ]  [ Last ]  [ Manuals ]

Visit the Metrowerks website at: http://www.metrowerks.com
For assistance contact Metrowerks Technical Support at: cw_support@metrowerks.com
Copyright © 2000, Metrowerks Corp. All rights reserved.

Last updated: July 21, 2000