Richard Rathe has worked with C since 1985. He is an assistant professor of family medicine at the University of Florida and a researcher in medical computing and informatics. You may contact Richard at Box J-215 JHMHC, University of Florida, Gainesville, FL 32610
The Macintosh lets users manipulate graphic screen elements with a mouse or keyboard. A typical Macintosh application will feature one or more windows of related text and graphic elements, such as those used to edit text. When a user initiates an action such as a mouse click, the action is recorded as an event and placed in a queue. The application must then respond to this event as appropriate.
Since the burden of managing this complexity is left to the programmer, even simple Macintosh applications are prone to bugs and are difficult to maintain. Object-oriented programming techniques can help reduce the complexity. In this article, I use standard C to define window objects as part of a generic application. Any Macintosh program derived from these templates will easily support many windows of several different types.
Object-Oriented Style
Encapsulation is the combination of data with functions that act on that data. Together, data and data-specific functions constitute a class. All or part of a class's data might be private, invisible outside the class. A class's functions are often called methods and are invoked in response to function call messages. An object is an actual instance of a class, a "value."A polymorphic function takes on different behavior depending on which object invokes it. The classic example is the draw() function in the world of circle, square, and triangle shaped objects. Three different (virtual) functions exist, one for each shape, but all share the same name. Sending a draw message to any shape object results in the correct version of draw() being called by virtue of late binding.
Because object-oriented programming encourages deriving new classes from existing classes, it should increase your productivity. Code reuse minimizes maintenance headaches. A derived class might inherit most of its data and functionality from its parent, but might add new data elements or functions. You can also override specific functions by defining new functions that have the same name, but different behavior. Using derived classes takes advantage of inheritance.
The refCon Field
The key to creating window objects on the Macintosh is the reference constant, or refCon field, provided as part of the built-in window data structure. These data structures and functions are part of the Macintosh ROM toolbox and can be called directly from an application as if they were language extensions. The application programmer uses this 32-bit integer to store application specific data, such as storing a pointer to an additional data structure.
Event Records
The Macintosh maintains information about events in the event record data structure, which has the following fields:
The meaning of each of these fields depends on the type of event. For instance, a mouse-related event record might contain a pixel location in the where field.
- what
- message
- when
- where
- modifiers
GetNextEvent()
Each time a user, application, system, or network event occurs, the Macintosh OS creates a new event record and places it in the event queue. Your application can access this queue by calling the built-in function GetNextEvent(). You can use a related function, WaitNextEvent(), with newer versions of the system software to allow background multi-processing. In virtually every Macintosh application, GetNextEvent() is called repeatedly as part of an event loop (Listing 1) to get event records off the queue for processing.
activate Events
On the Macintosh, only one window is on top or "active," but you can change which window is active at any time. While the Macintosh system software draws the window frames, it's your application's responsibility to manage the windows' contents. A window becoming active generates an activate event. The previously active window generates an inactivate event. These events allow your application to do required window housekeeping.
update Events
Whether a window is active or not, you may need to redraw all or part of it the screen changes. The system generates an update event in response to such a need.
mouseDown Events
Mouse presses generate mouseDown events. The meaning of a given mousepress event depends on where it occurred. Basic tasks such as moving a window are handled by built-in functions. Complex tasks such as scrolling text must be handled by the application. Mouse clicks inside the menu bar are a separate issue not covered here.
keyDown Events
A keyDown event record contains a key code and information about any modifier keys (command or option) that might have been used. The interpretation of a keyDown is entirely application-specific. When users hold down a key continuously, they generate an autoKey event.
Declaring Window Classes
The generic event loop in Listing 1 provides infrastructure for creating a window class. The WIND typedef (Listing 2) defines generic window data and window messages/methods. Notice the use of the **data handle. A handle is a pointer to a pointer. The memory manager can compact the heap with the intermediate master pointer, while the application deals with the handle exclusively. Macintosh memory management makes extensive use of relocatable, handle-based memory blocks for things such as text and graphics. Following the window data, I declare standard window operations as pointers to functions.The function create_t_window() (see Listing 3) creates an instance of the text window class. Both window and WIND data structures are allocated from the heap using the built-in functions NewPtr() and NewWindow(). The arguments to New-Window() specify the window rectangle, its title, visibility, and so on. The window methods are added, and finally the window class data t_windinfo is inserted into the t_window refCon field. The new window object joins the window list maintained by the system software.
Window Methods
The window methods are specified in a separate file for each window class. The activate method for the text window class t_activate() (Listing 4) takes a window pointer and a WIND data structure as its arguments. First, you obtain the TextEdit data handle. (TextEdit is the name for the built-in ROM functions that support simple text editing.) Then, you call any of several update functions depending on whether the active-Flag is set. These functions include: activate/ deactivate; hilite scroll bar; transfer to/from scrap buffer; and enable/disable menus.
Window Messages
Window messages are dispatched from the event loop based on the event type. The activate event dispatcher in Listing 5 simply obtains the WIND data structure and calls the dereferenced activate method *windinfo->activateproc(). The activate method is polymorphic, with the actual function call being determined at runtime. A summary of the entire process is shown in Figure 1.
Off And Running
The body of a typical application (see Listing 6) consists of initialization and the main event loop. Once control is handed over to the user, the main event loop controls all interaction with the application. Window objects are created dynamically as needed. The appropriate window methods maintain the contents of each window, and the Macintosh operating system maintains the windows themselves.You can implement a variety of window classes with the object-oriented approach. Note that all text windows support editing, scrolling, cut/paste, find/replace, and disk access. A list window class provides spreadsheet-like tables to display data. These tables support the selection of individual cells and scrolling in two dimensions. Finally, the picture window class is used to display graphics. Refer to Figure 2 for an example application.
The Macintosh lacks a standard console, so there's no place to display output from printf() or assert() statements used to debug programs. Therefore, I derived a debugging window class from the text window classes for this purpose. The debugging window class inherits most of the messages/methods of the text window class, but includes a new debug() function. Arguments to debug() are displayed in the debugging window. Debugging output can be scrolled, edited, and saved as a text file.
The Payoff
Programmers will benefit from this object-oriented approach. Since global window pointers are no longer needed, the number of windows is dynamic. You can avoid the lengthy conditional or case statements used to dispatch events. Events will be "transparently" directed to the correct window regardless of the window's position or type. Each window will respond as expected to the events it receives. You can easily integrate new windows, created on the fly, into the application's display. Inheritance simplifies the definition of new window types since existing types form the basis for new window classes. Finally, reusable code and decreased window-related complexity help streamline the development of Macintosh applications.
Annotated Bibliography
Apple Computer, Inc. Inside Macintosh (vol I - V), Addison-Wesley (1984-1988). The definitive documentation for programming the Macintosh.Chernicoff, Stephen. Macintosh Revealed (vol. I and II), Hayden Books (1987). A good programmer's introduction. Summarizes information found in Inside Macintosh. Unfortunately, the example code is written in Pascal.
Jonathan Amsterdam, "Object-Oriented Mac Windows", BYTE (July 1989); 14(7) pages 277-287. A similar but more complicated approach to Macintosh window support using object-oriented techniques. Uses the refCon field to store window data and methods as discussed here.
JS Linowes, "It's an Attitude," BYTE (Aug 1988); 13(8), pages 219-224. Another example of object-oriented programming techniques in "straight C."