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

 

Chapter 11.

 

Windows



LWindow is a complex class that descends from and adds a great deal to LView. You will use this class often in your PowerPlant programming, both directly and as a base class for your own window classes. It serves as a wrapper class for the Macintosh Toolbox WindowRecord structure, so you can easily create and use windows in PowerPlant.

The good news is, you already know 90% of what you need to know to use windows effectively. Chapter 7, "Views" introduced you to LView and its descendants, including LWindow. At the time we skipped any detailed discussion of the single most important kind of view, the window.

In this chapter we concentrate on those aspects of window objects that make a window different from other views. In our discussion we will cover three main topics:

Along the way we will also encounter several window-related utilities in PowerPlant.


What is a Window

On the Macintosh, a window is the visual representation of a very special data structure that incorporates a GrafPort for drawing and other data to control drawing characteristics. On a monitor, this structure shows up as a window.

In PowerPlant, a window is an object of the LWindow class. This object is connected to a Mac OS WindowRecord, and appears on screen as an ordinary Macintosh window.

Figure 11.1 illustrates the inheritance chain for LWindow.

LWindow hierarchy:

As you can see from the class diagram, LWindow has three direct ancestors.

First, LWindow inherits from LView, a descendant of LPane. That gives a window all the features of panes and views. Windows can receive and respond to mouse clicks within their bounds. They can contain other panes. Windows can perform all the other magic associated with panes and views, not the least of which is the ability to have attachments.

LWindow inherits from LCommander. That means that a window can receive and handle menu commands and key presses. It can update and manage menus as any other commander can.

Finally, LWindow inherits from LModelObject. Therefore, a window can respond to Apple events-it is scriptable.

LWindow has one descendant in PowerPlant, LDialogBox.

If you stop to think about all the important classes from which LWindow inherits, you see that its behavior encapsulates everything we talked about in chapters 6, 7, and 9 on panes, views, and commanders. That's a lot of power.

Beyond that, LWindow has significant attributes and behaviors that are unique to it. In the rest of this chapter we discuss what they are.


See also

Chapter 15, "Periodicals and Attachments," Chapter 12, "Dialogs," and the PowerPlant Reference for more on scriptability.


Window Characteristics

Do not lose sight of the fact that, although we mention this only in passing, everything you have learned about panes, views, and commanders applies to windows. That means that windows have an image, manage coordinate conversions, respond to clicks, and all the rest.

In addition to all those characteristics and behaviors, windows also have the following additional characteristics.

We discuss additional window-related behaviors in "Working With Windows."


Window Attributes

Each window has a set of special attributes that define the kind of window it is, and how the window behaves. These attributes determine the layer in which the window appears, the various controls that appear around the perimeter of the window (like a close box or zoom box), and some other aspects of window behavior.

Table 11.1 lists all the window attributes and their meaning. In the sections following the table we discuss each of these attributes in some detail.

Window attributes:

 

Attribute
Purpose
windAttr_Modal  
in modal layer  
windAttr_Floating  
in floating layer  
windAttr_Regular  
in regular layer  
windAttr_CloseBox  
has close box  
windAttr_TitleBar  
has title bar  
windAttr_Resizable  
is resizable  
windAttr_SizeBox  
has grow box  
windAttr_Zoomable  
is zoomable and has zoom box  
windAttr_ShowNew  
show immediately when created  
windAttr_HideOnSuspend  
hide window when application is suspended  
windAttr_EraseOnUpdate  
erase before drawing  
windAttr_Enabled  
is enabled  
windAttr_Targetable  
is targetable  
windAttr_GetSelectClick  
process click that selects window  
windAttr_DelaySelect  
process click, then (perhaps) select window  


Window layers

PowerPlant uses three layers to determine how a window behaves in certain respects. From front to back, the layers are:

Modal windows are always in front, and all other windows are inactive when a modal window is present. Modal windows must be dismissed before you can perform other actions in the program. The modal dialog is a perfect example. We'll discuss modal windows in Chapter 12, "Dialogs."

Floating windows are always active, except when a modal window is active. The floating layer is used for tool palettes and other kinds of accessory windows. These "float" above the regular front window. You may activate any regular window, and a floating window remains displayed in front of it. It appears to be active and frontmost, even though the regular window may be frontmost as far as the Mac OS is concerned.

As you'll see in the Working With Windows section, implementing floating windows is simple. Floating windows are one of the really nice features of PowerPlant.

Regular windows are beneath all modal and floating windows. The top regular window is active, except when a modal window is active. All other regular windows are inactive. Windows in the regular layer behave like normal Macintosh windows.


Peripheral window parts

The next set of window attributes determine whether the window has a close box, a title bar, a zoom box, and or a grow box.

The CloseBox attribute determines whether the window has a close box. Regular and floating windows typically do. Modal windows typically do not.

The TitleBar attribute determines whether the window has a title bar. Typically, a non-moveable modal dialog does not have a title bar. All other windows have a title bar.

The Resizable attribute determines whether the user can manually resize the window by clicking and dragging in the bottom right corner of the window. Note that being resizable does not automatically give the window a size box. You must set the SizeBox attribute to have a size box appear in the window.

The SizeBox attribute determines whether the window has a size box. If this attribute is set, the Resizable attribute should also be set. A resizable window may not have a size box, but all windows with a size box should be resizable.

The Zoomable attribute determines two things: whether the windows is zoomable, and if it has a zoom box. All zoomable windows have a zoom box. Modal windows are typically not zoomable. Regular and floating windows may or may not be zoomable.


Drawing attributes

Three attributes control various facets of how a window draws.

The ShowNew attribute determines whether a new window is initially visible or not.

The HideOnSuspend attribute is typically true for floating windows. When an application is suspended-sent to the background-the human interface guidelines dictate that all floating palettes should be hidden. This attribute is typically false for regular and modal windows. Regular and modal windows become inactive, but do not hide.

The EraseOnUpdate attribute determines how window drawing occurs. If this attribute is true, PowerPlant erases the contents of the window before drawing. This attribute is typically true for windows.


Clicking attributes

The remaining window attributes control how the window responds to clicks.

The Enabled attribute determines whether the window is enabled-that is, can it respond to clicks.

The Targetable attribute determines whether the window is or can contain a command target object-that is, can it respond to menu commands and keystrokes. Most windows are targetable. This feature isn't really enforced. For example, assume you have a window with an LTextEditView object. If this attribute is not set for the window, the LTextEditView object can still become the target object. However, when the window is deactivated and reactivated, the LTextEditView will not be restored as the target object.

The GetSelectClick attribute determines what you do with the click that activates the window. For regular windows, usually the click activates the window, and does nothing else. For tool palettes in the floating layer, you typically set this attribute to true. In that case, the click that activates the window is also treated as a click in the window contents (for example, a click on a button in the tool palette). This maintains the illusion that the palette is always active.

If the DelaySelect attribute is true, a click in an inactive window is first treated as a click in the contents. After the click event is processed, if the mouse button has been released in the window, the window is selected. You set this attribute to true to support drag and drop. In the drag and drop human interface, you should be able to drag selected information out of a background window without activating the window.


WARNING!

To support dragging from an inactive window, you must override LPane::Click(). See the PowerPlant Advanced Topics chapter on Drag and Drop for a thorough discussion of this issue.



Setting window attributes

The LWindow class has three accessors related to attributes. You use these accessors to determine the state of the window's attributes, or to modify the attributes at runtime. The functions and their purpose are listed in Table 11.2.

Window attribute accessors:

 

Function
Purpose
HasAttribute()  
returns whether the attribute is set  
SetAttribute()  
set the specified attribute  
ClearAttribute()  
clear the specified attribute  

Typically you set the attributes in Constructor when you design the visual interface for your application, or before creating a window on the fly. Setting or clearing attributes does not have an immediate effect on a window that has already been created. For example, setting the windAttr_CloseBox attribute doesn't suddenly give the window a close box.


Window Size and Zooming

In addition to the many window attributes, a window has its own unique way of keeping track of its dimensions.

Windows may have a minimum and maximum size (although this feature is only important for resizable windows). Windows also have a standard size-used for zooming.


Minimum and maximum sizes

These sizes control how large or small the window may become as the user resizes the window manually. If the window is not resizable, these values are not used.

The minimum and maximum sizes are stored in a single Rect data member named mMinMaxRect. The top and left fields of the Rect define the minimum size. The bottom and right fields of the Rect define the maximum size. For example, if the mMinMaxRect fields were {100,100,300,400}, then the minimum window size would be 100 x 100 pixels, and the maximum window size would be 300 x 400 pixels.

The accessors for this data are GetMinMaxSize() and SetMinMaxSize(). Typically you set this information in Constructor when defining the window characteristics.

The minimum size should be large enough to show some meaningful content. In general, a window shouldn't be smaller than a size of 100 by 100 pixels.

The maximum size should be large enough to show all the data in the window. By default, PowerPlant sets the maximum size to 32K by 32K. In Constructor you see this value expressed as -1.


Standard size and zooming

Clicking the zoom box of a zoomable window toggles the window between its standard state and its user state. The standard state is the optimal size and position for the window. The user state is the runtime size and position of the window that the user sets manually on the desktop. The user state changes when the user drags or resizes the window.

When the user clicks the zoom box, PowerPlant checks whether the window is in the standard state. If it is, PowerPlant resizes the window to the user state. If the window is not in the standard state, PowerPlant resizes the window to the standard state.

The standard size characteristic of a window defines the size of the window in its standard state. This value is stored in an SDimension16 data member named mStandardSize. The accessors are GetStandardSize() and SetStandardSize().

Typically you set this information in Constructor when defining the window characteristics. The default standard size is 32K by 32K. In Constructor you see this value expressed as -1.

When PowerPlant resizes a window to its standard state, PowerPlant uses either the standard state value or the size of the dominant screen, whichever is smaller. The dominant screen is the screen that contains most of the window. If the screen dimensions are used, PowerPlant allows a two-pixel margin around the edges of the screen and the menu bar.

For example, if the standard state of a window is 600 by 400 pixels, it will fit comfortably on a 13-inch monitor. If most of the window is on a Classic-sized monitor, PowerPlant resizes the window to 508 by 318 pixels.

Standard and User States:

Window Descriptor

As you know, every pane has GetDescriptor() and SetDescriptor() accessors for the pane's descriptor characteristic. The LWindow class uses these accessors to retrieve or modify the window title.

Once again, you can set the title in Constructor. However, this is one case where you are likely to use the accessors fairly regularly in your code. The title of a document-related window is likely to change as the user saves the document to a file.


Window Kind

To help you distinguish PowerPlant windows from non-PowerPlant Macintosh windows, PowerPlant stores a special value in the WindowRecord's windowKind field. PowerPlant assumes that any window whose windowKind is greater than or equal to PP_Window_Kind is a PowerPlant window. This constant is defined in LWindow.h, and its value is 20000.

You can use this feature to keep track of different kinds of PowerPlant windows in your application, if that is necessary in your design although you could accomplish the same goal using the pane ID. Using the window kind is the only way to distinguish subclasses of LWindow at runtime using a WindowRecord. Just give different kinds of windows unique window kinds greater than PP_Window_Kind. You can also use this feature to distinguish between PowerPlant and other windows. We'll discuss how to set the windowKind field in "Creating a Window."

To get a PowerPlant window's window kind, use LWindow's GetMacPort() member function. Cast the returned GrafPtr to a WindowPeek so you can access the windowKind field.

Getting the window kind:


WindowPeek thePeek = (WindowPeek)theWindow-> GetMacPort();
short theKind = thePeek->windowKind;


Working With Windows

Because you already know about how to work with panes, views, and commanders, you already know most of what there is to know about windows. However, LWindow does have its own behaviors in addition to those inherited from other classes.

In this section we discuss:


Creating a Window

You can create a window using Constructor, or on the fly in your code. We talk about each method. Then we discuss what you do when you derive your own class from LWindow.


Using Constructor

Creating a window in Constructor is simple.

While in the Constructor project window, select the Windows and Views resources, and choose New Resource (command-K) from the Edit menu. Fill in the details in the Create New Resource dialog to create a new PPob resource.

Creating a new window:

Double-click the new PPob resource to see the layout editor for the new window. Double-click the new window in the layout editor to see and set the window characteristics, as shown in Figure 11.4.

Setting window properties with Constructor:

Note that a window has some of the same information as a pane, including location, size, and class ID. There is no binding information, because this is the top view. There is no pane ID for the same reason. Remember, when you derive your own classes you must change the class ID to your own unique value and register the class with PowerPlant before creating any objects of that class.

All of the window attributes we discussed earlier are available. You select a window layer. You select the peripheral window parts for your window. You set the other clicking and drawing attributes. You set the minimum, maximum, and standard sizes. And you set the window title.

You can set the window proc by choosing an item from the popup menu illustrated in Figure 11.5.

Window kind options:

For regular windows, you choose the Document window item. The movable modal type is most appropriate when creating dialog boxes. Making a floating window is as simple as choosing the correct window type and putting the window in the floating layer. The "side bar" type puts the floating window's "title bar" on the side of the window rather than across the top.

Use the floating window types exclusively for floating windows. Good interface design dictates that floating windows look different from other windows, because they behave differently.


NOTE

To use a floating window, you must also use the UFloatingDesktop.cp implementation of the UDesktop class. See "UDesktop."


You can use System 7's auto-positioning feature for windows. Choose an item from the popup menu illustrated in Figure 11.6.

Window positioning options:

Finally, you can set initial values for a user constant or the window refCon. The latter represents the classic Mac OS WindowRecord refCon field. PowerPlant uses the refCon field to store a pointer to the LWindow object. You cannot use the refCon for other purposes. Use the userCon field instead.

Use the refCon field in Constructor if you want your window to have a unique window kind characteristic. Make sure the value is greater than PP_Window_Kind. When PowerPlant creates the window object, it moves the value from the refCon field of the resource to the windowKind field in the WindowRecord, and replaces the refCon with a pointer to the window object.

All of the possibilities for all of the characteristics give you a plethora of possible window designs. Creating unique and interesting windows is as simple as making a few choices in the pane Property Inspector window for the LWindow object.

With a PPob resource, creating the window (and all of its contents) in code is simple. Call LWindow's class creator function, LWindow::CreateWindow(). PowerPlant does the rest.


See also

"Register PowerPlant Classes."


Creating a window on the fly

If you wish to create a window on the fly, you use the SWindowInfo structure, detailed in Listing 11.2. This structure specifies the values required to build a window. You define an SWindowInfo structure, and fill in the values. In the structure you provide the resource ID of a WIND resource, the layer in which to place the window, the attributes, the minimum, maximum, and standard sizes, and the userCon value.

To set the window kind from a resource, set the refCon value in the window's WIND resource to be your desired window kind. You can also change the windowKind field directly after creating the window. Make sure the value is greater than PP_Window_Kind for a PowerPlant window.

Then you call the LWindow constructor. There is a constructor that takes a reference to an SWindowInfo structure as a parameter.

The SWindowInfo structure:


struct SWindowInfo {
  ResIDT        WINDid;
  SInt16        layer;
  UInt32        attributes;
  SInt16        minimumWidth;
  SInt16        minimumHeight;
  SInt16        maximumWidth;
  SInt16        maximumHeight;
  SDimension16  standardSize;
  SInt32        userCon;
};

You can use LWindow-specific calls to set or modify other information about the window after you create it. After you have built the window, you can add whatever panes and views you wish.


Deriving your own windows

Creating your own window class is a fairly common occurrence. There are many reasons why you might want to extend the functionality of the basic LWindow class.

For example, you might want a window that can listen to control items (like an LDialogBox object). You might want your window to zoom or grow in unusual ways. There's no telling what you might want to do with a window.

In the case of deriving from LWindow, you are very likely to override the functions listed in Table 11.3.

Commonly overridden LWindow functions:

 

Function
Purpose
ClickSelf()  
interaction outside of subpanes  
FinishCreateSelf()  
complete window setup  
FindCommandStatus()  
handle menu updating  
ObeyCommand()  
respond to window-level commands  

You are familiar with all four of these functions from our earlier discussions of panes, views, and commanders.

Beyond the functions listed in Table 11.3, it is difficult to typify the other functions you will override. Suffice it to say, if your window has unique behavior, you override those functions necessary to implement that behavior. To determine what functions you need to override, explore the LWindow class to see how the default behavior is implemented. We discuss some of these functions in the "Managing Window Behavior."

For example, if you want to modify how a window resizes (perhaps to allow switching between a few specific sizes) you should examine LWindow::ClickInGrow(), LWindow::DoSetBounds(), and LWindow::DoSetZoom() to see if you should override them.


Drawing a Window and Its Contents

A window is a view. Drawing a window is just like drawing a view. You call the window's Draw() method. LWindow does not override this method. The inherited LView::Draw() function first calls the window's own DrawSelf() function, then walks through the list of subpanes and tells each of them to Draw().

The LWindow::DrawSelf() function erases the window contents (if the EraseOnUpdate attribute is set), and then draws the size box (if there is one). The net effect is the window erases its contents, and then the subpanes draw themselves into the empty window.


Offscreen Drawing

In certain situation, you may want to use LOffscreenView for drawing. You can put an LOffscreenView object into the window to enclose several other panes. When you draw the subpanes, they all draw in the offscreen view. When all panes are finished drawing, the complete image is blitted to the screen. Even though this is a trifle slower than drawing the individual panes directly on screen, the net effect appears faster to the user because all the panes appear simultaneously. This technique is also useful if the panes overlap and might cause flicker while drawing.

LOffscreenView creates and destroys a temporary GWorld every time you draw. If you want a view or panes within the view to use a GWorld that lasts for more than one update, use LGWorld.

Examine the function definitions in the UGWorld.cp file. For example, if you wanted an individual pane to use an LGWorld, you would create a new LGWorld object from the constructor, and delete the LGWorld object in the destructor. You could then use the LGWorld to store the visual image of the pane. Listing 11.3 is one example of how you might do this.

Simple example for LGWorld:


MyPane::MyPane() {
  Rect frame;
  CalcLocalFrameRect(frame);
  mGWorld = new LGWorld(frame, 8);
}
MyPane::~MyPane() {
  delete mGWorld;
}

MyPane::DrawSelf() {
  Rect frame;
  CalcLocalFrameRect(frame);
  mGWorld->CopyImage(GetMacPort(), frame);
}

This hypothetical MyPane class would need a new function to draw the contents of the pane into the GWorld in the first place, and to update those contents when necessary. Such a function might look something like Listing 11.4.

Drawing in an LGWorld object:


MyPane::DrawInGWorld {
  mGWorld->BeginDrawing(); // Draw in GWorld
  // code to draw pane
  mGWorld->EndDrawing();

You use the LGWorld function BeginDrawing() to prepare the GWorld, and EndDrawing() when you're finished. All the drawing in between occurs in the GWorld and not on screen.


TIP

LGWorld is another independent PowerPlant class. You can use LGWorld without using any other part of PowerPlant.



See also

"LOffscreenView."


Managing Window Behavior

Windows have several common behaviors with which you are familiar, including activating, dragging, resizing, zooming, and closing. In this section we examine the behavior of an LWindow object and the default implementation of these functions in LWindow. Unless your window does something unusual you shouldn't have to override this behavior.


Selecting, showing, and hiding a window

Remember the three facets of a pane's state: visible/hidden, active/inactive, enabled/disabled. All of these apply to windows. Windows, of course, do unique things to manage state, but the general principles remain the same.

In addition, you may select a window, and respond when an application as a whole suspends or resumes. Table 11.4 lists some of the functions related to the window's state.

Some LWindow state-related functions:

 

Function
Purpose
Select()  
bring window to front  
Suspend()  
behavior when application suspends  
Resume()  
behavior when application resumes  
ShowSelf()  
make window visible  
HideSelf()  
make window invisible  
ActivateSelf()  
make window active  
DeactivateSelf()  
make window inactive  

There are two points to make about these functions.

First, the four "self" functions are protected member functions. Typically you would never call them directly. They are listed here for information only. You should use the corresponding public interface of Show(), Hide(), Activate(), and Deactivate(), each of which calls the corresponding "self" routine. To implement unique behavior in a derived class, you would override the "self" routines.

Second, if you examine the source code for these functions, you'll see that several of them call static members of the UDesktop class. UDesktop encapsulates much of PowerPlant's window management behavior.


See also

the discussion of "State" and "UDesktop."


Handling clicks

A click in a window is, in many ways, the defining event in an application. What you do in response to a click determines how your application behaves.

Clicks in a PowerPlant window can be thought of as occurring in one of two general locations: in the peripheral window parts or in the window content.

LWindow has default behaviors for handling most if not all of these situations for you. LWindow::HandleClick() parses the click and dispatches control based upon the location of the click.


Click in a peripheral control

A click in a peripheral control ultimately results in one of these functions being called, whichever is appropriate for the click:

If you examine the code for these functions, you'll see that each of them uses Apple events while implementing their respective behavior. As a result, these actions are scriptable and recordable in a PowerPlant application.

The default implementation of these functions in LWindow is likely to suit your needs just fine. If it does not, you can derive your own window class and override whichever behavior does not fit your precise needs.


See also

"Closing a window."


Click in the content area of a window

LWindow::ClickInContent() handles a click in the content area of a window. This function selects the window if necessary. It then calls Click() to handle the click. LWindow does not override Click(), so it uses LView::Click().

LView::Click() identifies whether the click occurred inside a pane within the window. If it is within a pane, the function calls the pane's Click() function so the pane can respond.

If the click is within the window contents but not inside a pane, control passes one step higher up the inheritance chain to LPane::Click(). After performing some housekeeping details, LPane::Click() calls ClickSelf().

Neither LWindow nor LView overrides ClickSelf(). Therefore, the LPane::ClickSelf() function executes. The default implementation of LPane::ClickSelf() does nothing. If you want your window to respond to clicks outside of any subpanes, override ClickSelf().


Closing a window

LWindow has two functions associated with closing a window, AttemptClose() and DoClose(). These very similar functions are used at different times and in different circumstances. Understanding the differences can help you decide which to use, and which to override.

Listing 11.5 displays the code for AttemptClose().

LWindow::AttemptClose() function:


if ((mSuperCommander == nil) ||
      mSuperCommander->AllowSubRemoval(this))
{
  // Send Close AE for recording only
  SendSelfAE(kAECoreSuite, kAEClose, false);
  delete this;
}

The code for DoClose() is almost identical.

LWindow::DoClose() function:


if ((mSuperCommander == nil) ||
      mSuperCommander->AllowSubRemoval(this))
{
     delete this;
}

The difference between these functions is that AttemptClose() sends an Apple event. As a result, a scripting environment can record the window closure. Otherwise, the functions are identical.

Within PowerPlant, AttemptClose() is called in response to a click in the close box of a window. This reflects PowerPlant's support for script recordability.

The DoClose() function is called in two cases: when LDialogBox::ListenToMessage() receives a close message; and when the window receives an Apple event to close.

If you want to close a window, call AttemptClose(). This is your best bet to ensure your application's recordability. If you override window-closing behavior, AttemptClose() is again the best bottleneck. However, good design dictates that the same perceived behavior should occur whether the user clicks in the close box, or the window receives a close Apple event. To ensure that the same behavior occurs, you may need to override DoClose() as well, depending upon what you do in your override of AttemptClose().

Finally, note that each function calls the commander's AllowSubRemoval() function. Why? For example, in a typical implementation a document is the supercommander of a window. This gives the document an opportunity to check whether there are any changes, and gives the user the chance to save them.

Because both AttemptClose() and DoClose() pass through the AllowSubRemoval() bottleneck, you can institute your required closing behavior-such as a "save changes" check-in this function once, and leave AttemptClose() and DoClose() alone.


WARNING!

If the floating window is targetable, or it contains the current target object (such as an editable text field), when you issue a Close command it will operate on the floating window! That's because this is the window that contains the target object. You may want to override the default closing behavior to close the frontmost regular window in this case. Your user interface will determine precisely how you should handle the close behavior.



Window Utilities in PowerPlant

PowerPlant has two classes that contain a variety of utilities for managing window-related tasks. They are UWindows and UDesktop. All of the functions in both of these classes are static, so you can call them at any time.


UWindows

The UWindows utility functions relate to Mac OS WindowRecords, not to PowerPlant LWindow objects. You can use the UWindows functions without using any other part of PowerPlant.

The functions in UWindows relate to finding the dimensions or front-to-back order of an individual Mac OS window record. There is also a function for finding the device on which most of a specified rectangle appears. Table 11.5 lists the functions.

UWindows functions:

 

Function
Purpose
GetWindowContentRect()  
return the bounding rectangle of the content region  
GetWindowStructureRect()  
return the bounding rectangle of the structure region  
FindDominantDevice()  
return the GDevice which contains the largest portion of the specified rectangle  
FindNthWindow()  
return a WindowPtr to the nth window  
FindWindowIndex()  
return index position of a window  
FindNamedWindow()  
return a WindowPtr to the window with the specified name  

Consult the PowerPlant Reference for details on how to use these functions.


UDesktop

UDesktop encapsulates much of PowerPlant's low-level window management behavior. Unlike UWindows, UDesktop is dependent upon the LWindow class. Where functions in UWindows typically have a Mac OS WindowPtr as a parameter or return value, UDesktop uses pointers to LWindow objects. As a result, you can't use UDesktop without using a significant part of PowerPlant.

Just like UDesktop relies on LWindow, LWindow relies on the existence of the UDesktop functions. You must include some file in your project which implements the class defined in UDesktop.h. PowerPlant has two versions of UDesktop. You can use UDesktop.cp or UFloatingDesktop.cp, but not both. If you attempt to include both, you will get a link error for multiple definitions of the same class. You can substitute your own implementation of UDesktop if you wish.


TIP

The implementation of UDesktop found in UDesktop.cp does not support floating windows. Use UFloatingDesktop.cp in your project if you use floating windows.


Table 11.6 lists all the member functions of UDesktop. Remember that in this context the term "window" refers to an LWindow object, not a Mac OS WindowRecord, unless otherwise specified.

UDesktop functions:

 

Function
Purpose
NewDeskWindow()  
create a new Mac OS window  
WindowIsSelected()  
returns whether window is at the top of its layer  
SelectDeskWindow()  
bring window to top of its layer and activate it  
ShowDeskWindow()  
make a window visible  
HideDeskWindow()  
make a window invisible  
DragDeskWindow()  
drag a window  
Suspend()  
suspend all windows  
Resume()  
resume all windows  
Deactivate()  
deactivate all windows  
Activate()  
reactivate appropriate windows  
FetchTopRegular()  
return top regular window  
FetchTopFloater()  
return top floating window  
FetchBottomFloater()  
return bottom floating window  
FetchTopModal()  
return top modal window  
FetchBottomModal()  
return bottom modal window  
FrontWindowIsModal()  
returns whether front window is modal  
NormalizeWindowOrder()  
restore window order  

You are free to call UDesktop functions directly at any time if they suit your purpose. Most of the time, you won't need to use them directly. PowerPlant uses these as utility functions to perform what are, for the most part, low-level window management tasks. However, you might find some of them useful in particular circumstances where you need to know some desktop detail, such as whether the front window is modal or not.


Dealing with the Window Manager

When dealing with windows in a PowerPlant application, you must still work directly with the Mac OS Window Manager from time to time. PowerPlant provides several functions for dealing with the Mac OS GrafPort, or an LWindow object's associated WindowPtr.

Table 11.7 lists the available functions.

GrafPort and WindowPtr utility functions:

 

Function
Purpose
UQDGlobals::
GetCurrentPort()
 
return current GrafPort from QuickDraw globals  
LPane::
GetMacPort()
 
return WindowPtr containing this pane  
LWindow::
GetMacPort()
 
return WindowPtr associated with this LWindow object  
LWindow::
FetchWindowObject()
 
return a pointer to the LWindow object associated with the specified WindowPtr  

Both the GetCurrentPort() and FetchWindowObject() functions are static functions, so they are always available.

Using these four functions in appropriate combination you can do some interesting things.

For example, you can always find the front window object by getting the current port and then finding the associated window object. If you have a pane object, you can get the Mac OS WindowPtr for the window that contains this pane by calling GetMacPort().

You may recall a discussion about "Finding the topmost view." If the topmost view is a window, you can get the topmost view this way:

Finding the containing LWindow object:


GrafPtr theWindowP = thePane->GetMacPort();
LWindow* topView = LWindow::FetchWindowObject(theWindowP);

PowerPlant is a flexible tool. In many cases PowerPlant gives you several ways to solve a problem. Getting the topmost window object is but one example.


Summary

In this chapter you learned all about windows in PowerPlant. We discussed what a window is, and the LWindow class hierarchy.

We discussed the additional features of a window that distinguish it from other views or panes, including special window attributes, special sizes, and the LWindow use of the descriptor characteristic.

Window attributes control a window's layer, the controls around the edges of the window, how the window draws, and how it handles clicks.

We discussed how to work with windows, including how to create windows, draw windows, draw off screen, handle clicks, and close a window.

Finally, we discussed a variety of window-related utility functions in PowerPlant that allow you to manage window behavior, and work with the Mac OS Window Manager.


Code Exercise

In this code exercise you write an application named "Windows." In the process you implement a feature that is common to many applications-a Window menu that lists open windows. You also write the code to create both a floating window and a regular window.

As you have in several other exercises, you'll accomplish this in two sections. First you examine the interface, and then you write the code.


The Interface

The final application creates two different kinds of windows, as shown in Figure 11.7: a regular window and a floating window.

The windows in "Windows":

Both windows are empty. This exercise concentrates on creating windows and working with menus.

Open the Windows.ppob project file in Constructor and examine the two PPob resources. PPob resource ID 1000 is the regular window. Examine the characteristics of this window.

The features of this window are pretty common. The window kind is a document window, and the window is in the regular window layer. The window uses the auto-position feature. In the clicking and drawing section, notice that the window does not get the select click and does not hide on suspend.

The window has a class ID of RegW. This is a custom window with some unique behaviors. In particular, the FinishCreateSelf() and destructor functions are different from LWindow. These functions add or remove the window in the Window menu.

Now, open the PPob for the floating window, and examine the window's characteristics as illustrated in Figure 11.8.

The floating window properties:

There are several differences between the Tools window and the regular window. Most importantly, the window kind is a floating window and it is in the floating layer. These two features control most of the window's basic appearance and behavior. However, there are other important characteristics.

The window is not targetable or initially visible. The window has a close box and a title bar. There is no size box or zoom box. The window is not resizable. A typical floating window does not resize or zoom.

Next, observe the clicking and drawing features. This window gets the select click. As a result, a click that activates the window is also treated as a click in the window content. You'll see how this works when you write code to respond to a click.

This window also hides when the application is suspended. Again, this is not mandatory. This feature reflects the typical behavior of a floating window in the Mac human interface.

Finally, this is a custom window with class ID FltW. In this case, the difference between this window and the behavior of LWindow is in the ClickSelf() and ClickInGoAway() functions. You'll write the code for these functions a little later in this exercise.

You can close the PPob windows and the Constructor project file.


The Windows Application

Before we get started on the actual code, let's take a quick look at where we're going so you know where all the pieces fit.

The Window menu is implemented as a custom class derived from LMenu. CWindowMenu has some additional features we'll explore in just a bit. The application can't use the PowerPlant default menu-creation mechanism because PowerPlant creates LMenu objects. This application creates a CWindowMenu object explicitly and adds it to the menu bar. The application stores a pointer to the CWindowMenu object in a global variable, gWindowMenu.

When the user creates or destroys a regular window, the window title is added or removed as an item in the Window menu. You write the code to make that happen. You also write the code that allows the application to run everything. Let's get to it.

1. Examine CWindowMenu.

class declaration CWindowMenu.h

When you look at the class declaration, you see that this class inherits from LMenu. In addition to the usual constructors and destructor, CWindowMenu declares five new functions. They are:

You write InsertWindow() and RemoveWindow() in the next two steps to add or remove items from the Window menu. The other three functions are provided for you. MenuItemToWindow() returns the LWindow pointer for a given menu item. WindowToMenuItem() returns the correct menu item for a given LWindow pointer. SetCommandKeys() assigns numerical command keys to the first nine open windows.

There are two new data members as well, mBaseItems and mWindowList.

The first item in the Window menu is an item to show or hide the Tools window. If there is a regular window open, the next item in the Window menu is a separator bar. These are the "base" items tracked in the mBaseItems member. All other items in the menu match the titles of various open windows.

The mWindowList member is a list of open regular windows.

When you are through studying, close the file.

2. Add a window to the menu.

InsertWindow() CWindowMenu.cp

The existing code in this function first determines whether the window is already in the list. If it is not, you must accomplish these tasks.

a. Add the window to the window list.

Use mWindowList, and call InsertItemsAt() to add a new item to the list.

b. Get the window title.

Use the window's GetDescriptor() function.

c. Add an item to the Window menu.

This is a bit more complex. You use the CWindowMenu's inherited InsertCommand() function. However, it calls the Toolbox InsertMenuItem() function. The Toolbox function recognizes and uses "metacharacters" so you can accomplish tasks like setting a mark or command key when you insert an item. Unfortunately, one or more characters in your window title might be misinterpreted as a metacharacter, causing odd results.

To avoid this problem, when you call InsertCommand(), pass a blank space as the text for the new menu item. You also specify a menu command. Use cmd_UseMenuItem. Add the item to the end of the menu.

After the call to InsertCommand(), use the Toolbox SetMenuItemText() function to change the text for the item to match the window title.

d. Adjust the command keys.

The Window menu has a feature that assigns numerical command keys to the first nine windows in the window list. Call SetCommandKeys() to implement this behavior.

Existing code handles the else condition when the window is already in the list. It changes the text of the menu item to match the window title. As a result, you can use this function for two purposes: to add a new window to the menu, or to modify the menu when the window title changes.


      mBaseItems++;
    }
  }
  
  // Add the window to the list.
  mWindowList.InsertItemsAt( 1, LArray::index_Last, &inWindow );
  
  // Get the window title.
  Str255 theTitle;
  inWindow->GetDescriptor( theTitle );
  
  // Insert title into the menu as a -1 item.
  InsertCommand("\p ", cmd_UseMenuItem, 16000 );
  ::SetMenuItemText( GetMacMenuH(),
             ::CountMItems( GetMacMenuH() ), theTitle );
  // Renumber the command keys.
  SetCommandKeys();

} else { // Already in list

3. Remove a window from the menu.

RemoveWindow() CWindowMenu.cp

Existing code ensures that the window you are removing actually exists. If it does, you have two tasks to accomplish.

a. Remove the menu item corresponding to the window.

The order of window titles in the menu matches the order of windows in the window list. Get the index number of the window from the window list. Use the menu object's inherited RemoveItem() function to remove the item from the menu. Don't forget to add mBaseItems to the index value of the window to get the correct menu item number.

b. Remove the window from the window list.

Use mWindowList, and call Remove() to remove the specified window from the list.


// Remove the item from the menu.
Assert_( mWindowList.FetchIndexOf( 
                &inWindow ) != arrayIndex_Bad );
RemoveItem( mWindowList.FetchIndexOf( 
              &inWindow ) + mBaseItems );
// Remove the window from the list.
mWindowList.Remove( &inWindow );

if ( mWindowList.GetCount() == 0 && mBaseItems > 1 ) {

The existing code then handles the separator, removing it when there are no more windows in the Window menu. The existing code also resets the command keys for the open windows.

When you are through, save your work and close the file. It's time to implement window behavior.

4. When creating a window, add it to the menu.

FinishCreateSelf() CRegularWindow.cp

When the user creates a regular window, you want to add it to the Window menu. The FinishCreateSelf() function is designed for the tasks you must accomplish to finish a pane, view, or control.

The Mac human interface says that new windows not associated with any file on disk should be named "Untitled" followed by a number. The existing code uses the UWindows::FindNamedWindow() utility to ensure that you have a unique and appropriate title to assign to your window.

When you have a good title, you have two tasks to accomplish.

a. Set the window title.

Use the window object's SetDescriptor() function.

b. Add the window to the Window menu.

Use the gWindowMenu global variable. (You'll initialize this variable in a subsequent step.) Use the CWindowMenu object's InsertWindow() function.


  theTitle += (LStr255) theNumber;
}
// Set window title.
SetDescriptor( theTitle );

// Add the window to the window menu.
gWindowMenu->InsertWindow( this );

5. When destroying a window, remove it from the menu.

~CRegularWindow() CRegularWindow.cp

Use the gWindowMenu global variable. (You'll initialize this variable in a subsequent step.) Use the CWindowMenu object's RemoveWindow() function.


CRegularWindow::~CRegularWindow()
{
  // Remove the window from the window menu.
  gWindowMenu->RemoveWindow( this );
}

You have completely implemented all the new behavior of the CRegularWindow object. Save your work and close the file.

6. Handle a click in the floating window content.

ClickSelf() CFloatingWindow.cp

In a typical application the window's contents would usually handle the click. However, the Tools window is empty, and a click in the content has no real significance. The code you write in this step is for instructional purposes only, just so you can see how a floating window responds to a click.

Make the window beep when there is a click in the contents.


#pragma unused ( inMouseDown )
  ::SysBeep (30);

7. Handle a click in the floating window close box.

ClickInGoAway() CFloatingWindow.cp

When the user clicks in the close box for the floating window, you could destroy the window. When the user wants to see the Tools window again, you would have to build it from scratch. This application uses a different strategy. It creates the window once, and then shows or hides the window.

Existing code calls the Toolbox TrackGoAway() function. If the call returns true, hide the window.

There is one other task you must perform. You must set the menu update flag as well. PowerPlant does not update menus for clicks in the title bar or peripheral controls. However, a click in the close box should modify the Window menu. The first item in the Window menu says (alternatively) either Show Tools or Hide Tools.


if ( ::TrackGoAway( GetMacPort(), inMacEvent.where ) ) {
  // Hide the window.
  Hide();

  // Update the menus.
  SetUpdateCommandStatus( true );
}

You'll create and show the window in subsequent steps. However, you have fully implemented the unique behavior of the floating window. Save your work and close the file. All that remains is to use these items-the menu, the regular window, and the floating window-in the finished application. (See "When To Update Menus.")

8. Install the Window menu.

Initialize() CWindowsApp.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. After that, 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. It's always wise to check that creation was a success. You can use ThrowIfNil_.

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 );

9. Create the Tools window.

Initialize() CWindowsApp.cp

As we discussed above, this application's strategy for the Tools palette is to create the window once, then show and hide it as necessary. You should create the Tools palette in the application's Initialize() function.

To do so, call the LWindow::CreateWindow() function. The declared constant for the PPob resource is rPPob_FloatingWindow.

You can use the LWindow::CreateWindow() function because the floating window-although it has slightly different behavior-is identical to an LWindow object with respect to the data required to create the window object and its contents.

However, you should typecast the return value from LWindow::CreateWindow() to be a CFloatingWindow pointer. Store the result in an application data member, mToolsWindow.


theMBar->InstallMenu( gWindowMenu, 0 );
// Create the tools window.
mToolsWindow = dynamic_cast<CFloatingWindow *>
            (LWindow::CreateWindow( rPPob_FloatingWindow, this ));
ThrowIfNil_( mToolsWindow );

10. Create a regular window at launch.

StartUp() CWindowsApp.cp

The StartUp() function is called when the application launches without documents. As a result, a typical use for this function is to create a default window if the user does not open a document.

That's just what the "Windows" application does. When the application launches, a window opens automatically. To implement this behavior, simply call the application's ObeyCommand() function. Use the cmd_New command. (See "PowerPlant and Apple Events.")


CWindowsApp::StartUp()
{
  ObeyCommand( cmd_New, nil );
}

11. Respond to commands.

ObeyCommand() CWindowsApp.cp

In this function you should respond to three commands: a selection in the Window menu, cmd_New, and cmd_ToolsWindow. In the substeps in this step, you handle each command.

a. Handle a Window menu command.

Recall that when you inserted an item in the Window menu, you assigned cmd_UseMenuItem as the corresponding command number. That means PowerPlant generates a synthetic command for each item.

Existing code identifies synthetic commands, and tests to ensure that the command is from the Window menu. If it is, you get the window object that corresponds to the menu item. Use the CWindowMenu's MenuItemToWindow() function. Then, if there is a window and it is visible, select the window. Use UDesktop::SelectDeskWindow().


if (theMenuID == gWindowMenu->GetMenuID() ) {
  // Get window corresponding to the menu item.
  LWindow *theWindow =
         gWindowMenu->MenuItemToWindow( theMenuItem );
  
  if ( theWindow != nil && theWindow->IsVisible() ) {
    // Bring the window to the front.
    UDesktop::SelectDeskWindow( theWindow );
  }
} else { // Synthetic command not in Window menu

b. Create a new regular window.

The existing code has a case statement for cmd_New. In response to that command, you should create and show the window. Use the LWindow class creator function. The declared constant for the PPob resource ID is rPPob_RegularWindow.


case cmd_New:
{
  // Create the window.
  LWindow *theWindow;
  theWindow = LWindow::CreateWindow( rPPob_RegularWindow, this );
  Assert_( theWindow != nil );
  // Show the window.
  theWindow->Show();
}
break;

c. Show or hide the tools window.

The application receives the cmd_ToolsWindow command when the user selects the first item in the Window menu. In response to this command, you should toggle the visibility of the window. Determine if the window is visible. It it is, hide it. If it is not, show it. Use the mToolsWindow data member.


case cmd_ToolsWindow:
{
  // Toggle visible state of the tools window.
  if ( mToolsWindow->IsVisible() ) {
    mToolsWindow->Hide();
    
  } else {
    mToolsWindow->Show();
  }
}
break;

Notice that the default case for both synthetic and non-synthetic commands passes any unrecognized command to the inherited ObeyCommand() function for further processing.

12. Update menus.

FindCommandStatus() CWindowsApp.cp

Finally, you must update menus appropriately. You should respond to three kinds of items: Window menu items, the New item in the File menu, the Tools window item. In the substeps in this step, you handle each item.

a. Update Window menu items.

Recall that when you inserted an item in the Window menu, you assigned cmd_UseMenuItem as the corresponding command number. That means PowerPlant generates a synthetic menu command for each item. It also means that PowerPlant will call the FindCommandStatus() function for each item.

Existing code identifies synthetic commands, and tests to ensure that the item is from the Window menu. If it is, you should get the window object that corresponds to the menu item. Use CWindowMenu's MenuItemToWindow() function. If there is a window, enable the item, use a mark, and set the mark to noMark. Then, if the window is the top window, set the mark to a check mark. Use UDesktop::FetchTopRegular() to identify the top regular window.


if (theMenuID == gWindowMenu->GetMenuID() ) {
  // Find window corresponding to the menu item.
  LWindow *theWindow = 
                gWindowMenu->MenuItemToWindow( theMenuItem );
  
  if ( theWindow != nil ) {
    // All window items enabled and use a mark.
    outEnabled = true;
    outUsesMark = true;
    outMark = noMark;

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

      // Check menu item for top regular window.
      outMark = checkMark;
    }
  }
} else { // Synthetic command not in Window menu

b. Enable the New item in the File menu.

The existing code has a case statement for cmd_New. Enable the item.


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

c. Set the Show/Hide Tools text.

The application receives the cmd_ToolsWindow command for the first item in the Window menu. Enable the item. Set the text based on the visibility of the window. Determine if the window is visible. It it is, the item should say "Hide Tools." If the window is not visible, the item should say "Show Tools." Use the outName parameter to set the item text. PowerPlant does the rest.


case cmd_ToolsWindow:
{
  // Item is always enabled.
  outEnabled = true;
  
  // Toggle the menu item text.
  if ( mToolsWindow->IsVisible() ) {
    LString::CopyPStr( "\pHide Tools", outName);
  } else {
    LString::CopyPStr( "\pShow Tools", outName);
  }
}
break;

Notice that the default case for both synthetic and non-synthetic commands passes any unrecognized command to the inherited FindCommandStatus() function for further processing.

13. Build and run the application.

All right! Time to see the result of your work. Make the project and run it. When you do, an empty window should appear with the name "Untitled 1."

Look in the Window menu. The first item should be Show Tools, followed by a separator bar and the name of the regular window. Close the window and look in the Window menu again. How does it differ?

When you launch the application, the Tools window is not visible. Remember, the PPob for the Tools window specified that the window was not initially visible. As a result, although you create the window at launch, the window does not appear automatically.

Choose the Show Tools item. The Tools window appears. Look in the Window menu again, and the first item should say "Hide Tools."

Create a new regular window. The floating window remains active. Click in the Tools window. The computer beeps. Although you have clicked in another window, the regular window remains active. Play with the Tools window. Move it around. Click in the close box, and the window disappears. Show it again, then choose Hide Tools from the Window menu. The window hides.

Create several new regular windows. Look in the Window menu, and observe the various menu items. Close some windows, and observe what happens in the Window menu. Every time you open or close a regular window, the menu adjusts accordingly.

Activate various windows and observe the check mark in the Window menu. The mark should always denote the currently active regular window. Try the command keys for Window menu items. The corresponding window should become active.

If you would like to expand on this application, here are some suggestions.

Add a ClickSelf() function to the regular window, and make it beep just like the floating window. Rebuild the app, make two windows, and click the inactive window. The window activates, but does not beep. Click again in the active window, and it beeps. Now, use Constructor to turn on the GetSelectClick feature of the regular window. Repeat the experiment. What happens this time? Does the window beep when you activate it? It should, because the activating click now activates the window and is passed to the window for processing. Turn off the GetSelectClick feature for the floating window and see if it makes a difference. It shouldn't. Why not? Because a floating window is always active. Create a function that allows the user to change a window title. Update the Window menu along with the window title. Put some content in the windows!

 

Congratulations! You've passed another milestone. You have now worked extensively with menus, and created and used both regular and floating windows. In the next chapter we cover another kind of window in great detail-dialogs.

However, before you go running off to the next stage of the adventure, we want you to stop for a moment and think about the concept of adding a Window menu to an application.

As implemented in this chapter, the menu is an integral part of the application. There is nothing wrong with that. However, adding such a menu to existing code or removing it from a project would entail some substantial changes scattered here and there in the code. For example, you would have to modify window creation, window destruction, the application object's FindCommandStatus() and ObeyCommand() functions, as well as the application constructor.

You might wish it could be easier than this. Well, it is. We are going to revisit this topic in Chapter 15, "Periodicals and Attachments." Keep it in mind.

 

 


[ 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