Previous Book Contents Book Index Next

Inside Macintosh: Programmer's Guide to MacApp / Part 1 - MacApp Theory and Architecture
Chapter 5 - Events and Commands

Performing Operations With Command Objects

MacApp provides a flexible mechanism for using command objects to perform operations in your application. This mechanism supports recording command operations with Apple events and performing command operations with attached scripts.

The section "The Command Queue," beginning on page 99, describes how command objects are stored in a command queue and processed by the application object. This section provides additional detail on how command objects work, including a description of how a command operation is performed, undone, and redone.

Command Handlers

A command handler is an object instantiated from the TCommandHandler class or one of its subclasses. MacApp uses command-handler objects to help manage doing, undoing, and redoing of commands. The TApplication, TDocument, TWindow, and TView classes are all command-handling classes. Any command-handler object can have a command object associated with it through its fLastCommand field.

The Application Object

The application object, instantiated from your subclass of one of MacApp's application classes, plays several important roles in performing commands. When you post a command object to be performed, MacApp stores it in the application object's command queue (page 99). The application object periodically retrieves commands from the queue and performs them. It also provides methods to help in performing undo and redo for command objects (page 125).

Command Objects

A command object is an object instantiated from the TCommand class or one of its subclasses. A command's DoIt method performs the command operation. A simple command is one that does not support undo--its fCanUndo field is set to FALSE (the default is TRUE). For a command that does support undo, the UndoIt method undoes the command operation and the RedoIt method redoes the operation.

The Command Context

When you initialize a command object, you associate a context with it. The context is a reference to a command-handler object, such as a document or view. It is possible to have one active command per context. For example, an application can manage a separate undo operation for each of its open documents.

You can get the context from a command-handler object (such as a window or document) by calling its GetContext method. The default implementation of GetContext, in TCommandHandler, just returns a reference to the command handler itself. For view objects, GetContext returns a reference to the view's document, if it has one--otherwise, it returns a reference to the view's window.

The Command Context for Classes That Handle Apple Events

Classes that handle Apple events normally mix in the MScriptableObject class. MScriptableObject supplies the GetCommandContext method.

If your application mixes MScriptableObject with a class that is not a descendant of TCommandHandler, and that class creates command objects to handle Apple events, it should override the GetCommandContext method. Otherwise, the default Apple event target object, usually the application object, will serve as the command's context.

When a Command Is Complete

A simple command is complete as soon as its DoIt method is executed. An undoable command remains active, allowing a user to undo or redo the command operation until another undoable command is associated with the same context. A command object is automatically freed when it is completed, unless its fFreeOnCompletion field is set to FALSE (the default value is TRUE).

When a command is completed and the command operation was left in the done state, MacApp calls the command object's Commit method. You override the Commit method to perform a task after the command operation is completed. For example, a command may mark data for deletion, then unmark or re-mark it as the user chooses Undo and Redo. The overridden Commit method, which is called only if the command is completed in the done state, actually deletes the data.

Command Numbering

Each command object has a command number, which is stored in the command's fIdentifier field. Many command numbers also serve as menu item command numbers. MacApp uses the command number to identify the command and to set the Undo/Redo menu item text (page 125). For more information on command numbers, see "MacApp's Command-Numbering System," beginning on page 131

Performing a Command Operation

When your application needs to respond to a user action, you create a command object and associate a command-handler context with it. Then you call the context's PostCommand method to insert the command into the application object's command queue.

The application object retrieves and processes items from its command queue in first-in, first-out order. When the application object processes a command object, it calls the command's Process method. The Process method calls the PerformCommand method of the command object's context.

In some situations, your application may need to perform an operation immediately. For example, if you respond to an Apple event by posting a command, you may get a time-out error while the command is in the queue. So instead, you should create and initialize a command object, then call its Process method directly, instead of posting the command to the command queue.

The next section describes how a command object can send an Apple event to record its operation or allow an attached script to handle the operation. For commands that send an Apple event, you again may want to call Process directly to control the order in which Apple events are recorded.

Command Objects and Apple Events

A command object based on the TCommand class can send an Apple event describing its command operation. MacApp's Apple event-dispatching mechanism (described in "Dispatching Apple Events," beginning on page 150) allows attached scripts to respond to Apple events, so using command objects lets your application handle user actions in a flexible way. The next two sections demonstrate how a MacApp application handles an operation specified directly by a user action and how it handles the same operation specified by an Apple event.

Using a Command Object That Sends an Apple Event

To handle a user menu choice with a command object that sends an Apple event:

  1. In the DoMenuCommand method of a command-handler object (such as a document or view), you create and post a command object to handle the user menu choice. You set the command's fUseAppleEvent field to TRUE, indicating that the command should send an Apple event when it is performed.
  2. The application object retrieves the command object from the command queue and tells the command's context (a command-handler object such as a document or view; usually the same object that created the command) to perform the command. Since fUseAppleEvent is TRUE, the context asks the command to supply an Apple event describing the command operation, then sends the resulting Apple event. At this point, the Apple event may be recorded.
  3. MacApp's predispatch callback routine dispatches the Apple event to its specified object, typically a command handler such as a document or view object and possibly the same context that sent the Apple event. This gives any script attached to the target object a chance to handle the Apple event. (The predispatch handler is installed only if at least one object in the application currently has an attached script.)
  4. If the Apple event isn't handled by a script, MacApp's standard callback routine, TOSDispatcher::DispatchHandler, dispatches the event to its specified object. However, the DispatchHandler method has built-in machinery that attempts to associate the target object with a command handler (by calling the IsPendingAction method, which in turn calls the GetCommandContext method), and to determine whether the command handler is the same one that sent the current Apple event.

    For example, if a context, such as a document object, has performed a command that has sent an Apple event to make the operation recordable, it isn't necessary to dispatch the Apple event to the context to create a command to handle the operation, since the context has already created such a command.

    On the other hand, if the Apple event was sent by the currently pending command, the DispatchHandler method returns without handling the event. As a result, the command's context continues to perform the command and calls the DoIt method, which does the command operation.

    Finally, if a failure occurs while processing the Apple event, the pending command will serve as a fallback. This is why MacApp's scripting architecture has commands create Apple events.

Using a Command Object to Respond to an Apple Event

The previous section describes how to use a command object to handle an operation specified by a user menu choice. This section describes how to handle the same operation when it is specified by an Apple event. The Apple event may be sent by a script, or by an external process or other source outside the application. If recording is on, the Apple event will already have been recorded before it is received by MacApp:

  1. MacApp's predispatch callback routine dispatches the Apple event to its specified object, typically a command handler such as a document or view object. This gives any script attached to the target object a chance to handle the Apple event. (The predispatch handler is installed only if at least one object in the application currently has an attached script.)
  2. If the Apple event isn't handled by an attached script, MacApp's standard callback routine dispatches the event to its specified object. The DispatchHandler method determines that the Apple event wasn't sent by the target object itself as part of a command operation. It goes ahead and calls the target object's DoScriptCommand method, passing in a MacApp command number for the specified event.
  3. In the DoScriptCommand method of the target object (an object based on a class that mixes in MScriptableObject), you create a command object to perform the specified operation. You set the command's fUseAppleEvent field to FALSE, indicating that the command should not send an Apple event when it is performed, since the command is already being created in response to a received Apple event.

    You do not post the command to the command queue. Instead, you call its Process method to process the command immediately. Then, if an error occurs, you can handle it while you still have access to the Apple event that initiated the command.

  4. Because fUseAppleEvent is FALSE, the context does not ask the command to supply an Apple event describing the command operation. It just calls the DoIt method, which performs the command operation.

Although the processes described in this and the previous section are somewhat complex, they can be reduced to a couple of fairly straightforward rules:

In the TCommand class, fUseAppleEvent defaults to FALSE and MakeAppleEvent just returns NULL. Subclasses override the MakeAppleEvent method to create an Apple event that describes the command operation. Several MacApp command classes provide examples of this mechanism, including TNewDocumentCommand, TPrintCommand, and TQuitAppCommand.

Linked Commands

MacApp supports "linking" of two commands, which means that doing, undoing, redoing, or committing either command causes its linked command to be done, undone, redone, or committed as well. For example, to perform a drag operation that causes data to be moved between two views, the source view creates a command to delete the data and the destination view creates a command to insert the data. The commands are then linked together, so undoing or redoing the operation changes the data in each view as required. (This process is described in greater detail in Chapter 9, "Drag and Drop.")

MacApp's TCommandHandler class automatically manages operations on linked commands. For example, when MacApp performs a command, it checks the command's fValidationFailed field. If either command of a linked pair of commands fails to complete because of a validation error, MacApp ensures that both commands are left undone and that the Clipboard is also left in its previous state.

Undoing a Command Operation

When you define a command class to perform an undoable operation, you supply DoIt, UndoIt, and RedoIt methods. MacApp supplies a mechanism for calling these methods at the appropriate time, making much of the work of undoing and redoing a command operation automatic.

The Undo/Redo Menu Item

The Undo/Redo menu item appears in the Edit menu. It is enabled or disabled by the following process:

  1. MacApp calls gApplication->SetupTheMenus whenever the status of menu items might need to be updated (see "Enabling and Disabling Menu Items," beginning on page 306).
  2. The SetupTheMenus method first disables each menu item on each menu MacApp manages, then calls the HandleSetupMenus method of the current target object.
  3. The HandleSetupMenus method causes the DoSetupMenus method to be called for each behavior object and event-handler object in the target chain.
  4. For the TCommandHandler class, the HandleSetupMenus method calls the SetupUndoMenu method.
  5. If the command-handler object has an undoable command associated with it, SetupUndoMenu enables the cUndo menu command. It then calls an application method, SetUndoText, passing a Boolean value that indicates whether the command is in the done or undone state.
  6. The SetUndoText method determines what text should go in the Undo/Redo menu item and sets that text. It calls the global routine CommandToName to get the menu item text based on the command number, then prepends the word "Undo" or "Redo", depending on the state of the command. For a Copy command in the done state, for example, the text would be set to "Undo Copy".

Setting the text for an Undo/Redo menu item depends on information stored in the application's 'CMNU' resources. These resources describe an application's menus and associate a command number with each menu item.

For commands that do not appear on any menu, you specify the Undo/Redo text in a special 'CMNU' resource called the Buzzwords menu. This resource is described in "The Buzzwords 'CMNU' Resource," beginning on page 305. The Buzzwords menu does not appear in your application's menu bar--you use it only to supply MacApp with information about commands that do not appear on any menu.

If, for example, your application has a drawing command that is initiated by dragging with the mouse (it doesn't appear on any of your application's menus), you supply an entry to the Buzzwords menu that specifies "Drawing" as the Undo/Redo text for the command. As a result, MacApp can display the text "Undo Drawing".

The Undo/Redo Flow of Control

When the Undo/Redo menu item is enabled, the user can reverse the state of the current command by typing Command-Z or choosing the Undo menu item. The result is a call to the current target object's HandleMenuCommand method. The flow of control to the HandleMenuCommand method is shown in Figure 5-7. (The general operation of the HandleMenuCommand method is shown in Figure 5-4.)

The cUndo menu command is typically handled by the DoMenuCommand method of the TDocument class, which calls the application object's DoUndoRedo method. The DoUndoRedo method posts a TClientCommand to send an Undo Apple event.

The Undo event sent by the TClientCommand object is received immediately and dispatched to the document object's DoScriptCommand method. The same dispatching occurs if an Undo event specifying the document object as its target is received from a script or other source. The document's DoScriptCommand method handles the Undo event by calling the application object's UndoRedoInContext method.

Both the TDocument and the TApplication classes handle the cUndo command number by calling UndoRedoInContext from their DoScriptCommand methods. If you handle undo/redo in a context other than an application or document, your context's DoScriptCommand method should also call the application object's UndoRedoInContext method.

Figure 5-7 Initiating an undo operation

The application's UndoRedoInContext method creates and posts a TUndoRedoCommand object, which is inserted into the application's command queue. Figure 5-8 continues where Figure 5-7 leaves off, at the current target object's HandleMenuCommand method.

Figure 5-8 Handling an Undo command or Undo Apple event

Figure 5-8 shows the steps leading to the creation and posting of a TUndoRedoCommand object, including the case of an Undo event received from an attached script or from a source outside the application. The application object retrieves the TUndoRedoCommand object from the command queue and processes it, resulting in a call to the DoIt method. The TUndoRedoCommand::DoIt method retrieves a command from its context, typically the document object. If the command is currently in the done state, DoIt calls the command's UndoIt method; if the command is in the undone state, DoIt calls the RedoIt method.

This process is slightly more complicated when the command is a linked command. Linked commands are described in "Linked Commands," beginning on page 123.

Commands and Change Notification

When the TCommandHandler::DoPerformCommand method calls a command object's Commit method after the command's operation is performed, it also calls the command's DoNotification method. The TCommand::DoNotification method calls the Changed method of the object referenced by its fObjectToModify field. The fObjectToModify field normally refers to the command's context--the same object referred to by the fContext field.

Your application can take advantage of this notification mechanism. For example, the Changed method of the TDocument class modifies the document's change count, to keep track of whether the document needs to be saved.

Commands and the Clipboard

MacApp's command mechanism makes it relatively easy to work with the Clipboard. For example, MacApp automatically switches back and forth between Undo and Redo Clipboard views as the user chooses to undo and redo a command that affects the Clipboard. MacApp's Clipboard support can display 'PICT' and 'TEXT' data types in a Clipboard view. You can build on MacApp's support to cut and paste your private data types as well.

Basic Clipboard support is described in the next section. Commands that work with the Clipboard are implemented in Chapter 22, "Working With the Clipboard."

Previous Book Contents Book Index Next

© Apple Computer, Inc.
25 JUL 1996