TweetFollow Us on Twitter

Compiler Knows
Volume Number:8
Issue Number:4
Column Tag:TCL Workshop

The Compiler Knows!!

Object Oriented Programming (OOP) with Symantec's THINK Class Library (TCL)

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

About the author

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

This article addresses Object Oriented Programming (OOP) as implemented by Symantec’s THINK Class Library (TCL). In order to construct my first comprehensive OOP-based application using the TCL, I was forced to plow through the TCL source code to determine not only what to do, but also the order in which to do it. This article describes the results of this labor by presenting this “ray tracing.”

In particular, I will describe how the TCL:

• creates and initializes an application object.

• creates and initializes the all-purpose Event Loop.

• starts up your application.

• runs and runs and runs and ... that Event Loop until you quit the application.

• creates the document object(s) that the application object supervises.

• creates MENUs.

• implements MENU commands such as “Save” and “Copy.”

• handles Desk Accessories.

• and assorted (?sordid?) other knick-knacks.

In some respects I’m trying to emulate two of my heroes. One is Sandy Mossberg who wrote for “Nibble” Magazine many years ago. Sandy patiently and methodically disassembled both DOS 3.3 and ProDOS of the Apple ][e world and then, line-by-line, guided you through their respective mazes. Another hero is Forrest Tanaka from MacDTS. He balances Macintosh bits and pixels. If only I could come remotely close to emulating the quality of their gymnastics.

Before I actually begin the “ray tracing” I promised you, let me praise the Technical Support folk at Symantec ... they actually didn’t yell at me for asking what I’m confident is a lot of stupid questions. After one of my many, many inquiries I made the mistake of asking “How does it know?” The response, “The COMPILER knows!”, is the cause for the sub-title of this series of articles.

Also, let me praise the TCL manual that accompanies either of their compilers. One, it’s short and well indexed. I do have just one serious suggestion for improvement of the indexing. I cannot count the number of times I have been plowing through the TCL source code and asked in what Class a given Instance Variable is defined, for example, “itsFile.” The index provided in the back of the TCL Manual itemizes all the Methods, but not the Variables. Symantec, please, please supply a separate Appendix that alphabetically lists all the Instance Variables; then, for each Variable, give the owning class, variable type and whether it is a private, a protected and/or a Class Variable. I have taken the liberty of developing my own and have provided it to “MacTutor” on disk ... trust me, it is absolutely invaluable !!! {both “MacTutor” and this disk file}

In addition I actually follow most of the manual. With respect to the choice word, “most”, I wish to loudly broadcast that there is absolutely no substitute for plowing through the TCL source code, all three notebooks of it. Although the TCL’s “Class Browser” is positively excellent, my old eyes cannot tolerate staring at the computer monitor all !?*! day. The source code is very well documented ... so give your printer some exercise <with apologies to the forest conservationists>.

Some OOP Properties

Those of you that have elicited the courage to dig into OOP realize that one of the five principal properties that makes OOP work is INHERITANCE, that is, any public object or class instance inherits all the public and protected instance variables and methods from its superclass. For example, a document (CDocument) inherits all the variables and methods of CBureaucrat via the following class hierarchy:

|CBureaucrat
|CDirectorowner
|CDirector
VCDocument

Similarly, a window (CWindow) inherits the same stuff from CBureaucrat via a slightly different hierarchy:

|CBureaucrat
|CView
VCWindow

We describe below how my application object is initialized by sending the message IApplication to this object. The expression StarterApp->IApplication means “Send the message IApplication to my application object StarterApp.” Another expression CApplication->IApplication means “Send the same message to the object CApplication.” This ability of sending the same message to different objects is because of another OOP principal = POLYMORPHISM. In similar fashion, any CWindow instance or object can be sent a CView message and any CDocument object can be sent a CDirector message.

In this case, the different objects are in the same hierarchy so we also invoke INHERITANCE. When we attempt to send the message IApplication to my application object, StarterApp, we find that its class does not have a message/method = IApplication. “The COMPILER knows!” So we go up the INHERITANCE chain to the superclass = CApplication and ‘hit pay dirt.’ “The COMPILER knows!” all about lookup tables.

Creating & Initializing the Application Object

I’ve got a lot more to say about documents and views; but before I do, let’s back up and start at the beginning. I define the beginning as the code that resides in your file usually named “main.c” or “start.c” or whatever:

/* 1 */

#include “CmyApp.h”
void  main (void){
 CStarterApp*StarterApp;
 StarterApp = new(CStarterApp);
 StarterApp->IApplication(kExtraMasters, kRainyDayFund,
 kCriticalBalance, kToolboxBalance);
 StarterApp->Run();
 StarterApp->Exit();
}/* main */

new() allocates memory for a new object which is a Handle if you have selected “Classes are indirect by default” as one of your options. After creating the application object, we initialize it, naturally.

Remember all that intro code you laboriously duped for every app you coded:

/* 2 */

InitGraf(&thePort);
InitFonts();
InitWindows();
etc. etc. etc.

Well, IApplication does all that stuff for you, plus a lot more:

• Calls InspectSystem() which determines your hardware & system configuration for you, for example,

/* 3 */

 error = SysEnvirons(SysEnvironsVersion, &theWorld);

TCL’s equivalent of the SysEnvRec, theWorld, is the TCL global constant of type, tSystem. This global is named “gSystem.” InspectSystem fills in all the fields of gSystem, e.g., gSystem.hasWNE and gSystem.hasColorQD.

• Makes a CDesktop object for you -- gotta have a desktop because that’s the beast that encloses all your blasted windows.

• Makes a CBartender object that implements the Menubar and all of its goodie ROM routines such as MenuKey, MenuSelect etc.

• Makes a CSwitchboard object for you and stores it in CApplication’s instance variable, “itsSwitchboard”. This contraption addresses, for example, the all-purpose Event Loop.

• Calls:

/* 4 */
            
gApplication = this;

where “this” is the object that was sent the IApplication message which just happens to be my StarterApp of class = CStarterApp. Note that “this” is of type or class = CApplication in general, and specifically of class = CStarterApp, a descendant of CApplication. Since we start our lookup process at the bottom of the INHERITANCE chain, we quit at the first successful bottommost point. So every time the TCL refers to gApplication, “The COMPILER knows!” it is referring to my CStarterApp object.

• etc ...

Starting Up

Where do you stuff things to display when your app initially starts up, such as a spiffy window that displays your company logo together with the version number of your software?

After IApplication finishes doing its thing, Run() is called within your main() routine. The very first thing Run() does is call Preload(). The latter takes care of opening and/or printing your app’s files that you double-clicked or that you selected to print from the Finder. After these chores are completed, CApplication::StartUpAction is called:

/* 5 */

CyourApp::StartUpAction (short numPreloads)  {
 DoSomethingSpiffyHere();
 inherited::StartUpAction(numPreloads);
}

Note that I sent the message = StartUpAction to your app’s superclass = CApplication after I executed my spiffy stuff. The reason for this order is that CApplication::StartUpAction calls _FlushEvents and, given System 6 and no files to open/print, immediately opens a blank or new document. So, if you reversed the above order, you’d open this new doc and then do your spiffy stuff. Although there’s nothing inherently wrong with this, I’d much rather look at any intro window, etc. first, before I stared at a blank window.

Runs and runs and

So, thanks to INHERITANCE, we’ve called our superclass’ Run() method. The latter has:

/* 6 */

 do{
 Process1Event();
 } while (running);

where “running” is set to true by IApplication. Process1Event, in turn, calls:

/* 7 */

 /* Stuff to determine if DA is active */
 /* +++++ */
 itsSwitchboard->ProcessEvent();

ProcessEvent, a method of CSwitchboard, calls:

!codeexamplee

nd/* 8 */

 /* isMyEvent = a local variable
 ** Ditto for macEvent
 */
 isMyEvent = GetAnEvent(&macEvent);
 if (isMyEvent)
 DispatchEvent(&macEvent);
 else DoIdle(&macEvent);


GetAnEvent calls either GetNextEvent or WaitNextEvent depending on whether gSystem.hasWNE is false or true, respectively. As I have previously explained, the .hasWNE and remaining fields of the struct = “tSystem” are quantified by CApplication::InspectSystem. Yes, we’re diddling with GetAnEvent within the file “CSwitchboard.c” but these fields are fields of a global, gSystem. If we got a non-null event, then DispatchEvent calls:

/* 9 */

switch (macEvent->what) {
 case mouseDown:
 DoMouseDown(macEvent);
 break;
 case mouseUp:
 DoMouseUp(macEvent);
 break;
 case keyDown:
 case keyUp:
 case autoKey:
 DoKeyEvent(macEvent);
 break;
 case activateEvt:
 if (macEvent->modifiers & activeFlag)
 DoActivate(macEvent);
 else DoDeActivate(macEvent);
 break;
 // et cetera
}
/* end switch */

In short, the Event Loop cycles or “runs on-and-on” within CApplication’s method = Process1Event() ... until, of course, the Boolean = running is set to false whereupon we fall through the “do-while” loop, exit Run() and finally call the method, Exit(). Later on I’ll show how CApplication::running is set = false.

Bye-Bye !!!

If our application has special handles etc to dispose of, then we should create:

/* 10 */

void  CStarterApp::Exit (void){
 // Do our stuff here, followed by
 // the standard stuff
 /* For now, inherited Exit is empty */
 inherited::Exit();
 }   /* Exit */

Note that we do not call _ExitToShell here. In effect, we ‘fall out the bottom’ of the TCL code at which point “The COMPILER knows!” to call _ExitToShell and adds two bytes = 0xA9F4 at the very end of the compiled object code.

The Gopher and things

CSwitchboard::DispatchEvent calls DoKeyEvent when you press “CMD-Q” which method sees that the cmdKey has been pressed, then calls MenuKey and eventually calls the gGopher’s “DoCommand” method. If a window is showing, the document that supervises this window is the Gopher. Alternately, the Gopher could be one of the window’s sub-views. The Gopher is the CBureaucrat object that is at the lowest end of the Chain of Command, the Buck Private so-to-speak. Given the absence of a document gGopher or any other user-specified gGopher, then the next gGopher up the Chain of Command is the application

AHHHH HAH !!

CApplication’s DoCommand method calls:

/* 11 */

switch (theCommand) {
 case cmdNew:
 SetCursor(*gWatchCursor);
 CreateDocument();
 break;
 case cmdOpen:
 // ...
 break;
 case cmdQuit:
 Quit();
 break;
 // et cetera
}/* end switch */

and the Quit method sets running = false, assuming you don’t change your mind via the usual alert that tells, for example, that you have not saved all your changed documents.

I suppose one of your docs could have a cmdQuit command that is tested for in its “DoCommand” method, thereby duplicating what CApplication’s “DoCommand” will eventually do ... but why??? The “eventually” addresses the fact that your doc’s “DoCommand” will call inherited::DoCommand if it does NOT have a cmdQuit. The “inherited” means CDocument::DoCommand. The latter does not have a cmdQuit either, so it calls CDirector::DoCommand which also fails to have a cmdQuit. So CBureaucrat::DoCommand gets called, bypassing CDirectorOwner::DoCommand because the latter does not exist. CBureaucrat::DoCommand then calls:

/* 12 */

itsSupervisor->DoCommand

The original contraption that began this chain of calls was your document, so “itsSupervisor” pertains to the doc. Since the supervisor of a doc is the application, your app’s “DoCommand” gets called. Because your app does not need to test for cmdQuit, your default case within your switch loop reads:

/* 13 */

inherited::DoCommand(cmdNbr);

So CApplication::DoCommand gets called. Q.E.D.

Before any of you readers get into a hiss, I’ll talk about command numbers and things at the end of this article.

Finally, we’re outta here !! that is, back to the Finder.

Documents, Supervisors and “itsGopher”

A couple of dozen words ago, I started talking about documents, supervisors, Gophers and things. Let’s create an instance of a sub-class of CDocument. We do not create a new (CDocument) because CDocument is an abstract class. What we do create is an instance of a sub-class of CDocument which I’ll call CMeter. I’ll present more horrifying details later, but for the moment let’s pretend that our contraption looks like this:

/* 14 */

struct CMeter : CDocument {
 void IMeter (...)
 /* etc */
};
struct ColePane : CPane {
 void IPane (...)
 void Draw (...)
 /* etc */
};
struct CBasement : ColePane    {
 void IPane (...)
 void Draw (...)
 /* etc */
};

We are in our application’s “DoCommand” method when we call:

/* 15 */

case cmdProgress:
 myMeter = new (CMeter);
 myMeter->IMeter(..., this);
 /* etc */
 break;

Here, “this” = the object that was sent “DoCommand”, our application. “this” is the supervisor of the meter doc since an application always supervises its documents. The first thing that IMeter should do is to init its superclass by calling inherited::IDocument which translates “Send the message = IDocument to the meter’s superclass”. CDocument::IDocument calls IDirector to init CDocument’s superclass ... init the first floor, init the second floor, init the third, etc. IDirector eventually calls:

/* 16 */

itsGopher = this;

where “this” is the original object or instance that was sent the IDirector message to begin with. Here, the message of interest is IDirector because we’re inside IDirector when we use the keyword “this”. However, the original receiver of a message that resulted in IDirector getting sent was my meter. Without sending IMeter, IDirector would not have been sent and the object receiving IMeter was my doc. Remember, the meter sent the doc sent the director, so the “this” is the meter. Since a meter is a document, the “this” is really the document. By the way, “itsGopher” is an instance variable of CDirector and, therefore, CMeter inherits it:

 | CDirector::itsGopher
 | CDocument
 V CMeter

“gGopher”, Windows and Sub-Views

But so far, all we’ve filled-in is an instance variable = itsGopher ... what about the global = gGopher?

• Within our IMeter:

/* 17 */

 itsWindow = new (CWindow);
 /* Remember, a document will inherit
 ** CDirector::itsWindow    */
 itsWindow->IWindow(yourWINDid, false, gDesktop, this);
 olePane = new (ColePane);
 olePane->IPane (itsWindow = the enclosure, this = the supervisor, ...);
 itsMainPane = olePane;

 itsWindow->Select();

Well, it appears we finally got a window. Earlier I stated that a document supervises a window. We can see how this happens by examining what IWindow does:

• yourWINDid is the ID of your attached ‘WIND’ resource that describes this window, so IWindow calls GetResource('WIND', yourWINDid). If you wish to create a new window ‘on the fly’, call:

/* 18 */

itsWindow->INewWindow(...);

instead which will call _NewWindow or _NewCWindow depending on whether gSystem.hasColorQD is false or true, respectively. Given an attached 'WIND' resource, IWindow calls either _GetNewWindow or _GetNewCWindow. Whether you create the window from a 'WIND' resource or ‘on the fly’, the resulting port is stored in CView::macPort (= GrafPtr = WindowPtr). Remember a CDocument inherits the instance variables of CDirector, including “itsWindow”.

• A routine called by both IWindow and INewWindow is IWindowX. The latter calls:

/* 19 */

SetWRefCon(macPort, (long) this);

“this” is the object that was sent the message = IWindowX and said object is our CWindow = itsWindow. Therefore, if we’ve got a pane or sub-pane of a window out there, all we need to call is:

/* 20 */

ourCWindow = ourCPane->GetWindow();

which method simply calls the companion ROM routine = _GetWRefCon. So every time we create a CWindow, we stuff its object reference in the window record’s refCon field. Retrieving the window pointer itself is equally easy:

      /* 21 */

ourWPtr = ourCPane->GetMacPort();

Since a pane is a view, this calls CView::GetMacPort which simply returns CView’s instance variable = macPort.

• the Boolean parm tpassed to IWindow ells the TCL whether this dude is a floating window or not and is stored in CWindow::floating.

• next comes the window’s enclosure. Both IWindow and INewWindow call IView which fills in CView::itsEnclosure, in this case with gDesktop. The desktop is the top-most enclosure of all windows.

• finally, the last parameter passed to IWindow is the window’s supervisor of class CDirector. Before IView finishes, it initializes its superclass by calling CBureaucrat::IBureaucrat which sets CBureaucrat::itsSupervisor to our passed “this”. Once again, “this” appears within CMeter::IMeter so “this” = the object that was sent the message IMeter which just happens to be my document. Remember ... a CDocument descends from CDirector which is a descendant of CBureaucrat. The TCL manual explains that a “director is a bureaucrat that supervises a window”. I guess that must be right because a document is a director is a bureaucrat and we just finished stuffing our doc into “itsSupervisor”. Remember ... the “it” of “itsSupervisor” is our window since a window is what gets supervised here.

Like CDocument, CPane is an abstract class so we create an instance of a sub-class of CPane by calling new(CmyPane) instead of new(CPane).

IPane is passed an enclosure = the window and a supervisor = this where “this” is !!! once again!!! referenced within the message routine = IMeter and we already know a meter is a document. The object that was sent the message of IMeter is the meter or document. IPane calls IView which fills in CView::itsEnclosure = itsWindow since CWindow is a descendant of CView. In turn, IView calls IBureaucrat which fills in CBureaucrat::itsSupervisor = this(doc) since we passed “this” to IPane from within IMeter.

What if you had then called before Select():

/* 22 */

 basement = new (CBasement);
 basement->IPane(olePane, this, ...);
 /* and on and on and on and on...*/

The “this” demonstrates that the supervisor of all panes and sub-panes is the document. Make that “all views and sub-views” because CPane descends from CView. What’s happening here?

Once again, “this” appears within CMeter::IMeter so “this” = my document. IPane calls IView which eventually sets up CView::itsEnclosure and CBureaucrat::itsSupervisor as I have ‘pane’fully described (Good Grief !!!, John). IPane continues and eventually calls IPaneX() which calls:

/* 23 */

itsEnclosure->AddSubView(this);

This “this” appears within CPane::IPaneX() so the contraption that was sent the FIRST message = IPaneX was “olePane”. Therefore, “this” = “olePane”. So ... itsWindow encloses “olePane”. “olePane” encloses “basement” after the second call to IPane. The top view is “olePane” and a sub-view is “basement”.

What does CView::AddSubView(CView *theSubView) do? The very first time IView was called, CView::itsSubViews, type = CList, was set = NULL so AddSubView then initialized this CList by calling IList. AddSubView followed that with:

/* 24 */

// TCL passes “this” as theSubView:
itsSubViews->Add(theSubView);

So, the 1st item in the itsSubViews CList is “olePane” and the 2nd item becomes “basement” when IPane is called again.

Window Activation and “gGopher”

CWindow’s method = Select calls ROM’s _SelectWindow which obviously causes the usual Activate Event. The Event Loop running around deep within the method = Run() will call CSwitchboard::DispatchEvent which calls

CSwitchboard::DoActivate which eventually calls

/* 25 */

 CWindow::Activate.

The latter calls ((CDirector*)itsSupervisor):: ActivateWind(this) where “this” is always the beast that was sent the original message. The original message was Select which was sent to “itsWindow” within IMeter. Go back a page and verify this if you wish. So, this “this” is my window.

Anyway, CDirector::ActivateWind continues by calling

/* 26 */

CBureaucrat::BecomeGopher(true);

which then sets:

/* 27 */

gGopher = this;

Hey Forrest ...watch this juggling act !!!!!

This “this” is not the “this” passed to ActivateWind which was my window. Since any “this” is the beast that was sent the message and since the message of interest here is ActivateWind, who in blazes was sent ActivateWind:

((CDirector*)itsSupervisor

that’s WHO!!! And what is “itsSupervisor”?

MY DOCUMENT !!!!!

SO ... my doc is the gGopher after my window is activated.

!!!!! THANK GOODNESS !!!!!

because now:

CBureaucrat::itsGopher matches gGopher.

More DoCommand

A few zillion words ago I addressed the message “DoCommand”, which is an instance method of the following classes:

|CApplication
|CBureaucrat
|CDirector
VCDocument

It is also an instance method of CAbstractText which addresses _TECut, _TECopy etc. Since CAbstractText descends from CPanorama and since panoramas are a separate subject unto themselves, I will address just the four listed above.

Remember!! the highest possible Gopher that can handle a command is your application and if it cannot, then the command gets ‘washed out to sea’.

Every object’s “DoCommand” should address the commands unique to it. When you Quit, what are you quitting ... your application. When you open a document, the document does not exist in memory yet. What supervises a document ... the application ... so your CyourApp class should address the cmdOpen command within

/* 28 */

CyourApp::DoCommand(long cmdNbr).

After your doc has been opened and you then wish to close it, why not just call:

/* 29 */

this->Close(...);

within CyourDoc::DoCommand? Seems logical enough, especially since I’ve just ‘pane’fully {not again!!!} described and re-described that “this” is your blasted document!!

Now ... when you quit your application you call CyourApp::Quit() which, in turn, calls CApplication::Quit(). I do NOT wish to imply that you should override CApplication::Quit. This is not necessary. As I mentioned almost at the beginning of this article, any unique Handles, etc. that need to be disposed of should be addressed within CyourApp::Exit(). There is a CyourApp::Quit, however, even if you did not write one. The reason is that the real call effectively is:

/* 30 */

gApplication->Quit();

and gApplication = CyourApp after CyourApp::IApplication gets through with:

/* 31 */

// See beginning of article:
gApplication = this;

Remember that there is one and only one application object floating around in the foreground at a given time and it’s stored within gApplication. Since CyourApp->Quit does not and should not exist in your source, its superclass’ CApplication::Quit is called.

CApplication::Quit starts closing (translate: “send cmdClose command to”) all the open windows, then the supervisors of the open windows, that is, all the open documents. When all these are closed, there are no more. So, the cmdClose command propagates up to the supervisor of CDocument which is CApplication.

/* 32 */

CApplication::DoCommand (cmdClose)

closes any frontmost DA that is open. Simple, ain’t it ?!*!?

It’s interesting that if there are DA windows percolating behind our app’s windows, they do NOT get closed. The same sort of thing holds true if, for example, two DAs are on top ... the second one remains open.

MENUs and Commands

I’ve thrown around command numbers like confetti. What is this contraption? It’s like and un-like the long integer returned by _MenuSelect. They’re long integers appended to your 'MENU' item names in your attached resource file, e.g.:

/* 33 */

resource  'MENU'   (mFile){
 mFile,
 ...,
 "File",
 {
 /* [1] */
 "Making Progress#1024", noIcon, noKey, noMark, bold;
 ...;
 }
};

You can also create some Menus ‘on-the-fly’; however, Symantec’s TCL Manual does a superb job explaining this option ... so get cracking!!!

The TCL loads all your MENUs stored in your 'MBAR' resource. The default 'MBAR' within Symantec’s “Starter.Π.rsrc” reads:

/* 34 */

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

The loading is accomplished when CApplication::IApplication(...) calls CApplication::SetupMenus(). The latter calls CApplication::MakeBartender() which calls CBartender::IBartender(MBARapp) where MBARapp = 1 within TCL’s <Constants.h> interface file. Since you’ll probably wish to have your own MENUs, try this in your “SARez” resource description file:

/* 35 */

 include "Starter.Π.rsrc";

You gotta include all the above standard stuff that TCL expects such as ALRTs and their associated DITLs. But then when you set up your SARez Options file, be certain to choose “Write output to a new file...” as the option for the Resource Output File popup menu and also check the box = “Merge Resources into Resource File”. In this manner, even if the IDs of your own 'MBAR' or 'MENU' resources conflict with those in the above include file, yours will take precedence and overwrite the duplicates. I suppose you could change the value of MBARapp in a #define statement placed within your “CyourApp.h” file, but then you’d have to match this change in your SARez “.r” file. But why ????? go to all this trouble when an ID = 1 is as good as any other number.

Getting back to IBartender(MBARid), it calls GetResource('MBAR', MBARid), gets the MENU count from the leading integer of the ID array that forms the total content of an 'MBAR' resource and then cycles through each ID by calling GetMenu(ID). For each cycle, IBartender fills in every field of a special MenuEntry record which is as follows:

/* 36 */

typedef  struct  {
 short  MENUid;
 MenuHandle macMenu;
 DimOptiondimming;
 Booleanunchecking : 1;
 BooleanhasHMenus  : 1;
 BooleaninMenuBar  : 1;
 BooleanlastEnable : 1;
 short  numCmds;
 long **theCommands;
} MenuEntry, *MenuEntryP, **MenuEntryH;

One of CBartender’s instance variables is “numMenus” which is the total number of Menu IDs = the above leading integer and another is “theMenus” of type MenuEntryH. IBartender creates a _NewHandle with

/* 37 */

size = numMenus*sizeof(MenuEntry);

and stores this Handle in CBartender::theMenus. As we cycle through each ID, TCL places each 'MBAR'’s 'MENU' ID in the MENUid field of “theMenus” handle, calls _GetMenu & places the result in macMenu, sets dimming = dimALL and fills in all the remaining fields except numCmds.

Disable, then Enable

Back to IBartender in a moment ... in the meantime the user clicks the mouse in the menubar. Your mouse click weaves its way down to CSwitchboard::GetAnEvent and onto CSwitchboard::DoMouseDown which then calls:

/* 38*/

 gDesktop->DispatchClick(macEvent);

Since we’re in the menubar, we then call:

/* 39*/

 CBartender::UpdateAllMenus();

which then sets the enableFlags of each MenuHandle:

/* 40 */

 (*theMenus)->macMenu

Within IBartender the TCL set the dimming field of each MenuEntry’s record = dimAll, so UpdateAllMenus sets the above enableFlags =

/* 41 */

 'MENU' resource’s enableFlags & 0x00000001

which disables all MENU items. So when you initially pull down a Menu, all items are immediately disabled. Then, UpdateAllMenus calls:

/* 42 */

 gGopher->UpdateMenus();

Given an open window, the gGopher = your CDocument so the TCL calls;

/* 43 */

 CyourDoc->UpdateMenus();

YOUR UpdateMenus() should first: call:

/* 44 */

 inherited::UpdateMenus();

to address the standard stuff and follow with selected enabling of the appropriate Menu items via:

/* 45 */

 gBartender->EnableCmd(yourSpecialCmdNbr);

Keep in mind that your doc’s UpdateMenus should not handle all commands, e.g., cmdQuit, so the TCL travels up the chain of command to the supervisor of CyourDoc = CyourApp. Personally speaking I prefer the Edit Menu items to be disabled when there is no window present. Furthermore, how can I close a non-existent window?

The TCL handles both of these scenarios for you. CDocument::UpdateMenus eventually winds its way up to CDirector::UpdateMenus which will enable cmdClose only if there’s a window showing. If you have a text pane as a sub-view to your window, the text pane’s UpdateMenus method was actually called first, way before CDocument::UpdateMenus. All text panes are sub to CAbstractText and the latter’s “UpdateMenus” will enable the Edit Menu items as is appropriate; for example, if some text is copied to the Clipboard, then “Paste” is enabled.

Eventually your MENU Command will wind its way up to your application object if none of your CViews or CDocuments handle it. So I just have to write:

/* 46 */

void  CStarterApp::UpdateMenus (void)  {
 
#define mySpecialCmd 1024

 // Enable standard commands.
 inherited::UpdateMenus();    
 if (!gInBackground)
 gBartender->EnableCmd(mySpecialCmd);
}/* UpdateMenus */

When CSwitchboard::DispatchEvent detects a Suspend event under Multifinder, we wind our way down to CApplication::Suspend where gInBackground is set = TRUE. Of course, the converse is true for a Resume event. NOT!!! If a DA is in front upon resuming, gInBackground remains true as it should, given a Multifinder OSEvt.

Desk Accessories and MENUs

The TCL uses two CApplication methods/messages = SwitchFromDA and SwitchToDA which call Resume() and Suspend() respectively. These Switch routines are called by CApplication::Process1Event which is the heart of the Event or “do-while” loop within Run(). Before itsSwitchboard->ProcessEvent is called by Process1Event, the TCL tests for a DA window being in front. Afterwards, Process1Event tests again for a DA now being active. If a DA was not up previously, but now is, then SwitchToDA is called. Conversely, if a DA was up previously, but now is not, then SwitchFromDA is called. If the before and after states are identical, then nothing is called.

One reason for these Switch routines is to provide the programmer an opportunity to disable whole Menus by calling:

/* 47 */

gBartender->DisableMenu(yourID);

The programmer would continue to disable individual Menu items ONLY within CyourApp::UpdateMenus.

These Switch methods then continue with disabling pending activate/deactivate events by setting the Toolbox globals CurActivate/CurDeactive = NULL. Said disabling is done because the above-mentioned ProcessEvent sets up the main Event loop and the latter has already addressed activate/deactivate events for you and you do not want to duplicate events.

A Soapbox

Speaking of duplication ... SwitchTo/From DA then continues and calls Suspend/Resume and these toggle the TCL global gInBackground accordingly. This is where the real rub enters because they are called after ProcessEvent finishes with the GetNextEvent/WaitNextEvent stuff. If you already had a suspend/resume event addressed by the main Event loop, the call to Suspend/Resume by the Switch methods would be the second time they would have been called. Effectively, these Switch routines are being called in a pseudo-Idle() loop because they’re being called within Process1Event.

What happens when you’re operating under System 6 and you’ve activated a DA while holding down the <Option> key under Multifinder or are operating under just plain Finder period? Under this scenario, calling one of the above Switch DA routines toggles gInBackground which nullifies the standard interpretation of the latter for exclusive operation under Multifinder. Given just Finder or the <Option> keypress under Multifinder, your application is STILL in the foreground and there is no suspension of anything; there is only the usual deactivate/activate event pair which the main Event loop has already taken care of.

I’ll grant you that this is just an interpretation and is definitely not set in concrete. TCL’s different interpretation is definitely not wrong by any means. My focus here is to just support consistency and nothing golden. If you wish to return to the older interpretation, the solution is to isolate the use of these Switch routines to just disabling whole Menus; otherwise these methods should be empty. In short, just override these Switch methods.

In my travels through the TCL source, I have discovered and reported to Symantec a boo-boo with the handling of DAs while operating just the Finder with System 6 and earlier. The problem surfaces upon activating a DA in this scenario; namely, the DA does NOT respond to mouse clicks when either in front of your app’s window or in back. Symantec has confirmed this observation and will correct it ASAP.

How to Defeat Disabling

Okay, now I’m off my soapbox, so let’s press on. If I have absolutely NO reason whatsoever to want my special Menu item to be disabled even if a DA is up, why disable it to begin with and have to waste subsequent source code to immediately enable it? This makes sense, so how do I prevent the Bartender from disabling the blasted item to begin with? The answer is that within your CyourApp::SetupMenus method you call:

/* 48 */

CBartender::SetDimOption(MENUid, aDimOption);

with the passed aDimOption = dimNONE or dimSOME. In this manner, CBartender::UpdateAllMenus will either not dim at all or just selectively dim.

Note within CStarterApp::UpdateMenus above that my command number = 1024 which is the lowest command number that my app can use since 1-1023 are reserved by TCL.

Extracting Command Numbers

Please go back to my before soapbox discussion of IBartender and the TCL record or struct = MenuEntry. With each cycle through the 'MBAR' record of Menu IDs, IBartender parses the name of each and every item by calling CBartender::ExtractCommands, looking for the first ‘#’ which serves as the delimiter between the item’s text and the actual command number in your 'MENU' resource. This CMD_DELIMITER is a constant character in TCL’s <Constants.h> interface file so, in theory, you can change it with an appropriately placed #define statement, but why??? The very simple reason is that the TCL assumes this CMD_DELIMITER is not in your real item name and, therefore, serves only to delimit. If you absolutely insist on having a ‘#’ as part of one or more Menu item names, then you must change CMD_DELIMITER.

Anyway, said parsing is accomplished when ExtractCommands calls CBartender::ParseItemString to separate out the Menu item string and store the result in:

/* 49 */

 (*macMenu)->menuData

via a call to _SetItem. The actual command number that follows the CMD_DELIMITER in your 'MENU' resource is stored in the Handle component of MenuEntry = “theCommands” and ditto for each and every Menu item in succession. Only one “theCommands” Handle is created for each Menu. So there is one MenuEntry for each Menu and each Menu’s “theCommands” ends up being a concatenated string of command numbers, one for each item. Each command number occupies 4 bytes because its maximum value is 65535 and we can get the number of command numbers stored in “theCommands” via a call to _CountMItems. In short, it is not necessary that “theCommands” be an array with the usual leading count integer.

SO ... our command numbers do not appear in our pulled-down Menu because we’ve removed them (not from the 'MENU' resource) by calling SetItem to change the item names. Remember ... we have not yet called _MenuSelect, one of whose missions in life is to draw the pull-down Menu. We also now have a copy of our command numbers in our Handle, “theCommands”.

Now what ??? At this juncture, I’m going to focus on the straightforward case of positive command numbers and let you folks read all about creating Menus ‘on the fly’ in the TCL Manual. Way back I stated that gDesktop-> DispatchClick was called upon clicking in the Menubar whereupon UpdateAllMenus was called to initially dim or disable all Menu items. The latter then called:

/* 50 */

gGopher->UpdateMenus();

to selectively re-enable the items appropriate to the Gopher. DispatchClick then calls _MenuSelect and separates out the Menu ID and the item # from the long word result of _MenuSelect. Said ID and item # are then passed to CBartender::FindCmdNumber which scans CBartender::theMenus (type = MenuEntryH) to find the command number corresponding to the passed Menu ID and item #.

WALLAH!!!

DispatchClick then sends:

/* 51 */

gGopher->DoCommand(yourCmdNbr);

and we’re done.

The next question that needs answering is what happens if FindCmdNumber cannot find a command number based on the passed Menu ID and item # parsed from the long result from _MenuSelect. The answer is that FindCmdNumber returns cmdNull if it’s a matter of not finding the passed ID. I’ll permit you kind readers an opportunity to scan the TCL source to investigate other possible results passed back by FindCmdNumber. Obviously 0 will not match any of your application’s command numbers. Now we’re ready for another soapbox!!

Balloon Help MENU

Quite naturally you do not place the ID of the Balloon Help MENU in your 'MBAR' resource. Furthermore, I submit that you must NOT do so because IBartender uses _GetMenu to retrieve the appropriate Menu Handle. Inside Macintosh, Vol. 6, stipulates that to retrieve the Balloon Help MenuHandle, use HMGetHelpMenuHandle. As a direct result of this void, FindCmdNumber will dutifully return a result = cmdNull. I’ve attempted many approaches to this challenge such as calling CBartender::InsertMenuCmd, but to no avail. My last resort was to create my own CBartender object and override FindCmdNumber in this fashion:

/* 52 */

struct CmyApp : CApplication   {
 void MakeBartender (void);
 /* etc */
}
struct CmyBar : CBartender   {
 long FindCmdNumber (short MENUid, short itemNo);
};

void CmyApp::MakeBartender (void)  {

 gBartender = new (CmyBar);
 gBartender->IBartender (MBARapp);
}

long  CmyBar::FindCmdNumber (short MENUid, short itemNo)  {

 long result;

 result = inherited:: FindCmdNumber(MENUid, itemNo);
 if (result == cmdNull && MENUid == kHMHelpMenuID)
 /* Since kHMHelpMenuID is already
 ** negative, do as _MenuSelect:  */
 result = ( (long)MENUid << 16 ) + itemNo;

 return (result);

}/* FindCmdNumber */

This definitely works because then all I have to do in my CmyDoc’s or my CmyApp’s DoCommand is this:

/* 53 */

void  CStarterApp::DoCommand (long theCommand)     {

 /* Although the Help Manager adds not
 ** only your item but a disabled dotted
 ** line before your addition,  we do NOT
 ** add 1 and make it 4, for example?? */

 #definemyItem   3 // below std 2

 if (theCommand < 0) {
 /* For the Help MENU, theCommand is
 **negative because the menu id in its
 ** High word is negative (= -16490). */

 switch (HiShort(theCommand)) {
 
 case kHMHelpMenuID:
 if (LoShort(theCommand) == myItem)   {
 TRY
 {
 }
 CATCH
 {
 }
 ENDTRY;
 }
 break;

 /* etc */
 
 } /* end: switch */
 } /* end: theCommand < 0 */
 
 else { /* theCommand > 0 */
 } /* end: theCommand > 0 */
 
}/* DoCommand */

Not only does it look simple, folks, but it really works. But I confess that I am not at all pleased with it academically because I cannot believe Symantec overlooked the Balloon Help Menu ... I simply have not yet figured out how to handle the latter.

The MacTutor Disk

Don’t forget -- I’ve put on the “MacTutor” disk a file that alphabetically lists all the TCL <v 1.1.2> Instance variables ... plus lots of source code that addresses:

• off-screen bitmaps & pixmaps (read color)

• floating windows & palettes

• scrolling PICTures

• Balloon Help {when you activate my special item under the Help Menu, you’ll see a scrolling PICTure. Click the mouse to see it change direction. Press <any key> to dispose of the window}

• tear-off Menus. “Wanna blow your mind??” -- listen up!!! I’ve gone to a great deal of trouble explaining why the TCL Manual stipulates that “a director is a bureaucrat that supervises a window”. A tear-off Menu, Class = CTearOffMenu, supervises the torn-off window. Well ... at least that is consistent since CTearOffMenu descends from CDirector. But there’s more ... way, way above I explained that a document supervises all panes or sub-views of a window. The supervisor of the pane of a torn-off Menu or window is your application.

Back to the Future

Well, it looks like I’ve got the meat for my next article. I am almost finished with the development of a new class = CQuickTime. There will be a quiz tomorrow morning on what that class addresses ... and maybe even an article.

In long, I’m done for now -- see the little guy -->

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Capture One 11.0.1.40 - RAW workflow sof...
Capture One is a professional RAW converter offering you ultimate image quality with accurate colors and incredible detail from more than 400 high-end cameras -- straight out of the box. It offers... Read more
Capture One 11.0.1.40 - RAW workflow sof...
Capture One is a professional RAW converter offering you ultimate image quality with accurate colors and incredible detail from more than 400 high-end cameras -- straight out of the box. It offers... Read more
GraphicConverter 10.5.4 - $39.95
GraphicConverter is an all-purpose image-editing program that can import 200 different graphic-based formats, edit the image, and export it to any of 80 available file formats. The high-end editing... Read more
Dash 4.1.3 - Instant search and offline...
Dash is an API documentation browser and code snippet manager. Dash helps you store snippets of code, as well as instantly search and browse documentation for almost any API you might use (for a full... Read more
Microsoft OneNote 16.9 - Free digital no...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more
DEVONthink Pro 2.9.17 - Knowledge base,...
Save 10% with our exclusive coupon code: MACUPDATE10 DEVONthink Pro is your essential assistant for today's world, where almost everything is digital. From shopping receipts to important research... Read more
OmniGraffle 7.6 - Create diagrams, flow...
OmniGraffle helps you draw beautiful diagrams, family trees, flow charts, org charts, layouts, and (mathematically speaking) any other directed or non-directed graphs. We've had people use Graffle to... Read more
iFinance 4.3.7 - Comprehensively manage...
iFinance allows you to keep track of your income and spending -- from your lunchbreak coffee to your new car -- in the most convenient and fastest way. Clearly arranged transaction lists of all your... Read more
Opera 50.0.2762.58 - High-performance We...
Opera is a fast and secure browser trusted by millions of users. With the intuitive interface, Speed Dial and visual bookmarks for organizing favorite sites, news feature with fresh, relevant content... Read more
Microsoft Office 2016 16.9 - Popular pro...
Microsoft Office 2016 - Unmistakably Office, designed for Mac. The new versions of Word, Excel, PowerPoint, Outlook and OneNote provide the best of both worlds for Mac users - the familiar Office... Read more

Latest Forum Discussions

See All

Around the Empire: What have you missed...
Around this time every week we're going to have a look at the comings and goings on the other sites in Steel Media's pocket-gaming empire. We'll round up the very best content you might have missed, so you're always going to be up to date with the... | Read more »
Everything about Hero Academy 2: Part 4...
In this part of our Hero Academy 2 guide, we're going to have a look at some of the tactics you're going to need to learn if you want to rise up the ranks. We're going to start off slow, then get more advanced in the next section. [Read more] | Read more »
All the best games on sale for iPhone an...
Another week has flown by. Sometimes it feels like the only truly unstoppable thing is time. Time will make dust of us all. But before it does, we should probably play as many awesome mobile videogames as we can. Am I right, or am I right? [Read... | Read more »
The 7 best games that came out for iPhon...
Well, it's that time of the week. You know what I mean. You know exactly what I mean. It's the time of the week when we take a look at the best games that have landed on the App Store over the past seven days. And there are some real doozies here... | Read more »
Popular MMO Strategy game Lords Mobile i...
Delve into the crowded halls of the Play Store and you’ll find mobile fantasy strategy MMOs-a-plenty. One that’s kicking off the new year in style however is IGG’s Lords Mobile, which has beaten out the fierce competition to receive Google Play’s... | Read more »
Blocky Racing is a funky and fresh new k...
Blocky Racing has zoomed onto the App Store and Google Play this week, bringing with it plenty of classic kart racing shenanigans that will take you straight back to your childhood. If you’ve found yourself hooked on games like Mario Kart or Crash... | Read more »
Cytus II (Games)
Cytus II 1.0.1 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0.1 (iTunes) Description: "Cytus II" is a music rhythm game created by Rayark Games. It's our fourth rhythm game title, following the footsteps of three... | Read more »
JYDGE (Games)
JYDGE 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: Build your JYDGE. Enter Edenbyrg. Get out alive. JYDGE is a lawful but awful roguehate top-down shooter where you get to build your... | Read more »
Tako Bubble guide - Tips and Tricks to S...
Tako Bubble is a pretty simple and fun puzzler, but the game can get downright devious with its puzzle design. If you insist on not paying for the game and want to manage your lives appropriately, check out these tips so you can avoid getting... | Read more »
Everything about Hero Academy 2 - The co...
It's fair to say we've spent a good deal of time on Hero Academy 2. So much so, that we think we're probably in a really good place to give you some advice about how to get the most out of the game. And in this guide, that's exactly what you're... | Read more »

Price Scanner via MacPrices.net

Deals on clearance 15″ Apple MacBook Pros wit...
B&H Photo has clearance 2016 15″ MacBook Pros available for up to $800 off original MSRP. Shipping is free, and B&H charges NY & NJ sales tax only: – 15″ 2.7GHz Touch Bar MacBook Pro... Read more
Apple restocked Certified Refurbished 13″ Mac...
Apple has restocked a full line of Certified Refurbished 2017 13″ MacBook Airs starting at $849. An Apple one-year warranty is included with each MacBook, and shipping is free: – 13″ 1.8GHz/8GB/128GB... Read more
How to find the lowest prices on 2017 Apple M...
Apple has Certified Refurbished 13″ and 15″ 2017 MacBook Pros available for $200 to $420 off the cost of new models. Apple’s refurbished prices are the lowest available for each model from any... Read more
The lowest prices anywhere on Apple 12″ MacBo...
Apple has Certified Refurbished 2017 12″ Retina MacBooks available for $200-$240 off the cost of new models. Apple will include a standard one-year warranty with each MacBook, and shipping is free.... Read more
Apple now offering a full line of Certified R...
Apple is now offering Certified Refurbished 2017 10″ and 12″ iPad Pros for $100-$190 off MSRP, depending on the model. An Apple one-year warranty is included with each model, and shipping is free: –... Read more
27″ iMacs on sale for $100-$130 off MSRP, pay...
B&H Photo has 27″ iMacs on sale for $100-$130 off MSRP. Shipping is free, and B&H charges sales tax for NY & NJ residents only: – 27″ 3.8GHz iMac (MNED2LL/A): $2199 $100 off MSRP – 27″ 3.... Read more
2.8GHz Mac mini on sale for $899, $100 off MS...
B&H Photo has the 2.8GHz Mac mini (model number MGEQ2LL/A) on sale for $899 including free shipping plus NY & NJ sales tax only. Their price is $100 off MSRP. Read more
Apple offers Certified Refurbished iPad minis...
Apple has Certified Refurbished 128GB iPad minis available today for $339 including free shipping. Apple’s standard one-year warranty is included. Their price is $60 off MSRP. Read more
Amazon offers 13″ 256GB MacBook Air for $1049...
Amazon has the 13″ 1.8GHz/256B #Apple #MacBook Air on sale today for $150 off MSRP including free shipping: – 13″ 1.8GHz/256GB MacBook Air (MQD42LL/A): $1049.99, $150 off MSRP Read more
9.7-inch 2017 WiFi iPads on sale starting at...
B&H Photo has 9.7″ 2017 WiFi #Apple #iPads on sale for $30 off MSRP for a limited time. Shipping is free, and pay sales tax in NY & NJ only: – 32GB iPad WiFi: $299, $30 off – 128GB iPad WiFi... Read more

Jobs Board

*Apple* Retail - Multiple Positions - Apple,...
Job Description:SalesSpecialist - Retail Customer Service and SalesTransform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* Data Center Site Selection and Strat...
# Apple Data Center Site Selection and Strategy Research Analyst Job Number: 83708609 Santa Clara Valley, California, United States Posted: 18-Jan-2018 Weekly Hours: Read more
Security Engineering Coordinator, *Apple* R...
# Security Engineering Coordinator, Apple Retail Job Number: 113237456 Santa Clara Valley, California, United States Posted: 18-Jan-2018 Weekly Hours: 40.00 **Job Read more
Firmware Engineer - *Apple* Accessories - A...
# Firmware Engineer - Apple Accessories Job Number: 113422485 Santa Clara Valley, California, United States Posted: 18-Jan-2018 Weekly Hours: 40.00 **Job Summary** Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.