TweetFollow Us on Twitter

TearOff TCL
Volume Number:9
Issue Number:2
Column Tag:TCL Workshop

Related Info: Menu Manager Window Manager

TCL Knick Knacks

The compiler knows!!

By John A. Love, III, MacTutor Regular Contributing Author

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

About the author

John is a member of the Washington Apple Pi Users’ Group from the greater Washington D.C. metropolitan area and can be reached on America Online{John Love} and on GEnie {J.LOVE7}.

This series

The purpose of this continuing series is to laboriously trace through the source code of Symantec’s THINK Class Library (TCL) that serves as the Object Oriented Programming (OOP) arm for their Pascal and C compilers to provide the answers to the age-old question, “Who does what to whom?”.

Forrest Tanaka and Sandy Mossberg wake up, this one’s for you!!!

This article ...

In the first of my “TCL Knick-Knacks” series which appeared in last August’s issue, I promised an article that addressed Tear-Off Menus (TOM), and since a promise is a promise ...

Remember that great article by Don Melton and Mike Ritter that appeared in MacTutor way, way back in April, 1988? Truly a rosy masterpiece that addressed “Tear-Off Menus & Palettes”. However, I recall all the frustration that I encountered in understanding the thorny “Why all this blasted overhead just to handle a window, even a menu-type window?” Well the folks at Symantec have successfully removed the absolute necessity of having to understand all the thorny details.

In my particular implementation, a Tear-Off Menu is both a floating window and a floating palette with each pane in this palette functionally equivalent to an individual Menu item. First, I will stipulate all a programmer must do to implement my variety of Tear-Off Menus using the TCL and then I will amplify on each step.

All this code is included on MacTutor’s disk from last August’s issue. It is also included in my “Feature Flick” package which addresses QuickTime™ and introduces my new class = CQuickTime. You can find the latter on America Online.

First, what to do ...

1) include a 'WDEF' or Window Definition Procedure in the resource file.

2) include a 'MDEF' or Menu Definition Procedure in the resource file.

3) create a 'MENU' for your Tear-Off Menu with the same ID as the 'MDEF' and then place this ID in your 'MBAR' resource.

4) create a 'WIND' resource whose ID also equals that of the 'MDEF'. For the sake of consistency, I make the IDs of the 'MDEF', 'MENU' and 'WIND' all match. Using this approach, it ’s much easier to keep things straight when you have more than one Tear-Off Menu .

5) sub-class TCL’s CGridSelector. CGridSelector is a CSelector is a CPanorama is a CPane and is functionally a palette or array of grid elements wherein each Menu item of your Tear-Off Menu is a distinct pane or grid element of this CGridSelector.

/* 1 */
struct  CCustomSelector : CGridSelector
{
void    DrawItem (short theItem, Rect *theBox);
// Inherited from CSelector:
void    DoClick (Point hitPt, short modifierKeys, long when);
};

At a minimum, override CGridSelector’s DrawItem for drawing. Optionally, override DoClick which CGridSelector inherits from CSelector for selection. Drawing occurs when you pull down the menu from the main menubar or in response to updates after your menu has been torn-off. DoClick comes into play when you click on one of the grid items or panes of your already torn-off menu.

6) sub-class TCL’s CTearOffMenu. CTearOffMenu is a CDirector. As I “panefully” described last August, “a director is a bureaucrat that supervises a window”. So, instead of a CDocument supervising our window, our CTearOffMenu does the supervising.

/* 2 */
struct CTearMenuDir : CTearOffMenu 
{
 void   ITearMenuDir (CApplication *aSupervisor);
 void   DoCommand (long theCommand);
 void   CloseWind (CWindow *theWindow);
};

At a minimum, override CTearOffMenu’s ITearOffMenu (I call it ITearMenuDir). Within ITearMenuDir, call ITearOffMenu, inherited from the superclass, to create your floating window. Then create and initialize your CGridSelector. The new CGridSelector serves as the main CPane of the CTearOffMenu; as a matter of fact, the created CGridSelector is stuffed into CTearOffMenu::itsPane. IGridSelector sets up the Menu Command Number Base for the array of grid items or Menu items; given this base #, the Command Number for each grid/Menu item is separated from its predecessor by one. You also pass to IGridSelector the number of rows & colums in this array or matrix of Menu items. For a conventional Menu there is obviously only one column, however, for a graphic Palette such as the Pattern Menu in MacPaint™, there are multiple columns.

After you call IGridSelector and do some more housekeeping, create and initialize a CSelectorMDEF object. ISelectorMDEF calls IPaneMDEF which calls IMenuDefProc to fill in your 'MDEF' resource stub. Given this filling in, when the Menu Manager calls _MenuSelect, for example, your 'MDEF' is called to magically draw the Menu and choose a particular Menu item.

You pass the above-mentioned CGridSelector object (= CPane) to ISelectorMDEF which, in turn, passes the same object into IPaneMDEF for storage in CPaneMDEF::itsPane. As a direct result, CTearOffMenu::itsPane = CPaneMDEF::itsPane = the new CGridSelector. This is a key point, so hold this thought

I do not use DoCommand, inherited from CTearOffMenu’s superclass = CDirector, in my simple example. I refer you to the “Art Class” example provided by Symantec on their TCL disk(s) wherein DoCommand is overidden.

I’ll reveal later why I override CTearOffMenu::CloseWindow.

7) Within your CMovieApp:

/* 3 */

struct CMovieApp : CApplication
{
 // One instance variable:
 CTearMenuDir  *itsTearMenu;

 // So we have floating windows:
 void   MakeDesktop (void);

 // etc.
 void   SetUpMenus (void);

 void   SwitchToDA (void); 
 void   SwitchFromDA (void); 

 void   DoCommand (long theCommand);
 void   DoKeyDown (char theChar, Byte keyCode, EventRecord *macEvent);

}; /* CMovieApp */

a) place a reference to your CTearOffMenu in an instance variable, said reference being created within CMovieApp::SetUpMenus. This is optional; however, as shown later, I need this reference in my CMovieApp::DoKeyDown.

b) override CApplication::MakeDesktop in order to place a new(CFWDesktop) into the global = gDesktop. In this manner, the desktop will support floating windows properly.

c) override CApplication::SetUpMenus. I will address this in detail later.

d) override CApplications’s SwitchToDA and SwitchFromDA to disable and enable, respectively, your Tear-Off Menu(s). These paired Switch routines are typically the place for disabling and enabling whole Menus only, whereas your UpdateMenus method(s) can disable/enable either whole Menus or individual Menu items:

/* 4 */

 void CMovieApp::SwitchToDA (void)
 {
 gBartender-> DisableMenu(kTearMenu);

 inherited::SwitchToDA();
 } /* SwitchToDA */

 void CMovieApp::SwitchFromDA (void)
 {
 gBartender->EnableMenu(kTearMenu);

 inherited::SwitchFromDA();
 } /* SwitchFromDA */

e) override CApplication::DoCommand to respond to the selection of a discrete pane or Menu item of your CGridSelector palette. This response occurs when you either pull down your Menu to select one of the items as if it were an ordinary Menu or tear it off and subsequently click on the content portion of the torn-off Menu’s floating window.

f) override CApplication::DoKeyDown to response to <CMD> key shortcut selections of the various grid elements within your palette. I don’t have any <CMD> key shortcuts, but I allow the user to use <CMD-Tab> to alternately show or hide the torn-off Menu {HyperCard™, anyone?}.

Next, the details ...

Above, I grouped all the resource stuff together and ditto for the Tear-Off classes and CMovieApp modifications. For the purposes of introduction, these groupings serve well. However, when it now comes time to “follow the bouncing ball”, I am going to be bouncing from group-to-group, mixing it up so-to-speak. I believe this is mandatory in order to logically follow all the interactions as well as the reasons behind them.

The window

Here’s a portion of my “movies.r” that SARez™ chews on:

/* 5 */

// Special WDEF for Floating Windows:
include "Windoid";
#define kWindoid 104

// Apple reserves 0 -> 127:
#define kTearMenu1000

resource'WIND' (kTearMenu, "1st Floating Tearoff Menu",
 purgeable)
{
{40,  40, 110, 112},
// = 16*kWindoid + noGrowDocProc:
1668,
invisible,
goAway,
0x0,
"",
/* I define SystemSevenOrLater
** (found in "Types.r") = true
** in my "movies.r" file:    */
noAutoCenter
};

Note in the above 'WIND' resource description that the defProc field implements the arithmetic mandated by the Window Manager, namely, 16*the ID of the 'WDEF' resource + the window variant. In this manner, the Window Manager uses my 'WDEF' for drawing the window. Take a long stare at the shape and appearance of MacPaint’s tear-off Palette, especially that of its title bar. That is the 'WDEF' that is the sole occupant of the TCL file = “Windoid” that Symantec passes along with their TCL package, together with its source code, natch.

When the TCL initializes your Tear-Off Menu object (my ITearMenuDir calls TCL’s ITearOffMenu), the ID of your 'WIND' resource is passed to inherited::ITearOffMenu as a parameter. The latter calls:

/* 6 */

// CDirector::itsWindow
itsWindow = new (CWindow);
itsWindow->IWindow(WINDid,
 TRUE,  /* floating */
 gDesktop,
 this /* supervisor */);

So a window is created and we’re off and running. Remember, this = the object that was sent the message = ITearOffMenu. Therefore, the supervisor of the floating window becomes the CDirector = my CTearMenuDir, a sub-class of CTearOffMenu. As you will see shortly, this initialization takes place below within CMovieApp::SetUpMenus but cool it.

Next, we’ve got to make the TCL handle our Tear-Off Menu properly, and since the latter is a floating window:

/* 7 */

void  CMovieApp::MakeDesktop (void)
{
 gDesktop = new (CFWDesktop);
 ((CFWDesktop*)gDesktop)-> IFWDesktop(this);
}/* MakeDesktop */

Therefore, the window’s enclosure = gDesktop passed to IWindow above is a CFWDesktop, so everything’s right with the world. Speaking of floating windows, there is a popular, but erroneous, conception that all floating windows must disappear when your application is suspended. Apple’s Human Interface Guidelines stipulate that this be true for Tear-Off Menus; they make no such statement for all floating windows, leaving the programmer the option to do his/her own thing for other floating windows. The mandatory disappearance of floating Menus is logical since MultiFinder™ has switched out your Menus a floating Menu for a non-existent Menu ??? get a life!!! The TCL implements the Human Interface Guidelines precisely within CTearOffMenu’s Suspend and Resume methods.

The Menu

When the Menu Manager is called, e.g., _MenuSelect, the Menu’s 'MDEF' is called via something akin to:

;8

 clr.l    -(sp)
 push.w   #ID
 _GetMenu
 move.l   (sp)+, a0  ; = MenuHandle
 move.l   (a0), a1
 ; menuDefProc is filled in by
 ; _GetMenu after it loads the
 ; 'MENU' resource.  This field
 ; then contains a Handle to my
 ; 'MDEF' because I place the
 ; latter's ID in the 'MENU':
 push.w   theMessage
 push.l   a0
 ; push additional parms here
 ; depending on the Message:
 move.l   menuDefProc(a1),a1
 move.l   (a1),a1
 jmp      (a1)

The TCL implements a similar methodology. Before I give all the gory details of this methodology, first let me present what the 'MDEF' and 'MENU' resources look like:

/* 9 */

// more on this buzzard later:
typedef struct   GenericMDEFRec {
short   JMPinstruction;
VoidFuncdefProc;
CMenuDefProc*itsMenuDefProc;
}GenericMDEFRec,
 *GenericMDEFPtr,
 **GenericMDEFHand;

#define kMDEF  kTearMenu

/* Only this stub appears
** in your .rsrc file:    */
data  'MDEF'(kMDEF, "1st Floating Tearoff Menu", purgeable)
{
$"4EF9" // “jmp”
$"00000000" // “defProc”
$"00000000" // “itsMenuDefProc”
};

The above jmp (a1) effectively jumps to the address containing 4EF900000000, thus in turn executing another jump to the address contained in the next four bytes represented here by zeros. We’ll see below that after ITearOffMenu creates your floating window it calls CMenuDefProc::IMenuDefProc and that these zeros will be filled in by IMenuDefProc with the effective address of TCL’s GenericMDEF. The latter is a generic Menu Definition Procedure which uses Object C. It receives a mDrawMsg to draw the Menu, a mChooseMsg to handle selection from the Menu, a mSizeMsg to determine dimensions of the Menu and a mPopupMsg to specify placement of a popup Menu.

/* 10 */

resource'MENU' (kMDEF, "1st Floating Tearoff Menu",
 purgeable)
{
kMDEF,
// By my convention, MDEF ID = Menu ID:
kMDEF,
0x7FFFFFFF,
enabled,
"Tear off this hummer !!!",
{
/* Item(s) drawn “on-the-fly” by
** DrawMenu as discussed below.  */
}
};

#define mMovie   20

resource 'MBAR' (MBARapp, "MBAR", purgeable)
{
{/* array MenuArray: 5 elements */
 /* [1] */
 mApple,
 /* [2] */
 mFile,
 /* [3] */
 mEdit,
 /* [4] */
 mMovie,
 /* [5] */
 kTearMenu
}
};

void  CMovieApp::SetUpMenus (void)
{
 CTearMenuDir  *cryBaby   = nil;

 /* Superclass takes care of 
 ** adding menus specified in
 ** a MBAR id = 1 resource:   */
 inherited::SetUpMenus();

 /* Director for a tear-off Menu:
 ** ( I’ll explain later on. )*/
 cryBaby = new (CTearMenuDir);
 // Supervisor = your app:
 cryBaby->ITearMenuDir(this);
 itsTearMenu = cryBaby;

}/* SetUpMenus */

Remember that your CMovieApp::SetUpMenus calls inherited::SetUpMenus so that your 'MBAR' resource gets loaded into memory to create the main Menubar, including your Tear-Off Menu. Internal to this loading process, _GetMenu is called and thus the menuDefProc field of the tear-off 'MENU' resource gets filled in with a Handle to my 'MDEF'. _GetMenu is able to accomplish this because the 'MENU' resource contains the ID of the 'MDEF' that should be used. I will explain later my initialization routine = ITearMenuDir. In the meantime, however, the latter eventually calls ITearOffMenu to create your floating window as well as IMenuDefProc to fill the defProc field of your 'MDEF' stub with the effective address of TCL’s GenericMDEF. It looks like we’re almost in business. Before I blow town, i.e., exit my ITearMenuDir, I move the blasted window off-screen so it’s not seen. In short, I keep it handy for later when I tear-off the Menu.

/* 11 */

void  CMenuDefProc::IMenuDefProc (short MDEFid)
{
 GenericMDEFHand theMDEF;

 theMDEF = (GenericMDEFHand) GetResource('MDEF', MDEFid);
 FailNILRes(theMDEF);
 (**theMDEF).defProc = (VoidFunc) GenericMDEF;
 // last 4 bytes of stub:
 (**theMDEF).itsMenuDefProc = this;
 FlushCache();
}

IMenuDefProc also fills in the last four bytes of your 'MDEF' resource stub with the object that was sent the message to begin with and that object is a descendant of CMenuDefProc. I’ll talk about which descendant later but in the meantime, recall when I stated above that thanks to _GetMenu and IMenuDefProc the Menu Manager will jump to the address of TCL’s GenericMDEF. The TCL depicts the latter as:

/* 12 */

pascal void GenericMDEF (short theMessage, register 
 MenuHandle macMenu, Rect *menuRect, Point hitPt,
 short *whichItem)
{
 GenericMDEFHand theMDEF;
 CMenuDefProc  *theMenuDefProc;

 theMDEF = (GenericMDEFHand) (**macMenu).menuProc;
 /* itsMenuDefProc = “this”
 ** in IMenuDefProc: */
 theMenuDefProc = (**theMDEF).itsMenuDefProc;

 switch (theMessage)
 {
 case mDrawMsg:
 theMenuDefProc-> DrawMenu(macMenu, menuRect);
 break;

 // etc.
 }
}

Remember that the object stored in the itsMenuDefProc field of your 'MDEF' stub is the object that was sent the message = IMenuDefProc and said object is our mystical descendant of CMenuDefProc. So in short, for example, when _MenuSelect sends a mDrawMsg to our 'MDEF', we end up calling CMenuDefProc::DrawMenu(...), or really the DrawMenu method of its descendant since the latter must override the parent’s DrawMenu because the parent’s DrawMenu is empty. “Simple, ain’t it ?!*!?”

/* 13 */

class CMenuDefProc : public CObject
{
public:

void  IMenuDefProc(short MDEFid);

virtual voidDrawMenu(MenuHandle macMenu, Rect *menuRect);
virtual voidChooseItem(MenuHandle macMenu, Rect *menuRect, Point hitPt, 
short *whichItem);
virtual voidSizeMenu(MenuHandle macMenu);
virtual voidPlacePopup(MenuHandle macMenu, Rect *menuRect, Point hitPt, 
short *whichItem);
};

and in similiar fashion for the other messages.

Let’s review the bidding

Okay, now I think it’s time to reveal what ITearMenuDir looks like:

/* 14 */

void  CTearMenuDir::ITearMenuDir (CApplication *aSupervisor)
{
 CCustomSelector *myMenu = NULL;
 CSelectorMDEF   *myMDEF = NULL;
 BooleansavedAlloc;
 PicHandlemyPic;
 Rect   pictRect;

First, call ITearOffMenu to create a floating window with ID = kTearMenu and set itsWindow from CTearOffMenu's superclass = CDirector. CMovieApp::SetUpMenus passed this as the Tear-Off Menu's Supervisor. this is referenced within a CMovieApp method, so this must be my application. Therefore, my app supervises my Tear-Off Menu.

ITearOffMenu, in the process of creating our floating window, passes this as the window’s supervisor . This this is the object that was sent our message = ITearOffMenu, which I called cryBaby within my SetUpMenus method (go back a couple of pages to confirm this). So, the this passed as the floating window’s supervisor is our Tear-Off Menu object = CTearMenuDir. As a direct result, our app supervises our Tear-Off Menu which, in turn, supervises our floating window.

“A director is a bureaucrat that supervises a window.” The above is consistent with this because CTearMenuDir descends from CTearOffMenu which descends from CDirector. Down a little bit we will make the supervisor of the CGridSelector (is a CSelector is a CPanorama is a CPane is a sub-view of our floating window) our application. Normally, the supervisor of a CPane is a document.

I mentioned way, way back within this article’s introduction that a CGridSelector is functionally a palette or array of grid elements wherein each Menu item of our Tear-Off Menu is a distinct pane or grid element of this CGridSelector. The fact that I have only one grid element or pane in my very simple array becomes relevant only to the discussion of Command Numbers which I’m intentionally postponing until further on.

/* 15 */

inherited::ITearOffMenu(aSupervisor, kTearMenu);

// Get size of grid element:
savedAlloc = SetAllocation(kAllocCanFail);
myPic = GetPicture(kMAC);
SetAllocation(savedAlloc);
FailNILRes(myPic);
pictRect = (**myPic).picFrame;
// Zero origin:
OffsetRect(&pictRect, -pictRect.left, -pictRect.top);
// Do NOT need after getting frame:
ReleaseResource(myPic);

IGridSelector calls ISelector which calls IPanorama which calls IPane to do all the requisite initialization of instance variables, including those of CGridSelector. For example:

• how many rows and columns are there in my grid array? (since I have only one item, my values are 1, 1)

• the width and height of each grid element (yes I’m implying that all grid elements have the identical size)

These next two instance variables are inherited from CSelector:

• when I pull down the Tear-Off Menu what item is initially highlighted? (the first item has a number = 1)

• the Command Base for my grid array of Menu items (I elected this to be equal to the ID of my TOM)

/* 16 */
myMenu = new (CCustomSelector);
myMenu->IGridSelector
 (
 itsWindow, /* From superclass of CTearOffMenu */
 aSupervisor,  /* My application */
 0, 0,
 sizELASTIC, sizELASTIC,
 0,/* Initial item to select = none in
 ** this case since items are numbered
 ** beginning at 1 */
 kTearMenu, /* Command Base */
 1, 1,  /* # of rows & columns */
 pictRect.right + 2, /* box width */
 pictRect.bottom + 2 /* & height */
 );

aSupervisor is successively passed up the inheritance chain to IBureaucrat and stored in CBureaucrat::itsSupervisor to handle the Commands that our CGridSelector (= = CView = CBureaucrat) cannot handle. I will discuss this key point in detail shortly.

IGridSelector sets CGridSelector::gridOn = true. Since I have only 1 element in my simple example, I don’t need a grid of orthogonal lines whose primary purpose in life is to visually differentiate adjacent multiple grid elements:

/* 17 */

myMenu->SetGridOn(false);
myMenu->FitToEnclosure(true, true);
myMenu->Activate();

itsPane = CTearOffMenu::itsPane = a CPane. My CCustomSelector = a CGridSelector = a CSelector = a CPanorama = a CPane. Therefore, both are CPanes and can be equated. More on this later:

/* 18 */

itsPane = myMenu;

Reset the minimum & maximum sizes of a window. TCL has these preset = 100 & GrayRgn's rgnBBox, respectively:

/* 19 */

SetRect(&itsWindow->sizeRect,
 pictRect.left  - 1,
 pictRect.top   - 1,
 pictRect.right   + 3,
 pictRect.bottom + 3);

Change the size of all the window's sub-view(s), the latter being now just my CGridSelector = myMenu in my simplistic example. Leave a blank 1 pixel border around the picture so it does NOT touch the window frame:

/* 20 */

itsWindow->ChangeSize(pictRect.right + 2, 
 pictRect.bottom + 2);

Leave same blank space in setting margins. This margin ALSO includes the regular window frame. So we have a 1 pixel blank space, a 1 pixel frame AND a 10 pixel drag bar on top:

/* 21 */

SetRect(&margins, 2, 12, 2, 2);

Now a real toughie

Go back to the segment of my introduction wherein I talked about ITearOffMenu, IGridSelector and ISelectorMDEF. The bottom line there was that CTearOffMenu::itsPane = CPaneMDEF::itsPane = my new CGridSelector. The first and third pieces are equated above (itsPane = myMenu). When we pass my new CGridSelector to ISelectorMDEF below, the same CGridSelector is passed to IPaneMDEF which then equates the second and third pieces.

/* 22 */

myMDEF = new (CSelectorMDEF);
myMDEF->ISelectorMDEF(kTearMenu, myMenu, this);

What I deliberately postponed telling you ’til now is that also passed to ISelectorMDEF is this which is the object that was sent the original message = ITearMenuDir which is my cryBaby addressed in my CMovieApp::SetUpMenus. cryBaby is a CTearMenuDir is a CTearOffMenu is a CDirector. ISelectorMDEF not only passes on my CGridSelector object ( = myMenu) to IPaneMDEF for stuffing into CPaneMDEF::itsPane, but it also passes on cryBaby. IPaneMDEF then stuffs cryBaby into CPaneMDEF::itsTearOffMenu. This last stuffing is used by the TCL when I discuss later on tearing off your Menu so hold this thought as well.

AHHHHH-HAH!!!

Remember back a few pages when I said that a descendant of CMenuDefProc gets sent a DrawMenu message when _MenuSelect sends a mDrawMsg to TCL’s GenericMDEF. Well the descendant is CSelectorMDEF and it’s this descendant object that IMenuDefProc, called by my ITearMenuDir within CMovieApp::SetUpMenus, stuffs into the itsMenuDefProc field of my 'MDEF' resource stub.

Dig into the TCL source code and observe that CSelectorMDEF does not have a DrawMenu method; however, its superclass = CPaneMDEF does. So when TCL’s GenericMDEF calls DrawMenu, CPaneMDEF::DrawMenu gets called. Ditto for SizeMenu. If responding to a mChooseMsg CSelectorMDEF::ChooseItem gets called; in short, CSelectorMDEF does not have to travel up to its superclass to find a ChooseItem method. Finally, if responding to a mPopupMsg, we need to travel all the way up to CMenuDefProc to access CMenuDefProc::PlacePopup since none of the sub-classes of CMenuDefProc have a PlacePopup method.

The DrawMenu routine of CPaneMDEF calls itsPane->Draw where itsPane is an instance variable of CPaneMDEF. Note from previous discussions that IPaneMDEF stuffs my CGridSelector object {which is a CSelector is a CPanorama which descends from CPane} into the itsPane instance variable belonging to CPaneMDEF.

So, when CPaneMDEF::DrawMenu is eventually called by TCL’s GenericMDEF, CPaneMDEF::itsPane->Draw gets called. Since itsPane is a CGridSelector object, take a gander at CGridSelector::Draw. The latter sweeps through all the rows and columns of your CGridSelector matrix of elements, or really array of Menu items, and calls CGridSelector::DrawItem for each element or Menu item.

Let’s blow town:

/* 23 */

 // Keep handy, but out of the way:
 itsWindow->MoveOffScreen();
 itsWindow->Select();
}/* ITearMenuDir */

Here’s what my own DrawItem method looks like:

/* 24 */

void  CCustomSelector::DrawItem (short theItem, Rect *theBox)
{
/* Passed area rect is in QuickDraw coordinates */

 #define  kMAC   3000

 BooleansavedAlloc;
 PicHandlemyPic;
 Rect   pictRect;
 
 switch (theItem){
 
 case 1:
 savedAlloc = SetAllocation(kAllocCanFail);
 myPic = GetPicture(kMAC);
 SetAllocation(savedAlloc);
 FailNILRes(myPic);

 pictRect = (**myPic).picFrame;
 CenterRects(&pictRect, theBox);
 DrawPicture(myPic, &pictRect);

 /* Do NOT need after drawing: */
 ReleaseResource(myPic);
 break;
 
 default:
 // Empty for now:
 inherited::DrawItem(theItem, theBox); 
 break; 
 } /* only 1 item */
}/* DrawItem */

Once again, notice that I only have one pane or grid element or Menu item. Given a more complex grid of Menu items, take a gander at Figure 37-1 in Symantec’s “Object Oriented Programming Manual” for the numbering scheme associated with multiple grids.

Tear Off this hummer

Let’s talk a little bit more about CTearOffMenu:

/* 25 */

class CTearOffMenu : public CDirector
{
public:
 CPane  *itsPane;
 Point  corner;
 Rect   margins;

 void   ITearOffMenu (CApplication *aSupervisor,
 short WINDid);

 virtual void  Suspend (void);
 virtual void  Resume (void);
 virtual void  CloseWind (CWindow *theWindow);
 
 virtual void  TornOff (Point aCorner);
 virtual void  MoveToCorner (void);
 virtual WindowPtr  GetMacWindow (void);
 virtual void  SetMargins (Rect *aMargins);
 virtual void  GetMargins (Rect *theMargins);
};

I’ve already presented a flood of words about ITearOffMenu. The Suspend and Resume methods call CWindow::HideSuspend and CWindow::ShowResume to make the Tear-Off Menu disappear and re-appear, respectively, per the specifications of Apple’s Human Interface Guidelines. I’ll postpone addressing CloseWind until later (trust me!!!).

CSelectorMDEF::ChooseItem method detects a Tear-Off event by following your dragging motion. If you’ve dragged beyond the confines of the pulled-down Menu and not stayed within them as you would for a normal choose, then CPaneMDEF::TearOffMenu is called. After some moderate housekeeping, CPaneMDEF::itsTearOffMenu->TornOff is eventually called. Remember when I said a zillion words ago that IPaneMDEF stuffed my CTearOffMenu object = cryBaby into CPaneMDEF::itsTearOffMenu? Well “there she blows!”.

With the calling of TornOff, a new CTearChore is created, initialized and subsequently added to CApplication::itsUrgentChores via:

/* 26 */

gApplication-> AssignUrgentChore(newTearChore);

When ITearChore is called by AssignUrgentChore, this is passed as a parameter where this = the object that was sent the original message. The original message = TornOff, so this this = CPaneMDEF::itsTearOffMenu. ITearChore places the passed this into the instance variable = CTearChore::itsTearOffMenu.

AssignUrgentChore calls:

/* 27 */

 newTearChore->Perform();

which then calls:

/* 28 */

 newTearChore::itsTearOffMenu-> MoveToCorner();

CTearOffMenu::MoveToCorner eventually calls _MoveWindow, passing TRUE as the update parameter. An update Event occurs and guess what !!! Remember, thanks to CMovieApp::SetUpMenus, my window has been hiding in the weeds, i.e., off-screen. The “corner” the window is moved to is CTearOffMenu::corner, which is initialized to {0, 0} by ITearOffMenu. Given a tear-off as detected by ChooseItem (described above), CTearOffMenu::TornOff is called. Passed to TornOff is a single parameter = the mouse location in local Menu pane coordinates. The TCL has already setup the GrafPort so that its origin is the topLeft of the Menu. This is why when there is no tear off and the corner instance variable remains at its initialized value = {0, 0}, the Menu simply “plops down”. When there is a tear-off and TornOff is called, the passed corner changes with the mouse movement and is stored in CTearOffMenu::corner. When _MoveWindow is subsequently called, it’s passed the instance variable. As a direct result, the Tear-Off Menu follows your mouse as you drag.

Let’s start to pick up some loose pieces that we’ve only touched on so far.

The two remaining methods of your CApplication that you should override are DoCommand and DoKeyDown.

command numbers

I still need to talk about how tear-off Menus assign Command Numbers, but here is a kernel for this method so we can subsequently discuss Command Numbers in a logical manner:

/* 29 */

void  CMovieApp::DoCommand (long theCommand)
{
 GrafPtrsavePort;
 Str255 theDAName;

 if (theCommand < 0)
 {
 if (HiShort(-theCommand) == MENUapple)
 {
 /* BIG-time TCL boo-boo for not 
 ** saving & restoring Port:   */
 GetPort(&savePort);
 GetItem(GetMHandle(MENUapple), LoShort(-theCommand),
 theDAName);
 OpenDeskAcc(theDAName);
 SetPort(savePort);
 }
 else if (HiShort(-theCommand) == kTearMenu)
 {
 enum {
 OFF,
 ONApple = MENUapple
 };
 HiliteMenu(ONApple);
 DoCommand(cmdAbout);
 HiliteMenu(OFF);
 } 
 else
 inherited::DoCommand(theCommand);
 
 } /* end: theCommand < 0 */
 else /* theCommand > 0 */
 {
 switch (theCommand)
 {
 case cmdAbout:
 TRY
 {
 }
 CATCH
 {
 }
 ENDTRY;
 break; /* cmdAbout */
 
 // etc.

 default:
 
 inherited::DoCommand(theCommand); 
 } /* end: switch */
 } /* end: theCommand > 0 */
}/* DoCommand */

First, note that I have placed the test for a TOM Command Number in my application’s DoCommand method. Go back a page or so and review the fact that the supervisor of a TOM is your app (I pass this to ITearMenuDir within the CMovieApp’s SetUpMenus). Also recall that the superisor of my TOM’s main pane, my CGridSelector, is also my app. Notice that this Command Number is negative which unambiguously implies that it’s created “on-the-fly”. These are key points, so keep them safe.

Additionally see that within my app’s version of DoCommand, I only test for the high word of the Command Number, that is, the Menu ID. I am not interested in the low word = the Menu item because my CGridSelector has only one pane. If I had a Tear-Off Menu akin to MacPaint’s Pattern Menu I would have multiple grid elements whose Command Numbers were separated by one. As a direct result, I would then be concerned with the low word.

Two zillion words ago I stated that my CMovieApp::SetUpMenus called my ITearMenuDir method and that the latter called IGridSelector. Go back and review that my CGridSelector serves as my Tear-Off Menu’s main CPane when I call:

/* 30 */

 itsPane = myMenu;

IGridSelector, in turn, calls ISelector to place the passed Command Number base integer into CSelector::commandBase. For my simplistic example wherein my Tear-Off Menu has only one pane or grid element or Menu item, I simply pass the ID of my TOM.

Tear-Off the Menu ...

Assuming you tear-off the Menu then, as I’ve previously explained, CPaneMDEF’s TearOffMenu is dutifully called the latter eventually calls CTearOffMenu’s TornOff method to create a new urgent CTearChore and assign said CTearChore to gApplication by calling CApplication::AssignUrgentChore the latter calls CTearChore::Perform which, in turn, calls CTearOffMenu’s MoveToCorner the latter finally calls _MoveWindow to allow the Torn-Off Menu to follow your dragging motion.

Okay, I’m off

Given a tear-off, then we access our pane(s) or Menu item(s) by clicking on them just as if we were clicking on a pane of an ordinary, oh-hum window. CSwitchboard’s DoMouseDown effects the necessary processing by calling gDesktop->DispatchClick (remember, our gDesktop is really a CFWDesktop object). DispatchClick calls _FindWindow to determine where we clicked. Naturally, the returned partCode = inContent in which case CWindow::DispatchClick is called. The latter calls CView::DispatchClick to find out which sub-View has been hit via a call to CView::FindSubView. Given success, CView::DispatchClick calls the pertinent sub-View’s DoClick method. The pertinent sub-View is a CGridSelector object which inherits DoClick from its superclass = CSelector. The pertinent sub-View is my CGridSelector because within my ITearMenuDir I call:

/* 31 */

 myMenu->IGridSelector(...);

Because a CGridSelector (= myMenu) is a CPane, after several jumps up the inheritance chain, eventually IGridSelector calls IPaneX which calls:

/* 32 */

 itsEnclosure->AddSubView(this);

where the pane’s enclosure = our floating window and this = my CGridSelector. Eventually, the TCL calls CView::AddSubView which does exactly that add the sub-View to the CWindow’s CList instance variable = CView::itsSubViews.

I have chosen to “roll my own” DoClick method as follows:

/* 33 */

void  CCustomSelector::DoClick (Point hitPt,
 short modifierKeys, long when)
{
 short  itemHit, count;
 long   finalTicks;
 extern short  gClicks;
 
 itemHit = FindItem(hitPt);

 // Ignore all clicks beyond 2:
 if ( (itemHit != selection) &&
   (gClicks == 2) )
 {
 for (count = 2; count > 0; count--)
 {
 HiliteItem(itemHit, hiliteON);
 Delay(20, &finalTicks);
 HiliteItem(itemHit, hiliteOFF);
 Delay(20, &finalTicks);
 }
 itsSupervisor->DoCommand(-(((long)commandBase << 16 ) + 
 itemHit));
 }
}/* DoClick */

Notice that when we call IGridSelector from within ITearMenuDir, the CMovieApp is passed as the supervisor to handle the Commands that our CGridSelector ( = = CBureaucrat) cannot handle. So when DoClick calls:

/* 34 */

 itsSupervisor->DoCommand(...);

CMovieApp::DoCommand is really being called, another reason for testing for a Tear Menu Command Number within your CApplication’s DoCommand. Also, note that we negate the Command Number when we call DoCommand, thus creating the Command Number “on the fly”.

No tear-off

What about pulling down the Menu, but not tearing it off? CSelectorMDEF::ChooseItem calls CSelector’s special HiliteItem method and then stuffs the selected item number into the *whichItem passed by reference to ChooseItem by the Menu Manager just like it’s normally done. ChooseItem fills in the low memory long word global = MenuDisable and exits.

In this scenario, i.e., no tear-off, ChooseItem was originally called by _MenuSelect which returns a long word with the Menu ID in the High word and the Menu item in the low word. _MenuSelect is called from within CDesktop::DispatchClick. The latter continues by calling CBartender’s FindCmdNumber and passing the result to:

/* 35*/

 gGopher->DoCommand(cmdNbr);

FindCmdNumber, quite naturally, finds our Tear-Off Menu because we placed its ID in our 'MBAR' resource. However, since we did not place any Command Numbers in our Tear-Off 'MENU' resource, FindCmdNumber returns the negative of the Tear-Off Menu ID in the High word and the negative of the selected Menu item in the low word of the composite long word result. Just like a 'FONT' Menu or the Apple Menu, eh? So, once again, we have a negative Command Number just as we did when we executed CSelector’s DoClick method.

Whew I thought I was in trouble for a second

What’s the gGopher here?

Maybe you have just activated a oh-hum window supervised by a particular CDocument in which case the gGopher becomes that document (see my August, 1992 article). The TCL goes to yourDoc->DoCommand. If the TCL fails to find the appropriate Command Number there, the TCL calls:

/* 36 */

 inherited::DoCommand( );

Since a CDocument is a CDirector is a CDirectorOwner is a CBureaucrat, we wind our way up to CBureaucrat::DoCommand which then calls:

/* 37 */

 itsSupervisor->DoCommand( );

We are now looking at the supervisor of the CDocument and that is our app.

Okay that’s one down.

Maybe we have just torn off our Menu, so CTearOffMenu::MoveToCorner is eventually called as previously described. CWindow::Select is called after the window is moved to follow your mouse. When CWindow::Activate is subsequently called, CDirector::Activate ends up being called. The latter detects that we have a floating window, so CBureaucrat::BecomeGopher is not called. As a direct result, the gGopher stays put, remaining whatever it was as a result of our non-floating windows. So we end up going to CMovieApp::DoCommand just as we did before we tore-off our Menu.

HyperCard™ anyone

In order to fill in the details of showing/hiding my floating window via pressing <CMD-Tab>, let me present my app’s DoKeyDown method, explaining as I go:

/* 38 */

void  CMovieApp::DoKeyDown (char theChar, Byte keyCode, EventRecord *macEvent)
{
 enum {
 OFF,
 ONApple = MENUapple
 };
 long   finalTicks;
 WindowPeek tearPeek;
 Point  tlWind,
 defaultPt = {100, 100};
 Rect   desk;
 CWindow*theWindow;

 if (macEvent->modifiers & cmdKey)
 {
 if (theChar == '/')
 {
 HiliteMenu(ONApple);
 DoCommand(cmdAbout);
 HiliteMenu(OFF);
 } /* CMD-? */ 
 else if (keyCode == 0x0D)
 { /* = 'w', regardless
 ** of modifier key*/
 if (macEvent->modifiers & optionKey)
 { // = <CMD-Option> w
 theWindow = gDesktop-> GetTopWindow();
 while (theWindow != nil)
 {
 theWindow->Close();
 theWindow = gDesktop-> GetTopWindow();
 }
 }
 }

 // okay, now we get to my <CMD-Tab> stuff:
 else if (keyCode == 0x30 ||
 keyCode == 0x48)  // <Tab>
 {
 if (itsTearMenu->corner.h == 0 && 
  itsTearMenu->corner.v == 0)

Never torn-off before. The reason is that when ITearOffMenu is called by my ITearMenuDir, CTearOffMenu::corner is initialized to {0, 0} and does not change until we tear-off the Menu. Only then does CTearOffMenu::TornOff get called to create a new CTearChore. Subsequently, CTearOffMenu::MoveToCorner is called to alter the instance variable = corner. As a direct result, we must call the TornOff method directly to execute a tear-off:

/* 39 */
          
 itsTearMenu-> TornOff(defaultPt);
 
 else
 {
 // Toggle between show & hide:
 tearPeek = (WindowPeek) itsTearMenu->GetMacWindow();
 tlWind = topLeft( (**(tearPeek ->contRgn)).rgnBBox );
 ((CFWDesktop*)gDesktop)-> GetBounds(&desk);
 
 if (tlWind.v > desk.bottom && 
  tlWind.h > desk.right)

Torn-off before, BUT subsequently closed because CTearOffMenu::CloseWind calls CWindow::MoveOffScreen. Put the window back where it was BEFORE you closed it:

/* 40 */

 itsTearMenu-> TornOff(itsTearMenu->corner);
 else
 itsTearMenu-> CloseWind(itsTearMenu->itsWindow);
 }
 
 } /* CMD-Tab */
 } /* CMD-key pressed */
 else if (keyCode == KeyHelp)
 {
 HiliteMenu(ONApple);
 DoCommand(cmdAbout);
 HiliteMenu(OFF);
 } /* Help Key */
 else inherited::DoKeyDown(theChar, keyCode, macEvent);
}/* DoKeyDown */

Overidding CloseWind, inherited from CTearOffMenu’s superclass = CDirector, completes the discussion:

/* 41 */

void  CTearMenuDir::CloseWind (CWindow *theWindow)
{
 WindowPeek tearPeek;
 Point  tlWind;

If we insist on toggling the showing/hiding of the torn-off Menu via a keystroke, we canNOT save the current position of the associated window JUST in our DoKeyDown(...) method. For example, what if we move our window, close it by clicking in the goAway box and then hit <CMD-Tab> to re-show it. The window will be shown in its position prior to our move. We could do our saving within our application's Idle() method, but that consumes unnecessary time.

/* 42 */

 tearPeek = (WindowPeek) GetMacWindow();
 // corner in GLOBAL coordinates:
 tlWind = topLeft( (**(tearPeek-> contRgn)).rgnBBox );

 SetPt(&corner, tlWind.h, tlWind.v);

 inherited::CloseWind(theWindow);
}/* CloseWind */

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »
Explore some of BBCs' most iconic s...
Despite your personal opinion on the BBC at a managerial level, it is undeniable that it has overseen some fantastic British shows in the past, and now thanks to a partnership with Roblox, players will be able to interact with some of these... | Read more »

Price Scanner via MacPrices.net

You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more
Sunday Sale: 13-inch M3 MacBook Air for $999,...
Several Apple retailers have the new 13″ MacBook Air with an M3 CPU in stock and on sale today for only $999 in Midnight. These are the lowest prices currently available for new 13″ M3 MacBook Airs... Read more
Multiple Apple retailers are offering 13-inch...
Several Apple retailers have 13″ MacBook Airs with M2 CPUs in stock and on sale this weekend starting at only $849 in Space Gray, Silver, Starlight, and Midnight colors. These are the lowest prices... Read more
Roundup of Verizon’s April Apple iPhone Promo...
Verizon is offering a number of iPhone deals for the month of April. Switch, and open a new of service, and you can qualify for a free iPhone 15 or heavy monthly discounts on other models: – 128GB... Read more

Jobs Board

Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
Liquor Stock Clerk - S. *Apple* St. - Idaho...
Liquor Stock Clerk - S. Apple St. Boise Posting Begin Date: 2023/10/10 Posting End Date: 2024/10/14 Category: Retail Sub Category: Customer Service Work Type: Part Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.