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

 

Chapter 15.

 

Periodicals and Attachments



You have come a long way. This is the last chapter of the PowerPlant Book. At the end of this chapter we'll look back at everything we have covered so far, and discuss briefly where you should go from here.

In this chapter we close out the core elements of PowerPlant with a discussion of two important parts of the PowerPlant application framework:

We saved these two topics for last for good reason.

Periodicals are very easy to implement, and can serve for any kind of time-dependent function. They aren't restricted to particular application tasks like file I/O or menu handling. Periodicals are unbounded in terms of utility. Therefore it helps to have a good understanding of PowerPlant before discussing periodicals.

That is even more true for attachments. Attachments are perhaps the most astonishingly powerful and simple concept in all of PowerPlant. They are the epitome of elegance in design and implementation. You are really going to like attachments.


Periodicals

In this section we discuss LPeriodical, and how PowerPlant implements repetitive tasks. This is a very cool and simple feature of PowerPlant. The topics are:

Periodicals are very easy to implement.


What Is a Periodical

In PowerPlant terminology, an object that receives time on a regular basis is a periodical. LPeriodical is an abstract base class. It inherits from no other class. Because it is an abstract class, you cannot instantiate a pure LPeriodical object. You must derive a new class that inherits from LPeriodical.

In PowerPlant, LPeriodical is used exclusively as a mix-in class. Whenever an object needs to get time repeatedly, it inherits from LPeriodical. Figure 15.1 illustrates the PowerPlant classes that inherit from LPeriodical.

LPeriodical in PowerPlant:

The subclasses shown in Figure 15.1 belong to various class hierarchies in PowerPlant. Except for LGrowZone, each also inherits from other classes besides LPeriodical.

Because it is so loosely tied to the rest of PowerPlant, you can use LPeriodical separately in non-PowerPlant projects if you wish.

The only other class you need to use is LArray. LPeriodical maintains lists of objects that need regular attention. Those lists reflect the way that you use LPeriodical in PowerPlant.


Periodical Characteristics

PowerPlant deals with two kinds of periodicals, repeaters and idlers. A repeater gets time after every event. An idler gets time at every idle event.

LPeriodical has two data members, sRepeaterQ and sIdlerQ. Each of these data members is a pointer to an LArray object. Each represents a queue of objects, repeaters and idlers respectively.

Both of these data members are static, so they are class variables. There is one and only one instance of each of these variables shared by all periodical objects. Because they are static data members, any application that has periodicals has one list of repeaters, and one list of idlers.


Working With Periodicals

This section covers everything you need to work with periodicals. After looking at the member functions, we examine

Table 15.1 lists the LPeriodical member functions.

LPeriodical functions:

 

Function
Purpose
StartRepeating()  
add object to repeater list  
StopRepeating()  
remove object from repeater list  
DevoteTimeToRepeaters()  
walk through all repeaters, call SpendTime() for each  
StartIdling()  
add object to idler list  
StopIdling()  
remove object from idler list  
DevoteTimeToIdlers()  
walk through all idlers, call SpendTime() for each  
SpendTime()  
perform a periodical task  

The LPeriodical destructor also deserves mention. It removes the periodical from either or both the repeater and idler queues.

Typically, the only function you override is SpendTime(). It is a pure virtual function and must be overridden in any subclass of LPeriodical. To understand these functions, let's look at how PowerPlant gives time to periodicals.


Repeaters

PowerPlant calls LPeriodical::DevoteTimeToRepeaters() every time through the main event loop. You can find this code in LApplication::ProcessNextEvent(). The DevoteTimeToRepeaters() function walks through the list of repeaters and calls each repeater's SpendTime() function.

To add an object to the repeater list, simply call that periodical's StartRepeating() function. In response, the object is added to the repeater queue. When control returns to the main event loop, the object's SpendTime() function will be called.


NOTE

The SpendTime() function will be called on the same pass through the main event loop in which the repeater is added to the repeater queue.


PowerPlant calls the SpendTime() function for every repeater on each pass through the event loop. PowerPlant does not call one repeater on one pass through the event loop, and another repeater on another pass.

To remove a periodical from the repeater queue, call StopRepeating(). You do not need to make this call if you destroy the object. The destructor does that for you.


Idlers

When PowerPlant receives an idle event or a mouse-moved event, it calls UseIdleTime(), which in turn calls LPeriodical::DevoteTimeToIdlers(). DevoteTimeToIdlers() walks through the list of idlers and calls each idler's SpendTime() function.

To add an object to the idler queue, simply call that periodical's StartIdling() function. In response, the object is added to the idler queue. The next time there is an idle event or a mouse-moved event, PowerPlant calls the object's SpendTime() function.

This is the same SpendTime() function called when the object is a repeater. PowerPlant calls the SpendTime() function for every idler for each idle or mouse-moved event.

To remove a periodical from the idler queue, call StopIdling(). You do not need to make this call if you destroy the object. The destructor does that for you.


TIP

A periodical can safely delete itself. The queue's for idlers and repeaters (LArray objects) are safe against insertions or removals while an iterator is traversing the list. See "Arrays."



Spending time

A single periodical can be on both the repeater and idler queues simultaneously. You can put the same periodical on either queue at any time. The two queues are fully independent.

Membership in either queue means that the periodical's SpendTime() function is called. In LPeriodical, this is a pure virtual function. You must override this function in the derived class.

The SpendTime() function can do just about anything you want. You can maintain a progress bar, run a simple animation, blink a cursor, and so on. The PowerPlant classes that inherit from LPeriodical give a hint at the flexibility of this system.

LTextEditView and LEditField inherit from LPeriodical so that they can blink the text cursor. An object of either class works the same way. Each is an idler. In the BeTarget() function, the object installs itself in the idler queue by calling StartIdling(). In the DontBeTarget() function, each calls StopIdling(). In the SpendTime() function, each calls ::TEIdle(). As a result, while the object is the target object, the text cursor blinks. Very simple.

LMovieController is a periodical so that it can handle QuickTime movies properly. When a movie is present, the controller should receive every event so that the Toolbox can handle movie-related events. To support this feature of the Mac OS, LMovieController is a repeater. The LMovieController constructor calls StartRepeating() to put the controller in the repeater queue. As long as the movie controller exists, its SpendTime() function is called from the event loop for each event. In SpendTime(), the controller receives the event and calls ::MCIsPlayerEvent() for processing. When the movie controller is destroyed, it removes itself from the repeater queue.

LGrowZone is also a repeater. The LGrowZone constructor calls StartRepeating() to install itself in the repeater queue. The SpendTime() function implements part of the PowerPlant memory strategy we discussed in "Setup Memory Management." If the memory reserve has been released, SpendTime() attempts to restore the reserve. This function also warns the user of memory problems if necessary.

These four examples give you a taste of the power and flexibility of the PowerPlant periodical design. Using the identical mechanism, PowerPlant implements cursor updating, event processing, and memory management. Not bad.

Your use of LPeriodical is limited only by your imagination. If you have a situation where an object needs time repeatedly, make it a periodical. Design the SpendTime() function to perform the necessary tasks. Install the object in the appropriate queue-either repeater or idler-at the appropriate times, and remove it from the queue when finished.

Remember that your periodical object is not required to respond every time SpendTime() is called. Your object can keep track of the passage of time, and only do something if a required interval has passed. For example, you might want to update a timer every minute. Your timer object might get called several thousand times during that minute, but only act when a full minute has passed.

The periodical mechanism does not guarantee that your object will receive time within a certain time limit. The mechanism depends on the main event loop. Repeaters will get time on every pass through the event loop. If a single event requires a lot of time to process, your repeater must wait. Idlers get time at idle events. If no idle event is forthcoming, idlers get no time.

In actual practice, the event loop typically cycles several times a second. Intervals between idle events are usually very short. However, if your object is extremely fussy about receiving time at precise intervals, you will have to implement some other mechanism to ensure that your object gets called-probably a Time Manager task.


NOTE

The StDialogHandler class implements its own event loop. That loop uses the same design as the main event loop, so repeaters and idlers are called while a modal dialog based on StDialogHandler is active.



Attachments

An attachment is an object that-typically-alters the runtime behavior of another object. It is connected (attached) to the affected object. We refer to these two objects as the attachment and the host.

The attachment mechanism is very general-and very powerful. Exactly what an attachment is, and how to use one, becomes clear as we discuss:


What Is an Attachment

There are two parts to the attachment mechanism in PowerPlant: the objects to which you connect the attachments, and the attachments themselves.

Objects to which you can connect an attachment are said to be "attachable." Be careful of the terminology here. The term "attachable" in normal usage implies that something is capable of being attached to something else. In PowerPlant, we use the converse meaning. Saying that an object is attachable means something can be attached to it.

An object to which an attachment is connected is called a host.

There are two corresponding base classes, LAttachable and LAttachment. Let's look at LAttachable first, and then at LAttachment.

Figure 15.2 illustrates the PowerPlant classes that are attachable-that is, to which you can hook an attachment.

LAttachable class hierarchy:

Stop for a moment and consider the import of Figure 15.2. Every commander class can have attachments-windows, applications, and so on. Event dispatchers can have attachments-applications and StDialogHandler. Every pane class-panes, views, and controls-can have attachments. In other words, every visual, command, and event-related element in PowerPlant can have attachments.

LAttachment describes the features of a generic attachment. PowerPlant also includes several attachment classes ready for your use. Figure 15.3 lists the PowerPlant attachment classes.

LAttachment class hierarchy:

If you look at the list of subclasses that derive from LAttachment, you begin to get clues about the usefulness of attachments. PowerPlant uses attachments for modifying the appearance of a pane, for modifying the behavior of a pane, to support commands and keystrokes, the undo mechanism, and the clipboard. You can use attachments for other purposes as well. That's a lot of utility for one design pattern.


TIP

For programmers familiar with MacApp, PowerPlant attachments supply the functionality of the MacApp adorner and behavior classes.


We'll discuss the individual attachment classes in "Specific PowerPlant Attachments." For now, let's get an overview of how attachments work, and then look at the features of attachments in general.


Attachment Strategy

The PowerPlant approach to attachments is similar to the periodical mechanism with which you are already familiar. It is a very simple strategy.

As you know, there are attachments and hosts. These objects are closely connected.

Each host maintains a list of its own attachments. As a result, an object may have several attachments.The host can add or remove attachments from the list, so you can modify the list of attachments at runtime.

At certain well-defined moments, PowerPlant tells the host to walk through the list of attachments. We'll discuss the precise moments in "When and how PowerPlant calls attachments." PowerPlant sends a specific message that identifies the task that the host is about to undertake. PowerPlant also sends any necessary data that might be required to fulfill the task. This message is sent before the host performs its principal, underlying task.

In response, the host tells each attachment in the list to do whatever it is the attachment does. The attachment determines if the message is one to which it should respond. If the attachment is designed to respond to the message, the attachment executes.

The attachment also returns a Boolean value that tells PowerPlant whether the host object should still execute the original task.

Using this mechanism you can modify the appearance and/or behavior of any attachable object at runtime, without modifying the underlying object. You simply add or remove attachments.

That's all there is to it. The attachment mechanism is like a blend of the periodical and messaging systems. At certain well defined moments, a message is sent to each attachment. If the attachment recognizes the message, it responds and performs its task.

To implement this strategy, both attachable objects and attachments have data members and functions. Let's see what they are as we examine attachment characteristics.


Attachment Characteristics

In this section we examine both:

We look at LAttachable first, because it is an extremely simple class that implements the whole strategy.


Features of attachable objects

LAttachable has one data member, mAttachments. This is a pointer to an LArray of attachment objects.

Table 15.2 lists all the LAttachable functions, except for the constructor and destructor.

LAttachable functions:

 

Function
Purpose
AddAttachment()  
add an attachment to the list  
RemoveAttachment()  
remove an attachment from the list  
RemoveAllAttachments()  
remove all attachments from the list  
ExecuteAttachments()  
walk the list, call each attachment's Execute() function  

The first three functions modify the contents of the attachment list. The class destructor calls RemoveAllAttachments() when destroying an attachable object. You can use it directly yourself if you want to remove all attachments for any reason.

The ExecuteAttachments() function is the dispatch mechanism for the host. This is the function that PowerPlant calls when it wants attachments to do their work.

Typically you will never override any of these functions. LAttachable is a complete, fully-realized class that provides all the functionality you are likely to need.


Features of attachments

LAttachment is almost as simple as LAttachable. It has three data members, listed in Table 15.3.

LAttachment data members:

 

Data Type
Member
Description
LAttachable*  
mOwnerHost  
pointer to the host attachable object  
MessageT  
mMessage  
message to which the attachment responds  
Boolean  
mExecuteHost  
whether the host object should also execute  

Each attachment is designed to respond to a particular message. That value is kept in the mMessage member. We'll discuss the possible messages in "Working With Attachments."

The class declares eight functions. Six of these functions are simple accessors for the three data members. There are two functions designed to implement attachment functionality, listed in Table 15.4.

LAttachment functions:

 

Function
Purpose
Execute()  
if message is right, call ExecuteSelf()  
ExecuteSelf()  
perform the attachment task  

The Execute() function does the necessary testing. If the message received is the message for which the attachment is designed, it calls ExecuteSelf(). If the attachment executes, Execute() returns the value of mExecuteHost. Otherwise it returns true. PowerPlant uses this value to decide whether the host object should also perform the task in question.

The only function you typically override is ExecuteSelf(). In fact, in LAttachment this is an empty function. You can study the PowerPlant attachment classes like LPaintAttachment to see how they implement ExecuteSelf().


Working With Attachments

In this section we discuss the code-level details you need to implement attachments in your PowerPlant application. We discuss:


When and how PowerPlant calls attachments

PowerPlant asks attachments to execute before:

At these moments, PowerPlant calls a host's ExecuteAttachments() function. It passes two parameters: a specific message, and a pointer to additional data.

Table 15.5 summarizes each call to ExecuteAttachments() in PowerPlant. Each entry in the table lists the type of host object; the message sent; the data sent; the task or function that may be performed immediately after the attachments execute.

PowerPlant use of attachments:

 

Host
Message
Data
Before
application  
msg_Event  
EventRecord*  
event dispatch  
StDialog
Handler  
msg_Event  
EventRecord*  
event dispatch  
commander  
the command  
command data  
ObeyCommand()  
commander  
msg_Command
Status
 
SCommandStatus*  
FindCommand
Status()
 
commander  
msg_KeyPress  
EventRecord*  
HandleKeyPress()  
commander  
msg_PostAction  
LAction*  
sending action to supercommander  
pane  
msg_Click  
SMouseDownEvent*  
ClickSelf()  
pane  
msg_DrawOrPrint  
Rect* (frame)  
DrawSelf()  
pane  
msg_DrawOrPrint  
Rect* (frame)  
PrintPanelSelf()  
pane  
msg_AdjustCursor  
EventRecord*  
AdjustCursorSelf()  

Pay particular attention to the items in the "Before" column. An object may have several attachments. If any attachment returns false, these functions do not execute. Attachments can control whether event dispatch occurs, panes draw, commanders handle commands, and so forth. This gives each attachment the opportunity to tell your application "I have completely handled this situation, you can ignore it." If all attachments return a value of true, then the host function executes normally, after the attachments completes their work.


TIP

if you call ExecuteAttachments() yourself and send the message msg_AnyMessage, all attachments will execute.


In the discussion of "Specific PowerPlant Attachments" you will see examples of attachments that are designed to respond to various kinds of messages.


Creating your own attachments

Creating an attachment is a fairly straightforward process. Use the PowerPlant attachment classes as examples.

You declare your derived class to inherit from LAttachment. Then you override ExecuteSelf().

The ExecuteSelf() function does whatever it is you need to do. Attachments can be designed to respond to a specific message, or a group of messages if you override Execute().

The next sections give you some ideas about when you use attachments, and what you might do with them.


When to use attachments

In general, an attachment is an excellent solution when you have an independent behavior that you wish to implement for a variety of panes or commanders, either in the same project or in different programming projects.

An attachment is also an excellent solution when you want to modify the behavior of a pane or commander dynamically. You can add and remove attachments at will depending upon the application's context.

You can think of an attachment as a kind of inheritance (in a very loose sense) for behaviors. An attachment connects a special function to an object, without the need to create a new class of object. If you think of an object as the sum of itself and its attachments, you can modify the composition of the object dynamically by adding or removing attachments.


Uses for attachments

About the only limitation to attachments is your imagination. The ideas described in this section are but a sampling of what you can do.

Consider the three principal kinds of objects that can host attachments. Applications are commanders and event handlers. Commanders handle commands and keystrokes. Panes handle clicks. Some panes are also commanders.

You may design an attachment for event pre-processing. Before any event is ever dispatched, the application's attachments get a crack at it. If ever there was a boundless horizon, this is it. You can do anything you want with the event, and subsequently short circuit event dispatch or allow the event to be handled normally. It's up to you.

A commander's attachments get first crack at all commands. You may design an attachment to handle a specific kind of command. In a traditional approach, your commander's ObeyCommand() function handles commands. You might want to create a command-handler attachment that you can connect to any appropriate commander. Then you don't have to duplicate code. If you decide a commander should respond to a particular kind of command, you simply hook up an attachment that does the work.

You could use a command-level attachment to create a demo version of an application. In the demo version, you hook up an attachment that intercepts certain commands-for example the New command-to disable them.

You might design an attachment to handle menu updating. A commander's attachments get first crack at menu update requests as well. PowerPlant provides an attachment for this purpose, LCommandEnabler. Rather than write the code directly into your FindCommandStatus() function, you can hook attachments to your commander to handle whatever menu commands you need to take care of.

With respect to panes, attachments have an opportunity to execute before drawing, clicking, and cursor adjustment. You may do some fancy drawing in or around a pane. Perhaps you have some panes that you want to have a fancy border. Create a border attachment and hook it up to those panes. If you want any unique behavior to occur when a pane is clicked, create an attachment to implement the behavior. The possibilities are endless.


Specific PowerPlant Attachments

Looking at some real attachments will help you grasp the power and potential in the attachment design pattern. As we stated before, PowerPlant provides several attachment classes. You can use these whenever appropriate in your own applications. You can also use them as models for your own attachments. In this section we discuss the following attachments:

All of these classes are declared in UAttachments.h and defined in UAttachments.cp.

The LUndoer class, which derives from LAttachment, is more than a simple attachment. It is the basis for the PowerPlant implementation of undo functionality.


See also

The PowerPlant Reference for more on action classes and undo.


LBeepAttachment

This is a simple attachment designed to respond to any message you want, typically a click message. When you create the object, you specify the message to which you want the attachment to respond. When attached to any host, this attachment beeps when the appropriate message is received.


LBorderAttachment

This attachment is designed to respond to msg_DrawOrPrint. When you create this attachment, you specify a PenState, a foreground color, and a background color. You also specify whether the host should draw as well.

This attachment draws a border around the pane's frame with the specified pen.


LPaintAttachment

This attachment is designed to respond to msg_DrawOrPrint. When you create this attachment, you specify a PenState, a foreground color, and a background color. You also specify whether the host should draw as well.

This attachment paints the host using the specified PenState settings and foreground and background colors. The painted rectangle is inset from the pane's frame by the size of the pnSize field of the PenState. This lets you use an LPaintAttachment in conjunction with an LBorderAttachment to draw a filled rectangle.

Because attachments draw first, you can use this attachment to fill in a background of a pane before drawing occurs.


LEraseAttachment

This attachment is designed to respond to msg_DrawOrPrint. It simply erases the pane before drawing.


LCommandEnablerAttachment

This attachment responds to msg_CommandStatus. This is a good example of a command-updating attachment. When you create the attachment, you specify the command that should be enabled.

When it executes, this attachment enables the menu item associated with the command. It does not set a mark or do any other item manipulation. It also prevents the host from executing, because it has already enabled the command in question.

You can use this attachment as a model for an attachment that handles other updating tasks. For example, you might create a check mark attachment that puts a check mark in front of a menu item.


LKeyScrollAttachment

This attachment is a good example of keystroke preprocessing. It is also an excellent example of the kind of task for which an attachment is ideally suited.

This attachment handles scrolling a view using keyboard navigation keys: Home, End, PageUp, and PageDown. This kind of functionality is a very nice thing to add to a view. Rather than writing code to implement this functionality in every scrolling view class, why not create an attachment that does the processing for you? Then, if you ever want to add this feature to a view, simply create and connect the attachment.

This particular attachment responds to msg_KeyPress. When you create the attachment, you provide a pointer to an LView object. This is the scrolling view. Because this attachment is responding to a keystroke, the attachment must be hosted by a commander.

If you have a view that is also a commander-a class derived from both LView and LCommander-you can attach an LKeyScrollAttachment to it to implement keyboard navigation.

If your view is not a commander, you can attach the LKeyScrollAttachment to a superview that is a commander (such as the window containing the view).


WARNING!

If you can delete the scrolling view independently of the commander that has the attachment, you must take care to delete the attachment as well. If you do not, the attachment keeps the pointer to the now-deleted view (a dangling pointer) and you're in for big trouble.



Summary

In this chapter you learned how elegant design can make difficult programming tasks much easier to accomplish.

By the simple expedient of making an object inherit from LPeriodical, you ensure that it receives time on a regular basis. Implementing any time-dependent task becomes trivial. The object receives attention on every pass through the event loop, or at idle time, depending upon whether you install it in the repeater queue or the idler queue. You override SpendTime(), and you're done.

PowerPlant's use of attachments reflects an extraordinarily simple, powerful, and unbounded design pattern. This kind of elegance can be found elsewhere in PowerPlant-for example, in the broadcast/listen messaging mechanism. But nowhere else are true power and simplicity so well combined.

You create an attachment, specify the type of message to which it should respond, and override the ExecuteSelf() function. You connect the attachment to an appropriate host object. PowerPlant gives your attachment the opportunity to execute at several points in the ordinary flow of events.

Using attachments, you can create independent behaviors that you attach or remove from objects dynamically.

Because this chapter is the end of the PowerPlant Book, you'll find a brief recap after the code exercise that sums up where we have been. But first, let's jump into the final code exercise.


Code Exercise

This is it, the goodies you've been waiting for. In this code exercise you get a glimpse at the real power of object-oriented programming with a well-designed application framework.

Best of all, you're going to create two pieces of code that are valuable, real-world additions to your personal collection of reusable code. One is a periodical, and the other is an attachment. Each demonstrates the ease with which you can use both of these marvelous PowerPlant features.

Appropriately, the application you write in this code exercise is titled "Goodies." Let's look at the interface briefly, and then build a periodical and an attachment.


The Interface

In the Goodies application, you create a window that displays a progress bar, as shown in Figure 15.4. This application also has a Window menu, just like the menu you built in the code exercise in Chapter 11, "Windows."

There isn't any real task going on that requires a progress bar, this is just a demonstration. The window has a button to start the bar as if there were something going on. When you click the Start button, the "barber pole" progress bar animates. The text in the button changes to Stop, and the caption changes to Busy.

The Goodies window:

Open the Goodies.ppob project file in Constructor and examine the PPob resource for this window. Pay particular attention to the characteristics of the barber pole pane, as shown in Figure 15.5.

Barber pole properties:

This pane represents a custom class, with ID BarP. It also has two custom data items, the First PICT ID and Last PICT ID. To animate the barber pole, the pane cycles through a list of PICTs, displaying each picture in turn. In this case there are four PICTs, numbered from 1000 to 1003. The PICT resources have been provided for you in Goodies.rsrc.

Feel free to examine the custom pane type-the CTYP resource-as well.You created a custom pane in the code exercise in Chapter 15, "Controls and Messaging."


Implementing Goodies

In this section you implement a periodical task and an attachment. The progress window is a periodical. The Window menu is an attachment.

This is the same Window menu you created in Chapter 11, so you should be familiar with how it works. The Window menu is implemented as a custom class derived from LMenu. The application can't use the PowerPlant default menu-creation mechanism because PowerPlant creates LMenu objects. This application creates a CWindowMenu object and adds it to the menu bar.

In Chapter 11, you added code to the application's ObeyCommand() and FindCommandStatus() functions to manage the Window menu. In this exercise you write similar code, but put it in the attachment's ExecuteSelf() function! Let's get started.

1. Examine the CBarberPolePane class

class declaration CBarberPolePane.h

Look at the code that declares this class. First, notice that this class inherits from both LPane and LPeriodical. It has a class ID BarP. There are several constructors, and a destructor.

The class overrides the SpendTime() function, as is necessary in any descendant of LPeriodical. The class also overrides the DrawSelf() function inherited from LPane.

Finally, notice the data members and the kThrottleTicks constant. The object stores the resource IDs of the first, last, and current PICT on display. It stores the time to change pictures in mNextTime. It changes the picture every kThrottleTicks ticks on the system clock.You'll write the code to do this in the next step.

Close the file when you are through examining this class.

2. Animate the barber pole pane.

SpendTime() CBarberPolePane.cp

The existing code gets the current tick count. After that, you:

a. Determine if it is time to change pictures.

Test the current tick count against mNextTime.

b. Advance to the next picture.

If it is time to change, increment mCurrPictID.

c. Keep the picture in the proper range.

Make sure mCurrPictID stays in the range defined by the mFirstPictID and mLastPictID.

d. Draw the picture.

Call the pane's Draw() function.

e. Reset the time to change pictures.

Add kThrottleTicks to the current time, and store the result in mNextTime.


SInt32 theCurrTicks = ::TickCount();
if ( theCurrTicks > mNextTime ) {
  // Increment the pict id.
  mCurrPictID++;
  
  // Rollover if needed.
  if ( mCurrPictID > mLastPictID )
    mCurrPictID = mFirstPictID;
  
  // Redraw.
  Draw( nil );
  
  // Get the next time.
  mNextTime = ::TickCount() + kThrottleTicks;
}

Take a quick look at the DrawSelf() function, just to see what it does. It gets the current picture and draws it. Notice that in SpendTime() you call Draw(), not DrawSelf(). Draw() takes care of setup tasks, and calls DrawSelf() for you.

When you are through, save your changes and close the file.

3. Manage the periodical.

SetBusyState() CProgressWindow.cp

CProgressWindow is also a custom class. It inherits from both LWindow and LListener. When a CProgressWindow is created, it installs itself as a listener to the Start/Stop button in the window. That button sends a msg_ProgressControl message that the window receives in its ListenToMessage() function. Feel free to study the code to see how it works.

ListenToMessage() calls SetBusyState() to do the work, and passes a Boolean value. If the value is true, the window is becoming busy and the pane should animate. Otherwise, the window is not busy and the animation should stop. The existing code stores this value in the window's mBusy data member. Then:

a. Get the barber pole pane.

The declared constant for this pane ID is kBarberPolePane.

b. Turn on the animation if busy.

Install the pane in the idler queue. Use the StartIdling() function.

Change the text in the Start button to "Stop." The declared constant for this pane ID is kProgressControlButton.

Change the text in the status caption to "Busy" The declared constant for this pane ID is kProgressMessageCaption.

c. Turn off the animation if not busy.

Remove the pane from the idler queue. Use the StopIdling() function.

Change the text in the Stop button to "Start." Change the text in the status caption to "Not Busy." See substep b for the names of the constants for these pane IDs.


mBusy = inBusy;
// Get the barber pole pane.
CBarberPolePane *theBarberPolePane;
theBarberPolePane = dynamic_cast<CBarberPolePane *>
                         (FindPaneByID( kBarberPolePane ));
Assert_( theBarberPolePane != nil );

if ( mBusy ) {

  // Start the barber pole idling.
  theBarberPolePane->StartIdling();
  
  // Set the button title.
  SetDescriptorForPaneID( kProgressControlButton, "\pStop" );
  
  // Set the message caption.
  SetDescriptorForPaneID( kProgressMessageCaption, "\pBusy" );
  
} else {

  // Stop the barber pole idling.
  theBarberPolePane->StopIdling();
  
  // Set the button title.
  SetDescriptorForPaneID( kProgressControlButton, "\pStart" );
  
  // Set the message caption.
  SetDescriptorForPaneID( kProgressMessageCaption, "\pIdle" );
}

You have now completely implemented the animated barber pole pane. When the user clicks Start, it animates. When the user clicks Stop, it stops. Save your work and close the file.

In the rest of this exercise, you add a Window menu attachment to the application. First, we'll examine the Window menu code. After that, you have four principal tasks to accomplish. You must install the menu in the menu bar, create an attachment to handle the menu, connect the attachment to the application object, and implement menu functionality in the attachment.

4. Examine the Window menu.

class declaration CWindowMenu.h

Examine the member functions in this class. You may recall these functions from Chapter 11, because you wrote some of them. The InsertWindow(), RemoveWindow(), MenuItemToWindow(), WindowToMenuItem(), and SetCommandKeys() functions are all identical to the code you wrote in that previous code exercise. Feel free to refer to that chapter for a refresher on the menu operations.

What's new in this file is the declaration of the CWindowMenuAttachment class. It inherits from LAttachment. It has one significant function, ExecuteSelf(). It has a single data member-a pointer to the Window menu object. You'll write the ExecuteSelf() function a little later.

When you're through examining the class declaration, close the file.

5. Install the Window menu.

Initialize() CGoodiesApp.cp

As we mentioned at the start of this section, you cannot rely on the PowerPlant menu-creation mechanism because it creates LMenu objects. The Window menu is a CWindowMenu object.

In the application constructor, existing code registers the custom classes. In the Initialize() function, you have three tasks to accomplish.

a. Create a CWindowMenu object.

Use the new operator. The declared constant for the MENU resource ID is rMENU_Window. Store the result in the global variable, gWindowMenu.

b. Get the application's LMenuBar object.

Use LMenuBar::GetCurrentMenuBar().

c. Add the new menu to the menu bar.

Use the menu bar's InstallMenu() function.


  // Make the window menu.
  gWindowMenu = new CWindowMenu( rMENU_Window );
  ThrowIfNil_( gWindowMenu );
  // Get the menu bar.
  LMenuBar *theMBar = LMenuBar::GetCurrentMenuBar();
  ThrowIfNil_( theMBar );

  // Install the window menu.
  theMBar->InstallMenu( gWindowMenu, 0 );

6. Connect a Window menu attachment to the application.

Initialize() CGoodiesApp.cp

The code for this step goes right after the code you wrote in the previous step. You have two tasks.

a. Create a CWindowMenuAttachment.

Use the new operator and create a CWindowMenuAttachment object. You write this constructor in the next step.

b. Connect the attachment to the application.

Call the application's AddAttachment() function to connect the attachment to the application. Add the new attachment to the end of the attachment list. Specify that the application owns the attachment. This makes the application responsible for deleting the attachment when the application is deleted.


theMBar->InstallMenu( gWindowMenu, 0 );
  // Install the window menu attachment.
  CWindowMenuAttachment *theAttachment;
  theAttachment = new CWindowMenuAttachment( gWindowMenu );
  AddAttachment( theAttachment, nil, true );

Save your work and close the file.

7. Define the CWindowMenuAttachment constructor.

CWindowMenuAttachment() CWindowMenu.cp

To create this attachment, you must do two things.

a. Call the LAttachment constructor.

Set this attachment so it responds to any message, and allows the host to execute. This attachment must receive all messages so that it can identify and respond to menu commands and menu updating.

b. Initialize the CWindowMenuAttachment.

Set the mWindowMenu data member.


CWindowMenuAttachment::CWindowMenuAttachment(
                                 CWindowMenu *inWindowMenu )
  : LAttachment( msg_AnyMessage, true ),
       mWindowMenu( inWindowMenu )
{
}

Excellent! You have installed the menu, created the attachment, and connected the attachment to the application. The final task is to implement menu functionality.

In Chapter 11, you did this in the traditional way-by modifying the application's ObeyCommand() and FindCommandStatus() functions. In the next two steps you implement the same kind of functionality in the ExecuteSelf() function of the Window menu attachment.

8. Identify menu update requests.

ExecuteSelf() CWindowMenu.cp

Before updating menus, PowerPlant sends a message to the application's attachments. This happens in the LCommander::ProcessCommandStatus() function. The message has two parameters. The first is the message itself, msg_CommandStatus. The second parameter is a pointer to an SCommandStatus structure. That structure holds the data you normally find in the FindCommandStatus() parameters.

The attachment receives this message at menu update time. Existing code says that the host should execute, identifies the message received, and defines local variables-including a pointer to an SCommandStatus structure.

After that, you:

a. Determine if this is an item you should update.

All commands from the Window menu are synthetic, so call the static function LCommander::IsSyntheticCommand(). The attachment is not itself a commander, so you must specify the class. Also make sure the menu ID matches the mWindowMenu menu ID. If the item is a synthetic command from the Window menu, then you have identified an item that you must update.

b. Get the window object associated with the menu item.

Use CWindowMenu's MenuItemToWindow() function.

c. Handle the item here.

If there is a window, set the mExecuteHost item to false. You are taking care of the item entirely right here, so the host object (in this case the application) does not need to ask a commander to set this menu item.

d. Set the menu item status.

Set fields in the SCommandStatus structure. Enable the item, use a mark, and set the mark to noMark. If the window is the top window, set the mark to a check mark. Use UDesktop::FetchTopRegular() to identify the top window.


SCommandStatus *theStatus = static_cast<SCommandStatus *>
                                       (ioParam);
if (LCommander::IsSyntheticCommand(
    theStatus->command, theMenuID, theMenuItem)
    && theMenuID == mWindowMenu->GetMenuID() ) {
  
  // Find window corresponding to the menu item.
  LWindow *theWindow =
                  mWindowMenu->MenuItemToWindow( theMenuItem );
  
  if ( theWindow != nil ) {
    // Handle it's status here.
    mExecuteHost = false;
  
    // All window items enabled and use a mark.
    *theStatus->enabled = true;
    *theStatus->usesMark = true;
    *theStatus->mark = noMark;

    if ( theWindow == UDesktop::FetchTopRegular() ) {

      // Check menu item for top regular window.
      *theStatus->mark = checkMark;
    }
  }
}

Remember, the attachment is not a commander. There is no inherited FindCommandStatus() function for items you don't update. If you don't handle it, the mExecuteHost value remains true, and the application asks a commander to take care of updating the item.

Your attachment is only pretending to be a commander, but doing quite a nice job of it. The next thing your attachment must do is obey a command!

9. Handle a Window menu command.

ExecuteSelf() CWindowMenu.cp

Every item in the Window menu has cmd_UseMenuItem as the corresponding command number. That means PowerPlant generates a synthetic menu command for each item in the menu.

Before calling the ObeyCommand() function, ProcessCommand() gives attachments an opportunity to handle a command. At that time it passes the command itself as the message.

The existing code identified the msg_CommandStatus message, and you handled that message in the previous step. If the message is not msg_CommandStatus, you must:

a. Determine if this is a command you should obey.

Call LCommander::IsSyntheticCommand(). Make sure the menu ID matches the mWindowMenu menu ID. If both conditions are true (it is a synthetic command from the Window menu) then you have identified a command you must handle.

b. Get the window object associated with the menu item.

Use CWindowMenu's MenuItemToWindow() function. If you have a window, then you have a command you must handle.

c. Handle the command here.

Set the mExecuteHost value to false. You are taking care of the command right here, so the host object (in this case the application) does not need to ask a commander to obey this command.

d. Bring the window to the front.

If the window is visible, bring it forward. Use UDesktop::SelectDeskWindow().


SInt16 theMenuItem;
if (LCommander::IsSyntheticCommand( 
    inMessage, theMenuID, theMenuItem )
    && theMenuID == mWindowMenu->GetMenuID() ) {

  // Find the window selected.
  LWindow *theWindow =
                  mWindowMenu->MenuItemToWindow( theMenuItem );
  
  if ( theWindow != nil ) {
  
    // Handle the command here.
    mExecuteHost = false;

    // Bring the window to the front.
    if ( theWindow->IsVisible() ) {
      UDesktop::SelectDeskWindow( theWindow );
    }
  }
}

Notice once again that you aren't a commander. You can't call an inherited ObeyCommand() function for commands you don't handle. If you don't handle it, the mExecuteHost value remains true, so the application will ask a commander to obey the command.

Save your work and close the file.

10. Build and run the application.

When the project builds correctly and you run the application, a progress window appears, like Figure 15.4. Click the Start button and the barber pole animates. Click Stop and the animation stops. Choose the New item to make more windows. Start them running. This is your periodical task at work. Observe that the animation runs even when the window is in the background.

Examine the items in the Window menu. There should be one for each window. The currently active window should have a check mark in front of the item. Choose an item, and the corresponding window should activate.

Each of these utility items-the progress window and the Window menu attachment-is a nice bit of reusable code.

The Window menu attachment can be dropped into any PowerPlant application. You must make a few changes for it to work. You add the menu to the menu bar when the application launches. You connect the attachment to the application. You modify your window's FinishCreateSelf() function to add an item for itself in the window. You modify the window's destructor to remove the corresponding item from the Window menu. That's it.

The progress window is highly reusable. Simply create the window whenever you need to display progress. Keep in mind, this particular brand of progress window works at idle time. It will not work to mark progress in a long task that does not return to the event loop. However, it works great when the task whose progress you are indicating is another periodical or regularly returns to the main event loop.

In fact, the CBarberPolePane class could be used more generally as CPictAnimator-displaying any series of PICT's in any appropriate circumstance. You could animate icons, spin arrows, or impement a slide show.

Possibilities for experimentation abound. Make a progress window that draws the percentage complete of a task. The task that requires the progress window should create it, and maintain a connection to the progress window. It can post the percentage complete, and the progress window can draw itself. Play with different ways of representing completeness. You can have a 3-D effect in the bar, an analog clock with a sweep that completes a circle, or a cup that fills with color, just to name a few.

For attachments, create a different kind of menu that provides useful functionality. You might want to try adding a debugging menu that you can use to turn debugging on and off. Perhaps you can implement a font menu as an attachment. You might create a "Demo" attachment that converts an application into a demo version by disabling certain commands. Experiment with PowerPlant's built-in attachments in UAttachment.cp. Try to think of other ways in which you can use attachments.

As always, have a good time exploring. Don't worry about getting lost. You are now ready to head out on your own into the vast spaces of the PowerPlant landscape.


Looking Backward, Looking Forward

It has been a long journey from your first PPEdit application in Chapter 1 to the boundless horizons of attachments. Along the way you have learned a lot about PowerPlant.

You have seen PowerPlant from a high-level that emphasizes the design patterns and principles behind this marvelous application framework. You have seen PowerPlant from the mid-level of classes and functionality, and how objects of various classes work together to implement the design principles. And you have seen PowerPlant from deep inside the code.

You have learned not only what PowerPlant is, but-more importantly-how to use it. After all, isn't that the real goal? You now have all the critical pieces, and you know where they belong in the big picture.

Still, there is more. PowerPlant is not a done deal. PowerPlant is a living, breathing piece of code that continues to grow and evolve. As the Mac OS changes, so too will PowerPlant. Metrowerks wants to keep you at the forefront of technology. The PowerPlant engineers are dedicated to keeping PowerPlant the best Macintosh application framework available anywhere.

Use the PowerPlant Reference freely. Browse the PowerPlant code. Read the other PowerPlant documentation available in the CodeWarrior package. And don't forget the appendices to this manual. You'll learn about a wide variety of PowerPlant utilities not mentioned elsewhere in this book.

It is our hope that in these pages you have seen what a truly elegant, powerful, and robust application framework-PowerPlant-can do for you as a programmer. We at Metrowerks want to welcome you to the world of PowerPlant programming.

Congratulations! And may you code in interesting times.

 

 


[ 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