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

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.