MacApp and C++ 2
Volume Number: 6
Issue Number: 9
Column Tag: Jörg's Folder
C++ and MacApp, Part II 
By Jörg Langowski, MacTutor Editorial Board
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
C++ and MacApp, Part II
Last month’s example was meant to give you an impression of the structure of a
larger MacApp program in C++ and of some specific problems which arise when using
the MacApp libraries from C++. We will now look into the structure of MacApp 2.0
applications in more detail, to see what happens at each point in a program.
We’ll start with a very simple example, the ‘nothing’ program that simply
displays a window with the text ‘MacApp®’ in it. This program (from the Apple C++
examples) is printed in listing 1. The ‘main program’ consists simply of a few lines:
First, most of the essential toolbox managers are initialized by a call to InitToolBox().
This procedure - through a call to the low-level routine DoRealInitToolBox() - calls
InitGraf, InitFonts, InitWindows, FlushEvents, InitMenus, TEInit, and InitDialogs. It
also sets a number of global MacApp variables, most of which won’t concern us here;
an important one is gConfiguration, which is the configuration record that defines the
system environment and allows us later to test whether we have a math coprocessor,
the Script Manager, or other essential things.
ValidateConfiguration tests the configuration record against a number of global
constants the values of which you can define at compile time in your program, if it
requires the presence of certain system services or hardware options. Since we
haven’t set any of those constants, our program will run on all machines.
Nevertheless, we do the test, in case additions are made later. If, for instance, your
program would need a floating point processor, you have to include at the beginning of
the program:
#define qNeedsFPU 1
If the program is then run on a machine without FPU,
ValidateConfiguration(&gConfiguration) will return false and the program abort with
an error dialog.
If the configuration is correct, the main code will be executed starting with a call
to InitUMacApp(8); this will allocate 8 blocks of master pointers. Furthermore,
various internal variables of MacApp will be initialized. If you intend to print from
your program, you must also call InitPrinting(). This routine initializes more
MacApp variables that are used in printing, and in particular creates one object of the
class TStdPrintHandler. This object is assigned to the global gPrintHandler.
You then create a new instance of your application class, in this case
TNothingApplication. Since there exist no constructors in Object Pascal, we have to
define our application’s initialization code in an initialization method. First we check
whether the object was successfully constructed by calling
FailNIL(gNothingApplication), which will abort with an error message if the handle
passed is NIL. If everything worked out, we can call our initialization method
INothingApplication. As a parameter to this method we pass the four-character
identification of the application’s main file type. In our case, the initialization code
basically calls IApplication; this routine, besides various other things, initializes the
application’s menu bar. The remaining code in INothingApplication is needed to pr event
the linker from stripping the class code for TDefaultView.
What does this mean? In larger applications with complicated view and document
structures, you tell the system how to create your views and documents explicitly by
overriding TDocument::DoMakeViews and TApplication::DoMakeDocument. These
methods will be called by MacApp when a new document is created (see below).
However, you may alternatively use a mechanism of view creation from templates.
MacApp will create a default view of type ‘dflt’ when starting the application if
DoMakeViews has not been overridden. If we register our view class TDefaultView
under the type name like ‘dflt’, MacApp will create a default view that contains the
methods of TDefaultView according to our own definition.
As you see, everything happens at run time, thus the methods will be called by
late binding. The linker doesn’t know that TDefaultView’s code will ever be called.
Since we have an intelligent linker, it will try to save space and not include that code
in the final application, which will result in a run time error when we’re trying to
find TDefaultView’s methods. The resort is to create one instance of TDefaultView, as
shown in the listing. If you initialize your own views by overriding TDocument::
DoMakeViews, this wouldn’t be necessary. Last month, I erroneously included the ‘dead
code strip suppression’ part in my example; those lines may be omitted without
consequences.
After all the preambles we can now call gNothingApplication->Run(), which
enters the main event loop. Most of the user actions that have not explicitly been
defined by overriding methods will now be handled automatically by MacApp. That is,
one new document will be created with the default view inside, that view can be
scrolled and the window resized, and the Apple, File and Edit menus function as you
would expect them to.
The only method that we override is TDefaultView::Draw(Rect *area). This
method does the actual drawing for our view - it simply draws the text ‘MacApp®’
surrounded by a gray border. Printing and saving of this document is handled by
MacApp.
TEDemo - last month’s example
You will now need to pull last month’s MacTutor out of the chaos on your desk, or
borrow a copy from a friend. That example was much longer and more complicated;
you see, however, that the main program remains almost identical, we only had to add a
call to InitUTEView() to be able to use a Text Edit view in our program. The actual
behavior of the program is implemented in all the methods that are overridden by our
definitions, and as you see, there are lots of them. First, the initialization method has
grown somewhat. TEApplication::ITEApplication() now initializes the arrays of
different text colors and icon rectangles in the icon palette. It still calls IApplication()
(from the superclass); this call is always required, and it sets up the menu bar.
When the application starts, it will open one new document of its standard type.
The routine that creates the document is TTEApplication::DoMakeDocument, and this is
called by TApplication::OpenNew, which in turn gets called by TApplication::
HandleFinderRequest. The latter routine is called once on entering the Run() method of
the application. DoMakeDocument, in turn, initializes the document by calling
ITEDocument.
The reason that we see our desired view(s) in the document window is because
TApplication:: OpenNew also calls the DoMakeViews method of the newly created
document. This method has been defined in the program, and it creates a palette view at
the top of the window which contains some icons, and a text view for the remaining
part of the window. It also creates the standard print handler.
The DoRead and DoWrite methods of TTEDocument have been explained in part last
month - it was here where we had to deal with the problem of passing procedure
parameters in C++. These methods get called when the Open and Save items in the File
menu are activated. The menu handler is called from the main event loop, which is part
of the application’s default Run method; no need to write explicit menu handlers here.
The methods of TPaletteView handle the icon selections in the palette at the top of
the window. DoMouseCommand looks which icon we clicked in, highlights the palette
appropriately and sets the variable fIconSelected to the number of the icon selected.
This variable is then later used to determine the cursor shape and the behavior of the
text view on mouse clicks and keystrokes.
In the methods of TTEView, we have to override the menu handling since we have a
new menu that determines the text color. Thus, the menu behavior is defined in the
methods DoSetupMenus and DoMenuCommand. DoSetupMenus is called regularly by
MacApp to make sure that changes to menus are made visible. DoMenuCommand is
called when a menu item is selected.
DoMouseCommand will draw a box in the window if the ‘drawing tool’ is selected
by methods from the Sketcher class which is defined further below; otherwise, it calls
the method inherited from the superclass, which in this case implements TextEdit
behavior for setting the caret position and selecting text.
TSketcher is the class of objects that draw rounded rectangles (TBoxes) in the
window. An object of this class gets instantiated when the mouse is clicked in the
window while the drawing tool icon is selected. The ‘Sketcher’ then tracks the mouse
and creates a new TBox object at the position given by the result of the mouse tracking.
TBox, finally, draws itself inside the window as a rounded rectangle.
I hope this explanation has made clear at least approximately how a MacApp
application sets itself up and uses the classes and methods defined in the program. We
shall see more examples in the near future. Meanwhile, I would like to ask the
remaining Forth believers to stay tuned; next month it’s your turn again.
Happy hacking.
Listing 1: nothing.cp
#include
#include
#include
const OSType kSignature = ‘SS01’;
const OSType kFileType = ‘SF01’;
class TNothingApplication : public TApplication {
public:
virtual pascal void
INothingApplication(OSType itsMainFileType);
};
class TDefaultView : public TView {
public:
virtual pascal void Draw(Rect *area);
};
pascal void
TNothingApplication::INothingApplication
(OSType itsMainFileType)
{ IApplication(itsMainFileType);
RegisterStdType(“\pTDefaultView”, ‘dflt’);
if (gDeadStripSuppression)
{ TDefaultView *aDfltView;
aDfltView = new TDefaultView;
}
}
pascal void TDefaultView::Draw(Rect *)
{ Rect itsQDExtent;
PenNormal(); PenSize(10, 10);
PenPat(qd.dkGray);
GetQDExtent(&itsQDExtent);
FrameRect(&itsQDExtent);
TextFont(applFont); TextSize(72);
MoveTo(45, 90); DrawString(“\pMacApp®”);
PenNormal();
}
TNothingApplication *gNothingApplication;
int main()
{ InitToolBox();
if (ValidateConfiguration(&gConfiguration))
{ InitUMacApp(8);
InitPrinting();
gNothingApplication = new TNothingApplication;
FailNIL(gNothingApplication);
gNothingApplication->INothingApplication(kFileType);
gNothingApplication->Run();
}
else StdAlert(phUnsupportedConfiguration);
return 0;
}