Object Shell 1
Volume Number: 6
Issue Number: 8
Column Tag: MacOOPs!
Object Shell, Part I
By Dr. Christian Stratowa, Vienna, Austria
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
[C. S. started his scientific career as a nuclear physicist (where he has made his
Ph.D.), which involved a lot of programming on mainframes. Later on his interests
changed, and he moved to Germany to start a career in molecular biology. Beginning
1984, he spent two years at UCSF in San Francisco, where he bought his first
Macintosh and became a Mac-addict. Back in Austria he is currently working in the
gene technology department of an international pharmaceutical company.]
Part1: Objects, Objects Everywhere!
“If you don’t learn object-oriented programming now, you will not be able to
program the Macintosh later.” I think this statement from the technical editor of Apple
Direct, a monthly newsletter from Apple Developer Services, is a good way to start my
article. It is also supposed to answer your obvious question why present here another
shell program although MacTutor has already published so many shells. But this is
only part of the story.
I really enjoy reading MacTutor, although as someone who does not have so much
time to program the Mac, I find it often hard to follow all the exciting advanced stuff.
Maybe others feel like me. Therefore I decided to write a program for “a bimp”
(“advanced beginner in Mac programming”) which should be useful as a basis to build
more advanced applications. However, when I started to write my own shell I found
none of the published code really satisfiable, including the one I had put (i.e. stolen)
together from different articles and books. Therefore I decided to restart from the very
beginning. Besides that, I may be a little old fashioned but as someone who has started
programming back in the bad old days on an IBM 1130 mainframe with 16 k(!)byte
core memory (do you remember this word?), I prefer to understand every bit of code,
and the best way is still to write your own. So here it is. Using OOPS (object oriented
programming style) it is a fairly complete shell program written in Object Pascal
(not MacApp, which I don’t have, but using mainly LS-Pascal 2.0 and sometimes TML
Pascal II running under MPW), which contains the following features:
• All menus, windows, scrollbars, documents and dialogs are defined as objects
which are kept in separate units (e.g. StdMenus, MyMenus, StdWindows,
MyDocuments). These units are put together as building blocks to make the final
program. Therefore to develop your own application you only need to exchange or
modify the units of interest or add your own units (e.g. MyWindows), without
changing any code in other units or in the main program.
• The program contains a texteditor with Font, Size, Style and Justification
submenus (using old Textedit) and a graphics demo program (drawing ovals,
spirals). For both text and graphics fore- and background color can be selected
from submenus (using old style QuickDraw).
• An unlimited number of windows can be opened. Text and graphics windows can be
open at the same time with the menubar changing dependent on the type of
Front window.
• In a submenu you can decide if the window should contain only a grow box or also
horizontal and/or vertical scrollbars. To be able to scroll one line a time, an
initial scroll delay is established.
• Cut, Copy and Paste is established between different text windows and different
graphics windows (but not between text and graphics windows, which would
probably be an article by itself).
• The program can save and open ‘TEXT’ and ‘PICT’ documents, whereby PICT
documents are saved with a 512 byte header so that MacDraw can open them too.
(The program can even open and save MacDraw documents which were saved as
PICT files.)
• Both TEXT and PICT documents can be printed on an ImageWriter or LaserWriter
using the standard PageSetup and Print dialog boxes.
• All Dialog and Alert boxes are automatically centered for every screen size used.
• Documents can be opened from the Finder by double-clicking.
• Multifinder is supported.
• Program, Text and Pict documents have their own icons.
Although ObjectShell should even run on an old 64k ROM Mac when you don’t use
hierarchical menus in unit MyMenus, I have only tested it on a Mac SE or II using
System 6.02, and printing on an ImageWriter (I and LQ) or a LaserWriter (Plus and
II NTX).
I hope this ObjectShell program, shown in Fig.1, will be a good starting point for
the beginning Mac programmer, which s/he could easily modify to write her/his own
applications. However, it should be clear by now, that there is no way to write a
program for the Mac without having at least the five volume set of Inside Macintosh
(IM). It would also be a good idea to have some of the books covering programming on
the Mac. A good starting point is to buy Steven Chernicoff’s Macintosh Revealed (MR),
which does at the moment (June 1989) consist of three volumes. Last but not least,
subscribing to MacTutor (MT) is a must! Supposing, that you have now everything you
need, let’s begin to cover the most important features of ObjectShell.
The World of Objects
Almost everything in the world is an object, a house, a door, windows, even more
abstract things like documents or menus. Our brain does even treat the stuff it sees on
our Macscreen like menus, windows or scrollbars as different objects. So it seems
quite naturally to handle these things already during program development as objects,
which can communicate together. This is the philosophy behind object oriented
programming. Each object does have its own kind of behavior, which can be inherited
by sub-objects, and can send messages to other objects.
In Object Pascal you define an object of type window the following way:
{1}
TWindow = object(TObject)
fWPtr: WindowPtr;
procedure DoOpen;
end;
var
oWindow: TWindow;
Here fWPtr is a field variable like the ones in a Pascal record, and DoOpen is a
method. As you can see, in Pascal an object is simply the natural extension of a record.
The actual variable you use is called oWindow. Let me state here the first important
point: Never forget, that oWindow is not the object itself but a handle to object
TWindow! (Actually it’s called object reference variable.) Therefore you have first to
create an object before you can use its fields and methods:
{2}
New(oWindow);
oWindow.fWPtr:= ....;
oWindow.DoNew;
Actually, to be able to use variable oWindow throughout my code, regardless of
the type of window I want to use, i.e. class TWindow or any of its subclasses
(descendants) TGrowWindow, TScrollWindow, etc. I create most of the objects the
following way:
{3}
var
oWindow: TWindow;
begin
New(TScrollWindow(oWindow));
...
oWindow.DoOpen;
...
end;
This sets oWindow to the window type I want and has the advantage, that further
code can use variable oWindow without a need to know which subclass of type TWindow
(e.g. TScrollWindow) we are definitively using. (Sorrowly, this elegant way can not be
used in TML-Pascal II. You have first to create a temporary variable for each subclass
and then set oWindow equal to this variable.)
After we have called a method from “outside”, that method begins to execute, and
we are “ inside” the object. From within a method it is not only possible to call other
methods of that object (self.Do...) but we can also take advantage of a feature called
inheritance. This has the advantage that you often need only add the code specific to the
subclass that overrides a method of its ancestor. In our example:
{4}
procedure TScrollWindow.DoOpen;
begin
self.DoNew;
inherited DoOpen;
{code specific to subclass}
end;
Before a scrollwindow can be opened, it has to be created with method self.DoNew.
To open it, first the method of its immediate ancestor is executed and then the subclass
specific code.
Once you have understood the concept of objects you may find it much easier to
write your own code that way. At least this was true for me. For a deeper coverage of
objects see earlier issues of MacTutor (MT 12/86, 2/87, 8/87).
Globals and the Main Program
As I have already said earlier ObjectShell is made of self-contained units, which
serve as building blocks to put together the final application, as shown in Fig.2 and 3.
At the top is unit “GlobalStuff” which does not only contain all global constants and
variables but also some procedures, which can be used throughout the program. The
main unit, program “ObjectShell”, is located at the bottom level.
The constants at the beginning of unit “GlobalStuff” are diverse program
parameters and can be changed by the programmer to fit her/his own needs. As an
example it is possible to limit the number of windows the application can open at once
to four by setting kMaxWindows=4. With kWindowKind you can determine if your
window should only have a growbox or also horizontal and/or vertical scroll bars. (In
our demonstration shell this choice is left to the user.) Similarly, kMaxEditItems is
the maximum number of editable text fields in any of the dialog boxes we are using.
By the way, for better readability of my code I use following conventions for
constants and variables:
kName ... global constant
cName ... local constant
gName ... global variable
vName ... local variable
uName ... variable global within unit
oName ... object type variable
fName ... field variable of object or record
The main unit, program ObjectShell itself is designed as general as possible.
More specific code is handled in units StdMenus and StdWindows, and
application-specific code in units MyMenus, MyDocuments (and if you design your own
windows, in unit MyWindows). Therefore, under normal circumstances there should
not be any need to change this code when you adopt ObjectShell to your own needs.
The main program does first call routines to initialize the Mac, different globals,
printing, and menus, and does then call CheckFinder in case ObjectShell has been
launched by double-clicking a document or by selecting a number of documents and
choosing “Open” from the Finder. When you select more than one document to open
from the Finder, procedure CheckFinder has to take care that the previously opened
window will be deactivated properly before creating a new window for the next
document. We have therefore to force a deactivation event to make sure the scrollbars
are unhighlited. Try to open some MacDraw documents together from the Finder to see
that this is not an obvious feature.
The heart of every Mac program is the main event loop, which repeats until
gDone returns TRUE, at which time procedure ShutDown is called to dispose of all
handles and regions still in use. The main event loop keeps track, if we press a key or
the mouse button, if a window needs to be activated or updated, or if other events have
occurred. For example, procedure DoDiskEvent makes sure that our program is able to
initialize new disks.
Let’s suppose we have clicked in the menubar. Procedure DoMouseDown does then
call DoMenuClick, which checks if we have clicked in one of the standard menus
(Apple, File, Edit) every application should support (ClickInStdMenus), or in one of
the menus specific to our application (ClickInMyMenus). At this point you can see,
that both the routines for setting up menus (InitStdMenus, InitMyMenus) and for
selecting a menu (ClickInStdMenus, ClickInMyMenus) are implemented in the
appropriate units StdMenus and MyMenus, respectively. So let’s first talk about
menus.
Attaching Menus
Menus are implemented as objects in unit StdMenus. Class TMenu does contain a
menuhandle fMenuHdl as field variable and two self-explanatory methods Create and
Choose. Subclass TAppleMenu does contain an additional variable and method for the
“About...”box (see MT 6/88 p.85).
In addition to InitStdMenus and ClickInStdMenus three other global procedures do
exist. SetStdMenuItems keeps track of the state of the menus, e.g. vMaxWFlag will
disable “New” in the File menu if the number of windows open equals kMaxWindows. It
is called immediately before calling MenuSelect or MenuKey, since these are the only
times the user can see the menu items. DisposeStdMenus is called at ShutDown,
although I don’t know if it is really necessary. However, it is always a good
programming habit to dispose of all handles yourself. Finally, procedure DoNew, which
is normally called from TFileMenu.Choose, must also be made accessible to procedure
CheckFinder to be able to supply a window when the user opens a document directly
from the Finder.
Windows to the World
Most computers have only one window to display information to the user, the
whole screen. This has also been true for the Mac when it became alive the first time
by displaying the words “Hello World” on Burell Smith’s screen. Since then the Mac
has changed the way people are using computers. Having covered already menus, let’s
now see how to handle windows. (Always refer to Inside Macintosh or Macintosh
Revealed for additional information!)
Analogous to menus we are defining windows as objects in unit StdWindows. A
window consists of the window frame containing title bar, drag region and close region,
and the content region, the area our application draws in. When defining object
TWindow we divide this content region into a control region (fControlRgn), where
growbox, scroll bars and maybe other controls can be placed, and a display region
(fDisplayRgn), where the contents of field oDocument are drawn. Placing object
oDocument as field variable in TWindow has the advantage that each document is
automatically attached to the window it belongs to. In the same way we attach scroll
bars oHBar and oVBar to TScrollWindow. (This is a good example of the power and
elegance of object oriented programming.)
Once “ handle” oWindow is created in DoNew in unit StdMenus, procedure
TWindow.DoNew will create the actual window with Toolbox function NewWindow. Most
of the window parameters are set in method Initialze, only its port rectangle is
calculated in function NewRect, which takes care that further windows will be offset
by kWOffset (see Fig.1). After having created the windowpointer fWPtr, we set the
window reference constant to self, which is the reference value of the object calling
the method. This will enable us later to find the window connected with windowpointer
vWPtr by calling:
{5}
oWindow:= TWindow(GetWRefCon(vWPtr));
This statement will be used by us whenever we need to know which window to
work with. After defining window’s control and display region, we finally create the
document oDocument attached to the window. Let me state here again, that once created
in this way, further coding can be done without knowing the type of document that has