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

 

Chapter 9.

 

Applications and Events



In this chapter we begin the process of building an application in PowerPlant. In the Basic Building Blocks chapters we used a class-centered perspective to learn about the pane classes. We talked all about LPane, LView, LControl, and their descendants.

From now on we're going to use a task-based perspective. We won't be able to avoid talking about classes, but for the most part you're going to see these discussions centered around how to use a class to accomplish a particular programming task.

In this chapter we discuss:

As usual, you will close out the chapter with a code exercise.


The Application Object

The application class is the basic foundation of a PowerPlant application. In this section we discuss the fundamental information you need to know to understand and use an application object, including:


Application Class Hierarchy

Figure 9.1 illustrates the class hierarchy. There are only two application classes in PowerPlant, LApplication and LDocApplication.

Application class hierarchy:

The LDocApplication class is effectively identical to LApplication, except that it provides member functions to support documents and printing. We will discuss documents in Chapter 13, "File I/O" and printing in Chapter 14, "Printing" later in this manual.

LApplication is the basis of our discussion in this chapter. We are going to concentrate on setting up an application, and on the event handling features of an application.

Notice that LApplication inherits from several mix-in classes. Each of these is the source for part of the nature of an application:

LEventDispatcher-an application receives, identifies, and dispatches events. (See "Event Handling and Dispatch.")

In addition to the behavior it inherits or overrides from its various base classes, an application has a few functions that it implements for itself. Table 9.1 lists each function and its purpose.

LApplication functions:

 

Function
Purpose
GetState()  
return what the application is doing  
SetSleepTime()  
set amount of time to surrender to background processes  
Run()  
process events until quitting  
ProcessNextEvent()  
retrieve and dispatch events  
ShowAboutBox()  
display the application's About box  
Initialize()  
perform setup work such as create/modify menus  
StartUp()  
respond to an Open Application Apple event  
SendAEQuit()  
send a Quit Application Apple event to self  
DoQuit()  
attempt to quit the application  


Application State

An application's state reflects what it is doing. Listing 9.1 contains the three possible states the application may be in.

EProgramState enumeration:


enum EProgramState {
  programState_StartingUp,
  programState_ProcessingEvents,
  programState_Quitting
};

PowerPlant uses these values internally, and typically you won't be concerned with or modify an application's state.

If you want to quit the application, you can do so by setting the application's mState data member to programState_Quitting. At the risk of repeating ourselves, PowerPlant's default behavior takes care of this for you. You shouldn't have to do this on your own. In fact, doing so may bypass features like checking to save changed documents before allowing the application to quit.


Deriving an Application

Typically you derive your own application class from either LApplication or LDocApplication. Most of an application's standard behavior, as implemented by PowerPlant default member functions, will suit you just fine.

You will certainly override some LApplication member functions, including:

In addition, you'll want to override some member functions inherited from LCommander including

ObeyCommand() FindCommandStatus()

We'll talk about these last three functions in the next chapter. In the rest of this chapter we'll talk about setting up an application and LApplication's event handling features.


Initializing an Application

Every C++ program needs a main() function. A PowerPlant application is no exception. PowerPlant itself does not have a main(), but the CHyperApp.cp file does. When you use PowerPlant stationery to create a new project, this file is automatically included in the project. Listing 9.2 contains the code for this main() function. We'll use it as an example throughout this chapter.

When you create a new project from stationery, you should open the CHyperApp.cp file and save it with a new name in your project's folder. This will automatically update the CodeWarrior project file at the same time. You can then make necessary changes to the file.

Alternatively, you can create your own version of main() that suits your own needs.

A sample main():


void main(void)
{
  // Set Debugging options
  SetDebugThrow_(debugAction_Alert);
  SetDebugSignal_(debugAction_Alert);
  // Initialize Memory Manager - Parameter is
  // number of Master Pointer blocks to allocate
  InitializeHeap(3);  

  // Initialize standard Toolbox managers
  UQDGlobals::InitializeToolbox(&qd);
  
  // Install a GrowZone function to catch
  // low memory situations.
  new LGrowZone(20000);

  // replace this with your App type
  CHyperApp theApp;
  theApp.Run();
}

Initializing and launching an application requires that these tasks be performed as necessary:

Let's look at each of these tasks. We're going to take the opportunity to explore some utilitarian features of PowerPlant in great detail, particularly debugging and memory management.


Set Debugging Options

PowerPlant has powerful debugging macros that you can use when developing software. You can read about these features in two files: UDebugging.h and UException.h. You should also consult the PowerPlant Reference.

You can use PowerPlant's debugging features without using any other part of PowerPlant. Simply include the header files and corresponding source files in your project.

The PowerPlant debugging-macro strategy is straightforward. You may "throw" an error or raise a signal. The "throw" occurs if you use the Throw_ macro. This is similar the C++ keyword throw, and in fact, eventually performs a C++ throw. Throw_ performs an extra notification of the error condition through a dialog or low-level debugger. We will use the macro name Throw_ to avoid confusion. In addition to Throw_ there are four signal macros that we'll refer to generically as Signal_.


WARNING!

PowerPlant debugging support requires the presence of a debugger like MacsBug (or other low-level debugger such as TMON or Jasik) or the CodeWarrior IDE. If you do not have such a debugger present, you will crash.


An error indicates something is wrong. A Signal_ indicates something unusual has happened or just an informative message. If you ignore an error, something bad happens. If you ignore a Signal_, nothing bad happens.

In general, you should Throw_ when an error occurs. An error may be defined as a situation which, if not handled, can cause significant problems (like crashing the computer.) You should raise a Signal_ for a condition which does not threaten the integrity of the application or the stability of the computer, but which is unusual.

For example, you might want to Throw_ an error if you encounter a nil handle. You might want to raise a Signal_ if you try to unlock an already unlocked handle.

To activate these macro capabilities, you #define two terms: Debug_Throw and Debug_Signal. The effects of defining or not defining these terms are listed in Table 9.2.

Effect of debugging options:

 

Term
Macro
action
Debug_Throw defined  
Throw_  
Throw_ invoked  
Debug_Throw not defined  
Throw_  
throw C++ exception  
Debug_Signal defined  
a signal  
macro invoked  
Debug_Signal not defined  
a signal  
nothing  

In either case-Throw_ or one of the four Signal_ macros-PowerPlant defines four possible actions to take, as shown in Listing 9.3.

Debug action enumeration:


typedef enum {
  debugAction_Nothing =0,
  debugAction_Alert = 1,
  debugAction_LowLevelDebugger = 2,
  debugAction_SourceDebugger = 3
} EDebugAction;

The alert action displays a dialog containing the exception code, as well as the source code file name and line number that generated the Throw_ or Signal_. The low-level debugger action displays a string-in MacsBug for example-identifying the routine and offset into the routine, and the exception code. The source-level debugger stops with the current statement arrow pointing to the line containing the Throw_ or Signal_.


WARNING!

If you use the low-level debug action and don't have a low-level debugger installed, you will crash when you Throw_ or Signal_. If you use the source-level debug action and don't have a source-level debugger running, you may crash, or you may break into a low-level debugger if one is available. We recommend you have a low-level debugger like MacsBug installed at all times.



TIP

It is best, for compatability purposes, not to use the source-level debug action. This avoids Mixed Mode Manager switches when debugging on PowerPC Mac OS computers and keeps your debugging information in tact.


PowerPlant maintains two global variables, gDebugThrow and gDebugSignal that specify which of the four possible actions to take on either a Throw_ or a Signal_. By default, gDebugThrow and gDebugSignal are set to debugAction_Nothing. You can set their values at any point in the program if you want to use different options in different sections of code. Usually, you set their values at the beginning of your main program.

For example, this code from main()...


SetDebugThrow_(debugAction_Alert);
SetDebugSignal_(debugAction_Alert);

...uses macros to set the global variable to a debug action.

Having defined Debug_Throw and Debug_Signal and having set the values for gDebugThrow and gDebugSignal, you can use the macros defined in UDebugging.h. Table 9.3 lists their usage.

PowerPlant debugging macros:

 

Macro
Usage
Throw_()  
perform debug action for a Throw_  
SignalPStr_()  
raise a signal, pass a Pascal string  
SignalCStr_()  
raise a signal, pass a C string  
SignalIf_()  
raise a signal if condition is true  
SignalIfNot_()  
raise a signal if condition is false  
Assert_()  
same as SignalIfNot_()  

Using these macros is pretty straightforward, with one exception. Remember that if Debug_Signal is not defined, then any signal macro does nothing. This includes the popular Assert_ macro. When your code is compiled, every occurence of a signal macro generates no code.

As a result, you should be very careful that the test inside the signal macro have no side effects. A test with side effects can lead to very subtle bugs creeping into your code when you turn debugging off. Take a look at this sample code.

A bad Assert_ test:


void main(void)
{
  int number = 5;
  Assert_(--number < 10);
  cout << "number = " << number << '\n';
}

The test in the Assert_ macro has the side effect of decrementing the value in number. When Debug_Signal is defined, the macro test executes, and number has a value of 4. When Debug_Signal is not defined, the macro generates no code, and number has a value of 5. The code does not run the same when debugging is off.

UException.h defines several more macros that are especially useful for Mac OS programming. The macros for throwing exceptions defined in UException.h all eventually invoke the underlying Throw_ macro.

More PowerPlant debugging macros:

 

Macro
Usage
ThrowIfOSErr_()  
test for OSErr, Throw_ if non-zero result  
ThrowOSErr_()  
Throw_ an error (use when you already know there's an error)  
ThrowIfNil_()  
Throw_ if the parameter is nil  
ThrowIfNULL_()  
Throw_ if the parameter is nil  
ThrowIfResError_()  
calls ResError(), Throw_ if non-zero result  
ThrowIfMemError_()  
calls MemError(), Throw_ if non-zero result  
ThrowIfResFail_()  
check a Resource Manager handle; if nil, Throw_ an error  
ThrowIfMemFail_()  
check a Memory Manager pointer or handle; if nil, Throw_ an error  
ThrowIf_()  
Throw_ if condition is true  
ThrowIfNot_()  
Throw_ if condition is false  
FailOSErr_()  
same as ThrowIfOSErr_()  
FailNIL_()  
same as ThrowIfNil_()  

You will find these macros used throughout the PowerPlant source code. You can use them in your own code as well. When you want to turn off all the debugging code, simply comment out your definition of Debug_Throw and/or Debug_Signal. If you turn off Debug_Throw, all of your Throw_ macro calls will automatically call the standard C++ throw().


TIP

PowerPlant has several stack-based classes for memory management. The advantage of stack-based objects is that the destructor is automatically called, even when there is an exception thrown. See "Stack-based memory classes."


A Throw_ occurs inside a Try_ block. The Try_ macro is the PowerPlant equivalent of the C++ try keyword. They are identical.

Of course you must have something to handle a Throw_. The PowerPlant macro is Catch_(). It maps directly to the C++ catch mechanism but will only catch a throw of type ExceptionCode. For example:


  Catch_(iErr)

and


  catch ( Exceptioncode iErr )

are equivalent. If you need a "catch all," use the C++ catch().

If you do not have a catch() handler of some type, your program will terminate. The LApplication::Run() function has a universal catch handler, so a PowerPlant application isn't likely to terminate. However, that handler simply displays a message.


TIP

Grab a good C++ book and look over the C++ exception handling mechanisms such as new_handler, unexpected(), terminate, etc. Remember, even though you are using the PowerPlant framework, you are still using in the C++ Language. All of the benefits and features of the C++ language are available to you...feel free to explore and use them!


None of the PowerPlant debugging features prevents you from using the standard C++ try, throw(), catch() exception handling mechanism. The PowerPlant macros map to this mechanism. However, the PowePlant macros offer an additional layer of information and debugging capability.


TIP

Although not related to PowerPlant, CodeWarrior also includes DebugNew-a utility for debugging memory allocation. Examine the file DebugNew.cp for additional debugging features regarding the new operator.



Initialize the Heap

The second task you must perform when launching an application is to initialize the application's heap. The sample main() function calls InitializeHeap(). This function is defined in UMemoryMgr.cp. It is not a member of any class.

Call this function at the beginning of your program (before initializing the Toolbox) to expand the heap zone to its maximum size and allocate a specified number of master pointer blocks. If you want to perform unusual tasks, such as modifying the size of the stack, you can replace InitializeHeap() or call your own function in addition, as appropriate.


Initialize the Toolbox

After initializing the application's memory space, you must set up the Mac OS Toolbox for your application's use. PowerPlant provides the UQDGlobals class to handle this for you. Our sample main() function calls UQDGlobals::InitializeToolbox(&qd).

This function initializes all the common Toolbox managers, as shown in Listing 9.5.

UQDGlobals::InitializeToolbox() snippet:


::InitGraf((Ptr) &sQDGlobals->thePort);
::InitFonts();
::InitWindows();
::InitMenus();
::TEInit();
::InitDialogs(nil);

If you have additional managers to initialize, you must do so at this phase of the startup process. You may override the UQDGlobals class, but a more typical solution would be to simply put the necessary code either in your own function or directly in main().


TIP

If your application uses QuickTime, initialize the QuickTime Manager with UQuickTime::Initialize().



Setup Memory Management

A good application framework provides significant assistance for managing memory. PowerPlant certainly qualifies.

PowerPlant establishes an emergency memory reserve and uses a GrowZone() function to release it. PowerPlant also provides a series of memory-related classes for helping you with typical memory-related housekeeping.


The LGrowZone class

LGrowZone encapsulates the PowerPlant memory management strategy. It has two parts: a memory reserve, and a strategy for asking other objects to release memory. Figure 9.2 illustrates the inheritance hierarchy for LGrowZone.

LGrowZone hierarchy:

Notice that LGrowZone is one of PowerPlant's free-standing classes. You can use LGrowZone by including nothing more than it, LPeriodical, LBroadcaster, and LListener in your projects.

In your application you create a single instance of LGrowZone. When you call the LGrowZone constructor, you specify the size of the memory reserve, as our main() function does with this code:


  new LGrowZone(20000);

Notice that LGrowZone inherits from LBroadcaster. When faced with a memory shortage, LGrowZone does two things. It asks all listeners to release memory, and it releases the memory reserve.

Objects that are able to free memory when needed should be listeners. You attach them to the LGrowZone object so that the listeners are notified when memory is low, using code like this:


  LGrowZone::GetGrowZone()->AddListener(myObject);

When memory runs short, the object receives a ListenToMessage() call with a msg_GrowZone message and a pointer to the number of bytes still needed. The object can then respond accordingly. The object tells LGrowZone the number of bytes freed. If it cannot release any memory, it must supply a zero so LGrowZone knows that memory was not released.

LGrowZone also inherits from LPeriodical. We discuss LPeriodical in greater detail in Chapter 15, "Periodicals and Attachments." In a nutshell, a periodical object is called from the main event loop either on every pass through the loop, or when null events are received.

In this case, LGrowZone's constructor sets up the object so that its SpendTime() function is called each time through the main event loop. In that function, if the reserve has been used up, LGrowZone tries to re-establish the reserve. If it cannot, it warns the user.

Consult the PowerPlant Reference for more details on LGrowZone.


Stack-based memory classes

PowerPlant gives you a set of simple classes to help you allocate and deallocate memory safely. These classes are all declared in UMemoryMgr.h.

In these utility classes, the constructor performs some action and the destructor undoes the action. The advantage of stack-based objects is that the destructor is automatically called, even when there is an exception thrown.

Effect of stack-based memory classes:

 

Class
Constructor
Destructor
StHandleLocker  
locks a given handle  
restores the handle's locked/unlocked state  
StHandleBlock  
allocates a relocatable block  
deallocates the block  
StClearHandle
Block  
allocates a relocatable block full of zeros  
deallocates the block  
StTempHandle  
allocates a relocatable block in temporary memory  
deallocates the block  
StPointerBlock  
allocates a non-relocatable block  
deallocates the block  
StClearPointer
Block  
allocates a clear non-relocatable block  
deallocates the block  
StResource  
gets the handle for the specified resource  
releases the resource handle  
StHandleState  
gets the handle state  
restores the handle state  

Note that the StHandleLocker class does not move the handle high in the heap.

The StHandleBlock class may use temporary memory if the application's heap is full. StClearHandleBlock does not.

There are two other block-related functions you may wish to use, BlocksAreEqual() and BlockCompare(). See the PowerPlant Reference for details.

Finally, UMemoryMgr.h also declares the StValueChanger template class. The constructor saves the original value and changes to the specified new value. The destructor restores the original value. This is a useful class for preserving and restoring state information.


Other possible memory strategies

PowerPlant does not use a memory pre-flighting strategy where memory requests are tested against available memory before an attempt is made to allocate the memory.

If you would like to implement memory pre-flighting, or use a memory management strategy with a finer resolution than simply releasing the memory reserve in one fell swoop, you may certainly do so. You may derive your own memory management class from LGrowZone, or create your own.


Check the Environment

Although not obvious, our sample main() function does a simple assessment of the operating environment. Your application may need to do more. Our main() creates an instance of the application object.


  CHyperApp theApp;

In the process, its constructor and the default LApplication constructor are called. In the LApplication::LApplication() constructor, PowerPlant looks for the version of QuickDraw in the environment (among other tasks). See Listing 9.6.

Lapplication::LApplication() snippet:


    // Check for Color QuickDraw
  SInt32 qdVersion = gestaltOriginalQD;
  ::Gestalt(gestaltQuickdrawVersion, &qdVersion);
  UEnvironment::SetFeature(env_SupportsColor,
               (qdVersion > gestaltOriginalQD));

PowerPlant uses the UEnvironment class to track several features, as shown in Listing 9.7.

PowerPlant environment tracking:


enum {
  env_SupportsColor       = 0x00000001,
  env_HasDragManager      = 0x00000002,
  env_HasThreadsManager   = 0x00000004,
  env_HasThreadManager    = 0x00000004,
  env_HasAOCE             = 0x00000008,  // obsolete
  env_HasStdMail          = 0x00000010,  // obsolete
  env_HasStdCatalog       = 0x00000020,  // obsolete
  env_HasDigiSign         = 0x00000040,  // obsolete
  env_HasQuickTime        = 0x00000100,
  env_HasAppearance       = 0x00001000,
  env_HasAppearanceCompat = 0x00002000,
  env_HasAaron            = 0x00004000,
  env_HasAppearance101    = 0x00008000   // AM 1.0.1 installed?
};

You can inquire if a feature is available by calling UEnvironment::HasFeature(), a static member function.

The application constructor is a good place for application-level initialization and environment testing. For example, The LApplication() constructor builds the menu bar. In your own constructor you may wish to do additional testing for other features of the environment, modify the application's sleep time, and so forth. You could also perform these tasks in a separate initialization function immediately after creating an application object if you wish.


See also

the PowerPlant Reference for more information on UEnvironment.


Register PowerPlant Classes

Your application object's constructor typically performs one more critical function-it registers the necessary PowerPlant classes. PowerPlant relies heavily on stream-based creator functions in its visual (UI) classes. The PPob resource encapsulates the information necessary to build you UI elements from scratch using this stream-based creator function technique.

When creating a PPob-based object, PowerPlant must know which creator function to call for which class. It does this by maintaining a table that associates a unique class ID with that class's stream-based constructor. You must have an entry in this table for the class creator function before instantiating a PPob-based object.

To register an individual class, you use the RegisterClass_() macro. You provide your class name, the macro does the work of creating a creator function and adding it to the class table. Your class must have a unique class ID in the class definition.


WARNING!

If you do not register each and every class that you directly utilize in your PPob resource(s), your application will not work properly. If you have the Signal_ debugging features turned on, you should get a "Unregistered ClassID" signal raised in UReanimator.cp when you try to instantiate a class without first registering that class.


Failing to register a class is perhaps the single, most common cause of problems encountered by new PowerPlant programmers.

Here's an example call to RegisterClass_() that registers the LButton class.


RegisterClass_(LButton);

It is not necessary to register PowerPlant classes that are not explicitly used in your PPob resource(s).

For example, if you have a class called CMyPane that inherits from LPane.You use CMyPane in your PPob but never use LPane (directly). You do of course need to register CMyPane, but you do not need to register LPane as you never directly utilize LPane in your PPob. In this case, you still need to include LPane.cp in your project since CMyPane inherits from it.


WARNING!

Previously, to register an individual class, you had to call URegistrar::RegisterClass() and provide the class ID and the creator function. This method is obsolete and should not be used. The obsolete method is currently supported for compatibility with existing classes, but will not be supported in the future. You should update your code accordingly.


Finally, don't forget that you must register any PPob-based class that you derive.


Run the Application

After you create your application object, perform any additional initialization, and register each and every PPob-based class you use, it's time to start the main event loop running. Our sample main() function does the following to accomplish this task:


  theApp.Run();

Just call the application object's Run() function, and you're on your way. The Run() function makes the menu bar and calls the LApplication::Initialize() function to perform additional setup such as modify the menus in the application. We'll discuss this task in the next chapter.


Event Handling and Dispatch

The application's Run() function calls ProcessNextEvent() repeatedly. ProcessNextEvent() does the real work. It:

Because LApplication inherits from LEventDispatcher, it can dispatch events. It calls the inherited DispatchEvent() function. This function parses the event and calls the appropriate handler.

Each handler performs whatever additional parsing (if any) is necessary. The handler might identify the most recently clicked pane, or retrieve the current target object in the command hierarchy. You can study the LEventDispatcher code to see the details.

The handler dispatches the event to the appropriate object. A click goes to a pane. A command or keystroke goes to a commander. The pane or commander is responsible for handling the event.

If a commander does not handle a command itself, it passes the command back up the command chain until someone does handle it. The ultimate supercommander is your application object. It is responsible for any command not handled by objects below it in the command hierarchy. We'll discuss this process in more detail in Chapter 10, "Commanders and Menus."

Before we do, notice that this section on events and dispatching does not include any instructions for typical ways in which you override or derive classes to modify the default event-dispatch behavior of PowerPlant. While you are certainly free to do so, it is unlikely that you will ever need to modify event handling and dispatch. This behavior is a gift from PowerPlant. Enjoy it.

However, there is one tricky detail that occasionally trips up new PowerPlant programmers-Apple events.


PowerPlant and Apple Events

PowerPlant relies on Apple events for some of its basic functionality. The process of launching an application is a good example. When you launch an application from the Finder, after the application launches it receives one of three Apple events from the Finder-open application, open documents, or print documents.

Assuming that you aren't opening or printing documents, the application receives the open application Apple event. In response to that event, the application calls the application's StartUp() function.

In this function you can perform some setup work for the application. For example, you might want to have a default window open on launch if the user isn't opening documents. There are any number of tasks you might perform in the StartUp() function.

However, for this to work your application must be aware of Apple events. To ensure that it is, go to the 68K Processor or PPC Processor target settings panel (depending on your build target), and examine the SIZE flags. Make sure the "isHighLevelEventAware" flag is checked(Figure 9.3). If it is not, your application will not receive Apple events, and StartUp() won't be called.

You must also have the proper `aedt' resources in your application. If you use PowerPlant project stationery, the file PP AppleEvents.rsrc is contains these resources and is included for you.

isHighLevelEventAware flag in target preferences:

Summary

In this chapter you learned how to initialize a PowerPlant application, and about the PowerPlant utilities designed to help you in that task.

PowerPlant has a powerful set of macro-based debugging features for throwing exceptions or raising signals. These features are implemented in an independent section of PowerPlant so that you can use the debugging features without using the rest of PowerPlant.

PowerPlant uses an emergency reserve memory management strategy in association with a well-designed LGrowZone object. The LGrowZone object broadcasts a need for memory. Objects you create that can release memory can listen to the LGrowZone object and respond to the plea for memory donations. You also learned about the many stack-based utility classes that can assist you in robust memory management.

You learned how to register PowerPlant pane classes, and about the importance of registering your own classes as well. Finally, you read about the event dispatch mechanism in PowerPlant.

Let's see how it all works in some real code.


Code Exercise

The application you work on in this exercise is named "Events." Because event dispatch is completely implemented in PowerPlant, this chapter emphasizes debugging and memory management. We also look at how the project sets up the development environment. These details are important when you want to turn debugging on, register new PowerPlant classes, and so forth.

As usual, we'll look at the application interface first, and then write code to implement the application.


The Interface

The Events application is a memory eater. Figure 9.4 shows you what the application looks like.

The Events application:

Examine the various panes in Constructor. In this code exercise the PPob resource is complete. You won't add or modify any panes.

There are three LGroupBox objects, of which we take no further note.

There are four LCaption objects. Two of them are simple labels. Two of them report information about the application's use of memory. The "Blocks" caption reports the number of blocks you have created. The "Size" caption reports how much memory the application has eaten.

The "Free Memory" caption is a custom caption pane. It reports available memory. You'll register this class when you write the application. The code for the class is provided for you.

There are three buttons. The Test Signal button sends a signal. The Test Throw button throws an exception. The New 10Kb Block button creates a memory block, if memory is available. The application keeps track of the allocated blocks.

The Release When Asked button controls the application's behavior when it runs out of memory. At that time, the LGrowZone object will ask its listeners for memory. The application listens for the message and responds.

Like the code you wrote in Chapter 8, the application listens to the controls in its window and responds appropriately. You'll write some of that code in this exercise.


Setting Up an Application

In this section you set up the development environment, set up debugging, and implement the Events application.

1. Examine project prefix.

C/C++ settings dialog Events.mcp

Click the Target Settings button on the Events.mcp project window (Figure 9.5). Then choose the C/C++ Language panel, as shown in Figure 9.6.

Target Settings button:


TIP

The toolbar on the IDE Project Window and the global toolbar are completely customizable. See the IDE User Guide for more information on how to customize CodeWarrior.


Events language settings:

Notice that the prefix file is EventsPrefix.h. In the PowerPlant stationery, the prefix file is PP_DebugHeaders68K or PP_DebugHeadersPPC. The PP_DebugHeaders.cp file used to build those precompiled headers contains the following code:


// Define all debugging symbols
#define Debug_Throw
#define Debug_Signal
// include the header files for the standard PowerPlant classes
#include <PP_ClassHeaders.cp>

One way to turn on debugging is to use PP_DebugHeaders for the correct build target (68K or PowerPC). To turn debugging off, change the project prefix to PP_MacHeaders68K or PP_MacHeadersPPC.

You'll accomplish the same goal in EventsPrefix.h in just a bit.

2. Set up the environment

no function EventsPrefix.h

You accomplish one primary in this step. You turn on debugging. The existing code includes the correct version of PP_MacHeaders depending on the build target.


#include <PP_MacHeaders.h>
// Define debugging symbols.
#define Debug_Throw
#define Debug_Signal

// Include the PowerPlant prefix file.
#include <PP_Prefix.h>

The PP_Prefix.h file defines some additional terms for the universal headers, and includes additional PowerPlant header files.

Save your work and close the file.

3. Examine the application class

class declaration CEventsApp.h

This particular application class inherits from LApplication and LListener. This allows the application to listen to the controls in the window. It also allows the application to listen to the LGrowZone object. Listing 9.8 contains the complete class declaration.

CEventsApp class declaration:


class CEventsApp : public LApplication, public LListener {
public:
               CEventsApp();
  virtual      ~CEventsApp();
  virtual void ListenToMessage( MessageT inMessage, 
                                void *ioParam );

protected:
  LWindow*     MakeEventsWindow();

private:
  LWindow*     mWindow;
  LArray       mBlockList;
};

This class overrides the ListenToMessage() function inherited from LListener. It declares a new function, MakeEventsWindow() to create the window. It has two new data members, mWindow and mBlockList. The former points to the application's only window. The latter is a list of allocated blocks of memory.

When you are through examining the declaration, close the file.

4. Build the application object

CEventsApp() CEventsApp.cp

The project's main() function is provided for you in this file. It initializes the heap, initializes the Toolbox, creates the LGrowZone object, and creates an application object. Of course, the application constructor is called at that moment.

In this step you write the application constructor. There are five tasks you must accomplish. They are:

a. Set the action to occur on Throw_ and Signal_.

Use the SetDebugThrow_ and SetDebugSignal_ macros. Set the response to debugAction_Alert so an alert is displayed in response to either a throw or signal.

b. Register required core PowerPlant classes.

Register LWindow, LCaption, LStdButton, LStdCheckBox, and LGroupBox.

c. Register any custom classes.

The only custom class is the CFreeMemoryCaption class.

d. Make a window.

Call the application's MakeEventsWindow() function. It returns the LWindow*. You can store it in mWindow. And, now that you have debugging features available, use a macro to check the LWindow pointer and ensure it isn't nil.

e. Link the application to the LGrowZone object.

Get the LGrowZone object and call its AddListener() function. You want to link the application object to the LGrowZone object.

The solution code is listed here for reference. This function is empty in the start code. You write the whole thing here.


// Setup the throw and signal actions.
SetDebugThrow_( debugAction_Alert );
SetDebugSignal_( debugAction_Alert );
// Register required core PowerPlant classes.
RegisterClass_(LWindow);
RegisterClass_(LCaption);
RegisterClass_(LStdButton);
RegisterClass_(LStdCheckBox);
RegisterClass_(LGroupBox);

// Register custom classes.
RegisterClass_( CFreeMemoryCaption );

// Create the single application window.
mWindow = MakeEventsWindow();
ThrowIfNil_( mWindow );

// Listen to messages from the grow zone.
LGrowZone::GetGrowZone()->AddListener( this );

5. Make the Events window.

MakeEventsWindow() CEventsApp.cp

The PPob that describes this window has been provided for you. In this step you accomplish two tasks.

a. Create the window.

Call LWindow::CreateWindow(), the class creator function. The resource ID constant is rPPob_EventsWindow. The window's supercommander is the application object.

This call returns a pointer to an LWindow object. Use a macro to ensure that the pointer is not nil. You can use Assert_.

b. Link the application to the controls in the window.

Call UReanimator::LinkListenerToControls(). The ID constant for the RidL resource is rRidL_EventsWindow.

The solution code for both tasks is listed here.


// Create the window.
LWindow* theWindow;
theWindow = LWindow::CreateWindow( rPPob_EventsWindow, this );
ThrowIfNil_( theWindow );
// Link the application (the listener) with the 
// controls in the window (the broadcasters).
UReanimator::LinkListenerToControls( this, theWindow,
                                     rRidL_EventsWindow );

theWindow->Show();

The existing code shows the window and returns the LWindow pointer to the caller-the application constructor in this case.

6. Respond to messages.

ListenToMessage() CEventsApp.cp

The application listens to the controls in the window. It also listens to the LGrowZone object. The application's ListenToMessage() function may receive four messages:

In this step you write some of the code to respond to the msg_GrowZone message, and all the code for testing the signal and throw mechanisms. The code for creating a memory block is provided for you.

a. Release memory if appropriate.

When you receive a msg_GrowZone message, send yourself a signal. Although you wouldn't do this in a real application, this is instructional here. When the LGrowZone object asks for memory, you'll hear about it.


case msg_GrowZone:
{
  // We're asking for memory, let user know
  SignalPStr_("\pLGrowZone asking for memory" );
  SInt32 theBytesFreed = 0;


WARNING!

Raising a signal here may cause a crash! This call causes PowerPlant to display a dialog. That dialog must be loaded into memory. Under just the wrong low-memory conditions, (and you'll be creating a low memory condition) there isn't enough room and a crash will result. You can omit this line of code without affecting the application. You just won't receive a notice when LGrowZone sends this message.


You should examine the remaining code in this case, it is very instructive. The code first determines the state of the check box. If memory release is allowed, the application walks through the list of memory blocks and releases them.

The code creates an iterator, starts with the first handle in the list, and operates on each handle. If the block is not a protected block, it removes the handle from the list and disposes of the handle. Notice that the iterator can modify the list while iterating! This causes no problems in PowerPlant. (See "Arrays.")

When complete, the application updates the two captions that reflect the number of blocks and the amount of memory in those blocks. Notice that the code uses an LStr255 object. This is a descendant of LString. Among other features, the LString class provides a series of operator overloads for working with strings. The code here takes advantage of the LString features to automatically convert a 32-bit number into a string, and uses the += operator to append strings. (See "LString.")

b. Send a signal.

When the application receives msg_TestSignal, send a signal.


case msg_TestSignal:
{
  // Raise a test signal.
  SignalPStr_( "\pSignal test" );
}
break;

c. Throw an exception.

When the application receives msg_TestThrow, throw an exception. You can use PowerPlant macros for the entire process. Create a try block. In the block, use Throw_ to throw an error. Use the value -1. In the catch block, you don't have to do anything. This is just a test.


case msg_TestThrow:
{
  try {
    // Throw an error.
    Throw_( -1 );
  } catch( LException& inErr ) {
    // Catch it here.
  }
}
break;

That's it. Save your work and close the file.

The code to handle msg_NewBlock has been provided for you. Study it as another example of list management and string manipulation in PowerPlant.

7. Build and run the application.

Make the project and run it. When you do, a window should appear containing all the views. See Figure 9.4.

Notice the amount of free memory, the number of allocated blocks, and the space required for those blocks. The Release When Asked check box should be on.

Click the New 10Kb Block button. Watch how the free memory, size, and number of blocks all change. Click the button repeatedly until you run out of memory. When you do, a signal dialog should appear telling you that LGrowZone is asking for memory.


NOTE

This is where you might crash. If the signal dialog does not appear and you crash, return to Step 6a and remove the code that calls SignalPString().


If the signal dialog appears, click OK. Then observe the amount of free memory, number of blocks, and size.

Now, turn off the Release When Asked check box. The application will no longer release memory when asked. Create new blocks until you run out of memory. Once again, you should see the signal dialog telling you that LGrowZone is asking for memory. Click OK. This time, memory is not released. You have run out of memory and you'll see a warning dialog, as shown in Figure 9.7.

The low-memory alert:

This is the standard PowerPlant low-memory alert. It isn't 100% appropriate in this circumstance, because there are no documents to close. In your own application, you can modify this alert to display a more accurate message.


TIP

The memory warning should never cause a crash, even in low memory conditions. These warning dialogs are preloaded and locked, so they are always available.


Don't forget to test the two Debug Messages buttons to observe the signal and throw dialogs.

If you want to explore, turn off signaling and see what happens when you run out of memory. Turn off all debugging and see what happens when you throw an exception. Use different PowerPlant macros to test values, send signals and throw exceptions. Use the standard C++ try, throw, and catch keywords rather than PowerPlant macros, and see if there's any difference.

Finally, if you look at the free-memory caption closely you may notice it flash every second or so. The code for this custom caption was provided for you. Feel free to explore that code. This caption inherits from LPeriodical and installs itself in a special queue. Every time there is an idle event, this caption's SpendTime() function is called. We'll discuss how this works in "Periodicals." If you explore this process, think about how you might eliminate or reduce the flashing. There is one straightforward solution. You could store the previous value, and only update the caption when the value changes. Implement that solution, or your own solution, and observe the difference.

 

Once again, congratulations are in order. This chapter deals primarily with low-level coding details like memory management and debugging.While this isn't the most glamorous part of PowerPlant, these are critical skills in the real world of software development. Best of all, you have now explored how to use them most effectively in PowerPlant, and practiced those skills.

In the next chapter you start working on menus. After that, you'll implement windows and dialogs. Then you'll work with documents, files, and printing. Finally, you'll study periodicals and attachments. The exciting stuff is coming!

 


[ 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