OOP Architectures 1
|Column Tag:||OOP Architectures
MacApp and THINK Class Library
By Larry Rosenstein, Apple Computer, Inc.; Joseph S. Terry, Jr., Ajalon Corporation
Object-oriented Design (OOD) ...
Design is the most difficult and important task of Object Programming (OP) and very few professional software engineers do it well. Everyone claims they know how to do it. Sure, lots of people use C++, Object PASCAL, and THINK C (i.e. C+-). Many software companies have made huge investments in training personnel in MacApp or TCL. Yet, with all the knowledge programmers have about programming in object-oriented languages, little time and effort has been spent on the radical difference in how one should design object-based programs.
Good design requires a through knowledge of the class library architecture that youre using. If you know the architecture youre fitting your code into, you can take maximum advantage of the facilities already there. An experienced OP practitioner with a solid knowledge of his or her class library will find as many ways of using existing code as other programmers find reasons to create new code, maybe more. The OP practitioner, obviously, will produce, on average, fewer bugs per hour coding and will produce more tasks accomplished per hour than their clever prodigiously coding colleague.
The following is an introductory article on MacApp and the THINK Class Library, two of the most prominent OP architectures on the Macintosh. The focus of these articles again is NOT how to program in the respective environments, but their functional design or architecture. How do they perform? What happens when you click on a window? What is the path of messages? This architectural knowledge is something that is either gained in the trenches or not at all. Many writers on Object-oriented Programming (OOP) do not find this subject nearly as important as method internal issues.
When designing any form whether it is a house, or a bridge, or a computer circuit or a program you need guidepost design rules to be used as a starter for a design. These rules would be different for each application type and established design school creating a spreadsheet-like shopping list of object design rules with application type along the top, above each column and each of the design schools listed on each row.
For OP program design one major division is the world inside of methods/routines vs. the world outside. One of the simplest rules is simply to separate, in your programs and your mind, the states of the hexadecimal, pointer driven, interrupt driven, server or client driven world, inside the message handlers of objects and the world outside which, if relieved of lower-level state maintenance, can become an object machine, in that, assumptions made are based on a very precise, very flexible model of process which is only dependent on input or well defined and flexible object states. Any class library is like a high level program interpreter that processes messages, changes state, and propagates states to other objects. The stack pointer is really just the current object and the program counter is the current message being serviced.
In OP, as in the software engineering of the recent and distant past, the focus has always been on detailed implementation issues and performance, algorithms and data structures, saving a little time here or a little space there. Nothing has changed. The experienced OP crafts-person is as concerned with efficiency as the early instruction cycle counting programmers (some still do that!). The efficiency sought is sought in the operation of a logical machine, the class library.
The difference in OOP is that the space and time issues are one level of abstraction removed from the bare semi-metal. Efficient use of space implies you would use the instance variables of super classes (whenever possible) and not gratuitously create a local or a global. But, when you do create instance variable data structures you can create them at the lowest level of the machine for efficiency. You can be positive that no other object will ever access your primitive data structures (without your consent). Efficient use of time means simply, to rely on another method to handle it. Passing the buck must be the most efficient operation implemented on any computer. The most efficiently written OOP method does nothing, it takes no time and no space,... let the class library handle it.
For some reason, articles that I have read about OP are mostly centered on implementing a particular facility (e.g. pop-up menus) or function in an OP framework. This is great for an experienced software architect (yes, you may call yourself that) with a specific problem, but it does nothing to help those that are just hanging onto the discussion with their fingernails.
Some authors seem to think that we would be offended by another discussion on architecture...
Larry and I want many more discussions of architecture, design, and other issues which are not directly related to the latest implementation techniques or trap calls. When a newcomer to OP asks his or her first questions, it is usually of the type What do I override to affect this or that behavior?. These are the kinds of questions that a good knowledge of class library architecture and this article will address.
As a first definition of OOD I offer:
Object-oriented design is the method which leads to software architectures based on the objects every system or subsystem manipulates (rather than the function it is meant to ensure).3
This is contrary, in a fundamental sense, to the nature of software engineering as it is practiced in the most of the free world (and beyond). In the C language, programmers are taught to call the fundamental algorithmic building blocks of the language functions. To explain a function in terms other than what it does, such as what it does it to, might seem most strange yet that is a primary focus of OOD technique. Having said that, functional design is what we are interested in here. How is the function or purpose of these libraries enhanced/retarded by the designs chosen?
Consider this article a proposal for a regular feature in MacTutor that would tackle these issues in a rigorous way and at the same time provide hard facts and cool source code for the working-stiff programmer who must make sense of these issues to produced better, more reliable, more maintainable programs (somebody like me!) -- Tell our favorite editor what you think!.
How are these two architectures put together to make programming the Macintosh easier?
What are some of the design decisions that are easily reversed and some that are not so easily reversed?
What kind of applications are they best for?
The structure of each article will be roughly the same. We will attempt to answer the same questions in each so that a comparative picture of the architectures will emerge. We will attempt a comparative architectural analysis in the summary at the end of Part Two. In this first, of a two-part article we will address the first five sections in the following list of sections and subsections.
1 - BIT O HISTORY
2 - ARCHITECTURAL OVERVIEW
3 - HOW THINGS WORK TOGETHER: An Example Message Trail
4 - STORING INFORMATION
B)IN MEMORY DATA STRUCTURES
5 - DISPLAYING INFORMATION
6 - EXECUTING COMMANDS
A)CONTROLLING THE CHAIN OF COMMAND
B)TO UNDO OR NOT TO UNDO
7 - FLOW OF CONTROL
8 - LOW MEMORY CONDITIONS
9 - PRINTING
10 - BACKGROUND TASKS
11 - ERROR HANDLING
12 - SUMMARY
MacApp - The E x p a n d a b l e Application
Apple Computer, Inc.
BIT O HISTORY
To understand the MacApp architecture, it helps to understand a bit of its history and how we developed MacApp. MacApp 2.0 is really the 3rd generation of an object-oriented application framework.
The roots of MacApp go back to the early 1980s and the Lisa project. Larry Tesler and other Lisa software engineers realized that developing interactive, graphical applications was difficult, and well-suited to an object-oriented approach. The result was the Lisa Toolkit. Several developers outside of Apple used the Toolkit to develop Lisa applications, but the Toolkit died along with the Lisa operating system. We began the MacApp project soon after, in order to provide the same kind of programming interface on the Macintosh O/S.
We followed the same approach when developing both the Lisa Toolkit and MacApp architectures. First, we examined the source code for existing applications, in order to discover the parts common to all applications. At the same time we looked for useful implementation techniques that individual programmers had learned, so that they could be incorporated once and for all in the application framework.
Second, we identified the basic elements of each application, and created a class that corresponded to each. Some of the obvious elements are the application itself, document, and window. A less-obvious element is the command. The result was the basic classes that make up MacApp: TApplication, TDocument, TWindow, and TCommand.
Of course, the actual design was a bit more complicated than that. We made various refinements to the set of classes as we went along. For example, different menu commands apply to different parts of the application; Quit applies to the application as a whole, while Save applies to the Document. This led us to implement a class (TEvtHandler) responsible for handling menu commands, as well as other kinds of events.
We also made some compromises in the design, for implementation reasons. Menus and menu items are important elements of a Macintosh application, but we did not implement corresponding classes in MacApp (or the Lisa Toolkit). The main reason was to avoid keeping in memory a separate object for each menu item.
We made other compromises in the name of simplification, based on developers experiences with the Lisa Toolkit. For example, the Toolkit had separate TSelection and TView classes that represented a selection and an visible representation of some data. In MacApp, however, the role of TSelection was folded into TView, because we found that there was often a 1-to-1 correspondence between TSelection and TView classes.
Most recently, however, the tide is turning in the opposite direction. Developers using MacApp are creating more sophisticated applications, which in turn demand more sophistication in MacApp itself. One example is the view mechanism in MacApp 2.0, which allows you to easily create complicated window layouts. This trend is continuing in MacApp 3.0.
There are 4 classes that make up the core of MacApp: TApplication, TDocument, TView, and TCommand.
TApplication is responsible for the applications main event loop, which receives events and distributes them to other parts of the application. It also handles commands that apply to the application as a whole. There is only one application object in existence.
TDocument contains the data associated with an open document. It is responsible for manipulating that data as well as reading & writing it. An application creates a document object for each opened document; there may be several application-specific subclasses of TDocument, if the application handles more than one kind of document.
TView is responsible for displaying some data on the screen. Views form a hierarchy, with the window at the root. Each view defines its own drawing space, and clips the drawing of its child views. Since the view is responsible for the visual appearance of the data, it also takes care of interpreting mouse clicks within its boundaries.
Figure: The Class Inheritance Tree for MacApp2.0. Taken from Into to MacApp 2.0, pg. 78
There are many views allocated for each open window. For example, a simple window with 2 scroll bars and a single scrolling area is implemented with 5 separate views.4 MacApp includes a library of view classes that can be put together, using the ViewEdit application, to define the appearance of a window.
You normally creates a subclass of TView to display application-specific data. Most dialogs, however, can be constructed entirely out of views supplied by MacApp. In that case, you only have to worry about what happens when the user clicks in the dialog.
TCommand represents a command to be executed and/or undone. It also handles mouse tracking.5 You create one subclass of TCommand for each kind of mouse tracking or command. In many cases, the same command subclass can handle more than one user command. For example, cut, copy, and clear are often handled by the same class.
You create an instance of the appropriate command class in response to menu selections, typing, or mouse clicks, and return the command object to MacApp, MacApp then takes care of calling methods of the command object to handle tracking and/or command execution.
HOW THINGS WORK TOGETHER
To see how these classes work together, lets assume we have an application running, and the user opens a document. Opening a document is an application-level command (since the command is available at all times), and it handled by the TApplication subclass.
TApplication calls a method (that you override) that creates a document object appropriate to the kind of file being opened. MacApp then calls a document method that reads the document from disk. Finally, MacApp calls a method that creates a view hierarchy (including a window) for displaying the document.
There are 2 things to note about this process. First, is that you only write code that creates the document object, reads the document from disk, and creates the view hierarchy. You dont have to write any code for selecting a menu command or opening/closing the disk file; MacApp takes care of these things for you.
Second, once youve written these methods, MacApp can use them in other situations. One example is the case when the user opens a document in the Finder. Another, is when the user chooses the Revert to Saved command. In that case, the Finder will call your document reading method to reread the document from disk.
Now that we have a window on the screen, suppose that the user clicks the mouse. The TApplication class receives the mouseDown event, since it implements the applications main event loop. TApplication then categorizes the mouse press. If the click is in the menu bar or in the structure part of the window (title bar, close box, etc.) then MacApp takes care of processing the click without any of your code getting involved.
Suppose the click was in the menu bar. In that case, MacApp takes care of pulling down the menu and selecting an item. The (menu id, item number) pair returned by the Menu Manager is mapped to a command number. MacApp uses command numbers to identify menu items because these are easier to handle (they are 2-byte integers) and it allows you to rearrange menu items without recompiling the program.
The command number of the selected item is passed to the target. The target is the object in the application that gets the first chance at processing the menu selection. If it chooses not to handle the command, it passes the command number to the next object in a chain of event-handlers. Generally, this chain follows the view hierarchy upward until it reaches the window (the root of the view hierarchy); then it proceeds to the document object and finally the application object.
Unless theres a program bug, some object in the chain will handle the command.6 In most cases, it does so by creating a command object and returning it to MacApp. MacApp then calls the DoIt method of the command object, in order to perform the command. The command object not only performs the command, but also saves enough information so that the command can be undone. If the user does select the Undo command, MacApp calls the UndoIt method of the same command object.
Instead, suppose the mouse click was within the content of the window. In that case, MacApp locates the view in the hierarchy on which the user clicked by starting at the root of the view hierarchy and following it down. MacApp then calls a method of that view to handle the mouse click. In most cases, the view creates a command object that tracks the mouse while the user presses the mouse button.
MacApp takes care of getting the current mouse position and passing it to the tracker object. It also takes care of automatically scrolling the view if the user moves the mouse outside it. When the user releases the button, the tracker returns a command object (usually itself) that encapsulates the action (e.g., sketching a shape). That command object is handled as described above.
Note the difference between handling a mouse click and menu command. Since a mouse click involves a position in the window, it is handled by searching down the view hierarchy. A menu selection has no positional information; it is handled by starting at the target (the current focus of attention) and working your way upward.
Figure: MacApp View Tree. Taken from Intro to MacApp 2.0, pg. 87
An important part of every application is its internal data structures. These structures determine the kinds of features the application can support, and how fast the application works. In most applications, the user opens a document in order to work with a certain set of data.
In MacApp, the TDocument class is used to store a documents data structures. MacApp does not impose any requirements or restrictions on how you implement your internal data structures; you do not even have to use an object-oriented language. This makes it possible to use libraries of code that you might have already implemented.
Normally, you would define methods in your document class to access and modify the internal data structures. Command objects, would call these methods to perform and undo commands. View objects would call these methods to access that data to be displayed.
The application object creates a document object in response to a New or Open command (as well as to opening icons in the Finder). This process involves 2 methods of TApplication. First, is KindOfDocument, which maps information about: (1) the document being opened (its file type, name, etc.) and (2) the command that started the process into a single integer. This number is passed to the second method, TApplication.DoMakeDocument, which creates the appropriate document object based on the number. Once the document object has been created, it is possible to send it a message to either initialize itself to a blank document or read itself from a disk file.
MacApp handles the details of getting the document name (either from a Standard File dialog or from the Finder) and opening the disk file. You simply have to create the appropriate document object, and write code that reads the disk file into memory.
Saving a document is more complicated because of the possible variations. In addition to the normal Save command, MacApp supports Save As..., which saves the document under a new name, and Save A Copy..., which saves a snapshot in a new file, while continuing to edit the original document. Also, in order to prevent the user from losing data, MacApp saves a document into a temporary file; it doesnt delete the original version until the save is successful.
As in the case of Open, MacApp takes care of the messy details of saving, such as creating and opening the file, renaming it after the save succeeds, etc. You simply write a method that tells MacApp how much disk space the document needs, and a method that writes the document to disk.
Once we have the document in memory, we have to display it on the screen. In MacApp, this is done using a view object. The view is responsible for drawing the data on the screen, as well as interpreting mouse clicks within itself.
In MacApp, views form a hierarchy, with the window as the root. In other words, each view has a list of subviews, and a reference to its superview. Each view defines its own coordinate system. When drawing, it doesnt have to take into account its location within its superview. Also, drawing within a view (including drawing done by its subviews) is clipped to the views boundary.
Drawing is done using QuickDraw. Before drawing within a view, therefore, we need to set the current grafPort, clipping, and origin. This is done by the views Focus method. In most cases, MacApp focuses on a view before calling that views drawing method.
A view hierarchy is created by the document object. When opening a document, MacApp calls the method TDocument.DoMakeViews after the document has been read from disk (or initialized to a blank document). This method is responsible for creating one or more windows, each with the appropriate hierarchy of views. It is possible to have more than one view of a document; for example, you can view a series of numbers as a table or as a graph.
Although you can create each view individually and connect them together in the desired hierarchy, it is more convenient to create the entire hierarchy from a view resource.7 MacApp includes an application called ViewEdit, which allows you to graphically create and arrange views, and which creates the corresponding view resources.8
A view resource contains the class name of each view, as well as the views attributes (such as its superview, its size, and its location within its superview). Creating a view hierarchy from a resource relies on MacApps mechanism for creating an object given its class name as a string. MacApp reads the views class name from the resource, creates the appropriate object, and finally calls the views IRes method to initialize the view using the attributes in the resource.
In most applications, you have to define a subclass of TView that will display your applications data on the screen. MacApp also includes many utility views that you use to construct your window.
For example, there is a view class that represents a standard Macintosh scroll bar. And there is a scroller class, which manages scrolling. To build a simple scrolling window, therefore, you place your custom view within a scroller, and place the scroller along with 2 scroll bars within a window.
MacApp also includes a set of control views (buttons, check boxes, radio clusters, pop-up menus, etc.), a view that interfaces to TextEdit, and a view that implements tables (a List Manager-replacement).
SYMANTECs THINK Class Library (TCL)
Joseph S. Terry, Jr.
BIT O HISTORY
The first commercial version of TCL was available in July 1989. At the Boston MacWorld in August 1988, a friend gave Symantec a demonstration of something that Greg H. Dow had done. They obviously liked what they saw and committed to making the THINK Class Library an integral part of their product. The PASCAL version was completed for the introduction of THINK Pascal 3.0 around January 1990.
I asked Greg Dow, Was TCL based on MacApp?. There are remarkable similarities and correspondences in the form and function of some classes. Greg said that the TCL was not based on MacApp, but that there are certain design imperatives, on a Macintosh, that clearly can only be addressed in a straight forward manner in a very few ways.
Greg is the editor for a new publication called SPLAsh, Symantec Programming Languages Association.9 This organization is dedicated to providing pertinent and practical information about using the Symantec Languages, C and Pascal, and the class library I discuss in this article which is available for C and Pascal.
To understand TCL requires that certain common words and phrases that are used in precise ways within TCL are defined and explained. All macintosh system events are translated into one of two different kinds of special TCL event, direct commands and visual messages. Objects may send these special TCL events to each other as well as receive them through normal channels.
Usually the result of a menu choice, these commands can also be sent from one object to another. This is used to trigger a specific action from an object or its ancestors.
Macintosh events that affect the appearance of the application such as mouse clicks in window contents, activate events, and update events.
An abstract class which implements a link in the chain of command. Most objects are descendents of this class.
An object that is a Bureaucrat that is passed a command if the current object cannot respond to the command.
Chain of Command
The path through objects from the first recipient of a direct command (contained in the gGopher global variable) to its supervisor and so on to the last one in the list, the application itself. Every object in the chain of command is a subclass of Bureaucrat.
An abstract class that is a direct descendent of Bureaucrat. There is only one of these objects per application run and it is always the highest level in the chain of command.
Includes all of the object classes that can be seen on the screen. All of these objects are subclasses of View.
An abstract class that is a direct descendent of Bureaucrat which manages the communication between an application and a window.
An abstract class that is a direct descendant of Director. The Director contains the reference to the window. The Document contains the reference to the file associated with that window and the pane or visual content of the window. Documents are the appropriate place to put the majority of the methods that present and manipulate this kind of information. (as in document kind)
An abstract class that is a direct descendent of Bureaucrat used to implement all objects which have a visual representation. These include Buttons, Scrollbars and Text items.
An abstract class that is a direct descendent of View and the basic drawing area on screen. Each pane has its own drawing environment and coordinate system. Window panes are the key concept of building screen displays in TCL. Each pane has an enclosure and a supervisor. The enclosure is either a window or another pane. It is the enclosure that determines if a pane will receive mouse clicks. If, for instance, you wanted to disable a large portion of a screen from active mouse clicks in an interactive way, you could simple set the largest enclosing panes instance variable wantsClicks inherited from the View class to false. You would do this by sending the message SetWantsClicks() with the parameter FALSE. This is true, of course, because all intervening panes must be willing to pass a click on to a subview before the little guys can respond to anything. Visual messages flow as follows:
An abstract class that is a direct descendent of CObject the root object. There is usually only one switchboard object per application. The switchboard processes ordinary Macintosh events into chain of command events usually passed to the gGopher global and visual messages usually passed on to the object in the gDesktop global, the top object in the visual hierarchy. This object is usually an instance of the Desktop class. The Desktop class is a direct descendent of the View class and therefore is a kind of Bureaucrat which receives and/or passes on commands.
The 7 most important classes that make up the core of TCL are: CBureaucrat, CApplication, CDirector, CDocument, CView, CPane, CSwitchboard.
Figure: The THINK Class Inheritance Tree. Taken from the THINK C Manual, pg. 197
HOW THINGS WORK TOGETHER
Using the same example as used for MacApp. We have an application running, and the user opens a document.
First, we will follow a command to open a document as it percolates through the object hierarchy.
We will select the open command from the file menu with the mouse, the Switchboard Object would categorize the mouseDown event, in ProcessEvent(), and send a DoMouseDown() message to itself.
DoMouseDown in the Switchboard object then sends a DispatchClick() message to the object in the gDeskTop global variable. After the DispatchClick() returns, DoMouseDown() saves the mouseDown information in a global called gLastMouseDown to facilitate the handling of double and triple click sensing.
DispatchClick() in the DeskTop object determines the location of the mouseDown. We are now, briefly, in the visual hierarchy. Upon determining that a menu has been chosen the DeskTop object sends the command associated with the menu to the object in the global gGopher.
Each menu item can have a positive integer associated with it through the resource used to store the menu or through procedures in the Bartender class. (as in Menu Bar-tender). These command numbers remain constant throughout program development and allow a programmer to rearrange the menus drastically, without affecting how and who responds to the commands from that menus items. Whether the items are selected using command key equivalents or the mouse, if they are normal menus, pop-ups, or hierarchicals, the same command number will be generated and used to initiate action.
This OpenDocument() command/message has just passed over from the visual hierarchy to the chain of command. The object in gGopher is likely to be the main pane in the active window of this application. This object will not understand or have a handler/method for the OpenDocument() command so it will be passed to the panes supervisor which will likely be a document object. You might think that here we will find an OpenDocument() method. But, that is not true. Documents dont open themselves. That would create somewhat of a chicken and egg problem. The document would have to exist before it was opened. No, the document class doesnt know OpenDocument() so it passes the command on to its supervisor, usually the Application object.
The Application object knows OpenDocument() and creates a new document, it then sends that Document an OpenFile() command.
The Document objects OpenFile() method creates a DataFile object. The DataFile object is stored in an instance variable of the Document object that owns this file. The standard implementation of Document only allows for one file, but a subclass of Document could easily be composed of three on-disk files as well as none. A document without a disk file might be a status document whose data was the state of other documents.
After some error checking the Document object sends a ReadAll() message to the DataFile object. This reads the entire data fork of the file into memory. Then the BuildWindow() message is sent to the Document object with a handle to the information just read in.
The BuildWindow() method first creates a new window. This new window is the enclosure for two(2) panes. A ScrollPane is created because we want to scroll across the data. The ScrollPane implements much of the behavior necessary to accomplish scrolling of graphics or text with little intervention from the programmer. The ScrollPane is enclosed by the Window object. Our MainPane, enclosed by the ScrollPane is the pane that implements our unique way of presenting the data, across which we will scroll. There is the Window, it contains a ScrollPane, the ScrollPane contains our MainPane. This is the hierarchical nature of the visual hierarchy. Windows and Panes are subclasses of View so that we have a view, within a view, within a view.
The itsMainPane instance variable of the Document determines much of the printing control information that is requested of a document before and during printing. The itsMainPane variable is used to identify which of potentially many panes is the one that should encapsulate the command and control for this particular window/view hierarchy.
You might use your main pane with the data representation code and command code together or you might have a pallet pane that controlled the view in the graphic pane. In this case you might want to set the pallet pane as the one that received questions about printing this window/view hierarchy. The itsGopher instance variable of the Document, inherited from the Director class, is usually the itsMainPane of a document whenever that document becomes the active one. The itsGopher instance variable is stored in the gGopher variable whenever the Document object becomes the active document. The variables itsMainPane and itsGopher of a document do not HAVE to be the same thing. They can be set to different panes or, for instance, one object called the Control object could be the itsGopher for a whole set of open documents.
Now that the data has been displayed we set the windows title and make it the active window by sending a Select() message to the itsWindow instance variable of the Document object.
Now, that our window is open and ready to respond to the world, the user clicks the mouse.
The Switchboard object determines that a mouseDown has occurred and sends a DoMouseDown() message to itself causing a DispatchClick() message to be sent to the DeskTop object through the gDeskTop global. As with MacApp, if the click is in the menu bar or in the structure part of the window (title bar, close box, grow box, zoom box, etc.) then TCL takes care of the processing with default behavior that is rarely overridden.
If the click is in a pane of a window then a UpdateWindows() message is sent to the DeskTop object. Next, a DispatchClick() message is sent to the Window enclosing the pane that received the click.
The Window/View object determines if there is a subview that received the click or if there are no subviews. If there is a subview that is under the position of the click that wants to respond to clicks, then the message DispatchClick() is sent to that pane/subview, otherwise if the Window/View wants to handle the click, the Window/View sends itself two messages that allow it to do so. This occurs recursively until there are no subviews or none that are willing to handle a click. In that case or in the case that the first Window/View chosen accepts the challenge, the following occurs.
First, a CountClicks() message is sent to the Window/View/Subview Object. This message will cause an global variable called gClicks to be incremented as long as the clicks are close in time and in the same part of a pane/subview.
Close in time is defined as less than the value returned by the ToolBox call GetDblTime(). The same part is determined by sending a HitSamePart() message to the Window/View/Subview object with the current and last click position. This method found in the View class should be overridden to determine when, for instance, in a text view a word is defined as the same part or a line or a paragraph. In a graphical view, an object as defined by the applications purpose would be defined in this method.
Next, a DoClick() message which is implemented as a NULL function in the released TCL is sent to the Window object. This method is an integral part of a text editor or a draw program or a database forms application. DoClick can do things like maintain the position of the cursor, that is which field it is in, when, in a database application the user clicks on a different field. DoClick might initiate a validation function as well to determine if the user can exit the field.
Figure: The THINK Class Library Visual Hierarchy. Taken from the THINK C Manual, pg. 198
The THINK Class Library provides an a small but well chosen set of file and data structure handling classes. The primary goal it seems was not to attempt an exhaustive toolchest of structures, but to provide a generally powerful strong foundation for the entrepreneurship of third parties. I hope software companies will soon be providing TCL Books. TCL Books would be collections of classes that are sold commercially and maintained by one company or entity.
The abstract class called File provides a base for file handling on the Macintosh. As a rule you will use a subclass of File called DataFile to do actual file manipulation. DataFile provides features for reading/writing raw bytes from/to files in a generalized way. Also, all access to files is to the data fork and not to the resource fork. If you wish to access resources in files you will have to write those routines yourself. The good news is that the ToolBox provides a very nice high level set of tools for manipulating and accessing resources in files.
If your file/document has a structure beyond the straight 5, 10, 100 bytes here and 1000 bytes there, then you should subclass DataFile and build specialized access methods.
When a document is opened TCL handles the getting of the file name. First, it creates a window then it tries to open a file to get data to present in that window. This file would then belong to the document and a reference to the file object would be stored in the itsFile instance variable of the Document class. From an architectural viewpoint, TCL is very simple and very flexible when it comes to file handling. Very few decisions have been preordained.
Data structures are a very important subject in the world of computers. It is generally recognized that algorithms and data structures equal programs, a book of a similar title Algorithms + Data Structures = Programs, by Niklaus Wirth10 is considered a classic on this fundamental viewpoint. In OP the data structure is the central element of the design phase. What are the objects my application must manipulate and what are their relationships. TCL includes three classes which are really just a start on a complete toolkit of data structures that a working programmer needs. These classes are Collection, Cluster, and List. A Collection is simply a container with a numItems instance variable and IsEmpty() and GetNumItems() methods. A Collection is an abstract class that provides an anchor for the data structures that are subclasses.
The Cluster class is an implementation of an unordered Collection. By unordered, we mean that when an object is stored in the Cluster it is stored in entry order and that the order is not related to the contents of the object itself. In other words, the objects are not sorted by size, for instance. If you wanted to determine which one was the largest, you would have to examine every single object, to be sure you found the largest object.
To find an object in a Cluster, you write a method that returns a true boolean value when applied to an object that meets the standards your method has set. Then, you would call the send the message FindItem() to the Cluster object, with your method as a parameter. FindItem will examine each object in turn with your method to determine which object to return to you. If none is found that passes your methods tests then NULL object pointer is returned.
Clusters introduce iteration over a set of objects, which is a very important and powerful facility for all kinds of programming tasks. Iteration allows you to write methods that you want to apply to all of the objects in a Cluster and then send the message DoForEach() to the Cluster object with your method as a parameter.
The power of the iteration concept is that is allows a kind of interrupt driven style of programming. In the world of Expert Systems it is called Forward Chaining. Where some part of the application will wait for a particular kind of object to appear in a Cluster and that will trigger other actions such as more, different types of objects being stored in that Cluster, which in turn spurs other Cluster watchers to action and so on.
Lists are a subclass of Cluster and provide for the ordering of objects based on any criteria you decide. If you want a set of people objects to be ordered by last name then that would be easily accomplished with a List object.
TCL provides all of the standard user interface glue for the programmer. When the dirty instance variable of a document has been set to true the default closing method asks Do you want to save changes to XYZ? This behavior can of course be easily and neatly modified.
All drawing takes place in objects of class Pane or subclasses of Pane. Windows are Views and contain Panes. Panes are part of the visual hierarchy and can also be part of the chain of command. Panes have an enclosure and a supervisor. Although they can be the same object, it is usually the case that the Panes enclosure is the visually enclosing Pane and the Supervisor is the director of the Window that contains the pane.
The root of the view hierarchy for TCL is the DeskTop object. This is the first object to receive mouse clicks anywhere on the screen, even in the menu bar. The DeskTop object allows TCL to implement concepts such as floating tool pallets with a minimum of extra work to manage clicks and window order. This also allows for an application to determine sublayers among its own document windows.
All drawing is done using the standard QuickDraw11 toolbox routines. Before sending a pane the draw() message, TCL sends the pane a prepare() message. Prepare returns the QuickDraw environment to a known state, currently the default routine sets the port, sets the origin, sets the clipping rectangle so that drawing only takes place within the visible portion of the pane and then sends the message Restore() to the Environment object that every pane contains in its itsEnvironment instance variable. The default function of the Restore() message is to execute the QuickDraw command PenNormal().
As it turns out, the normal initialization of a Pane does not create an environment object. Therefore, a Pane will be drawn in the correct port and with correct origins, but with the pen state, font, etc of the previous draw-ing else where on the screen. You may want to override this initialization process or simply, as a subclass of Pane, create and assign different environment objects to your itsEnvironment instance variable.
There are a few classes designed as building blocks for your own more complex Views/Panes. There is StaticText, EditText, Picture, Button, RadioButton, CheckBox and Border. All of these classes can be used to create panes of arbitrary complexity.
The RadioButton class has a sister class called RadioGroup that is meant to manage a cluster of RadioButtons. This is a simple example of the Cluster class in use and offers only a glimpse of the potential user interface uses of Clusters of objects.
To create a scolling view, you would first create a Panorama which is a subclass of Pane. A Panorama is a kind of Pane where the frame is the only part of the Panorama that can be seen, as in a normal Pane, but there is more information that can be scrolled into view. Next, create a ScrollPane or subclass thereof and then send the message installPanorama() with a your panorama as a parameter. The ScrollPane automatically adjusts settings of the scroll bars to the size of the Panorama and you can determine, when you initialize your ScrollPane, whether you want Horizontal or Vertical Scrollbars or both. Finally, you install the ScrollPane in a window or other pane and you have a scrollable, clickable scrolling pane.
TCL has no ViewEdit as MacApp does. Although many of the Views/Panes that come with the TCL are initialized by default from resources, such as Buttons, there is no MacDraw-like environment for creating these resources. Using ResEdit with TMPL resources is at best, time consuming, at worse error prone and not conducive to creative design.
Third parties arise! OP under TCL needs your products and demands your efforts. TCL has a large natural base because of the popularity of their compilers. I hope that support for TCL will become much better in the coming months and years.
1 trademark of Apple Computer, Inc.
2 Trademark of Symantec Corporation
3 Object-oriented Software Construction, by Bertrand Meyer, published by Prentice Hall
4 One view for the window, one for each scroll bar, one for the image being scrolled, and one for the scroller, which manages scrolling.
5 Another example of simplifying MacApp by combining two functions into one class. Since the result of a mouse action is often an undoable command, it was convenient to represent the tracking and the command in a single class.
6 Thats because MacApp disables all the menu items each time through the main event loop. It then follows the same chain, starting with the target, and gives each object a chance to enable the menu commands it is prepared to handle. Unless some object enables a menu item, it remains disabled.
7 It also turns out that the resource generally takes up less space than the corresponding code.
8 ViewEdit is written with MacApp, of course.
9 SPLAsh can be reached at 1678 Shattuck Ave. #302, Berkeley, CA, 94709/ the yearly membership is $30 USA, $40 Canada and Mexico and $60 international air mail.
10 Algorithms + Data Structures = Programs published by Prentice Hall.
11 QuickDraw, a trade name of Apple Computer, Inc.