SpInside Macintosh 2.0.2
Table of Contents
Preface
_______________________________________________________________________________
PREFACE
_______________________________________________________________________________
About SpInside Macintosh
Inside Macintosh: The Book
The Languages
What’s in Each Volume
Version Numbers
Compatibility
A Horse of a Different Color
The Structure of a Typical Chapter
Conventions
_______________________________________________________________________________
»ABOUT SPINSIDE MACINTOSH
_______________________________________________________________________________
SpInside Macintosh is an attempt at putting the entire contents of “Inside Macintosh” into a useable electronic format. It has been inspired by developer feedback on the Technical Notes Stack, “Phil & Dave’s Excellent CD”, and by the need for an electronic version of our beloved “Inside Mac.” At this stage, SpInside Macintosh is nothing more than a rough development PROTOTYPE.
It combines “Inside Macintosh” Volumes I-V into a single, sometimes coherent, electronic source. This text has not been rewritten for this format (i.e., we even left the Lisa references), but we did try to correct small things where we could. Information from Volumes IV and V has been inserted where deemed appropriate into the original text; however, some paragraphs may seem out of place. We tried to note machine- or system software-dependent references where the text may not have been clear, and we also incorporated an interim chapter on the Script Manager 2.0 and completely replaced the Sound Manager chapter. Hopefully, we haven’t introduced any new errors to the original text.
The chapters are numbered according to their order in this stack, and other than navigation through this stack, these numbers have neither a correlation to the original chapter numbers nor any other significance.
We’re distributing SpInside Macintosh as a development prototype because we feel it is more important for you to have it to use right now than to wait for us to finish a release-quality version. We also really want your feedback on it, so you, the real users of “Inside Macintosh,” can have a hand in designing your ideal electronic version instead of us telling you how it should be. Tell us what you like and dislike about the format, organization, and usefulness (or lack thereof). It is this feedback, both good and bad, that will ultimately decide the future of SpInside Mac and its derivatives.
Thanks for your support and especially for your patience. Have at it!
_______________________________________________________________________________
»Inside Macintosh: The Book
Inside Macintosh is a five-volume set of manuals that tells you what you need to know to write software for the Macintosh family of computers. Although directed mainly toward programmers writing standard Macintosh applications, Inside Macintosh also contains the information needed to write simple utility programs, desk accessories, device drivers, or any other Macintosh software. It includes:
• the user interface guidelines for applications on the Macintosh
• a complete description of the routines available for your program
to call (both those built into the Macintosh and others on disk),
along with related concepts and background information
• a description of the Macintosh 128K, 512K, and Plus hardware
It does not include information about:
• Programming in general.
• Getting started as a developer. For this, write to:
Developer Programs
Apple Computer, Inc.
20525 Mariani Avenue, M/S 75-2C
Cupertino, CA 95014
(408) 974-4897
• Any specific development system, except where indicated. You’ll
need to have additional documentation for the development system
you’re using.
• The Standard Apple Numerics Environment (SANE), which your program
can access to perform extended-precision floating-point arithmetic
and transcendental functions. This environment is described in the
Apple Numerics Manual.
• A description of Macintosh family hardware since the Macintosh Plus.
Refer to the “Macintosh Family Hardware Reference” for this information.
• A description of card architecture and programming techniques for
slot-based Macintosh systems. Refer to “Designing Cards and Drivers
for the Macintosh II and Macintosh SE” for this information.
You should already be familiar with the basic information that’s in Macintosh, the owner’s guide, and have some experience using a standard Macintosh application (such as MacWrite).
_______________________________________________________________________________
»The Languages
The routines described in this book are written in assembly language, but (with a few exceptions) they’re also accessible from higher-level languages. The first four volumes of Inside Macintosh document the interfaces to these routines on the Lisa Workshop development system. A powerful new development system, the Macintosh Programmers Workshop (MPW), is now available. Volume V documents the MPW Pascal interfaces to the routines and the symbolic identifiers defined for assembly-language programmers using MPW. These identifiers are usually identical to their Lisa Workshop counterparts. If
you’re using a different development system, its documentation should tell you how to apply the information presented here to that system.
Inside Macintosh is intended to serve the needs of both high-level language and assembly-language programmers. Every routine is shown in its Pascal form (if it has one), but assembly-language programmers are told how they can access the routines. Information of interest only to assembly-language programmers is set apart and labeled so that other programmers can conveniently skip it.
Familiarity with MPW Pascal (or a similar high-level language) is recommended for all readers, since it’s used for most examples. MPW Pascal is described in the documentation for the Macintosh Programmer’s Workshop.
_______________________________________________________________________________
»What’s in Each Volume
Inside Macintosh consists of five volumes. Volume I begins with the following information of general interest:
• a “road map” to the software and the rest of the documentation
• the user interface guidelines
• an introduction to memory management (the least you need to know,
with a complete discussion following in Volume II)
• some general information for assembly-language programmers
It then describes the various parts of the User Interface Toolbox, the software in ROM that helps you implement the standard Macintosh user interface in your application. This is followed by descriptions of other, RAM-based software
that’s similar in function to the User Interface Toolbox. (The software overview in the Road Map chapter gives further details.)
Volume II describes the Operating System, the software in ROM that does basic tasks such as input and output, memory management, and interrupt handling. As in Volume I, some functionally similar RAM-based software is then described.
Volume III discusses your program’s interface with the Finder and then describes the Macintosh 128K and 512K hardware. A comprehensive summary of all the software is provided, followed by some useful appendices and a glossary of all terms defined in Inside Macintosh.
Volume IV is a companion to the first three volumes that gives specific information on writing software to take advantage of the features of the Macintosh Plus and the Macintosh 512 enhanced. A familiarity with the material presented in the first three volumes is assumed, since most of the information presented in Volume IV consists of changes and additions to that original material. This volume also introduces four additional chapters—“The System Resource File”, “The List Manager”, “The SCSI Manager”, and “The Time Manager”.
Volume V presents new material specific to the Macintosh SE and Macintosh II computers. Familiarity with the material presented in the first four volumes is assumed, since most of the information presented in Volume V consists of changes and additions to that original material.
_______________________________________________________________________________
»Version Numbers
This edition of SpInside Macintosh describes the following versions of the software:
• version 105 of the ROM in the Macintosh 128K or 512K
• version 112 of the ROM image installed by MacWorks in the Macintosh XL
• version 117 ($75) of the ROM in the Macintosh Plus and
Macintosh 512K enhanced
• version 118 ($76) of the ROM in the Macintosh SE
• version 120 ($78) of the ROM in the Macintosh II
• version 1.1 and 2.0 of the Lisa Pascal interfaces and
the assembly-language definitions
• version 2.0 of the MPW Pascal interfaces and
the assembly-language definitions
Some of the RAM-based software is read from the file named System (usually kept in the System Folder). This manual describes the software in the System file whose creation date is May 2, 1984, System file version 3.2 whose creation date is June 4, 1986, and System file version 4.1. In certain cases, a feature can be found in earlier versions of the System file; these cases are noted in the text.
_______________________________________________________________________________
»Compatibility
Version 117 ($75) of the ROM, also known as the 128K ROM, is provided on the Macintosh 512K enhanced and Macintosh Plus.
Note: A partially upgraded Macintosh 512K is identical to the Macintosh
512K enhanced, while a completely upgraded Macintosh 512K includes
all the features of the Macintosh Plus.
Version 105 ($69) of the ROM (the version described in the first three volumes of Inside Macintosh), also known as the 64K ROM, is provided on the Macintosh 128K and 512K.
Most applications written for the 64K ROM run without modification on machines equipped with the 128K ROM. Applications that use the routines and data structures found in the 128K ROM, however, may not function on machines equipped with the 64K ROM.
Programmers may wish to determine which version of the ROM is installed in order to take advantage of the features of the 128K ROM whenever possible. You can do this by checking the ROM version number returned by the Operating System Utility procedure Environs; if the version number is greater than or equal to 117 ($75), it’s safe to use the routines and data structures described in this volume.
Assembly-language note: A faster way of determining whether the 128K ROM
is present is to examine the global variable Rom85
(a word); it’s positive (that is, the high-order
bit is 0) if the 128K ROM is installed.
_______________________________________________________________________________
»A HORSE OF A DIFFERENT COLOR
_______________________________________________________________________________
On an innovative system like the Macintosh, programs don’t look quite the way they do on other systems. For example, instead of carrying out a sequence of steps in a predetermined order, your program is driven primarily by user actions (such as clicking and typing) whose order cannot be predicted.
You’ll probably find that many of your preconceptions about how to write applications don’t apply here. Because of this, and because of the sheer volume of information in Inside Macintosh, it’s essential that you read the Road Map chapter. It will help you get oriented and figure out where to go next.
_______________________________________________________________________________
»THE STRUCTURE OF A TYPICAL CHAPTER
_______________________________________________________________________________
Most chapters of Inside Macintosh have the same structure, as described below. Reading through this now will save you a lot of time and effort later on. It contains important hints on how to find what you’re looking for within this vast amount of technical documentation.
Every chapter begins with a very brief description of its subject and a list of what you should already know before reading that chapter. Then there’s a section called, for example, “About the Window Manager”, which gives you more information about the subject, telling you what you can do with it in general, elaborating on related user interface guidelines, and introducing terminology that will be used in the chapter. This is followed by a series of sections describing important related concepts and background information; unless
they’re noted to be for advanced programmers only, you’ll have to read them in order to understand how to use the routines described later.
Before the routine descriptions themselves, there’s a section called, for example, “Using the Window Manager”. It introduces you to the routines, telling you how they fit into the general flow of an application program and, most important, giving you an idea of which ones you’ll need to use. Often you’ll need only a few routines out of many to do basic operations; by reading this section, you can save yourself the trouble of learning routines you’ll never use.
Then, for the details about the routines, read on to the next section. It gives the calling sequence for each routine and describes all the parameters, effects, side effects, and so on.
Following the routine descriptions, there may be some sections that won’t be of interest to all readers. Usually these contain information about advanced techniques, or behind the scenes details for the curious.
For review and quick reference, each chapter ends with a summary of the subject matter, including the entire Pascal interface and a separate section for assembly-language programmers.
_______________________________________________________________________________
»CONVENTIONS
_______________________________________________________________________________
The following notations are used in Inside Macintosh to draw your attention to particular items of information:
Reader’s guide: Advice to you, the reader, that will help you decide whether
or not you need to understand the material in a specific
chapter or section.
Note: An item of technical information that you may find interesting or useful.
Warning: A point you need to be cautious about
Assembly-language note: Information of interest to assembly-language
programmers only. For a discussion of Macintosh
assembly-language programming, see the chapter
“Using Assembly Language”.
64K ROM note: A note that points out some difference between the 64K ROM
and 128K ROM.
[Not in ROM] Routines marked with the notation [Not in ROM] are not part of
the Macintosh ROM. Depending on which System file the user has
and on how complete the interfaces are in the development system
you’re using, these routines may or may not be available.
They’re available with Version 4.1 and later of the Macintosh
System file and in programs developed with the Macintosh
Programmer’s Workshop.
[Macintosh II] Routines marked with the name or names of specific models
work only on those machines.
A Road Map
_______________________________________________________________________________
A ROAD MAP
_______________________________________________________________________________
About This Chapter
Overview of the Software
The Toolbox and Other High-Level Software
The Operating System and Other Low-Level Software
A Simple Example Program
Where to Go From Here
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
This chapter introduces you to the “inside” of Macintosh: the Operating System and User Interface Toolbox routines that your application program will call. It will help you figure out which software you need to learn more about and how to proceed with the rest of the Inside Macintosh documentation. To orient you to the software, it presents a simple example program.
_______________________________________________________________________________
»OVERVIEW OF THE SOFTWARE
_______________________________________________________________________________
The routines available for use in Macintosh programs are divided according to function, into what are in most cases called “managers” of the feature that they support. As shown in Figure 1, most are part of either the Operating System or the User Interface Toolbox and are in the Macintosh ROM.
The Operating System is at the lowest level; it does basic tasks such as input and output, memory management, and interrupt handling. The User Interface Toolbox is a level above the Operating System; it helps you implement the standard Macintosh user interface in your application. The Toolbox calls the Operating System to do low-level operations, and you’ll also call the Operating System directly yourself.
RAM-based software is available as well. In most cases this software performs specialized operations (such as floating-point arithmetic) that aren’t integral to the user interface but may be useful to some applications.
_______________________________________________________________________________
»The Toolbox and Other High-Level Software
The Macintosh User Interface Toolbox provides a simple means of constructing application programs that conform to the standard Macintosh user interface. By offering a common set of routines that every application calls to implement the user interface, the Toolbox not only ensures familiarity and consistency for the user but also helps reduce the application’s code size and development time. At the same time, it allows a great deal of flexibility: An application can use its own code instead of a Toolbox call wherever appropriate, and can define its own types of windows, menus, controls, and desk accessories.
Figure 2 shows the various parts of the Toolbox in rough order of their relative level. There are many interconnections between these parts; the higher ones often call those at the lower levels. A brief description of each part is given below, to help you figure out which ones you’ll need to learn more about. Details are given in the Inside Macintosh chapter on that part of the Toolbox. The basic Macintosh terms used below are explained in Macintosh, the owner’s guide.
To keep the data of an application separate from its code, making the data easier to modify and easier to share among applications, the Toolbox includes the Resource Manager. The Resource Manager lets you, for example, store menus separately from your code so that they can be edited or translated without requiring recompilation of the code. It also allows you to get standard data, such as the I-beam pointer for inserting text, from a shared system file. When you call other parts of the Toolbox that need access to the data, they call the Resource Manager. Although most applications never need to call the Resource Manager directly, an understanding of the concepts behind it is essential because they’re basic to so many other operations.
Figure 1–Overview
Figure 2–Parts of the Toolbox
Graphics are an important part of every Macintosh application. All graphic operations on the Macintosh are performed by QuickDraw. To draw something on the screen, you’ll often call one of the other parts of the Toolbox, but it will in turn call QuickDraw. You’ll also call QuickDraw directly, usually to draw inside a window, or just to set up constructs like rectangles that you’ll need when making other Toolbox calls. QuickDraw’s underlying concepts, like those of the Resource Manager, are important for you to understand.
Graphics include text as well as pictures. To draw text, QuickDraw calls the Font Manager, which does the background work necessary to make a variety of character fonts available in various sizes and styles. Unless your application includes a font menu, you need to know only a minimal amount about the Font Manager.
An application decides what to do from moment to moment by examining input from the user in the form of mouse and keyboard actions. It learns of such actions by repeatedly calling the Toolbox Event Manager (which in turn calls another, lower-level Event Manager in the Operating System). The Toolbox Event Manager also reports occurrences within the application that may require a response, such as when a window that was overlapped becomes exposed and needs to be redrawn.
All information presented by a standard Macintosh application appears in windows. To create windows, activate them, move them, resize them, or close them, you’ll call the Window Manager. It keeps track of overlapping windows, so you can manipulate windows without concern for how they overlap. For example, the Window Manager tells the Toolbox Event Manager when to inform your application that a window has to be redrawn. Also, when the user presses the mouse button, you call the Window Manager to learn which part of which window it was pressed in, or whether it was pressed in the menu bar or a desk accessory.
Any window may contain controls, such as buttons, check boxes, and scroll bars. You can create and manipulate controls with the Control Manager. When you learn from the Window Manager that the user pressed the mouse button inside a window containing controls, you call the Control Manager to find out which control it was pressed in, if any.
A common place for the user to press the mouse button is, of course, in the menu bar. You set up menus in the menu bar by calling the Menu Manager. When the user gives a command, either from a menu with the mouse or from the keyboard with the Command key, you call the Menu Manager to find out which command was given.
To accept text typed by the user and allow the standard editing capabilities, including cutting and pasting text within a document via the Clipboard, your application can call TextEdit. TextEdit also handles basic formatting such as word wraparound and justification. You can use it just to display text if you like.
When an application needs more information from the user about a command, it presents a dialog box. In case of errors or potentially dangerous situations, it alerts the user with a box containing a message or with sound from the Macintosh’s speaker (or both). To create and present dialogs and alerts, and find out the user’s responses to them, you call the Dialog Manager.
Every Macintosh application should support the use of desk accessories. The user opens desk accessories through the Apple menu, which you set up by calling the Menu Manager. When you learn that the user has pressed the mouse button in a desk accessory, you pass that information on to the accessory by calling the Desk Manager. The Desk Manager also includes routines that you must call to ensure that desk accessories work properly.
As mentioned above, you can use TextEdit to implement the standard text editing capability of cutting and pasting via the Clipboard in your application. To allow the use of the Clipboard for cutting and pasting text or graphics between your application and another application or a desk accessory, you need to call the Scrap Manager.
Some generally useful operations such as fixed-point arithmetic, string manipulation, and logical operations on bits may be performed with the Toolbox Utilities.
The final part of the Toolbox, the Package Manager, lets you use RAM-based software called packages. The Standard File Package will be called by every application whose File menu includes the standard commands for saving and opening documents; it presents the standard user interface for specifying the document. Two of the Macintosh packages can be seen as extensions to the Toolbox Utilities: The Binary-Decimal Conversion Package converts integers to decimal strings and vice versa, and the International Utilities Package gives you access to country-dependent information such as the formats for numbers, currency, dates, and times.
_______________________________________________________________________________
»The Operating System and Other Low-Level Software
The Macintosh Operating System provides the low-level support that applications need in order to use the Macintosh hardware. As the Toolbox is your program’s interface to the user, the Operating System is its interface to the Macintosh.
The Memory Manager dynamically allocates and releases memory for use by applications and by the other parts of the Operating System. Most of the memory that your program uses is in an area called the heap; the code of the program itself occupies space in the heap. Memory space in the heap must be obtained through the Memory Manager.
The Segment Loader is the part of the Operating System that loads application code into memory to be executed. Your application can be loaded all at once, or you can divide it up into dynamically loaded segments to economize on memory usage. The Segment Loader also serves as a bridge between the Finder and your application, letting you know whether the application has to open or print a document on the desktop when it starts up.
Low-level, hardware-related events such as mouse-button presses and keystrokes are reported by the Operating System Event Manager. (The Toolbox Event Manager then passes them to the application, along with higher-level, software-generated events added at the Toolbox level.) Your program will ordinarily deal only with the Toolbox Event Manager and will rarely call the Operating System Event Manager directly.
File I/O is supported by the File Manager, and device I/O by the Device Manager. The task of making the various types of devices present the same interface to the application is performed by specialized device drivers. The Operating System includes three built-in drivers:
• The Disk Driver controls data storage and retrieval on 3 1/2-inch disks.
• The Sound Driver controls sound generation, including music composed
of up to four simultaneous tones.
• The Serial Driver reads and writes asynchronous data through the two
serial ports, providing communication between applications and serial
peripheral devices such as a modem or printer.
The above drivers are all in ROM; other drivers are RAM-based. There’s a Serial Driver in RAM as well as the one in ROM, and there’s a Printer Driver in RAM that enables applications to print information on any variety of printer via the same interface (called the Printing Manager). The AppleTalk Manager is an interface to a pair of RAM drivers that enable programs to send and receive information via an AppleTalk network. More RAM drivers can be added independently or built on the existing drivers (by calling the routines in those drivers). For example, the Printer Driver was built on the Serial Driver, and a music driver could be built on the Sound Driver.
The Macintosh video circuitry generates a vertical retrace interrupt 60 times a second. An application can schedule routines to be executed at regular intervals based on this “heartbeat” of the system. The Vertical Retrace Manager handles the scheduling and execution of tasks during the vertical retrace interrupt.
If a fatal system error occurs while your application is running, the System Error Handler assumes control. The System Error Handler displays a box containing an error message and provides a mechanism for the user to start up the system again or resume execution of the application.
The Operating System Utilities perform miscellaneous operations such as getting the date and time, finding out the user’s preferred speaker volume and other preferences, and doing simple string comparison. (More sophisticated string comparison routines are available in the International Utilities Package.)
Finally, there are three Macintosh packages that perform low-level operations:
the Disk Initialization Package, which the Standard File Package calls to initialize and name disks; the Floating-Point Arithmetic Package, which supports extended-precision arithmetic according to IEEE Standard 754; and the Transcendental Functions Package, which contains trigonometric, logarithmic, exponential, and financial functions, as well as a random number generator.
_______________________________________________________________________________
»A SIMPLE EXAMPLE PROGRAM
_______________________________________________________________________________
To illustrate various commonly used parts of the software, this section presents an extremely simple example of a Macintosh application program. Though too simple to be practical, this example shows the overall structure that every application program will have, and it does many of the basic things every application will do. By looking it over, you can become more familiar with the software and see how your own program code will be structured.
The example program’s source code is shown at the end of this chapter, which begins at the end of this section. A lot of comments are included so that you can see which part of the Toolbox or Operating System is being called and what operation is being performed. These comments, and those that follow below, may contain terms that are unfamiliar to you, but for now just read along to get the general idea. All the terms are explained at length within Inside Macintosh. If you want more information right away, you can look up the terms in the Glossary or the Index.
The application, called Sample, displays a single, fixed-size window in which the user can enter and edit text (see Figure 3). It has three menus: the standard Apple menu, from which desk accessories can be chosen; a File menu, containing only a Quit command; and an Edit menu, containing the standard editing commands Undo, Cut, Copy, Paste, and Clear. The Edit menu also includes the standard keyboard equivalents for Undo, Cut, Copy, and Paste: Command-Z,
X, C, and V, respectively. The Backspace key may be used to delete, and Shift-clicking will extend or shorten a selection. The user can move the document window around the desktop by dragging it by its title bar.
Figure 3–The Sample Application
The Undo command doesn’t work in the application’s document window, but it and all the other editing commands do work in any desk accessories that allow them
(the Note Pad, for example). Some standard features this simple example doesn’t support are as follows:
• Text cannot be cut (or copied) and pasted between the document
and a desk accessory.
• The pointer remains an arrow rather than changing to an I-beam
within the document.
• Except for Undo, editing commands aren’t dimmed when they don’t
apply (for example, Cut or Copy when there’s no text selection).
The document window can’t be closed, scrolled, or resized. Because the File menu contains only a Quit command, the document can’t be saved or printed. Also, the application doesn’t have “About Sample...” as the first command in its Apple menu, or a Hide/Show Clipboard command in its Edit menu (for displaying cut or copied text).
In addition to the code shown at the end of this chapter, the Sample application has a resource file that includes the data listed below. The program uses the numbers in the second column to identify the resources; for example, it makes a Menu Manager call to get menu number 128 from the resource file.
Resource Resource ID Description
Menu 128 Menu with the apple symbol as its title
and no commands in it
Menu 129 File menu with one command, Quit, with
keyboard equivalent Command-Q
Menu 130 Edit menu with the commands Undo (dimmed),
Cut, Copy, Paste, and Clear, in that order,
with the standard keyboard equivalents and
with a dividing line between Undo and Cut
Window 128 Document window without a size box;
template top left corner of (50,40) on QuickDraw’s
coordinate plane, bottom right corner of
(300,450); title “Sample”; no close box
Each menu resource also contains a “menu ID” that’s used to identify the menu when the user chooses a command from it; for all three menus, this ID is the same as the resource ID.
Note: To create a resource file with the above contents, you can use the
Resource Editor or any similar program that may be available on the
development system you’re using.
The program starts with a USES clause that specifies all the necessary Pascal interface files. (The names shown are for the Lisa Workshop development system, and may be different for other systems.) This is followed by declarations of some useful constants, to make the source code more readable. Then there are a number of variable declarations, some having simple Pascal data types and others with data types defined in the interface files (like Rect and WindowPtr). Variables used in the program that aren’t declared here are global variables defined in the interface to QuickDraw.
The variable declarations are followed by two procedure declarations: SetUpMenus and DoCommand. You can understand them better after looking at the main program and seeing where they’re called.
The program begins with a standard initialization sequence. Every application will need to do this same initialization (in the order shown), or something close to it.
Additional initialization needed by the program follows. This includes setting up the menus and the menu bar (by calling SetUpMenus) and creating the application’s document window (reading its description from the resource file and displaying it on the screen).
The heart of every application program is its main event loop, which repeatedly calls the Toolbox Event Manager to get events and then responds to them as appropriate. The most common event is a press of the mouse button; depending on where it was pressed, as reported by the Window Manager, the sample program may execute a command, move the document window, make the window active, or pass the event on to a desk accessory. The DoCommand procedure takes care of executing a command; it looks at information received by the Menu Manager to determine which command to execute.
Besides events resulting directly from user actions such as pressing the mouse button or a key on the keyboard, events are detected by the Window Manager as a side effect of those actions. For example, when a window changes from active to inactive or vice versa, the Window Manager tells the Toolbox Event Manager to report it to the application program. A similar process happens when all or part of a window needs to be updated (redrawn). The internal mechanism in each case is invisible to the program, which simply responds to the event when notified.
The main event loop terminates when the user takes some action to leave the program—in this case, when the Quit command is chosen.
That’s it! Of course, the program structure and level of detail will get more complicated as the application becomes more complex, and every actual application will be more complex than this one. But each will be based on the structure illustrated here.
PROGRAM Sample;
{ Sample -- A small sample application written by Macintosh User }
{ Education. It displays a single, fixed-size window in which the }
{ user can enter and edit text. }
{ The following two compiler commands are required }
{ for the Lisa Workshop. }
{$X-} {turn off automatic stack expansion}
{$U-} {turn off Lisa libraries}
{ The USES clause brings in the units containing the Pascal interfaces. }
{ The $U expression tells the compiler what file to look in for the }
{ specified unit. }
USES {$U Obj/MemTypes } MemTypes, {basic Memory Manager data types}
{$U Obj/QuickDraw} QuickDraw, {interface to QuickDraw}
{$U Obj/OSIntf } OSIntf, {interface to the Operating System}
{$U Obj/ToolIntf } ToolIntf; {interface to the Toolbox}
CONST
appleID = 128; {resource IDs/menu IDs for Apple, File, and Edit menus}
fileID = 129;
editID = 130;
appleM = l; {index for each menu in myMenus (array of menu handles)}
fileM = 2;
editM = 3;
menuCount = 3; {total number of menus}
windowID = 128; {resource ID for application's window}
undoCommand = l; {menu item numbers identifying commands in Edit menu}
cutCommand = 3;
copyCommand = 4;
pasteCommand = 5;
clearCommand = 6;
VAR
myMenus: ARRAY [l..menuCount] OF MenuHandle; {array of handles to the menus}
dragRect: Rect; {rectangle used to mark boundaries for}
{dragging window}
txRect: Rect; {rectangle for text in application window}
textH: TEHandle; {handle to information about the text}
theChar: CHAR; {character typed on the keyboard or keypad}
extended: BOOLEAN; {TRUE if user is Shift-clicking}
doneFlag: BOOLEAN; {TRUE if user has chosen Quit command}
myEvent: EventRecord; {information about an event}
wRecord: WindowRecord; {information about the application window}
myWindow: WindowPtr; {pointer to wRecord}
whichWindow: WindowPtr; {pointer to window in which mouse button}
{was pressed}
PROCEDURE SetUpMenus;
{ Set up menus and menu bar }
VAR
i: INTEGER;
BEGIN
{ Read menu descriptions from resource file into memory and store handles }
{ in myMenus array }
myMenus[appleM] := GetMenu(appleID); {read Apple menu from resource file}
AddResMenu(myMenus[appleM], 'DRVR'); {add desk accessory names to}
{Apple menu}
myMenus[fileM] := GetMenu(fileID); {read File menu from resource file}
myMenus[editM] := GetMenu(editID); {read Edit menu from resource file}
FOR i := l TO menuCount DO InsertMenu(myMenus[i], O); {install menus in}
{menu bar }
DrawMenuBar; {and draw menu bar}
END; {of SetUpMenu}
PROCEDURE DoCommand(mResult: LONGINT);
{ Execute command specified by mResult, the result of MenuSelect }
VAR
theItem: INTEGER; {menu item number from mResult low-order word)
theMenu: INTEGER; {menu number from mResult high-order word}
name: Str255; {desk accessory name}
temp: INTEGER;
BEGIN
theItem := LoWord(mResult); {call Toolbox Utility routines to set }
theMenu := HiWord(mResult); { menu item number and menu number}
CASE theMenu OF {case on menu ID}
appleID:
BEGIN {call Menu Manager to get desk accessory }
GetItem(myMenus[appleM], theItem, name); { name, and call Desk }
{ Manager to open }
temp := OpenDeskAcc(name); { accessory (OpenDeskAcc
{ result not used)}
SetPort(myWindow); {call QuickDraw to restore application }
END; {of appleID} { window as grafPort to draw in (may have }
{ been changed during OpenDeskAccc) }
fileID: doneFlag := TRUE; {quit (main loop repeats until}
{doneFlag is TRUE)}
editID:
BEGIN {call Desk Manager to handle editing}
{command if desk accessory window is}
IF NOT SystemEdit(theItem - 1) { the active window}
THEN {application window is the active window}
CASE theItem OF {case on menu item (command) number}
cutCommand: TECut(textH); {call TextEdit to handle command}
copyCommand: TECopy(textH);
pasteCommand: TEPaste(textH);
clearCommand: TEDelete(textH);
END; {of item case}
END; {of editID}
END; {of menu case} {to indicate completion of command, call }
HiliteMenu(O); { Menu Manager to unhighlight menu title }
{ (highlighted by MenuSelect) }
END; {of DoCommand}
BEGIN {main program}
{ Initialization }
InitGraf(@thePort); {initialize QuickDraw}
InitFonts; {initialize Font Manager}
FlushEvents(everyEvent, O); {call OS Event Manager to discard}
{ any previous events}
InitWindows; {initialize Window Manager}
InitMenus; {initialize Menu Manager}
TEInit; {initialize TextEdit}
InitDialogs(NIL); {initialize Dialog Manager}
InitCursor; {call QuickDraw to make cursor (pointer)}
{ an arrow}
SetUpMenus; {set up menus and menu bar}
WITH screenBits.bounds DO {call QuickDraw to set dragging boundaries;}
{ ensure at least 4 by 4 pixels will remain}
SetRect(dragRect, 4, 24, right - 4, bottom - 4); { visible}
doneFlag := FALSE; {flag to detect when Quit command is chosen}
myWindow := GetNewWindow(windowID, @wRecord, POINTER( - l)); {put up }
{application}
{window}
SetPort(myWindow); {call QuickDraw to set current grafPort }
{ to this window rectangle for text in}
txRect := thePort^.portRect; { window; call QuickDraw to bring }
InsetRect(txRect, 4, 0); { it in 4 pixels from left and right }
{ edges of window }
textH := TENew(txRect, txRect); {call TextEdit to prepare for }
{ receiving text}
{ Main event loop }
REPEAT {call Desk Manager to perform any periodic ) SystemTask;
{ actions defined for desk accessories}
TEIdle(textH); {call TextEdit to make vertical bar blink}
IF GetNextEvent(everyEvent, myEvent)
{call Toolbox Event Manager to get the next }
THEN { event that the application should handle}
CASE myEvent.what OF {case on event type}
mouseDown: {mouse button down: call Window Manager}
{ to learn where}
CASE FindWindow(myEvent.where, whichWindow) OF
inSysWindow: {desk accessory window: call Desk Manager}
{to handle it}
SystemClick {myEvent,whichWindow); inMenuBar:
{menu bar: call Menu Manager to learn }
{ which command, then execute it }
DoCommand(MenuSelect(myEvent.where));
inDrag: {title bar: call Window Manager to drag}
DragWindow(whichWindow, myEvent.where, dragRect);
inContent: {body of application window: }
BEGIN { call Window Manager to check whether }
IF whichWindow <> FrontWindow
{ it's the active window and make it }
THEN
SelectWindow(whichWindow) { active if not}
ELSE
BEGIN {it's already active: call QuickDraw to }
{ convert to window coordinates for }
{ TEClick, use Toolbox Utility BitAnd to}
{ test for Shift }
GlobalToLocal(myEvent.where);
extended := BitAnd(myEvent.modifiers, shiftKey) <> O;
TEClick(myEvent.where, extended, textH);
{ key down, and call TextEdit}
END; { to process the event}
END; {of inContent}
END; {of mouseDown}
keyDown, autoKey: {key pressed once or held down to repeat}
BEGIN
theChar := CHR(BitAnd(myEvent.message, charCodeMask));
{get the character}
IF BitAnd(myEvent.modifiers, cmdKey) <> 0
THEN {if Command key down, call Menu }
DoCommand(MenuKey(theChar)) { Manager to learn which command,}
ELSE { then execute it; else pass }
TEKey(theChar, textH); { character to TextEdit}
END;
activateEvt:
BEGIN
IF BitAnd(myEvent.modifiers, activeFlag) <> 0 THEN
{application window is becoming active:}
BEGIN { call TextEdit to highlight selection}
TEActivate(textH);
{ or display blinking vertical bar, and call}
DisableItem(myMenus[editM], undoCommand);
{ Menu Manager to disable Undo}
END {(since application doesn't support Undo)}
ELSE
BEGIN {application window is becoming inactive: }
TEDeactivate(textH);
{ unhighlight selection or remove blinking}
{ vertical bar, and enable Undo (since desk}
{ accessory may support it)}
EnableItem(myMenus[editM], undoCommand);
END;
END; {of activateEvt}
updateEvt: {window appearance needs updating}
BEGIN
BeginUpdate(WindowPtr(myEvent.message));
{call Window Manager to begin update}
EraseRect(thePort^.portRect);
{Call QuickDraw to erase text area}
TEUpdate(thePort^.portRect, textH);
{call TextEdit to update the text}
EndUpdate(WindowPtr(myEvent.message));
{call Window Manager to end update}
END; {of updateEvt}
END; {of event case}
UNTIL doneFlag;
END.
_______________________________________________________________________________
»WHERE TO GO FROM HERE
_______________________________________________________________________________
This section contains important directions for every reader of Inside Macintosh. It will help you figure out which chapters to read next.
The Inside Macintosh chapters are ordered in such a way that you can follow it if you read through it sequentially. Forward references are given wherever necessary to any additional information that you’ll need in order to fully understand what’s being discussed. Special-purpose information that can possibly be skipped is indicated as such. Most likely you won’t need to read everything in each chapter and can even skip entire chapters.
You should begin by reading the following chapters:
1. The Macintosh User Interface Guidelines. All Macintosh
applications should follow these guidelines to ensure that the end
user is presented with a consistent, familiar interface.
2. Macintosh Memory Management: An Introduction.
3. Using Assembly Language, if you’re programming in assembly language.
Depending on the debugging tools available on the development
system you’re using, it may also be helpful or necessary for high-level
language programmers to read this chapter. You’ll also have to read it
if you’re creating your own development system and want to know how to
write interfaces to the routines.
4. The chapters describing the parts of the Toolbox that deal with the
fundamental aspects of the user interface: the Resource Manager,
QuickDraw, the Toolbox Event Manager, the Window Manager, and the
Menu Manager.
Read the other chapters if you’re interested in what they discuss, which you should be able to tell from the overviews in this “road map” and from the introductions to the chapters themselves. Each chapter’s introduction will also tell you what you should already know before reading that chapter.
When you’re ready to try something out, refer to the appropriate documentation for the development system you’ll be using.
Compatibility Guidelines
_______________________________________________________________________________
COMPATIBILITY GUIDELINES
_______________________________________________________________________________
About This Chapter
Compatibility
General Guidelines
Memory
Assembly Language
Hardware
Determining the Features of a Machine
Localization
¿Pero, Se Habla Español?
Non-Roman Writing Systems
Applications in a Shared Environment
Summary of Compatibility Guidelines
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
Compatibility is a concern for anyone writing software. For some programmers, it’s a concern because they want to write software that will run, with little or no modification, on all versions of the Macintosh. Other programmers want to take advantage of particular software and hardware features; they need to know where and when these features are available.
This chapter gives guidelines for making it more likely that your program will run on different versions, present and future, of the Macintosh. It also gives tips for writing software that can be easily modified for use in other countries. Finally, it explains how to determine what features are available on a given machine.
_______________________________________________________________________________
»COMPATIBILITY
_______________________________________________________________________________
The key to compatibility is not to depend on things that may change. Inside Macintosh contains hundreds of warnings where information is likely to change; all of these warnings can be summarized by a single rule: use global variable names and system calls, rather than addresses and numeric values.
At the most basic level, all of the software and hardware components of the Macintosh—each line of ROM code, each RAM memory location, each hardware device—are represented by numbers. Symbolic names have been defined for virtually every routine, variable, data structure, memory location, and hardware device that your application will need to use. Use of these names instead of the actual numbers will simplify the process of updating your application when the numbers change.
_______________________________________________________________________________
»General Guidelines
Any field that’s marked in Inside Macintosh as “not used” should be considered
“reserved by Apple” and usually be left 0.
While Inside Macintosh gives the structure of low-level data structures (for instance, file control blocks, volume control blocks, and system queues), it’s best not to access or manipulate these structures directly; whenever possible, use the routines provided for doing this.
You shouldn’t rely on system resources being in RAM; on the Macintosh Plus, Macintosh SE, and Macintosh II, certain system resources are in ROM. Don’t assume, for example, that you can regain RAM space by releasing system resources.
A variety of different keyboards are available for the Macintosh; you should always read ASCII codes rather than key codes.
Don’t count on the alternate (page 2) sound or video buffers. On the Macintosh II, you can determine the number of video pages and switch between them; for details, see the Graphics Devices chapter.
To be compatible with printers connected directly to the Macintosh or via AppleTalk, use either the Printing Manager or the Printer Driver’s control calls for text-streaming and bitmap-printing (as documented in Inside
Macintosh). Don’t send ASCII codes directly to the Printer Driver. In general, you should avoid using printer-specific features and should not access the fields of the print record directly.
_______________________________________________________________________________
»Memory
You shouldn’t depend on either the system or application heap zones starting at certain addresses. Use the global variable ApplZone to find the application heap and the variable SysZone to locate the system heap. You should not count on the application heap zone starting at an address less than 65536; in other words, don’t expect a system heap that’s smaller than 64K in size.
Space in the system heap is extremely limited. In general, avoid using the system heap; if you must, allocate only very small objects (about 32 bytes or less). If you need memory that won’t be reinitialized when your application ends, allocate it with an 'INIT' resource; for details, see the System Resource File chapter.
The high-order byte of a master pointer contains flags used by the Memory Manager. In the future, all 32 bits of the pointer may be needed, in which case the flags byte will have to be moved elsewhere. For this reason, you should never set or clear these flags directly but should instead use the Memory Manager routines HPurge, HNoPurge, HLock, HUnlock, HSetRBit, HClrRBit, HGetState, and HSetState.
You should allow for a variety of RAM memory sizes. While 128K, 512K, 1 MB, and 2 MB are standard sizes, many other RAM configurations are possible.
NIL handles (handles whose value is zero) are common bugs; they typically come from unsuccessful GetResource calls and often result (eventually) in address errors. The 68020 does not give address errors when accessing data, so be sure to test your code for NIL handles and null pointers.
_______________________________________________________________________________
»Assembly Language
In general, you shouldn’t use 68000 instructions that depend on supervisor mode; these include instructions that modify the contents of the Status Register (SR). Programmers typically modify the SR only as a means of changing the Condition Code Register (CCR) half of the register; an instruction that addresses the CCR directly will work fine instead. You should also not use the User Stack Pointer or turn interrupts on and off.
Timing loops that depend on the clock speed of a particular processor will fail when faster processors are introduced. You can use the Operating System Utility procedure Delay for timing, or you can check the contents of the global variable Ticks. For more precise timings, you can use the Time Manager (taking advantage of the VIA timers). Several global variables also contain useful timing information; they're described in the Start Manager chapter.
If you wish to handle your own exceptions (thereby relying on the position of data in the exception’s local stack frame), be aware that exception stack frames vary within the 68000 family.
In particular, don't use the TRAP instruction. Also, the TAS instruction, which uses a special read-modify-write memory cycle, is not supported by the Macintosh SE and Macintosh II hardware.
A memory management unit in the Macintosh II may prevent code from writing to addresses within code segments. Also, the 68020 caches code as it’s encountered. Your data blocks should be allocated on the stack or in heap blocks separate from the code, and your code should not modify itself.
Note: You can determine which microprocessor is installed by calling
the SysEnvirons function; it’s described below.
The Floating-Point Arithmetic and Transcendental Functions Packages have been extended to take advantage of the MC68881 numerics coprocessor; using the routines in these packages will ensure compatibility on all current and future versions of the Macintosh. (For details on these packages, see the Floating-Point Arithmetic and Transcendental Functions Packages chapter.)
Memory locations below the system heap that aren’t documented may not be available for use in future systems. Also, microprocessors in the 68000 family use the exception vectors in locations $0 through $FF in different ways. In general, don’t depend on any global variable that isn’t documented in Inside Macintosh.
Don’t store information in the application parameters area (the 32 bytes between the application globals and the jump table); this space is reserved for use by Apple.
Don’t depend on the format of the trap dispatch table. Use the Operating System Utility routines GetTrapAddress and SetTrapAddress to access the trap dispatch table. You should also not use unassigned entries in the trap table, or any other unused low memory location.
Inside Macintosh documents the values returned by register-based routines;
don’t depend on return values that aren’t documented here.
_______________________________________________________________________________
»Hardware
As a general rule, you should never address hardware directly; whenever possible, use the routines provided by the various device drivers. The addresses of memory-mapped hardware (like the VIA1, VIA2, SCC, and IWM) are always subject to change, and direct access to such hardware may not be possible. For instance, the Macintosh II memory-management unit may prevent access to memory-mapped hardware. If you must access the hardware directly, get the base address of the device from the appropriate global variable; see the Macintosh Family Hardware Reference Manual for details.
Warning: Although there’s a global variable that contains the SCSI base
address, you should use the SCSI Manager; this is especially
important with regard to asynchronous operation.
Note: Copy-protection schemes that rely on particular hardware
characteristics are subject to failure when the hardware changes.
You should avoid writing directly to the screen; use QuickDraw whenever possible. If you must write directly to the screen, don’t “hard code” the screen size and location. The global variable ScreenBits contains a bit map corresponding to the screen being used. ScreenBits.bounds is the size of the screen, ScreenBits.baseAddr is the start of the screen, and ScreenBits.rowBytes gives the offset between rows.
Warning: The screen size can exceed 32K; use long word values in screen
calculations. Also, the screen may be more than one pixel in
depth; see the QuickDraw chapter for details.
There are many sizes of disks for the Macintosh from Apple, and more from
third-party vendors. Use the Standard File Package and File Manager calls to determine the number and size of disk drives.
_______________________________________________________________________________
»DETERMINING THE FEATURES OF A MACHINE
_______________________________________________________________________________
As the Macintosh family grows, applications need a reliable and comprehensive way of determining what software and hardware features are available on a given machine. Although the Operating System Utilities routine Environs indicates the type of machine and ROM version running, it provides no help in distinguishing between the plethora of different software feature sets and hardware configurations that an application may encounter.
A new function, SysEnvirons, provides detailed information about what software functionality (Color QuickDraw, as an example) is available, as well as what hardware devices (processors, peripherals, and so on) are installed or connected.
All of the Toolbox Managers must be initialized before calling SysEnvirons.
In addition, the AppleTalk Manager routine MPPOpen must be called if the driver version information in atDrvrVersNum is desired. SysEnvirons is not intended for use by device drivers, but can be called from desk accessories. (It does not assume that register A5 has been properly set up.)
FUNCTION SysEnvirons (versionReqested: INTEGER;
VAR theWorld: SysEnvRec) : OSErr; [Not in ROM]
X-Ref: Technical Note #129
Trap macro _SysEnvirons
On entry A0: sysEnvRec (pointer)
D0: versReqested (word)
On exit A0: sysEnvRec (pointer)
D0: result code (word)
Result codes noErr No error
envNotPresent SysEnvirons trap not present
envBadVers Nonpositive version number passed
envVersTooBig Requested version of SysEnvirons
call not available
In theWorld, SysEnvirons returns a system environment record describing the features of the machine. Designed to be extendible, SysEnvirons will be updated as new features are added, and the system environment record that’s returned will be expanded. System File 4.1 contains version 1 of SysEnvirons; subsequent versions will be incremented by 1.
The system environment record for version 1 of SysEnvirons contains the following fields:
TYPE SysEnvRec = RECORD
environsVersion: INTEGER;
machineType: INTEGER;
systemVersion: INTEGER;
processor: INTEGER;
hasFPU: BOOLEAN;
hasColorQD: BOOLEAN;
keyBoardType: INTEGER;
atDrvrVersNum: INTEGER;
sysVRefNum: INTEGER
END;
New versions of the call will add fields to this record. To distinguish between different versions of the call, and thereby between the different sizes of records they return, SysEnvirons returns its version number in the environsVersion field. If you request version 2, for instance, but only version 1 is available, the environsVersion field will contain the value 1, and the result code envVersTooBig will be returned. This tells you that only the information for version 1 has been returned in SysEnvRec.
The MPW 2.0 interface files contain code, or “glue”, for System file versions earlier than 4.1, as well as for the 64K and the Macintosh XL ROMs. The glue checks for the existence of the trap at runtime; if the call does not exist, the glue fills in all fields of the record except systemVersion and returns the result code envNotPresent.
Assembly-language note: As with the MoveHHi procedure, assembly-language
programmers using MPW should link with the glue and
execute
JSR SysEnvirons
If you’re using another development system, refer
to its documentation for details.
The machineType field returns one of the following constants:
CONST envMachUnknown = 0; {new version of Macintosh--not covered }
{ by this version of SysEnvirons}
env512KE = 1; {Macintosh 512K enhanced}
envMacPlus = 2; {Macintosh Plus}
envSE = 3; {Macintosh SE}
envMacII = 4; {Macintosh II}
envMacIIx = 5; {Macintosh IIx}
envMacIIcx = 6; {Macintosh IIcx}
envSE30 = 7; {Macintosh SE/30}
envPortable = 8; {Macintosh Portable}
envMacIIci = 9; {Macintosh IIci}
In addition to these, the glue for SysEnvirons may return one of the following:
CONST envMac = –1; {Macintosh with 64K ROM}
envXL = –2; {Macintosh XL}
The systemVersion field returns the version number of the System file represented as two byte-long numbers, separated by a period. (It is not a fixed point number.) For instance, System 4.1 returns $0410 or 04.10 in this field.
(Applications can use this for compare operations.) If SysEnvirons is called while a system earlier than System 4.1 is running, the glue will return a $0 in this field, and the result code envNotPresent will be returned.
The processor field returns one of the following constants:
CONST envCPUUnknown = 0; {new processor--not yet covered by this }
{ version of SysEnvirons}
env68000 = 1; {MC68000 processor}
env68010 = 2; {MC68010 processor}
env68020 = 3; {MC68020 processor}
env68030 = 4; {MC68030 processor}
The hasFPU field tells whether or not a Motorola MC68881 floating-point coprocessor unit is present. (This field does not apply to third-party memory-mapped coprocessor add-ons.)
The hasColorQD field tells whether or not Color QuickDraw is present. It does not indicate whether or not a color screen is present (high-level QuickDraw calls provide this information).
The keyboardType field returns one of the following constants:
CONST envUnknownKbd = 0; {Macintosh Plus keyboard with keypad}
envMacKbd = 1; {Macintosh keyboard}
envMacAndPad = 2; {Macintosh keyboard and keypad}
envMacPlusKbd = 3; {Macintosh Plus keyboard}
envAExtendKbd = 4; {Apple Extended keyboard}
envStandADBKbd = 5; {Apple Standard keyboard}
envPortADBKbd = 6; {Macintosh Portable keyboard}
envPortISOADBKbd = 7; {Macintosh Portable keyboard (ISO)}
envStdISOADBKbd = 8; {Apple Standard keyboard (ISO)}
envExtISOADBKbd = 9; {Apple Extended keyboard (ISO)}
If the Apple Desktop Bus™ is in use, this field returns the keyboard type of the keyboard on which a keystroke was last made.
ATDrvrVersNum returns the version number of AppleTalk, if it’s been loaded
(that is, if MPPOpen has been called); otherwise, 0 is returned in this field.
SysVRefNum returns the working directory reference number (or volume reference number) of the directory that contains the currently open System file.
_______________________________________________________________________________
»LOCALIZATION
_______________________________________________________________________________
Localization is the process of adapting an application to a specific language and country. By making localization relatively painless, you ensure that international markets are available for your product in the future. You also allow English-speaking users in other countries to buy the U.S. English version of your software and use it with their native languages.
The key to easy localization is to store the country-dependent information used by your application as resources (rather than within the application’s code). This means that text seen by the user can be translated without modifying the code. In addition, storing this information in resources means that your application can be adapted for a different country simply by substituting the appropriate resources.
_______________________________________________________________________________
»¿Pero, Se Habla Español?
Not all languages have the same rules for punctuation, word order, and alphabetizing. In Spanish, questions begin with an upside-down question mark. The roles of commas and periods in numbers are sometimes the reverse of what you may be used to; in many countries, for instance, the number 3,546.98 is rendered 3.546,98.
Laws and customs vary between countries. The elements of addresses don’t always appear in the same order. In some countries, the postal zone code precedes the name of the city, while in other countries the reverse is true. Postal zone codes vary in length and can contain letters as well as numbers. The rules for amortizing mortgages and calculating interest rates vary from country to country—even between Canada and the United States.
Units of measure and standard formats for time and date differ from country to country. For example, “lines per inch” is meaningless in the metric world—that is, almost everywhere. In some countries, the 24-hour clock prevails.
Words aren’t the only things that change from country to country. Telephones and mailboxes, to name just two examples often used in telecommunications programs, don’t look the same in all parts of the world. Either make your graphics culturally neutral, or be prepared to create alternate graphics for various cultures.
Mnemonic shortcuts (such as Command-key equivalents for menu items) that are valid in one language may not be valid in others; be sure all such shortcuts are stored as resources.
Keyboards vary from country to country. Keystrokes that are easily performed with one hand in your own country may require two hands in another. In France and Italy, for instance, typing numerals requires pressing the Shift key.
If you rely on properties of the ASCII code table or use data compression codes that assume a certain number of letters in the alphabet, remember that not all alphabets have the same numbers of characters. Don’t rely on strings having a particular length; translation will make most strings longer. (As an example, the length of Apple manuals has been known to increase as much as 30% in translation.) Also, some languages require two bytes instead of one to store characters.
_______________________________________________________________________________
»Non-Roman Writing Systems
The Script Manager contains routines that allow an application to function correctly with non-Roman scripts (or writing systems). It also contains utility routines for text processing and parsing, which are useful for applications that do a lot of text manipulation. General applications don’t need to call Script Manager routines directly, but can be localized for non-Roman alphabets through such script interface systems as Apple’s Kanji Interface System and Arabic Interface System. (Scripts and script interface systems are described in the Script Manager chapter in this volume.)
The International Utilities Package provides routines for sorting, comparing strings, and specifying currency, measurements, dates, and time. It’s better to use the routines in this package instead of the Operating System Utility routines (which aren’t as accurate and can’t be localized).
You should neither change nor depend upon the system font and system font size. Some non-Roman characters demand higher resolution than Roman characters. On Japanese versions of the Macintosh, for instance, the system font must allow for 16-by-16 pixel characters. You can use the global variables SysFontFam and SysFontSize for determining the system font and system font size.
The Menu Manager uses the system font and the system font size in setting up the height of the menu bar and menu items. Because the system font size can vary, the height of the menu bar can also vary. When determining window placement on the screen, don’t assume that the menu bar height is 20 pixels. Use the global variable MBarHeight for determining the height of the menu bar.
Avoid using too many menus; translation into other languages almost always widens menu titles, forcing some far to the right or even off the screen.
Most Roman fonts for the Macintosh have space above all the letters to allow for diacritical marks as with Ä or Ñ. If text is drawn using a standard font immediately below a dark line, for example, it will appear to be separated from the line by at least one row of blank pixels (for all but a few exceptional characters). Pixels in some non-Roman fonts, on the other hand, extend to the top of the font rectangle, and appear to merge with the preceding line. To avoid character display overlap, applications should leave blank space around text (as in dialog editText or statText items ), or add space between lines of text, as well as before the first line and after the last line of text.
The choice of script (Roman, Japanese, Arabic, and so on) is determined by the fonts selected by the user. If an application doesn’t allow the user to change fonts, or allows the user to select only a global font for the whole document, the user is restricted in the choice and mix of scripts.
If text must be displayed in either uppercase or lowercase, you should call the Script Manager Transliterate routine rather than the UprString routine (which doesn’t handle diacritical marks or non-Roman scripts correctly).
_______________________________________________________________________________
»APPLICATIONS IN A SHARED ENVIRONMENT
_______________________________________________________________________________
A number of new products create environments in which users can share information. Network file servers (like AppleShare™), for instance, make it possible for users to share data, applications, and disk storage space. Multitasking operating systems and programs like MultiFinder can also be considered shared environments, allowing data to be shared between applications.
To operate smoothly in a shared environment, you’ll need to be sensitive to issues like multiple file access, access privileges, and multiple launches. For a complete discussion of how to operate in shared environments, see the File Manager chapter.
_______________________________________________________________________________
»SUMMARY OF COMPATIBILITY GUIDELINES
_______________________________________________________________________________
Data Type
TYPE SysEnvRec = RECORD
environsVersion: INTEGER;
machineType: INTEGER;
systemVersion: INTEGER;
processor: INTEGER;
hasFPU: BOOLEAN;
hasColorQD: BOOLEAN;
keyBoardType: INTEGER;
atDrvrVersNum: INTEGER;
sysVRefNum: INTEGER
END;
_______________________________________________________________________________
Routine
FUNCTION SysEnvirons (versionRequested: INTEGER;
VAR theWorld: SysEnvRec) : OSErr; [Not in ROM]
Result Codes
Name Value Meaning
noErr 0 No error
envNotPresent –5500 SysEnvirons trap not present (System File earlier
than version 4.1); glue returns values for all
fields except systemVersion
envBadVers –5501 A nonpositive version number was passed—no
information is returned
envVersTooBig –5502 Requested version of SysEnvirons call was not
available
_______________________________________________________________________________
Assembly-Language Information
Structure of System Environment Record
environsVersion (word)
machineType (word)
systemVersion (word)
processor (word)
hasFPU (byte)
hasColorQD (byte)
keyBoardType (word)
atDrvrVersNum (word)
sysVRefNum (word)
sysEnvRecSize Size of system environment record
Routine
Trap macro On entry On exit
_SysEnvirons A0: sysEnvRecPtr (ptr) A0: sysEnvRecPtr (ptr)
D0: versRequested (word) D0: result code (word)
Variables
ApplZone Address of application heap zone
MBarHeight Height of menu bar (word)
MemTop Address of end of RAM
ScreenBits Bit map of screen in use (bitMapRec bytes)
SysZone Address of system heap zone
Ticks Current number of ticks since system startup (long)
Further Reference:
_______________________________________________________________________________
File Manager
Script Manager
Technical Note #129, _SysEnvirons: System 6.0 and Beyond
Technical Note #176, Macintosh Memory Configurations
Technical Note #180, MultiFinder Miscellanea
Technical Note #208, Setting and Restoring A5
Technical Note #212, The Joy Of Being 32-Bit Clean
Technical Note #227, Toolbox Karma
Technical Note #230, Pertinent Information About the Macintosh SE/30
Technical Note #258, Our Checksum Bounced
The Macintosh User Interface Guidelines
_______________________________________________________________________________
THE MACINTOSH USER INTERFACE GUIDELINES
_______________________________________________________________________________
About This Chapter
Introduction
Avoiding Modes
Avoiding Program Dependencies
Types of Applications
Using Graphics
Icons
Palettes
Components of the Macintosh System
The Keyboard
Character Keys
Modifier Keys: Shift, Caps Lock, Option, and Command
Control and Escape Keys
Function Keys
Typeahead and Auto-Repeat
Versions of the Keyboard
The Numeric Keypad
Arrow Keys
Appropriate Uses for the Arrow Keys
Moving the Insertion Point With Arrow Keys
Moving the Insertion Point in Empty Documents
Modifier Keys With Arrow Keys
Making a Selection With Arrow Keys
Extending or Shrinking a Selection
Collapsing a Selection
The Mouse
Mouse Actions
Multiple-Clicking
Changing Pointer Shapes
Selecting
Selection by Clicking
Range Selection
Extending a Selection
Making a Discontinuous Selection
Selecting Text
Insertion Point
Selecting Words
Selecting a Range of Text
Graphics Selections
Selections in Arrays
Windows
Multiple Windows
Opening and Closing Windows
The Active Window
Moving a Window
Changing the Size of a Window
Window Zooming
Effects of Dragging and Sizing
Scroll Bars
Automatic Scrolling
Splitting a Window
Panels
Commands
The Menu Bar
Choosing a Menu Command
Appearance of Menu Commands
Command Groups
Toggled Commands
Special Visual Features
Reserved Command Key Combinations
Standard Menus
The Apple Menu
The File Menu
New
Open
Close
Save
Save As
Revert to Saved
Page Setup
Print
Quit
The Edit Menu
The Clipboard
Undo
Cut
Copy
Paste
Clear
Select All
Show Clipboard
Font-Related Menus
Font Menu
FontSize Menu
Style Menu
Hierarchical Menus
Pop-Up Menus
Scrolling Menu Indicator
Text Editing
Inserting Text
Backspace
Replacing Text
Intelligent Cut and Paste
Editing Fields
Dialogs and Alerts
Controls
Buttons
Check Boxes and Radio Buttons
Dials
Dialogs
Modal Dialog Boxes
Modeless Dialog Boxes
Standard Close Dialog
Close Box Specifications
Alerts
Color
Standard Uses of Color
Color Coding
General Principles of Color Design
Design in Black and White
Limit Color Use
Contrast and Discrimination
Colors on Grays
Colored Text
Beware of Blue
Small Objects
Specific Recommendations
Color the Black Bits Only
Leave Outlines Black
Highlighting and Selection
Menus
Windows
Dialogs and Alerts
Pointers
Sound
When to Use Sound
Getting Attention
Alerts
Modes
General Guidelines
Don’t Go Overboard
Redundancy
Natural and Unobtrusive
Significant Differences
User Control
Resources
User Testing
Build User Testing Into the Design Process
Test Subjects
Procedures
Do’s and Don’ts of a Friendly User Interface
Bibliography
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
This chapter describes the Macintosh user interface, for the benefit of people who want to develop Macintosh applications. More details about many of these features can be found in the "About" sections of the other chapters of Inside Macintosh (for example, "About the Window Manager" ).
Unlike the rest of Inside Macintosh, this chapter describes applications from the outside, not the inside. The terminology used is the terminology users are familiar with, which is not necessarily the same as that used elsewhere in Inside Macintosh.
The Macintosh user interface consists of those features that are generally applicable to a variety of applications. Not all of the features are found in every application. In fact, some features are hypothetical, and may not be found in any current applications.
The best time to familiarize yourself with the user interface is before beginning to design an application. Good application design on the Macintosh happens when a developer has absorbed the spirit as well as the details of the user interface.
Before reading this chapter, you should have some experience using one or more applications, preferably one each of a word processor, spreadsheet or data base, and graphics application. You should also have read Macintosh, the owner's guide, or at least be familiar with the terminology used in that manual.
For more complete information about the Macintosh user interface, see Human Interface Guidelines: The Apple Desktop Interface (available through APDA). These guidelines are significantly extended from the guidelines chapter in the original Inside Macintosh; they include the principles behind the desktop interface used by both the Macintosh and Apple IIgs™, as well as specific guidelines for how interface elements should be used.
For more information about color, see the Color Manager and Color Picker Package chapters. Some reference works on color in the computer/user interface are listed at the end of this chapter. For more information about sound and menus, see the Sound and Menu Manager chapters, respectively.
_______________________________________________________________________________
»INTRODUCTION
_______________________________________________________________________________
The Macintosh is designed to appeal to an audience of nonprogrammers, including people who have previously feared and distrusted computers. To achieve this goal, Macintosh applications should be easy to learn and to use. To help people feel more comfortable with the applications, the applications should build on skills that people already have, not force them to learn new ones. The user should feel in control of the computer, not the other way around. This is achieved in applications that embody three qualities: responsiveness, permissiveness, and consistency.
Responsiveness means that the user's actions tend to have direct results. The user should be able to accomplish what needs to be done spontaneously and intuitively, rather than having to think: "Let's see; to do C, first I have to do A and B and then…". For example, with pull-down menus, the user can choose the desired command directly and instantaneously.
Permissiveness means that the application tends to allow the user to do anything reasonable. The user, not the system, decides what to do next. Also, error messages tend to come up infrequently. If the user is constantly subjected to a barrage of error messages, something is wrong somewhere.
The most important way in which an application is permissive is in avoiding modes. This idea is so important that it's dealt with in a separate section,
"Avoiding Modes", below.
The third and most important principle is consistency. Since Macintosh users usually divide their time among several applications, they would be confused and irritated if they had to learn a completely new interface for each application. The main purpose of this chapter is to describe the shared interface ideas of Macintosh applications, so that developers of new applications can gain leverage from the time spent developing and testing existing applications.
Consistency is easier to achieve on the Macintosh than on many other computers. This is because many of the routines used to implement the user interface are supplied in the Macintosh Operating System and User Interface Toolbox. However, you should be aware that implementing the user interface guidelines in their full glory often requires writing additional code that isn't supplied.
Of course, you shouldn't feel that you're restricted to using existing features. The Macintosh is a growing system, and new ideas are essential. But the bread-and-butter features, the kind that every application has, should certainly work the same way so that the user can easily move back and forth between applications. The best rule to follow is that if your application has a feature that's described in these guidelines, you should implement the feature exactly as the guidelines describe it. It's better to do something completely different than to half-agree with the guidelines.
Illustrations of most of the features described in this chapter can be found in various existing applications. However, there's probably no one application that illustrates these guidelines in every particular. Although it's useful and important for you to get the feeling of the Macintosh user interface by looking at existing applications, the guidelines in this chapter are the ultimate authority. Wherever an application disagrees with the guidelines, follow the guidelines.
_______________________________________________________________________________
»Avoiding Modes
"But, gentlemen, you overdo the mode."
— John Dryden, The Assignation, or Love in a Nunnery, 1672
A mode is a part of an application that the user has to formally enter and leave, and that restricts the operations that can be performed while it's in effect. Since people don't usually operate modally in real life, having to deal with modes in computer software reinforces the idea that computers are unnatural and unfriendly.
Modes are most confusing when you're in the wrong one. Being in a mode makes future actions contingent upon past ones, restricts the behavior of familiar objects and commands, and may make habitual actions cause unexpected results.
It's tempting to use modes in a Macintosh application, since most existing software leans on them heavily. If you yield to the temptation too frequently, however, users will consider spending time with your application a chore rather than a satisfying experience.
This is not to say that modes are never used in Macintosh applications. Sometimes a mode is the best way out of a particular problem. Most of these modes fall into one of the following categories:
• Long-term modes with a procedural basis, such as doing word processing
as opposed to graphics editing. Each application program is a mode in
this sense.
• Short-term "spring-loaded" modes, in which the user is constantly doing
something to perpetuate the mode. Holding down the mouse button or a key
is the most common example of this kind of mode.
• Alert modes, where the user must rectify an unusual situation before
proceeding. These modes should be kept to a minimum.
Other modes are acceptable if they meet one of the following requirements:
• They emulate a familiar real-life model that is itself modal, like
picking up different-sized paintbrushes in a graphics editor. MacPaint™
and other palette-based applications are examples of this use of modes.
• They change only the attributes of something, and not its behavior,
like the boldface and underline modes of text entry.
• They block most other normal operations of the system to emphasize the
modality, as in error conditions incurable through software ("There's
no disk in the disk drive", for example).
If an application uses modes, there must be a clear visual indication of the current mode, and the indication should be near the object being most affected by the mode. It should also be very easy to get into or out of the mode (such as by clicking on a palette symbol).
_______________________________________________________________________________
»Avoiding Program Dependencies
Another important general concept to keep in mind is that your application program should be as country-independent and hardware-independent as possible.
No words that the user sees should be in the program code itself; storing all these words in resources will make it much easier for the application to be translated to other languages. Similarly, there's a mechanism for reading country-dependent information from resources, such as the currency and date formats, so the application will automatically work right in countries where those resources have been properly set up. You should always use mechanisms like this instead of coding such information directly into your program.
The system software provides many variables and routines whose use will ensure independence from the version of the Macintosh being used—whether a Macintosh 128K, 512K, XL, or even a future version. Though you may know a more direct way of getting the information, or a faster way of doing the operation, it's best to use the system-provided features that will ensure hardware independence. You should, for example, access the variable that gives you the current size of the screen rather than use the numbers that match the screen you're using. You can also write your program so that it will print on any printer, regardless of which type of printer happens to be installed on the Macintosh being used.
_______________________________________________________________________________
»TYPES OF APPLICATIONS
_______________________________________________________________________________
Everything on a Macintosh screen is displayed graphically; the Macintosh has no text mode. Nevertheless, it's useful to make a distinction among three types of objects that an application deals with: text, graphics, and arrays. Examples of each of these are shown in Figure 1.
Text can be arranged in a variety of ways on the screen. Some applications, such as word processors, might consist of nothing but text, while others, such as graphics-oriented applications, use text almost incidentally. It's useful to consider all the text appearing together in a particular context as a block of text. The size of the block can range from a single field, as in a dialog box, to the whole document, as in a word processor. Regardless of its size or arrangement, the application sees each block as a one-dimensional string of characters. Text is edited the same way regardless of where it appears.
Graphics are pictures, drawn either by the user or by the application. Graphics in a document tend to consist of discrete objects, which can be selected individually. Graphics are discussed further below, under "Using Graphics".
Figure 1–Ways of Structuring Information
Arrays are one- or two-dimensional arrangements of fields. If the array is one-dimensional, it's called a form; if it's two-dimensional it's called a table. Each field, in turn, contains a collection of information, usually text, but conceivably graphics. A table can be readily identified on the screen, since it consists of rows and columns of fields (often called cells), separated by horizontal and vertical lines. A form is something you fill out, like a credit-card application. The fields in a form can be arranged in any appropriate way; nevertheless, the application regards the fields as in a definite linear order.
Each of these three ways of presenting information retains its integrity, regardless of the context in which it appears. For example, a field in an array can contain text. When the user is manipulating the field as a whole, the field is treated as part of the array. When the user wants to change the contents of the field, the contents are edited in the same way as any other text.
_______________________________________________________________________________
»USING GRAPHICS
_______________________________________________________________________________
A key feature of the Macintosh is its high-resolution graphics screen. To use this screen to its best advantage, Macintosh applications use graphics copiously, even in places where other applications use text. As much as possible, all commands, features, and parameters of an application, and all the user's data, appear as graphic objects on the screen. Figure 2 shows some of the ways that applications can use graphics to communicate with the user.
Figure 2–Objects on the Screen
Objects, whenever applicable, resemble the familiar material objects whose functions they emulate. Objects that act like pushbuttons "light up" when pressed; the Trash icon looks like a trash can.
Objects are designed to look good on the screen. Predefined graphics patterns can give objects a shape and texture beyond simple line graphics. Placing a drop-shadow slightly below and to the right of an object can give it a three-dimensional appearance.
Generally, when the user clicks on an object, it's highlighted to distinguish it from its peers. The most common way to show this highlighting is by inverting the object: changing black to white and vice versa. In some situations, other forms of highlighting may be more appropriate. The important thing is that there should always be some sort of feedback, so that the user knows that the click had an effect.
One special aspect of the appearance of a document on the screen is visual fidelity. This principle is also known as "what you see is what you get". It primarily refers to printing: The version of a document shown on the screen should be as close as possible to its printed version, taking into account inevitable differences due to different media.
_______________________________________________________________________________
»Icons
A fundamental object in Macintosh software is the icon, a small graphic object that's usually symbolic of an operation or of a larger entity such as a document.
Icons can contribute greatly to the clarity and attractiveness of an application. The use of icons also makes it much easier to translate programs into other languages. Wherever an explanation or label is needed, consider using an icon instead of text.
_______________________________________________________________________________
»Palettes
Some applications use palettes as a quick way for the user to change from one operation to another. A palette is a collection of small symbols, usually enclosed in rectangles. A symbol can be an icon, a pattern, a character, or just a drawing, that stands for an operation. When the user clicks on one of the symbols (or in its rectangle), it's distinguished from the other symbols, such as by highlighting, and the previous symbol goes back to its normal state.
Typically, the symbol that's selected determines what operations the user can perform. Selecting a palette symbol puts the user into a mode. This use of modes can be justified because changing from one mode to another is almost instantaneous, and the user can always see at a glance which mode is in effect. Like all modal features, palettes should be used only when they're the most natural way to structure an application.
A palette can either be part of a window (as in MacDraw™), or a separate window (as in MacPaint). Each system has its disadvantages. If the palette is part of the window, then parts of the palette might be concealed if the user makes the window smaller. On the other hand, if it's not part of the window, then it takes up extra space on the desktop. If an application supports multiple documents open at the same time, it might be better to put a separate palette in each window, so that a different palette symbol can be in effect in each document.
_______________________________________________________________________________
»COMPONENTS OF THE MACINTOSH SYSTEM
_______________________________________________________________________________
This section explains the relationship among the principal large-scale components of the Macintosh system (from an external point of view).
The main vehicle for the interaction of the user and the system is the application. Only one application is active at a time. When an application is active, it's in control of all communications between the user and the system. The application's menus are in the menu bar, and the application is in charge of all windows as well as the desktop.
To the user, the main unit of information is the document. Each document is a unified collection of information—a single business letter or spreadsheet or chart. A complex application, such as a data base, might require several related documents. Some documents can be processed by more than one application, but each document has a principal application, which is usually the one that created it. The other applications that process the document are called secondary applications.
The only way the user can actually see the document (except by printing it) is through a window. The application puts one or more windows on the screen; each window shows a view of a document or of auxiliary information used in processing the document. The part of the screen underlying all the windows is called the desktop.
The user returns to the Finder to change applications. When the Finder is active, if the user opens either an application a document belonging to an application, the application becomes active and displays the document window.
Internally, applications and documents are both kept in files. However, the user never sees files as such, so they don't really enter into the user interface.
_______________________________________________________________________________
»THE KEYBOARD
_______________________________________________________________________________
The Macintosh keyboard is used primarily for entering text. Since commands are chosen from menus or by clicking somewhere on the screen, the keyboard isn't needed for this function, although it can be used for alternative ways to enter commands.
The keys on the keyboard are arranged in familiar typewriter fashion. The U.S. keyboard on the Macintosh 128K and 512K is shown in Figure 3. The Macintosh XL keyboard looks the same except that the key to the left of the space bar is labeled with an apple symbol.
Figure 3–The Macintosh U.S. Keyboard
The standard keyboard for the Macintosh SE and Macintosh II includes a Control key and an Escape key. The optional extended keyboard has in addition 6 dedicated function keys, 15 function keys that are user-definable, and 3 LED indicators for key lock conditions. The Apple Extended Keyboard is shown in Figure 4.
Figure 4–The Apple Extended Keyboard
There are two kinds of keys: character keys and modifier keys. A character key sends characters to the computer; a modifier key alters the meaning of a character key if it's held down while the character key is pressed.
_______________________________________________________________________________
»Character Keys
Character keys include keys for letters, numbers, and symbols, as well as the space bar. If the user presses one of these keys while entering text, the corresponding character is added to the text. Other keys, such as the Enter, Tab, Return, Backspace, and Clear keys, are also considered character keys. However, the result of pressing one of these keys depends on the application and the context.
The Enter key tells the application that the user is through entering information in a particular area of the document, such as a field in an array. Most applications add information to a document as soon as the user types or draws it. However, the application may need to wait until a whole collection of information is available before processing it. In this case, the user presses the Enter key to signal that the information is complete.
The Tab key is a signal to proceed: It signals movement to the next item in a sequence. Tab often implies an Enter operation before the Tab motion is performed.
The Return key is another signal to proceed, but it defines a different type of motion than Tab. A press of the Return key signals movement to the leftmost field one step down (just like a carriage return on a typewriter). Return can also imply an Enter operation before the Return operation.
Note: Return and Enter also dismiss dialog and alert boxes
(see "Dialogs and Alerts").
During entry of text into a document, Tab moves to the next tab stop, Return moves to the beginning of the next line, and Enter is ignored.
Backspace is used to delete text or graphics. The exact use of Backspace in text is described in the "Text Editing" section.
The Clear key on the numeric keypad has the same effect as the Clear command in the Edit menu; that is, it removes the selection from the document without putting it in the Clipboard. This is also explained in the "Text Editing" section. Because the keypad is optional equipment on the Macintosh 128K and 512K, no application should ever require use of the Clear key or any other key on the pad.
_______________________________________________________________________________
»Modifier Keys: Shift, Caps Lock, Option, and Command
There are six keys on the keyboard that change the interpretation of keystrokes: two Shift keys, two Option keys, one Caps Lock key, and one Command key (the key to the left of the space bar). These keys change the interpretation of keystrokes, and sometimes mouse actions. When one of these keys is held down, the effect of the other keys (or the mouse button) may change.
The Shift and Option keys choose among the characters on each character key. Shift gives the upper character on two-character keys, or the uppercase letter on alphabetic keys. The Shift key is also used in conjunction with the mouse for extending a selection; see "Selecting". Option gives an alternate character set interpretation, including international characters, special symbols, and so on. Shift and Option can be used in combination.
Caps Lock latches in the down position when pressed, and releases when pressed again. When down it gives the uppercase letter on alphabetic keys. The operation of Caps Lock on alphabetic keys is parallel to that of the Shift key, but the Caps Lock key has no effect whatsoever on any of the other keys. Caps Lock and Option can be used in combination on alphabetic keys.
Pressing a character key while holding down the Command key usually tells the application to interpret the key as a command, not as a character (see
"Commands").
_______________________________________________________________________________
»Control and Escape Keys
The Control and Esc (Escape) keys should be used for their standard meanings; neither should be used as an additional command-key modifier. Since not all keyboards may have a Control or Esc key, neither should be depended upon.
The main use of the Control key is to generate control characters for terminal emulation programs. (The Command key is used for this purpose on terminals lacking a Control key.) A secondary use that also derives from past practice is calling user-defined functions, or macros. The varying placement of the Control key on different keyboards means that it should not be used for routine entry, as touch-typists may find its position inconvenient.
The Esc key has the general meaning “let me out of here”. In certain contexts its meaning is specific:
• The user can press Esc as a quick way to indicate Cancel in a dialog box.
• The user can press Esc to stop an operation in progress, such as printing.
(Using Esc this way is like pressing Command-period.)
• If an application absolutely requires a series of dialog boxes (a fresh
look at program design usually eliminates such sequences), the user
should be able to use Esc to move backward through the boxes.
Pressing Esc should never cause the user to back out of an operation that would require extensive time or work to reenter, and it should never cause the user to lose valuable information. When the user presses Esc during a lengthy operation, the application should display a confirmation dialog box to be sure Esc wasn’t pressed accidentally.
_______________________________________________________________________________
»Function Keys
There are two types of function keys: dedicated and user-definable. The user-definable keys—labeled F1 through F15—are not to be defined by an application. F1 through F4 represent Undo, Cut, Copy, and Paste, respectively, in any applications that use these commands.
The six dedicated function keys are labeled Help, Del, Home, End, Page Up, and Page Down. These keys are used as follows:
• Help: Pressing the Help key should produce help (it’s equivalent to
pressing Command-?). The sort of help available varies between
applications; if a full, contextual help system is not available,
some sort of useful help screen should be provided.
• Fwd Del: Pressing Fwd Del performs a forward delete: the character
directly to the right of the insertion point is removed, pulling
everything to the right of the removed character toward the insertion
point. The effect is that the insertion point remains stable while
it “vacuums” everything ahead of it.
If Fwd Del is pressed when there is a current selection, it has the
same effect as pressing Delete (Backspace) or choosing Clear from the
Edit menu.
• Home: Pressing the Home key is equivalent to moving the scroll boxes
(elevators) all the way to the top of the vertical scroll bar and to
the left end of the horizontal scroll bar.
• End: The flip-side of Home: it’s equivalent to moving the scroll boxes
(elevators) all the way to the bottom of the vertical scroll bar and to
the right end of the horizontal scroll bar.
• Page Up: Equivalent to clicking the mouse pointer in the upper gray
region of the vertical scroll bar.
• Page Down: Equivalent to clicking the mouse pointer in the lower gray
region of the vertical scroll bar.
Notice that the Home, End, Page Up, and Page Down keys have no effect on the insertion point or on any selected material. These keys change the screen display only, for three reasons:
• The analogy to scrolling means that the keys behave as users expect.
• Users can easily change the insertion point by clicking in the
jumped-to window.
• Window-by-window jumping with a moving insertion point can be done by
Command–arrow key combinations, as described in the “Arrow Keys” section.
Because the keys are visual only, the Page Up and Page Down keys jump relative to the visible window, not relative to the insertion point.
_______________________________________________________________________________
»Typeahead and Auto-Repeat
If the user types when the Macintosh is unable to process the keystrokes immediately, or types more quickly than the Macintosh can handle, the extra keystrokes are queued, to be processed later. This queuing is called typeahead. There's a limit to the number of keystrokes that can be queued, but the limit is usually not a problem unless the user types while the application is performing a lengthy operation.
When a character is held down for a certain amount of time, it starts repeating automatically. The user can set the delay and the rate of repetition with the Control Panel desk accessory. An application can tell whether a series of n keystrokes was generated by auto-repeat or by pressing the same key n times. It can choose to disregard keystrokes generated by auto-repeat; this is usually a good idea for menu commands chosen with the Command key.
Holding down a modifier key has the same effect as pressing it once. However, if the user holds down a modifier key and a character key at the same time, the effect is the same as if the user held down the modifier key while pressing the character key repeatedly.
Auto-repeat does not function during typeahead; it operates only when the application is ready to accept keyboard input.
_______________________________________________________________________________
»Versions of the Keyboard
There are two physical versions of the keyboard: U.S. and international. The international version has one more key than the U.S. version. The standard layout on the international version is designed to conform to the International Standards Organization (ISO) standard; the U.S. key layout mimics that of common American office typewriters. International keyboards have different labels on the keys in different countries, but the overall layout is the same.
Note: An illustration of the international keyboard (with Great Britain
key caps) is given in the Toolbox Event Manager chapter.
_______________________________________________________________________________
»The Numeric Keypad
An optional numeric keypad can be hooked up between the main unit and the standard keyboard on a Macintosh 128K or 512K; on the Macintosh XL, the numeric keypad is built in, next to the keyboard. Figure 5 shows the U.S. keypad. In other countries, the keys may have different labels.
Figure 5–Numeric Keypad
The keypad contains 18 keys, some of which duplicate keys on the main keyboard, and some of which are unique to the keypad. The application can tell whether the keystrokes have come from the main keyboard or the numeric keypad. The keys on the keypad follow the same rules for typeahead and auto-repeat as the keyboard.
Four keys on the keypad are labeled with “field-motion” symbols: small rectangles with arrows pointing in various directions. Some applications may use these keys to select objects in the direction indicated by the key; the most likely use for this feature is in tables. To obtain the characters
(+ * / ,) available on these keys, the user must also hold down the Shift key on the keyboard.
Since the numeric keypad is optional equipment on the Macintosh 128K and 512K, no application should require it or any keys available on it in order to perform standard functions. Specifically, since the Clear key isn’t available on the main keyboard, a Clear function may be implemented with this key only as the equivalent of the Clear command in the Edit menu.
_______________________________________________________________________________
»ARROW KEYS
_______________________________________________________________________________
The Macintosh Plus keyboard includes four arrow keys: Up Arrow, Down Arrow, Left Arrow, and Right Arrow.
Figure 6–Macintosh Plus Arrow Keys
_______________________________________________________________________________
»Appropriate Uses for the Arrow Keys
The arrow keys do not replace the mouse. They can be used in addition to the mouse as a shortcut for moving the insertion point and (under some circumstances) for making selections. The following rules are the minimum guidelines for the use of arrow keys, leaving application programmers relatively free to expand on them where things are left undefined. Extensions necessary for a particular application should be done in the spirit of the Macintosh user interface.
It’s up to you to decide whether it’s worth the effort to create arrow key shortcuts for mouse functions. Many users find that remembering a key combination on the order of Command–Shift–Left Arrow is more trouble than it’s worth and would rather use a mouse anyway. In other situations, it’s more convenient to use the keyboard. Some people have difficulty using a mouse and appreciate being able to use the keyboard instead.
You should make use of the arrow keys only where it’s appropriate to the application. Applications that deal with text or arrays (word processors, spreadsheets, and data bases, for example) have an insertion point. This insertion point can always be moved by the mouse and, with the new keyboard, with the arrow keys as well.
As a general rule, arrow keys are used to move the insertion point and to expand or shrink selections. Arrow keys are never used to duplicate the function of the scroll bars or to move the pointer. In a graphics application, the arrow keys should not be used to move a selected object.
_______________________________________________________________________________
»Moving the Insertion Point With Arrow Keys
The Left Arrow and Right Arrow keys move the insertion point one character left and right, respectively.
Up Arrow and Down Arrow move the insertion point up and down one line, respectively. The horizontal screen position should be maintained in terms of screen pixels but not necessarily in terms of characters, because the insertion point moves to the nearest character boundary on the new line. (Character boundaries seldom line up vertically when proportional fonts are used.) During successive movements up or down, you should keep track of the original horizontal screen position; otherwise, accumulated round-off errors might cause the insertion point to move a significant distance from the original horizontal position as it moves from line to line.
»Moving the Insertion Point in Empty Documents
Various text-editing programs treat empty documents in different ways. Some assume that an empty document contains no characters, in which case clicking at the bottom of a blank screen causes the insertion point to appear at the top. In this situation, Down Arrow cannot move the insertion point into the blank space (because there are no characters there).
Other applications treat an empty document as a page of space characters, in which case clicking at the bottom of a blank screen puts the insertion point where the user clicked and lets the user type characters there, overwriting the spaces. Down Arrow moves the insertion point straight down through the spaces.
Whichever paradigm you choose for your application, be consistent.
»Modifier Keys With Arrow Keys
Holding down the Command key while pressing an arrow key should move the insertion point to the appropriate edge of the window. If the insertion point is already at the edge of the window, the document should be scrolled one windowful in the appropriate direction and the insertion point should move to the same edge of the new windowful. Command–Up Arrow moves to the top of the window, Command–Down Arrow to the bottom, Command–Left Arrow to the left edge, and Command–Right Arrow to the right edge.
The Option key is reserved as a “semantic modifier” key. The application determines what the semantic units are. For example, in a word processor, where the basic semantic unit is the character and the next larger unit is the word, Option–Left Arrow and Option–Right Arrow might move the insertion point to the beginning and end, respectively, of a word. (Movement of the insertion point by word boundaries should use the same definition of “word” that the application uses for double clicking.) The next larger semantic unit could be defined as the sentence, in which case Option–Left Arrow and Option–Right Arrow would move the insertion point to the beginning or end of a sentence. In a programming language editor, where the basic semantic unit is the token and the next larger one might be the line, Option–Left Arrow and Option–Right Arrow might move the insertion point left and right to the beginning and end of the line, respectively.
In an application (such as a spreadsheet) that represents itself as an array, the basic semantic unit would be the cell. Option–Left Arrow would designate the cell to the left of the currently active cell as the new active cell, and so on. Using modifier keys with arrow keys doesn’t do anything to the data; Option–Left Arrow just moves the selection to the next cell to the left.
Though the use of multiple modifier key combinations (such as Command–Option–Left Arrow) is discouraged, it’s fine to use the Shift key with any one of the other modifier keys for making a selection (see “Making a Selection With Arrow Keys” below). Keep in mind that if multiple keys must be pressed simultaneously, they should be fairly close together—otherwise many people won’t be able to use that combination.
_______________________________________________________________________________
»Making a Selection With Arrow Keys
To use arrow keys to make a selection, the user holds down Shift while pressing an arrow key. Application programs that depend (as TextEdit does) on the numeric keypad should not use these Shift–arrow key combinations because the ASCII codes for the four Shift–arrow key combinations are the same as those for the keypad’s +, *, /, and = keys. If the use of Shift–arrow for making selections is more important to your application than the numeric keypad, the following paragraphs describe how it should work.
After a Shift–arrow key combination has been pressed, the insertion point moves and the range over which it moves becomes selected. If both the Shift key and another modifier key are held down, the insertion point moves (as defined for the particular modifier key) and the range over which the insertion point moves becomes selected. For example, Shift–Left Arrow selects the character to the left of the insertion point, Command–Shift–Left Arrow selects from the insertion point to the left edge of the window, and Option–Shift–Left Arrow selects the whole word that contains the character to the left of the insertion point (just like double clicking on a word).
A selection made using the mouse is no different from one made using arrow keys. A selection started with the mouse can be extended using Shift and Left Arrow or Right Arrow.
The two ends of a selected range have different characteristics and different names. The place where the insertion point was when selection was started is called the anchor point. The place to which the insertion point moves to complete the selection is called the active end. Once selection begins, the anchor point cannot be moved except by beginning a new selection. To extend or shrink a selection, the user moves the active end as specified here. As the active end moves, it can cross over the anchor point.
In a text application, pressing Shift and either Left Arrow or Right Arrow selects a single character. Assuming that Left Arrow key was used, the anchor point of the selection is on the right side of the selection, the active end on the left. Each subsequent Shift–Left Arrow adds another character to the left side of the selection. A Shift–Right Arrow at this point shrinks the selection. Figure 7 summarizes these actions.
Figure 7–Selecting With Shift–Arrow Keys
In a text application, pressing Option–Shift and either Left Arrow or Right Arrow selects the entire word containing the character to the left of the insertion point. Assuming Left Arrow was used, the anchor point is at the right end of the word, the active end at the left. Each subsequent Option–Shift–Left Arrow adds another word to the left end of the selection, as shown in Figure 8.
Figure 8–Selecting With Option–Shift–Arrow Keys
Pressing Command–Shift–Left Arrow selects the area from the insertion point to the left edge of the window. The anchor point is at the right end of the selection, the active end is at the left. Each subsequent Command–Shift–Left Arrow moves the document one windowful left and extends the selection to the left edge of the new window.
_______________________________________________________________________________
»Extending or Shrinking a Selection
To use arrow keys to extend or shrink a selection, the user holds down the Shift key (plus any defined modifiers) while pressing an arrow key. The arrow key moves the insertion point at the active end of the selection.
_______________________________________________________________________________
»Collapsing a Selection
When a block of text is selected, pressing either Left Arrow or Right Arrow deselects the range. If Left Arrow is pressed, the insertion point is left at the beginning of the previous selection; if Right Arrow, at the end of the previous selection.
_______________________________________________________________________________
»THE MOUSE
_______________________________________________________________________________
The mouse is a small device the size of a deck of playing cards, connected to the computer by a long, flexible cable. There’s a button on the top of the mouse. The user holds the mouse and rolls it on a flat, smooth surface. A pointer on the screen follows the motion of the mouse.
Simply moving the mouse results only in a corresponding movement of the pointer and no other action. Most actions take place when the user positions the “hot spot” of the pointer over an object on the screen and presses and releases the mouse button. The hot spot should be intuitive, like the point of an arrow or the center of a crossbar.
_______________________________________________________________________________
»Mouse Actions
The three basic mouse actions are:
• clicking: positioning the pointer with the mouse, and briefly
pressing and releasing the mouse button without moving the mouse
• pressing: positioning the pointer with the mouse, and holding
down the mouse button without moving the mouse
• dragging: positioning the pointer with the mouse, holding down
the mouse button, moving the mouse to a new position, and releasing
the button
The system provides “mouse-ahead”; that is, any mouse actions the user performs when the application isn’t ready to process them are saved in a buffer and can be processed at the application’s convenience. Alternatively, the application can choose to ignore saved-up mouse actions, but should do so only to protect the user from possibly damaging consequences.
Clicking something with the mouse performs an instantaneous action, such as selecting a location within a document or activating an object.
For certain kinds of objects, pressing on the object has the same effect as clicking it repeatedly. For example, clicking a scroll arrow causes a document to scroll one line; pressing on a scroll arrow causes the document to scroll repeatedly until the mouse button is released or the end of the document is reached.
Dragging can have different effects, depending on what’s under the pointer when the mouse button is pressed. The uses of dragging include choosing a menu item, selecting a range of objects, moving an object from one place to another, and shrinking or expanding an object.
Some objects, especially graphic objects, can be moved by dragging. In this case, the application attaches a dotted outline of the object to the pointer and moves the outline as the user moves the pointer. When the user releases the mouse button, the application redraws the complete object at the new location.
An object being moved can be restricted to certain boundaries, such as the edges of a window. If the user moves the pointer outside of the boundaries, the application stops drawing the dotted outline of the object. If the user releases the mouse button while the pointer is outside of the boundaries, the object isn’t moved. If, on the other hand, the user moves the pointer back within the boundaries again before releasing the mouse button, the outline is drawn again.
In general, moving the mouse changes nothing except the location, and possibly the shape, of the pointer. Pressing the mouse button indicates the intention to do something, and releasing the button completes the action. Pressing by itself should have no effect except in well-defined areas, such as scroll arrows, where it has the same effect as repeated clicking.
»Multiple-Clicking
A variant of clicking involves performing a second click shortly after the end of an initial click. If the downstroke of the second click follows the upstroke of the first by a short amount of time (as set by the user in the Control
Panel), and if the locations of the two clicks are reasonably close together, the two clicks constitute a double-click. Its most common use is as a faster or easier way to perform an action that can also be performed in another way. For example, clicking twice on an icon is a faster way to open it than selecting it and choosing Open; clicking twice on a word to select it is faster than dragging through it.
To allow the software to distinguish efficiently between single clicks and double-clicks on objects that respond to both, an operation invoked by double-clicking an object must be an enhancement, superset, or extension of the feature invoked by single-clicking that object.
Triple-clicking is also possible; it should similarly represent an extension of a double-click.
_______________________________________________________________________________
»Changing Pointer Shapes
The pointer may change shape to give feedback on the range of activities that make sense in a particular area of the screen, in a current mode, or both:
• The result of any mouse action depends on the item under the pointer
when the mouse button is pressed. To emphasize the differences among
mouse actions, the pointer may assume different appearances in different
areas to indicate the actions possible in each area. This can be
distracting, however, and should be kept to a minimum.
• Where an application uses modes for different functions, the pointer
can be a different shape in each mode. For example, in MacPaint, the
pointer shape always reflects the active palette symbol.
During a particularly lengthy operation, when the user can do nothing but wait until the operation is completed, the pointer may change to indicate this. The standard pointer used for this purpose is a wristwatch.
Figure 9 shows some examples of pointers and their effect. An application can design additional pointers for other contexts.
Figure 9–Pointers
_______________________________________________________________________________
»SELECTING
_______________________________________________________________________________
The user selects an object to distinguish it from other objects, just before performing an operation on it. Selecting the object of an operation before identifying the operation is a fundamental characteristic of the Macintosh user interface, since it allows the application to avoid modes.
Selecting an object has no effect on the contents of a document. Making a selection shouldn’t commit the user to anything; there should never be a penalty for making an incorrect selection. The user fixes an incorrect selection by making the correct selection.
Although there’s a variety of ways to select objects, they fall into easily recognizable groups. Users get used to doing specific things to select objects, and applications that use these methods are therefore easier to learn. Some of these methods apply to every type of application, and some only to particular types of applications.
This section discusses first the general methods, and then the specific methods that apply to text applications, graphics applications, and arrays. Figure 10 shows a comparison of some of the general methods.
__________________________________________________________________
»Selection by Clicking
The most straightforward method of selecting an object is by clicking on it once. Most things that can be selected in Macintosh applications can be selected this way.
Figure 10–Selection Methods
Some applications support selection by double-clicking and triple-clicking. As always with multiple clicks, the second click extends the effect of the first click, and the third click extends the effect of the second click. In the case of selection, this means that the second click selects the same sort of thing as the first click, only more of them. The same holds true for the third click.
For example, in text, the first click selects an insertion point, whereas the second click selects a whole word. The third click might select a whole block or paragraph of text. In graphics, the first click selects a single object, and double- and triple-clicks might select increasingly larger groups of objects.
_______________________________________________________________________________
»Range Selection
The user selects a range of objects by dragging through them. Although the exact meaning of the selection depends on the type of application, the procedure is always the same:
1. The user positions the pointer at one corner of the range and presses
the mouse button. This position is called the anchor point of the range.
2. The user moves the pointer in any direction. As the pointer is moved,
visual feedback indicates the objects that would be selected if the
mouse button were released. For text and arrays, the selected area is
continually highlighted. For graphics, a dotted rectangle expands or
contracts to show the range that will be selected.
3. When the feedback shows the desired range, the user releases the mouse
button. The point at which the button is released is called the endpoint
of the range.
_______________________________________________________________________________
»Extending a Selection
A user can change the extent of an existing selection by holding down the Shift key and clicking the mouse button. Exactly what happens next depends on the context.
In text or an array, the result of a Shift-click is always a range. The position where the button is clicked becomes the new endpoint or anchor point of the range; the selection can be extended in any direction. If the user clicks within the current range, the new range will be smaller than the old range.
In graphics, a selection is extended by adding objects to it; the added objects do not have to be adjacent to the objects already selected. The user can add either an individual object or a range of objects to the selection by holding down the Shift key before making the additional selection. If the user holds down the Shift key and selects one or more objects that are already highlighted, the objects are deselected.
Extended selections can be made across the panes of a split window. (See
“Splitting Windows”.)
_______________________________________________________________________________
»Making a Discontinuous Selection
In graphics applications, objects aren’t usually considered to be in any particular sequence. Therefore, the user can use Shift-click to extend a selection by a single object, even if that object is nowhere near the current selection. When this happens, the objects between the current selection and the new object are not automatically included in the selection. This kind of selection is called a discontinuous selection. In the case of graphics, all selections are discontinuous selections.
This is not the case with arrays and text, however. In these two kinds of applications, an extended selection made by a Shift-click always includes everything between the old selection and the new endpoint. To provide the possibility of a discontinuous selection in these applications, Command-click is included in the user interface.
To make a discontinuous selection in a text or array application, the user selects the first piece in the normal way, then holds down the Command key before selecting the remaining pieces. Each piece is selected in the same way as if it were the whole selection, but because the Command key is held down, the new pieces are added to the existing selection instead of supplanting it.
If one of the pieces selected is already within an existing part of the selection, then instead of being added to the selection it’s removed from the selection. Figure 11 shows a sequence in which several pieces are selected and deselected.
Not all applications support discontinuous selections, and those that do might restrict the operations that a user can perform on them. For example, a word processor might allow the user to choose a font after making a discontinuous selection, but not to choose Cut.
Figure 11–Discontinuous Selection
_______________________________________________________________________________
»Selecting Text
Text is used in most applications; it’s selected and edited in a consistent way, regardless of where it appears.
A block of text is a string of characters. A text selection is a substring of this string, which can have any length from zero characters to the whole block. Each of the text selection methods selects a different kind of substring. Figure 12 shows different kinds of text selections.
Figure 12–Text Selections
_______________________________________________________________________________
»Insertion Point
The insertion point is a zero-length text selection. The user establishes the location of the insertion point by clicking between two characters. The insertion point then appears at the nearest character boundary. If the user clicks to the right of the last character on a line, the insertion point appears immediately after the last character. The converse is true if the user clicks to the left of the first character in the line.
The insertion point shows where text will be inserted when the user begins typing, or where cut or copied data (the contents of the Clipboard) will be pasted. After each character is typed, the insertion point is relocated to the right of the insertion.
If, between the mouse-down and the mouse-up, the user moves the pointer more than about half the width of a character, the selection is a range selection rather than an insertion point.
_______________________________________________________________________________
»Selecting Words
The user selects a whole word by double-clicking somewhere within that word. If the user begins a double-click sequence, but then drags the mouse between the mouse-down and the mouse-up of the second click, the selection becomes a range of words rather than a single word. As the pointer moves, the application highlights or unhighlights a whole word at a time.
A word, or range of words, can also be selected in the same way as any other range; whether this type of selection is treated as a range of characters or as a range of words depends on the operation. For example, in MacWrite, a range of individual characters that happens to coincide with a range of words is treated like characters for purposes of extending a selection, but is treated like words for purposes of “intelligent” cut and paste (described later in the “Text Editing” section).
A word is defined as any continuous string that contains only the following characters:
• a letter (including letters with diacritical marks)
• a digit
• a nonbreaking space (Option-space)
• a dollar sign, cent sign, English pound symbol, or yen symbol
• a percent sign
• a comma between digits
• a period before a digit
• an apostrophe between letters or digits
• a hyphen, but not a minus sign (Option-hyphen) or a dash
(Option-Shift-hyphen)
This is the definition in the United States and Canada; in other countries, it would have to be changed to reflect local formats for numbers, dates, and currency.
If the user double-clicks over any character not on the list above, that character is selected, but it is not considered a word.
Examples of words:
$123,456.78
shouldn’t
3 1/2 [with a nonbreaking space]
.5%
Examples of nonwords:
7/10/6
blue cheese [with a breaking space]
“Yoicks!” [the quotation marks and exclamation point
aren’t part of the word]
_______________________________________________________________________________
»Selecting a Range of Text
The user selects a range of text by dragging through the range. A range is either a range of words or a range of individual characters, as described under “Selecting Words”, above.
If the user extends the range, the way the range is extended depends on what kind of range it is. If it’s a range of individual characters, it can be extended one character at a time. If it’s a range of words (including a single word), it’s extended only by whole words.
_______________________________________________________________________________
»Graphics Selections
There are several different ways to select graphic objects and to show selection feedback in existing Macintosh applications. MacDraw, MacPaint, and the Finder all illustrate different possibilities. This section describes the MacDraw paradigm, which is the most extensible to other kinds of applications.
A MacDraw document is a collection of individual graphic objects. To select one of these objects, the user clicks once on the object, which is then shown with knobs. (The knobs are used to stretch or shrink the object, and won’t be discussed in these guidelines.) Figure 13 shows some examples of selection.
Figure 13–Graphics Selections
To select more than one object, the user can select either a range or a multiple selection. A range selection includes every object completely contained within the dotted rectangle that encloses the range, while an extended selection includes only those objects explicitly selected.
_______________________________________________________________________________
»Selections in Arrays
As described above under “Types of Applications”, an array is a one- or two-dimensional arrangement of fields. If the array is one-dimensional, it’s called a form; if it’s two-dimensional, it’s called a table. The user can select one or more fields, or part of the contents of a field.
To select a single field, the user clicks in the field. The user can also implicitly select a field by moving into it with the Tab or Return key.
The Tab key cycles through the fields in an order determined by the application. From each field, the Tab key selects the “next” field. Typically, the sequence of fields is first from left to right, and then from top to bottom. When the last field in a form is selected, pressing the Tab key selects the first field in the form. In a form, an application might prefer to select the fields in logical, rather than physical, order.
The Return key selects the first field in the next row. If the idea of rows doesn’t make sense in a particular context, then the Return key should have the same effect as the Tab key.
Tables are more likely than forms to support range selections and extended selections. A table can also support selection of rows and columns. The most convenient way for the user to select a column is to click in the column header. To select more than one column, the user drags through several column headers. The same applies to rows.
To select part of the contents of a field, the user must first select the field. The user then clicks again to select the desired part of the field. Since the contents of a field are either text or graphics, this type of selection follows the rules outlined above. Figure 14 shows some selections in an array.
Figure 14–Array Selections
_______________________________________________________________________________
»WINDOWS
_______________________________________________________________________________
The rectangles on the desktop that display information are windows. The most commmon types of windows are document windows, desk accessories, dialog boxes, and alert boxes. (Dialog and alert boxes are discussed under “Dialogs and Alerts”.) Some of the features described in this section are applicable only to document windows. Figure 15 shows a typical active document window and some of its components.
Figure 15–An Active Window
_______________________________________________________________________________
»Multiple Windows
Some applications may be able to keep several windows on the desktop at the same time. Each window is in a different plane. Windows can be moved around on the Macintosh’s desktop much like pieces of paper can be moved around on a real desktop. Each window can overlap those behind it, and can be overlapped by those in front of it. Even when windows don’t overlap, they retain their front-to-back ordering.
Different windows can represent separate documents being viewed or edited simultaneously, or related parts of a logical whole, like the listing, execution, and debugging of a program. Each application may deal with the meaning and creation of multiple windows in its own way.
The advantage of multiple windows is that the user can isolate unrelated chunks of information from each other. The disadvantage is that the desktop can become cluttered, especially if some of the windows can’t be moved. Figure 16 shows multiple windows.
Figure 16–Multiple Windows
_______________________________________________________________________________
»Opening and Closing Windows
Windows come up onto the screen in different ways as appropriate to the purpose of the window. The application controls at least the initial size and placement of its windows.
Most windows have a close box that, when clicked, makes the window go away. The application in control of the window determines what’s done with the window visually and logically when the close box is clicked. Visually, the window can either shrink to a smaller object such as an icon, or leave no trace behind when it closes. Logically, the information in the window is either retained and then restored when the window is reopened (which is the usual case), or else the window is reinitialized each time it’s opened. When a document is closed, the user is given the choice whether to save any changes made to the document since the last time it was saved.
If an application doesn’t support closing a window with a close box, it
shouldn’t include a close box on the window.
_______________________________________________________________________________
»The Active Window
Of all the windows that are open on the desktop, the user can work in only one window at a time. This window is called the active window. All other open windows are inactive. To make a window active, the user clicks in it. Making a window active has two immediate consequences:
• The window changes its appearance: Its title bar is highlighted
and the scroll bars and size box are shown. If the window is being
reactivated, the selection that was in effect when it was deactivated
is rehighlighted.
• The window is moved to the frontmost plane, so that it’s shown in
front of any windows that it overlaps.
Clicking in a window does nothing except activate it. To make a selection in the window, the user must click again. When the user clicks in a window that has been deactivated, the window should be reinstated just the way it was when it was deactivated, with the same position of the scroll box, and the same selection highlighted.
When a window becomes inactive, all the visual changes that took place when it was activated are reversed. The title bar becomes unhighlighted, the scroll bars and size box aren’t shown, and no selection is shown in the window.
_______________________________________________________________________________
»Moving a Window
Each application initially places windows on the screen wherever it wants them. The user can move a window—to make more room on the desktop or to uncover a window it’s overlapping —by dragging it by its title bar. As soon as the user presses in the title bar, that window becomes the active window. A dotted outline of the window follows the pointer until the user releases the mouse button. At the release of the button the full window is drawn in its new location. Moving a window doesn’t affect the appearance of the document within the window.
If the user holds down the Command key while moving the window, the window
isn’t made active; it moves in the same plane.
The application should ensure that a window can never be moved completely off the screen.
_______________________________________________________________________________
»Changing the Size of a Window
If a window has a size box in its bottom right corner, where the scroll bars come together, the user can change the size of the window—enlarging or reducing it to the desired size.
Dragging the size box attaches a dotted outline of the window to the pointer. The outline’s top left corner stays fixed, while the bottom right corner follows the pointer. When the mouse button is released, the entire window is redrawn in the shape of the dotted outline.
Moving windows and sizing them go hand in hand. If a window can be moved, but not sized, then the user ends up constantly moving windows on and off the screen. The reason for this is that if the user moves the window off the right or bottom edge of the screen, the scroll bars are the first thing to disappear. To scroll the window, the user must move the window back onto the screen again. If, on the other hand, the window can be resized, then the user can change its size instead of moving it off the screen, and will still be able to scroll.
Sizing a window doesn’t change the position of the top left corner of the window over the document or the appearance of the part of the view that’s still showing; it changes only how much of the view is visible inside the window. One exception to this rule is a command such as Reduce to Fit in MacDraw, which changes the scaling of the view to fit the size of the window. If, after choosing this command, the user resizes the window, the application changes the scaling of the view.
The application can define a minimum window size. Any attempt to shrink the window below this size is ignored.
_______________________________________________________________________________
»Window Zooming
The more open documents on a desktop, the more difficult it is for the user to locate, select, and resize the one to be worked on. The 128K ROM includes a feature, known as window zooming, that allows users—with a single mouse click—to toggle the active window between its standard size and location and a predefined size and location.
The initial size and placement of a window is known as its standard state. The application program can supply values for the standard state; otherwise the full screen (minus a few border pixels) is assumed (see Figure 17). The standard state should be the most useful size and location for normal operations within the program—usually it’s the full screen.
Figure 17–Window in Standard State
The user cannot change the standard state, but the application can change it within context. For example, a word processor might define a size that’s wide enough to display a document whose width is as specified in Page Setup. If the user invokes Page Setup to specify a wider or narrower document, the application might then change the standard state to reflect that change.
Your application can also supply initial values for the second window state, known as the user state. If you don’t supply initial values, the user state is identical to the standard state until the user moves or resizes the window. When the standard state and user state are different (Figure 18 shows a hypothetical user state), clicking in the zoom-window box acts as a toggle between the two states.
Figure 18–Window in User State
Application developers are encouraged to take advantage of the zoom-window feature; details on using this feature are provided in the Window Manager chapter. You should not change the shape of the zoom-window box or change the interpretation of clicking on the the zoom-window box (shown in Figure 19). You should add no other elements to the title bar. Except in the zoom-window box and in the close box, clicking within the title bar should have no effect.
Figure 19–Zoom-Window Box Details
»Effects of Dragging and Sizing
Explicit dragging or resizing of the window is handled in the normal way, regardless of the presence or absence of the zoom-window feature. The effect of dragging or resizing depends on the state of the window and the degree of movement. A change, either in position or size, of seven pixels or less is insignificant. A change of more than seven pixels is a “significant change”.
If dragging or resizing occur when the window is in the standard state, a small change in the size or location of the window does not change the state, nor does it change the application-defined values for the size and location of the standard state. It does, of course, change the size or location of the window. A significant change in the size or location of the window switches the window to the user state and sets the values for the size and location of that state to those of the window.
If dragging or resizing occur when the window is in the user state, a change in size or location that leaves the window within seven pixels of the size and location specified as the standard state changes the state to the standard state, leaving the size and location of the user state unchanged. Any other change in size or location in the user state leaves the window in the user state and sets the values for the size and location of that state to those of the window.
_______________________________________________________________________________
»Scroll Bars
Scroll bars are used to change which part of a document view is shown in a window. Only the active window can be scrolled.
A scroll bar (see Figure 15) is a light gray shaft, capped on each end with square boxes labeled with arrows; inside the shaft is a white rectangle. The shaft represents one dimension of the entire document; the white rectangle
(called the scroll box) represents the location of the portion of the document currently visible inside the window. As the user moves the document under the window, the position of the rectangle in the scroll bar moves correspondingly. If the document is no larger than the window, the scroll bars are inactive (the scrolling apparatus isn’t shown in them). If the document window is inactive, the scroll bars aren’t shown at all.
There are three ways to move the document under the window: by sequential scrolling, by “paging” windowful by windowful through the document, and by directly positioning the scroll box.
Clicking a scroll arrow lets the user see more of the document in the direction of the scroll arrow, so it moves the document in the opposite direction from the arrow. For example, when the user clicks the top scroll arrow, the document moves down, bringing the view closer to the top of the document. The scroll box moves towards the arrow being clicked.
Each click in a scroll arrow causes movement a distance of one unit in the chosen direction, with the unit of distance being appropriate to the application: one line for a word processor, one row or column for a spreadsheet, and so on. Within a document, units should always be the same size, for smooth scrolling. Pressing the scroll arrow causes continuous movement in its direction.
Clicking the mouse anywhere in the gray area of the scroll bar advances the document by windowfuls. The scroll box, and the document view, move toward the place where the user clicked. Clicking below the scroll box, for example, brings the user the next windowful towards the bottom of the document. Pressing in the gray area keeps windowfuls flipping by until the user releases the mouse button, or until the location of the scroll box catches up to the location of the pointer. Each windowful is the height or width of the window, minus one unit overlap (where a unit is the distance the view scrolls when the scroll arrow is clicked once).
In both the above schemes, the user moves the document incrementally until it’s in the proper position under the window; as the document moves, the scroll box moves accordingly. The user can also move the document directly to any position simply by moving the scroll box to the corresponding position in the scroll bar. To move the scroll box, the user drags it along the scroll bar; an outline of the scroll box follows the pointer. When the mouse button is released, the scroll box jumps to the position last held by the outline, and the document jumps to the position corresponding to the new position of the scroll box.
If the user starts dragging the scroll box, and then moves the pointer a certain distance outside the scroll bar, the scroll box detaches itself from the pointer and stops following it; if the user releases the mouse button, the scroll box stays in its original position and the document remains unmoved. But if the user still holds the mouse button and drags the pointer back into the scroll bar, the scroll box reattaches itself to the pointer and can be dragged as usual.
If a document has a fixed size, and the user scrolls to the right or bottom edge of the document, the application displays a gray background between the edge of the document and the window frame.
_______________________________________________________________________________
»Automatic Scrolling
There are several instances when the application, rather than the user, scrolls the document. These instances involve some potentially sticky problems about how to position the document within the window after scrolling.
The first case is when the user moves the pointer out of the window while selecting by dragging. The window keeps up with the selection by scrolling automatically in the direction the pointer has been moved. The rate of scrolling is the same as if the user were pressing on the corresponding scroll arrow or arrows.
The second case is when the selection isn’t currently showing in the window, and the user performs an operation on it. When this happens, it’s usually because the user has scrolled the document after making a selection. In this case, the application scrolls the window so that the selection is showing before performing the operation.
The third case is when the application performs an operation whose side effect is to make a new selection. An example is a search operation, after which the object of the search is selected. If this object isn’t showing in the window, the application must scroll the document so as to show it.
The second and third cases present the same problem: Where should the selection be positioned within the window after scrolling? The primary rule is that the application should avoid unnecessary scrolling; users prefer to retain control over the positioning of a document. The following guidelines should be helpful:
• If part of the new selection is already showing in the window, don’t
scroll at all. An exception to this rule is when the part of the
selection that isn’t showing is more important than the part that is
showing.
• If scrolling in one orientation (horizontal or vertical) is sufficient
to reveal the selection, don’t scroll in both orientations.
• If the selection is smaller than the window, position the selection so
that some of its context is showing on each side. It’s better to put
the selection somewhere near the middle of the window than right up
against the corner.
• Even if the selection is too large to show in the window, it might be
preferable to show some context rather than to try to fit as much as
possible of the selection in the window.
_______________________________________________________________________________
»Splitting a Window
Sometimes it’s desirable to be able to see disjoint parts of a document simultaneously. Applications that accommodate such a capability allow the window to be split into independently scrollable panes.
Applications that support splitting a window into panes place split bars at the top of the vertical scroll bar and to the left of the horizontal one. Pressing a split bar attaches it to the pointer. Dragging the split bar positions it anywhere along the scroll bar; releasing the mouse button moves the split bar to a new position, splits the window at that location, and divides the appropriate scroll bar into separate scroll bars for each pane. Figure 20 shows the ways a window can be split.
Figure 20–Types of Split Windows
After a split, the document appears the same, except for the split line lying across it. But there are now separate scroll bars for each pane. The panes are still scrolled together in the orientation of the split, but can be scrolled independently in the other orientation. For example, if the split is vertical, then vertical scrolling (using the scroll bar along the right of the window) is still synchronous; horizontal scrolling is controlled separately for each pane, using the two scroll bars along the bottom of the window. This is shown in Figure 21.
Figure 21–Scrolling a Split Window
To remove a split, the user drags the split bar to either end of the scroll bar.
The number of views in a document doesn’t alter the number of selections per document: that is, one. The selection appears highlighted in all views that show it. If the application has to scroll automatically to show the selection, the pane that should be scrolled is the last one that the user clicked in. If the selection is already showing in one of the panes, no automatic scrolling takes place.
_______________________________________________________________________________
»Panels
If a document window is more or less permanently divided into different areas, each of which has different content, these areas are called panels. Unlike panes, which show different parts of the same document but are functionally identical, panels are functionally different from each other but might show different interpretations of the same part of the document. For example, one panel might show a graphic version of the document while another panel shows a textual version.
Panels can behave much like windows; they can have scroll bars, and can even be split into more than one pane. An example of a panel with scroll bars is the list of files in the Open command’s dialog box.
Whether to use panels instead of separate windows is up to the application. Multiple panels in the same window are more compact than separate windows, but they have to be moved, opened, and closed as a unit.
_______________________________________________________________________________
»COMMANDS
_______________________________________________________________________________
Once information that’s to be operated on has been selected, a command to operate on the information can be chosen from lists of commands called menus.
Macintosh’s pull-down menus have the advantage that they’re not visible until the user wants to see them; at the same time they’re easy for the user to see and choose items from.
Most commands either do something, in which case they’re verbs or verb phrases, or else they specify an attribute of an object, in which case they’re adjectives. They usually apply to the current selection, although some commands apply to the whole document or window.
When you’re designing your application, don’t assume that everything has to be done through menu commands. Sometimes it’s more appropriate for an operation to take place as a result of direct user manipulation of a graphic object on the screen, such as a control or icon. Alternatively, a single command can execute complicated instructions if it brings up a dialog box for the user to fill in.
_______________________________________________________________________________
»The Menu Bar
The menu bar is displayed at the top of the screen. It contains a number of words and phrases: These are the titles of the menus associated with the current application. Each application has its own menu bar. The names of the menus do not change, except when the user accesses a desk accessory that uses different menus.
Only menu titles appear in the menu bar. If all of the commands in a menu are currently disabled (that is, the user can’t choose them), the menu title should be dimmed (drawn in gray). The user can pull down the menu to see the commands, but can’t choose any of them.
_______________________________________________________________________________
»Choosing a Menu Command
To choose a command, the user positions the pointer over the menu title and presses the mouse button. The application highlights the title and displays the menu, as shown in Figure 22.
While holding down the mouse button, the user moves the pointer down the menu. As the pointer moves to each command, the command is highlighted. The command that’s highlighted when the user releases the mouse button is chosen. As soon as the mouse button is released, the command blinks briefly, the menu disappears, and the command is executed. (The user can set the number of times the command blinks in the Control Panel desk accessory.) The menu title in the menu bar remains highlighted until the command has completed execution.
Nothing actually happens until the user chooses the command; the user can look at any of the menus without making a commitment to do anything.
The most frequently used commands should be at the top of a menu; research shows that the easiest item for the user to choose is the second item from the top. The most dangerous commands should be at the bottom of the menu, preferably isolated from the frequently used commands.
Figure 22–Menu
_______________________________________________________________________________
»Appearance of Menu Commands
The commands in a particular menu should be logically related to the title of the menu. In addition to command names, three features of menus help the user understand what each command does: command groups, toggles, and special visual features.
»Command Groups
As mentioned above, menu commands can be divided into two kinds: verbs and adjectives, or actions and attributes. An important difference between the two kinds of commands is that an attribute stays in effect until it’s canceled, while an action ceases to be relevant after it has been performed. Each of these two kinds can be grouped within a menu. Groups are separated by dotted lines, which are implemented as disabled commands.
The most basic reason to group commands is to break up a menu so it’s easier to read. Commands grouped for this reason are logically related, but independent. Commands that are actions are usually grouped this way, such as Cut, Copy, Paste, and Clear in the Edit menu.
Attribute commands that are interdependent are grouped to show this interdependence. Two kinds of attribute command groups are mutually exclusive groups and accumulating groups.
In a mutually exclusive attribute group, only one command in the group is in effect at any time. The command that’s in effect is preceded by a check mark. If the user chooses a different command in the group, the check mark is moved to the new command. An example is the Font menu in MacWrite; no more than one font can be in effect at a time.
In an accumulating attribute group, any number of attributes can be in effect at the same time. One special command in the group cancels all the other commands. An example is the Style menu in MacWrite: The user can choose any combination of Bold, Italic, Underline, Outline, or Shadow, but Plain Text cancels all the other commands.
»Toggled Commands
Another way to show the presence or absence of an attribute is by a toggled command. In this case, the attribute has two states, and a single command allows the user to toggle between the states. For example, when rulers are showing in MacWrite, a command in the Format menu reads “Hide Rulers”. If the user chooses this command, the rulers are hidden, and the command is changed to read “Show Rulers”. This kind of group should be used only when the wording of the commands makes it obvious that they’re opposites.
»Special Visual Features
In addition to the command names and how they’re grouped, several other features of commands communicate information to the user:
• A check mark indicates whether an at tribute command is currently
in effect.
• An ellipsis (...) after a command name means that choosing that
command brings up a dialog box. The command isn’t actually executed
until the user has finished filling in the dialog box and has clicked
the OK button or its equivalent.
• The application dims a command when the user can’t choose it. If the
user moves the pointer over a dimmed item, it isn’t highlighted.
• If a command can be chosen from the keyboard, it’s followed by the
Command key symbol and the character used to choose it. To choose a
command this way, the user holds down the Command key and then presses
the character key.
_______________________________________________________________________________
»Reserved Command Key Combinations
There are several menu items, particularly in the File and Edit menus, that commonly have keyboard equivalents. For consistency, several of those keyboard equivalents should be used only for the commands listed below and should never be used for any other purpose. Desk accessories, which are accessible from all applications, assume that these Command-key combinations have the meanings listed here.
File Menu
Command-N (New)
Command-O (Open)
Command-S (Save)
Command-Q (Quit)
Note: The keyboard equivalent for the Quit command is useful in case
there’s a mouse malfunction, so the user will still be able to
leave the application in an orderly way (with the opportunity
to save any changes to documents that haven’t yet been saved).
Edit Menu
Command-Z (Undo)
Command-X (Cut)
Command-C (Copy)
Command-V (Paste)
The keyboard equivalents in the Style menu (listed below) are less strictly reserved. Applications that have Style menus shouldn’t use these keyboard equivalents for any other purpose, but applications that have no Style menus can use them for other purposes if needed. Remember that you risk confusing users if a given key combination means different things in different applications.
Style Menu
Command-P (Plain)
Command-B (Bold)
Command-I (Italic)
Command-U (Underline)
One keyboard command doesn’t have a menu equivalent:
Character Command
Period (.) Stop current operation
Several other menu features are also supported:
• A command can be shown in Bold, Italic, Outline, Underline,
or Shadow character style.
• A command can be preceded by an icon.
• The application can draw its own type of menu. An example of
this is the Fill menu in MacDraw.
_______________________________________________________________________________
»STANDARD MENUS
_______________________________________________________________________________
One of the strongest ways in which Macintosh applications can take advantage of the consistency of the user interface is by using standard menus. The operations controlled by these menus occur so frequently that it saves considerable time for users if they always match exactly. Three of these menus, the Apple, File, and Edit menus, appear in almost every application. The Font, FontSize, and Style menus affect the appearance of text, and appear only in applications where they’re relevant.
The Menu Manager now supports two new capabilities: hierarchical and pop-up menus. In addition, scrolling menus, introduced with the Macintosh Plus and Macintosh 512K Enhanced, are made visible with a scrolling menu indicator.
_______________________________________________________________________________
»The Apple Menu
Macintosh doesn't allow two applications to be running at once. Desk accessories, however, are mini-applications that are available while using any application.
At any time the user can issue a command to call up one of several desk accessories; the available accessories are listed in the Apple menu, as shown in Figure 23.
Accessories are disk-based: Only those accessories on an available disk can be used. The list of accessories is expanded or reduced according to what’s available. More than one accessory can be on the desktop at a time.
Figure 23–Apple Menu
The Apple menu also contains the “About xxx” menu item, where “xxx” is the name of the application. Choosing this item brings up a dialog box with the name and copyright information for the application, as well as any other information the application wants to display.
_______________________________________________________________________________
»The File Menu
The File menu lets the user perform certain simple filing operations without leaving the application and returning to the Finder. It also contains the commands for printing and for leaving the application. The standard File menu includes the commands shown in Figure 24. All of these commands are described below.
Figure 24–File Menu
»New
New opens a new, untitled document. The user names the document the first time it’s saved. The New command is disabled when the maximum number of documents allowed by the application is already open; however, an application that allows only one document to be open at a time may make an exception to this, as described below for Open.
»Open
Open opens an existing document. To select the document, the user is presented with a dialog box (Figure 25). This dialog box shows a list of all the documents, on the disk whose name is displayed, that can be handled by the current application. The user can scroll this list forward and backward. The dialog box also gives the user the chance to look at documents on another disk, or to eject a disk.
Figure 25–Open Dialog Box
Using the Open command, the user can only open a document that can be processed by the current application. Opening a document that can only be processed by a different application requires leaving the application and returning to the Finder.
The Open command is disabled when the maximum number of documents allowed by the application is already open. An application that allows only one document to be open at a time may make an exception to this, by first closing the open document before opening the new document. In this case, if the user has changed the open document since the last time it was saved, an alert box is presented as when an explicit Close command is given (see below); then the Open dialog box appears. Clicking Cancel in either the Close alert box or the Open dialog box cancels the entire operation.
»Close
Close closes the active window, which may be a document window, a desk accessory, or any other type of window. If it’s a document window and the user has changed the document since the last time it was saved, the command presents an alert box giving the user the opportunity to save the changes.
Clicking in the close box of a window is the same as choosing Close.
»Save
Save makes permanent any changes to the active document since the last time it was saved. It leaves the document open.
If the user chooses Save for a new document that hasn’t been named yet, the application presents the Save As dialog box (see below) to name the document, and then continues with the save. The active document remains active.
If there’s not enough room on the disk to save the document, the application asks if the user wants to save the document on another disk. If the answer is yes, the application goes through the Save As dialog to find out which disk.
»Save As
Save As saves a copy of the active document under a file name provided by the user.
If the document already has a name, Save As closes the old version of the document, creates a copy with the new name, and displays the copy in the window.
If the document is untitled, Save As saves the original document under the specified name. The active document remains active.
»Revert to Saved
Revert to Saved returns the active document to the state it was in the last time it was saved. Before doing so, it puts up an alert box to confirm that this is what the user wants.
»Page Setup
Page Setup lets the user specify printing parameters such as the paper size and printing orientation. These parameters remain with the document.
»Print
Print lets the user specify various parameters such as print quality and number of copies, and then prints the document. The parameters apply only to the current printing operation.
»Quit
Quit leaves the application and returns to the Finder. If any open documents have been changed since the last time they were saved, the application presents the same alert box as for Close, once for each document.
_______________________________________________________________________________
»The Edit Menu
The Edit menu contains the commands that delete, move, and copy objects, as well as commands such as Undo, Select All, and Show Clipboard. This section also discusses the Clipboard, which is controlled by the Edit menu commands. Text editing methods that don’t use menu commands are discussed under “Text Editing”.
If the application supports desk accessories, the order of commands in the Edit menu should be exactly as shown here. This is because, by default, the application passes the numbers, not the names, of the menu commands to the desk accessories. (For details, see the Desk Manager chapter) In particular, your application must provide an Undo command for the benefit of the desk accessories, even if it doesn’t support the command (in which case it can disable the command until a desk accessory is opened).
The standard order of commands in the Edit menu is shown in Figure 26.
Figure 26–Edit Menu
»The Clipboard
The Clipboard holds whatever is cut or copied from a document. Its contents stay intact when the user changes documents, opens a desk accessory, or leaves the application. An application can show the contents of the Clipboard in a window, and can choose whether to have the Clipboard window open or closed when the application starts up.
The Clipboard window looks like a document window, with a close box but usually without scroll bars or a size box. The user can see its contents but cannot edit them. In most other ways the Clipboard window behaves just like any other window.
Every time the user performs a Cut or Copy on the current selection, a copy of the selection replaces the previous contents of the Clipboard. The previous contents are kept around in case the user chooses Undo.
There’s only one Clipboard, which is present for all applications that support Cut, Copy, and Paste. The user can see the Clipboard window by choosing Show Clipboard from the Edit menu. If the window is already showing, it’s hidden by choosing Hide Clipboard. (Show Clipboard and Hide Clipboard are a single toggled command.)
Because the contents of the Clipboard remain unchanged when applications begin and end, or when the user opens a desk accessory, the Clipboard can be used for transferring data among mutually compatible applications and desk accessories.
»Undo
Undo reverses the effect of the previous operation. Not all operations can be undone; the definition of an undoable operation is somewhat application-dependent. The general rule is that operations that change the contents of the document are undoable, and operations that don’t are not. Most menu items are undoable, and so are typing sequences.
A typing sequence is any sequence of characters typed from the keyboard or numeric keypad, including Backspace, Return, and Tab, but not including keyboard equivalents of commands.
Operations that aren’t undoable include selecting, scrolling, and splitting the window or changing its size or location. None of these operations interrupts a typing sequence. For example, if the user types a few characters and then scrolls the document, the Undo command still undoes the typing. Whenever the location affected by the Undo operation isn’t currently showing on the screen, the application should scroll the document so the user can see the effect of the Undo.
An application should also allow the user to undo any operations that are initiated directly on the screen, without a menu command. This includes operations controlled by setting dials, clicking check boxes, and so on, as well as drawing graphic objects with the mouse.
The actual wording of the Undo command as it appears in the Edit menu is “Undo xxx”, where xxx is the name of the last operation. If the last operation isn’t a menu command, use some suitable term after the word Undo. If the last operation can’t be undone, the command reads “Undo”, but is disabled.
If the last operation was Undo, the menu command is “Redo xxx”, where xxx is the operation that was undone. If this command is chosen, the Undo is undone.
»Cut
The user chooses Cut either to delete the current selection or to move it. A move is eventually completed by choosing Paste.
When the user chooses Cut, the application removes the current selection from the document and puts it in the Clipboard, replacing the Clipboard’s previous contents. The place where the selection used to be becomes the new selection; the visual implications of this vary among applications. For example, in text, the new selection is an insertion point, while in an array, it’s an empty but highlighted cell. If the user chooses Paste immediately after choosing Cut, the document should be just as it was before the cut.
»Copy
Copy is the first stage of a copy operation. Copy puts a copy of the selection in the Clipboard, but the selection also remains in the document. The user completes the copy operation by choosing Paste.
»Paste
Paste is the last stage of a move or copy operation. It pastes the contents of the Clipboard into the document, replacing the current selection. The user can choose Paste several times in a row to paste multiple copies. After a paste, the new selection is the object that was pasted, except in text, where it’s an insertion point immediately after the pasted text. The Clipboard remains unchanged.
»Clear
When the user chooses Clear, or presses the Clear key on the numeric keypad, the application removes the selection, but doesn’t put it in the Clipboard. The new selection is the same as it would be after a Cut.
»Select All
Select All selects every object in the document.
»Show Clipboard
Show Clipboard is a toggled command. When the Clipboard isn’t displayed, the command is “Show Clipboard”. If the user chooses this command, the Clipboard is displayed and the command changes to “Hide Clipboard”.
_______________________________________________________________________________
»Font-Related Menus
Three standard menus affect the appearance of text: Font, which determines the font of a text selection; FontSize, which determines the size of the characters; and Style, which determines aspects of its appearance such as boldface, italics, and so on.
A font is a set of typographical characters created with a consistent design. Things that relate characters in a font include the thickness of vertical and horizontal lines, the degree and position of curves and swirls, and the use of serifs. A font has the same general appearance, regardless of the size of the characters. Most Macintosh fonts are proportional rather than fixed-width; an application can’t make assumptions about exactly how many characters will fit in a given area when these fonts are used.
»Font Menu
The Font menu always lists the fonts that are currently available. Figure 27 shows a Font menu with some of the most common fonts.
Figure 27–Font Menu
»FontSize Menu
Font sizes are measured in points; a point is about 1/72 of an inch. Each font is available in predefined sizes. The numbers of these sizes for each font are shown outlined in the FontSize menu. The font can also be scaled to other sizes, but it may not look as good. Figure 28 shows a FontSize menu with the standard font sizes.
Figure 28–FontSize Menu
If there’s insufficient room in the menu bar for the word FontSize, it can be abbreviated to Size. If there’s insufficient room for both a Font menu and a Size menu, the sizes can be put at the end of the Font or Style menu.
»Style Menu
The commands in the standard Style menu are Plain Text, Bold, Italic, Underline, Outline, and Shadow. All the commands except Plain Text are accumulating attributes; the user can choose any combination. A command that’s in effect for the current selection is preceded by a check mark. Plain Text cancels all the other choices. Figure 29 shows these styles.
Figure 29–Style Menu
_______________________________________________________________________________
»Hierarchical Menus
Hierarchical menus are a logical extension of the current menu metaphor: another dimension is added to a menu, so that a menu item can be the title of a submenu. When the user drags the pointer through a hierarchical menu item, a submenu appears after a brief delay.
Hierarchical menu items have an indicator (a small black triangle pointing to the right, to indicate “more”) at the edge of the menu, as illustrated in Figure 30.
Figure 30–Main Menu Before and After Submenu Appears
One main menu can contain both standard menu items and submenus; both levels can have Command-key equivalents. (The submenu title can’t have a Command-key equivalent, of course, because it’s not a command. Key combinations aren’t used to pull down menus.)
Two delay values enable submenus to function smoothly, without jarring distractions to the user: The submenu delay is the length of time before a submenu appears as the user drags the pointer through a hierarchical menu item. It prevents flashing due to rapid appearance–disappearance of submenus. The drag delay allows the user to drag diagonally from the submenu title into the submenu, briefly crossing part of the main menu, without the submenu disappearing (which would ordinarily happen when the pointer was dragged into another main menu item). See Figure 31.
Figure 31–Dragging Diagonally to a Submenu Item
Other aspects of submenus—menu blink for example—behave exactly the same way as in standard menus.
The original Macintosh menus were designed so that the user could drag the mouse across the menu bar and immediately see all of the choices currently available. Although developers have found they need more menu space, and hierarchical menus were designed to meet that need, it’s important that this original capability be maintained as much as possible. To keep this essential simplicity and clarity, follow these guidelines:
• Hierarchical menus should be used only for lists of related items,
such as fonts or font sizes (in this case, the title of the submenu
clearly tells what the submenu contains).
• Only one level of hierarchical menu should be used, although the
capability for more is provided. This one extra layer of menus
potentially increases by an order of magnitude the number of menu
items that can be used; if you need more layers than that, your
application is probably more complex than most users can understand,
and you should rethink your design.
_______________________________________________________________________________
»Pop-Up Menus
A pop-up menu is one that isn’t in the menu bar, but appears somewhere else on the screen (usually in a dialog) when the user presses in a particular place, as shown in Figure 32.
Figure 32–Dialog Box With Pop-Up Menus
Pop-up menus are used for setting values or choosing from lists of related items. The indication that there is a pop-up menu is a box with a one-pixel thick drop shadow, drawn around the current value. When the user presses this box, the pop-up menu appears, with the current value—checked and highlighted—under the pointer, as shown in Figure 33. If the menu has a title, the title is highlighted while the menu is visible.
Figure 33–Dragging Through a Pop-up Menu
The pop-up menu acts like other menus: the user can move around in it and choose another item, which then appears in the box, or can move outside it to leave the current value active. If a pop-up menu reaches the top or bottom of the screen, it scrolls like other menus.
When designing an application that uses pop-up menus, keep in mind the following points:
• Pop-up menus should only be used for lists of values or related items
(much like hierarchical menus); they should not be used for commands.
• You must draw the shadowed box indicating that there is a pop-up menu,
so the user knows that it’s there—pop-up menus should never be invisible.
• While the menu is showing, its title should be inverted. If several
pop-up menus are near each other, this lessens ambiguity about which
one is being used.
• The current value should always appear under the pointer when the
menu pops up, so that simply clicking the box doesn’t change the item.
• Hierarchical pop-up menus should not be used.
Always consider whether a pop-up menu is the simplest thing to use in each case. For example, rather than have a pop-up menu choose all paper sizes, icons could represent commonly used sizes, with a pop-up menu for non-standard sizes.
_______________________________________________________________________________
»Scrolling Menu Indicator
Scrolling menus were introduced with the Macintosh Plus and Macintosh 512K Enhanced, but this feature was invisible. When there were more than eighteen items in a menu (which can happen with fonts on a hard disk), the menu scrolled to show more items as the user moved the pointer past the last item; but users didn’t know whether there were any more items in a menu unless they happened to drag past the bottom of it. The scrolling menu feature is now made visible by an indicator (similar to the hierarchical menu indicator), which appears at the bottom of the menu when there are more items, as shown in Figure 34.
Figure 34–Scrolling Menus: Indicator at Bottom
The indicator area itself doesn’t highlight, but the menu scrolls as the user drags over it. When the last item is shown, the indicator disappears.
As soon as the menu starts scrolling, another indicator appears at the top of the menu to show that some items are now hidden in that direction (see
Figure 35).
Figure 35–Scrolling Menus: Indicator at Top
If the user drags back up to the top, the menu scrolls back down in the same manner. If the user releases the mouse button or selects another menu, and then selects the menu again, it appears in its original position, with the hidden items and the indicator at the bottom.
_______________________________________________________________________________
»TEXT EDITING
_______________________________________________________________________________
In addition to the operations described under “The Edit Menu” above, there are other ways to edit text that don’t use menu items.
_______________________________________________________________________________
»Inserting Text
To insert text, the user selects an insertion point by clicking where the text is to go, and then starts typing it. As the user types, the application continually moves the insertion point to the right of each new character.
Applications with multiline text blocks should support word wraparound; that is, no word should be broken between lines. The definition of a word is given under “Selecting Words” above.
_______________________________________________________________________________
»Backspace
When the user presses the Backspace key, one of two things happens:
• If the current selection is one or more characters, it’s deleted.
• If the current selection is an insertion point, the previous
character is deleted.
In either case, the insertion point replaces the deleted characters in the document. The deleted characters don’t go into the Clipboard, but the deletion can be undone by immediately choosing Undo.
_______________________________________________________________________________
»Replacing Text
If the user starts typing when the selection is one or more characters, the characters that are typed replace the selection. The deleted characters don’t go into the Clipboard, but the replacement can be undone by immediately choosing Undo.
_______________________________________________________________________________
»Intelligent Cut and Paste
An application that lets the user select a word by double-clicking should also see to it that the user doesn’t regret using this feature. The only way to do this is by providing “intelligent” cut and paste.
To understand why this feature is necessary, consider the following sequence of events in an application that doesn’t provide it:
1. A sentence in the user’s document reads:
Returns are only accepted if the merchandise is damaged.
The user wants to change this to:
Returns are accepted only if the merchandise is damaged.
2. The user selects the word “only” by double-clicking. The letters are
highlighted, but not either of the adjacent spaces.
3. The user chooses Cut, clicks just before the word “if”, and chooses Paste.
4. The sentence now reads:
Returns are accepted onlyif the merchandise is damaged.
To correct the sentence, the user has to remove a space between “are”
and “accepted”, and add one between “only” and “if”. At this point he
or she may be wondering why the Macintosh is supposed to be easier to
use than other computers.
If an application supports intelligent cut and paste, the rules to follow are:
• If the user selects a word or a range of words, highlight the selection,
but not any adjacent spaces.
• When the user chooses Cut, if the character to the left of the selection
is a space, discard it. Otherwise, if the character to the right of the
selection is a space, discard it.
• When the user chooses Paste, if the character to the left or right of the
current selection is part of a word, insert a space before pasting.
If the left or right end of a text selection is a word, follow these rules at that end, regardless of whether there’s a word at the other end.
This feature makes more sense if the application supports the full definition of a word (as detailed above under “Selecting Words”), rather than the definition of a word as anything between two spaces.
These rules apply to any selection that’s one or more whole words, whether it was chosen with a double click or as a range selection.
Figure 36 shows some examples of intelligent cut and paste.
Figure 36–Intelligent Cut and Paste
_______________________________________________________________________________
»Editing Fields
If an application isn’t primarily a text application, but does use text in fields (such as in a dialog box), it may not be able to provide the full text editing capabilities described so far. It’s important, however, that whatever editing capabilities the application provides under these circumstances be upward-compatible with the full text editing capabilities. The following list shows the capabilities that can be provided, from the minimal to the most sophisticated:
• The user can select the whole field and type in a new value.
• The user can backspace.
• The user can select a substring of the field and replace it.
• The user can select a word by double-clicking.
• The user can choose Undo, Cut, Copy, Paste, and Clear, as described
above under “The Edit Menu”. In the most sophisticated version, the
application implements intelligent cut and paste.
An application should also perform appropriate edit checks. For example, if the only legitimate value for a field is a string of digits, the application might issue an alert if the user typed any nondigits. Alternatively, the application could wait until the user is through typing before checking the validity of the field’s contents. In this case, the appropriate time to check the field is when the user clicks anywhere other than within the field.
_______________________________________________________________________________
»DIALOGS AND ALERTS
_______________________________________________________________________________
The “select-then-choose” paradigm is sufficient whenever operations are simple and act on only one object. But occasionally a command will require more than one object, or will need additional parameters before it can be executed. And sometimes a command won’t be able to carry out its normal function, or will be unsure of the user’s real intent. For these special circumstances the Macintosh user interface includes two additional features:
• dialogs, to allow the user to provide additional information before
a command is executed
• alerts, to notify the user whenever an unusual situation occurs
Since both of these features lean heavily on controls, controls are described in this section, even though controls are also used in other places.
_______________________________________________________________________________
»Controls
Friendly systems act by direct cause-and-effect; they do what they’re told. Performing actions on a system in an indirect fashion reduces the sense of direct manipulation. To give Macintosh users the feeling that they’re in control of their machines, many of an application’s features are implemented with controls: graphic objects that, when manipulated with the mouse, cause instant action with visible results. Controls can also change settings to modify future actions.
There are four main types of controls: buttons, check boxes, radio buttons, and dials (see Figure 37). You can also design your own controls, such as a ruler on which tabs can be set.
Figure 37–Controls
»Buttons
Buttons are small objects labeled with text. Clicking or pressing a button performs the action described by the button’s label.
Buttons usually perform instantaneous actions, such as completing operations defined by a dialog box or acknowledging error messages. They can also perform continuous actions, in which case the effect of pressing on the button would be the same as the effect of clicking it repeatedly.
Two particular buttons, OK and Cancel, are especially important in dialogs and alerts; they’re discussed under those headings below.
»Check Boxes and Radio Buttons
Whereas buttons perform instantaneous or continuous actions, check boxes and radio buttons let the user choose among alternative values for a parameter.
Check boxes act like toggle switches; they’re used to indicate the state of a parameter that must be either off or on. The parameter is on if the box is checked, otherwise it’s off. The check boxes appearing together in a given context are independent of each other; any number of them can be off or on.
Radio buttons typically occur in groups; they’re round and are filled in with a black circle when on. They’re called radio buttons because they act like the buttons on a car radio. At any given time, exactly one button in the group is on. Clicking one button in a group turns off the button that’s currently on.
Both check boxes and radio buttons are accompanied by text that identifies what each button does.
»Dials
Dials display the value, magnitude, or position of something in the application or system, and optionally allow the user to alter that value. Dials are predominantly analog devices, displaying their values graphically and allowing the user to change the value by dragging an indicator; dials may also have a digital display.
The most common example of a dial is the scroll bar. The indicator of the scroll bar is the scroll box; it represents the position of the window over the length of the document. The user can drag the scroll box to change that position. (See “Scroll Bars” above.)
_______________________________________________________________________________
»»Dialogs
Commands in menus normally act on only one object. If a command needs more information before it can be performed, it presents a dialog box to gather the additional information from the user. The user can tell which commands bring up dialog boxes because they’re followed by an ellipsis (...) in the menu.
A dialog box is a rectangle that may contain text, controls, and icons. There should be some text in the box that indicates which command brought up the dialog box.
The user sets controls and text fields in the dialog box to provide the needed information. When the application puts up the dialog box, it should set the controls to some default setting and fill in the text fields with default values, if possible. One of the text fields (the “first” field) should be highlighted, so that the user can change its value just by typing in the new value. If all the text fields are blank, there should be an insertion point in the first field.
Editing text fields in a dialog box should conform to the guidelines detailed above under “Text Editing”.
When the user is through editing an item:
• Pressing Tab accepts the changes made to the item, and selects the
next item in sequence.
• Clicking in another item accepts the changes made to the previous
item and selects the newly clicked item.
Dialog boxes are either modal or modeless, as described below.
»Modal Dialog Boxes
A modal dialog box is one that the user must explicitly dismiss before doing anything else, such as making a selection outside the dialog box or choosing a command. Figure 38 shows a modal dialog box.
Figure 38–A Modal Dialog Box
Because it restricts the user’s freedom of action, this type of dialog box should be used sparingly. In particular, the user can’t choose a menu item while a modal dialog box is up, and therefore can only do the simplest kinds of text editing. For these reasons, the main use of a modal dialog box is when
it’s important for the user to complete an operation before doing anything else.
A modal dialog box usually has at least two buttons: OK and Cancel. OK dismisses the dialog box and performs the original command according to the information provided; it can be given a more descriptive name than “OK”. Cancel dismisses the dialog box and cancels the original command; it should always be called “Cancel”.
A dialog box can have other kinds of buttons as well; these may or may not dismiss the dialog box. One of the buttons in the dialog box may be outlined boldly. The outlined button is the default button; if no button is outlined, then the OK button is the default button. The default button should be the safest button in the current situation. Pressing the Return or Enter key has the same effect as clicking the default button. If there’s no default button, Return and Enter have no effect.
A special type of modal dialog box is one with no buttons. This type of box just informs the user of a situation without eliciting any response. Usually, it would describe the progress of an ongoing operation. Since it has no buttons, the user has no way to dismiss it. Therefore, the application must leave it up long enough for the user to read it before taking it down.
»Modeless Dialog Boxes
A modeless dialog box allows the user to perform other operations without dismissing the dialog box. Figure 39 shows a modeless dialog box.
A modeless dialog box is dismissed by clicking in the close box or by choosing Close when the dialog is active. The dialog box is also dismissed implicitly when the user chooses Quit. It’s usually a good idea for the application to remember the contents of the dialog box after it’s dismissed, so that when it’s opened again, it can be restored exactly as it was.
Figure 39–A Modeless Dialog Box
Controls work the same way in modeless dialog boxes as in modal dialog boxes, except that buttons never dismiss the dialog box. In this context, the OK button means “go ahead and perform the operation, but leave the dialog box up”, while Cancel usually terminates an ongoing operation.
A modeless dialog box can also have text fields; since the user can choose menu commands, the full range of editing capabilities can be made available.
»Standard Close Dialog
When a user chooses Close or Quit from the File menu, and the active document has been changed, the Close dialog box appears, asking “Save changes before closing?” A great deal of work can be lost if a user mistakenly clicks the “No” button instead of “Cancel”. This is especially important to MultiFinder users, who often move from one application to another and become less aware of subtle differences between applications. To avoid confusion, all applications should use the same standard Close dialog. As shown in Figure 40, dialogs can have multiple lines of text.
Figure 40–A Standard Close Dialog
»Close Box Specifications
“Yes” and “No”, the two direct responses to the question “Save changes before closing?” are placed together on the left side of the box. “Yes”, the default button, is boldly outlined. “Cancel”, which cancels the close command, is to the right, separate from “Yes” and “No”.
After the user selects Close from the File menu, the text of the question in the Close box is generally “Save changes before closing?” However, if the user sees this dialog after choosing “Quit”, the text would instead be “Save changes before quitting?” If the application supports multiple windows, the text could be “Save changes to [document name] before closing window?” The box should always look the same and appear in the same place on the screen.
The box itself is 120 pixels high by 238 pixels wide. Its standard location is
(100,120)(220,358) but other locations may be appropriate.
Here are the other coordinates for the standard close box (assuming standard location):
the text (12,20)(45,223)
the word “yes” (58,25)(76,99)
the word “no” (86,25)(104,99)
the word “cancel” (86,141)(104,215)
If you must devise a close box different from the one described here, maintain the general arrangement of the buttons and remember that the user’s safest choice should be the default button and that the most dangerous choice should be the most difficult to make happen.
_______________________________________________________________________________
»Alerts
Every user of every application is liable to do something that the application won’t understand or can’t cope with in a normal manner. Alerts give applications a way to respond to errors not only in a consistent manner, but in stages according to the severity of the error, the user’s level of expertise, and the particular history of the error. The two kinds of alerts are beeps and alert boxes.
Beeps are used for errors that are both minor and immediately obvious. For example, if the user tries to backspace past the left boundary of a text field, the application could choose to beep instead of putting up an alert box. A beep can also be part of a staged alert, as described below.
An alert box looks like a modal dialog box, except that it’s somewhat narrower and appears lower on the screen. An alert box is primarily a one way communication from the system to the user; the only way the user can respond is by clicking buttons. Therefore alert boxes might contain dials and buttons, but usually not text fields, radio buttons, or check boxes. Figure 41 shows a typical alert box.
Figure 41–An Alert Box
There are three types of alert boxes:
• Note: A minor mistake that wouldn’t have any disastrous consequences
if left as is.
• Caution: An operation that may or may not have undesirable results
if it’s allowed to continue. The user is given the choice whether or
not to continue.
• Stop: A serious problem or other situation that requires remedial
action by the user.
An application can define different responses for each of several stages of an alert, so that if the user persists in the same mistake, the application can issue increasingly more helpful (or sterner) messages. A typical sequence is for the first two occurrences of the mistake to result in a beep, and for subsequent occurrences to result in an alert box. This type of sequence is especially appropriate when the mistake is one that has a high probability of being accidental (for example, when the user chooses Cut when there’s no text selection).
How the buttons in an alert box are labeled depends on the nature of the box. If the box presents the user with a situation in which no alternative actions are available, the box has a single button that’s labeled OK. Clicking this button means “I’ve read the alert.” If the user is given alternatives, then typically the alert is phrased as a question that can be answered “yes” or “no”. In this case, buttons labeled Yes and No are appropriate, although some variation such as Save and Don’t Save is also acceptable. OK and Cancel can be used, as long as their meanings aren’t ambiguous.
The preferred (safest) button to use in the current situation is boldly outlined. This is the alert’s default button; its effect occurs if the user presses Return or Enter.
It’s important to phrase messages in alert boxes so that users aren’t left guessing the real meaning. Avoid computer jargon.
Use icons whenever possible. Graphics can better describe some error situations than words, and familiar icons help users distinguish their alternatives better. Icons should be internationally comprehensible; they shouldn’t contain any words, or any symbols that are unique to a particular country.
Generally, it’s better to be polite than abrupt, even if it means lengthening the message. The role of the alert box is to be helpful and make constructive suggestions, not to give orders. But its focus is to help the user solve the problem, not to give an interesting but academic description of the problem itself.
Under no circumstances should an alert message refer the user to external documentation for further clarification. It should provide an adequate description of the information needed by the user to take appropriate action.
The best way to make an alert message understandable is to think carefully through the error condition itself. Can the application handle this without an error? Is the error specific enough so that the user can fix the situation? What are the recommended solutions? Can the exact item causing the error be displayed in the alert message?
_______________________________________________________________________________
»COLOR
_______________________________________________________________________________
Apple’s goal in adding color to the desktop user interface is to add meaning, not just to color things so they “look good”. Color can be a valuable additional channel of information to the user, but must be used carefully; otherwise, it can have the opposite of the effect you were trying for, and can be overwhelming visually (or look game-like).
Color is ultimately the domain of the user, who should be able to modify or remove any coloring imposed by the application. Unless you are implementing a color application such as a paint or draw program, you should consider color only for the data, not the interface.
In order to successfully implement color in an application, you should understand some of the complex issues surrounding its use. Many major theories on the proper use of color are not complete or well defined. The way in which the human eye sees color is not fully understood, nor are color’s subjective effects.
_______________________________________________________________________________
»Standard Uses of Color
In traditional user interface design, color is used to associate or separate objects and information in the following ways:
• discriminate between different areas
• show which things are functionally related
• show relationships between things
• identify crucial features
_______________________________________________________________________________
»Color Coding
Different colors have standard associations in different cultures. “Meanings” of colors usually have nothing to do with the wavelength of the color, but are learned through conditioning within a particular culture. Some of the more universal meanings for colors are
• Red: stop, error, or failure. (For disk drives, red also means
disk access in progress; don’t remove the disk or turn it off.).
• Yellow: warning, caution, or delay.
• Green: go, ready, or power on.
• Warm versus cold: reds, oranges, and yellows are perceived as hot
or exciting colors; blues and greens are cool, calm colors.
Colors often have additional standard meanings within a particular discipline: in the financial world, red means loss and black means gain. To a mapmaker, green means wooded areas, blue means water, yellow means deserts. In an application for a specific field, you can take advantage of these meanings; in a general application, you should allow users to change the colors and to turn off any color-coding that you use as a default.
For attracting the user’s attention, orange and red are more effective than other colors, but usually connote “warning” or “danger”. (Be aware, though, that in some cases, attracting the eye might not be what you want to do; for example, if “dangerous” menu items are colored red, the user’s eye will be attracted to the red items, and the user might be more likely to select the items by mistake.)
Although the screen may be able to display 256 or more colors, the human eye can discriminate only around 128 pure hues. Furthermore, when colors are used to signify information, studies have shown that the mind can only effectively follow four to seven color assignments on a screen at once.
_______________________________________________________________________________
»General Principles of Color Design
Two principles should guide the design of your application: begin the design in black and white, and limit the use of color, especially in the application’s use of the standard interface.
»Design in Black and White
You should design your application first in black and white. Color should be supplementary, providing extra information for those users who have color. Color shouldn’t be the only thing that distinguishes two objects; there should always be other cues, such as shape, location, pattern, or sound. There are several reasons for this:
• Monitors: Most of your users won’t have color. The majority of Macintosh
computers that Apple ships are black and white, and will continue to be
so for some time.
• Printing: Currently, color printing is not very accurate, and even when
high-quality color printing becomes available, there is usually a
significant change in colors between media.
• Colorblindness: A significant percentage of the population is colorblind
to some degree. (In Europe and America, about 8% of males and 0.5% of
females have some sort of defective color vision.) The most common form
of colorblindness is a loss of ability to distinguish red and green from
gray. In another form, yellow, blue, and gray are indistinguishable.
• Lighting: Under dim lighting conditions, colors tend to wash out and
become difficult for the eye to distinguish—the differences between colors
must be greater, and the number of colors fewer, for them to be
discernable. You can’t know the conditions under which your application
may be used.
»Limit Color Use
In the standard interface part of applications (menus, window frames, etc.), color should be used mimimally or not at all; the Macintosh interface is very succesful in black and white. You want the user’s attention focused on the content of the application, rather than distracted by color in the menus or scroll bars. Availability of color in the content area of your application depends on the sort of application:
• Graphics applications, which are concerned with the image itself,
should take full advantage of the color capabilities of Color QuickDraw,
letting the user choose from and modify as many colors as are available.
• Other applications, which deal with the organization of information,
should limit the use of color much more than this. Color-coding should
be allowed or provided to make the information clearer. Providing the
user with a small initial selection of distinct colors—four to seven at
most—with the capability of changing those or adding more, is the best
solution to this.
_______________________________________________________________________________
»Contrast and Discrimination
Color adds another dimension to the array of possible contrasts, and care must be given to maintain good readability and discernment.
»Colors on Grays
Colors look best against a background of neutral gray, like the desktop. Colors within your application will stand out more if the background and surrounding areas (such as the window frame and menus) are black and white or gray.
»Colored Text
Reading and legibility studies in the print (paper) world show that colored text is harder to read than black text on a white background. This also appears to be true in the limited studies that have been done in the computer domain, although almost all these studies have looked at colors on a black background, not the white background used in the Macintosh.
»Beware of Blue
The most illegible color is light blue, which should be avoided for text,
thin lines, and small shapes. Adjacent colors that differ only in the amount of blue should also be avoided. However, for things that you want to go unnoticed, like grid lines, blue is the perfect color (think of graph paper or lined paper).
»Small Objects
People cannot easily discriminate between small areas of color—to be able to tell what color something is, you have to have enough of it. Changes in the color of small objects must be obvious, not subtle.
_______________________________________________________________________________
»Specific Recommendations
Remember that color should never be the only thing that distinguishes objects. Other cues such as shape, location, pattern, or sound, should always be used in addition to color, for the reasons discussed above.
»Color the Black Bits Only
Generally, all interface elements should maintain a white background, using color to replace black pixels as appropriate. Maintaining the white background and only coloring what is already black (if something needs to be colored at all) helps to maintain the clarity and the “look and feel” of the Macintosh interface.
»Leave Outlines Black
Outlines of menus, windows, and alert and dialog boxes should remain in black. Edges formed by color differences alone are hard for the eye to focus on, and these objects may appear against a colored desktop or window.
»Highlighting and Selection
Most things—menu items, icons, buttons, and so forth—should highlight by reversing the white background with the colored or black bits when selected.
(For example, if the item is red on a white background, it should highlight to white on a red background.) However, if multiple colors of text appear together, Color TextEdit allows the user to set the highlighting bar color to something other than black to highlight the text better. The default for the bar color is always black.
»Menus
In general, the only use of color in menus should be in menus used to choose colors. However, color could also be useful for directing the user’s choices in training and tutorial materials: one color can lead the user through a lesson.
»Windows
Since the focus of attention is on the content region of the window, color should be used only in that area. Using color in the scroll bars or title bar can simply distract the user. (A possible exception would be coloring part of a window to match the color of the icon from which it came.)
»Dialogs and Alerts
Except for dialog boxes used to select colors, there’s no reason to color dialog boxes; they should be designed and laid out clearly enough that color isn’t necessary to separate different sections or items. Alert boxes must be as clear as possible; color can add confusion instead of clarity. For example, if you tried to make things clearer by using red to mean “dangerous” and green to mean “safe” in the Erase Disk alert, the OK button (“go”) would be red and the Cancel (“stop”) button would be green. Don’t do this.
»Pointers
Most of the time, when the pointer is being used for selecting and pointing, it should remain black—color might not be visible over potentially different colored backgrounds, and wouldn’t give the user any extra information. However, when the user is drawing or typing in color, the drawing or text-insertion pointer should appear in the color that is being used. Except for multicolored paintbrush pointers, the pointer shouldn’t contain more than one color at once—it’s hard for the eye to discriminate small areas of color.
_______________________________________________________________________________
»SOUND
_______________________________________________________________________________
The high-quality sound capabilities of the Macintosh let sound be integrated into the human interface to give users additional information. This section refers to sound as a part of the interface in standard applications, not to the way sound is used in an application that uses the sound itself as data, such as a music composition application.
_______________________________________________________________________________
»When to Use Sound
There are two general ways that sound can be used in the interface:
• It can be integrated throughout the standard interface to help make
the user aware of the state of the computer or application.
• It can be used to alert the user when something happens unexpectedly,
in the background, or when the user is not looking at the screen.
In general, when you put an indicator on the screen to tell the user something—for example, to tell the user that mail has come in, or to show a particular state—it’s also appropriate to use a sound.
»Getting Attention
If the computer is doing something time-consuming, and the user may have turned away from the screen, sound is a good way to let the user know that the process is finished, or it needs attention. (There should also be an indication on the screen, of course.)
»Alerts
Common alerts can use sounds other than the SysBeep for their first stage or two before bringing up an alert box. For example, when users try to paste when there’s nothing in the Clipboard, or try to backspace past the top of a field, different sounds could alert them.
»Modes
If your application has different states or modes, each one can have a particular sound when the user enters or leaves. This can emphasize the current mode, and prevent confusion.
_______________________________________________________________________________
»General Guidelines
Although the use of sound in the Desktop Interface hasn’t been investigated thoroughly, these are some general guidelines to keep in mind.
»Don’t Go Overboard
Be thoughtful about where and how you use sound in an application. If you overuse sound, it won’t add any meaning to the interface, and will probably be annoying.
»Redundancy
Sound should never be the only indication that something has happened; there should always be a visible indication on the screen, too, especially when the user needs to know what happened. The user may have all sound turned off, may have been out of hearing range of the computer, or may have a hearing impairment.
»Natural and Unobtrusive
Most sounds can be quite subtle and still getting their meaning across. Loud, harsh sounds can be offensive or intimidating. You should always use the sound yourself and test it on users for a significant period of time (a week or two, not twenty minutes) before including it in your application—if you turn it off after a day, chances are other people will, too. You should also avoid using tunes or jingles—more than two or three notes of a tune may become annoying or sound silly if heard very often.
»Significant Differences
Users can learn to recognize and discriminate between sounds, but different sounds should be significantly different. Nonmusicians often can’t tell the difference between two similar notes or chords, especially when the sounds are separated by a space of time.
»User Control
The user can change the volume of sounds, or turn sound off altogether, using the Control Panel desk accessory. Never override this capability.
»Resources
Always store sounds as resources, so users can change sounds and add additional sounds.
_______________________________________________________________________________
»USER TESTING
_______________________________________________________________________________
The primary test of the user interface is its success with users: can people understand what to do and can they accomplish the task at hand easily and efficiently? The best way to answer these questions is to put them to the users.
_______________________________________________________________________________
»Build User Testing Into the Design Process
Users should be involved early in the design process so that changes in the basic concept of the product can still be made, if necessary. Although there’s a natural tendency to wait for a good working prototype before showing the product to anyone, this is too late for the user to have a significant impact on design. In the absence of working code, you can show test subjects alternate designs on paper or storyboards. There are lots of ways that early concepts can be tested on potential users of a product. Then, as the design progresses, the testing can become more refined and can focus on screen designs and specific features of the interface.
_______________________________________________________________________________
»Test Subjects
There is no such thing as a “typical user”. You should, however, be able to identify some people who are familiar with the task your application supports but are unfamiliar with the specific technology you are using. These “naive experts” make good subjects because they don’t have to be taught what the application is for, they are probably already motivated to use it, and they know what is required to accomplish the task.
You don’t need to test a lot of people. The best procedure for formative testing (testing during the design process) is to collect data from a few subjects, analyze the results and apply them as appropriate. Then, identify new questions that arise and questions that still need answers, and begin all over again—it is an iterative process.
_______________________________________________________________________________
»Procedures
Planning and carrying out a true experimental test takes time and expert training. But many of the questions you may have about your design do not require such a rigid approach. Furthermore, the computer and application already provide a controlled setting from which objective data can be gathered quite reliably. The major requirements are
• to make objective observations
• to record the data during the user-product interaction
Objective observations include measures of time, frequencies, error rates, and so forth. The simple and direct recording of what the person does and says while working is also an objective observation, however, and is often very useful to designers. Test subjects can be encouraged to talk as they work, telling what they are doing, trying to do, expect to happen, etc. This record of a person’s thinking aloud is called a protocol by researchers in the fields of cognition and problem-solving, and is a major source of their data.
The process of testing described here involves the application designer and the test subjects in a regular cycle of feedback and revision. Although the test procedures themselves may be informal, user-testing of the concepts and features of the interface becomes a regular, integral part of the design process.
_______________________________________________________________________________
»DO’S AND DON’TS OF A FRIENDLY USER INTERFACE
_______________________________________________________________________________
Do:
• Let the user have as much control as possible over the appearance
of objects on the screen—their arrangement, size, and visibility.
• Use verbs for menu commands that perform actions.
• Make alert messages self-explanatory.
• Use controls and other graphics instead of just menu commands.
• Take the time to use good graphic design; it really helps.
Don’t:
• Overuse modes, including modal dialog boxes.
• Require using the keyboard for an operation that would be easier
with the mouse, or require using the mouse for an operation that
would be easier with the keyboard.
• Change the way the screen looks unexpectedly, especially by scrolling
automatically more than necessary.
• Redraw objects unnecessarily; it causes the screen to flicker annoyingly.
• Make up your own menus and then give them the same names as standard menus.
• Take an old-fashioned prompt-based application originally developed
for another machine and pass it off as a Macintosh application.
_______________________________________________________________________________
»BIBLIOGRAPHY
_______________________________________________________________________________
The following books are recommended reading for those interested in the effective use of color in the user interface.
Favre, J., and A. November. Color and Communication. Zurich, Switzerland: ABC Edition, 1979.
Greenberg, D., A. Marcus, A. Schmidt, and V. Gorter. The Computer Image. Menlo Park, California: Addison-Wesley Publishing Co., 1982.
Itten, J. The Elements of Color, edited by F. Birren. New York: Van Nostrand Reinhold Co., 1970.
Schneiderman, B. Designing the User Interface: Strategies for Effective Human-Computer Interaction. Reading, Massachusetts: Addison-Wesley Publishing Co., 1987.
Macintosh Memory Management - An Introduction
_______________________________________________________________________________
MACINTOSH MEMORY MANAGEMENT: AN INTRODUCTION
_______________________________________________________________________________
About This Chapter
The Stack and the Heap
Pointers and Handles
General-Purpose Data Types
Type Coercion
Summary
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
This chapter contains the minimum information you’ll need about memory management on the Macintosh. Memory management is covered in greater detail in the Memory Manager section.
_______________________________________________________________________________
»THE STACK AND THE HEAP
_______________________________________________________________________________
A running program can dynamically allocate and release memory in two places: the stack or the heap. The stack is an area of memory that can grow or shrink at one end while the other end remains fixed, as shown in Figure 1. This means that space on the stack is always allocated and released in LIFO (last-in-first-out) order: The last item allocated is always the first to be released. It also means that the allocated area of the stack is always contiguous. Space is released only at the top of the stack, never in the middle, so there can never be any unallocated “holes” in the stack.
Figure 1–The Stack
By convention, the stack grows from high toward low memory addresses. The end of the stack that grows and shrinks is usually referred to as the “top” of the stack, even though it’s actually at the lower end of the stack in memory.
When programs in high-level languages declare static variables (such as with the Pascal VAR declaration), those variables are allocated on the stack.
The other method of dynamic memory allocation is from the heap. Heap space is allocated and released only at the program’s explicit request, through calls to the Memory Manager.
Space in the heap is allocated in blocks, which may be of any size needed for a particular object. The Memory Manager does all the necessary “housekeeping” to keep track of the blocks as they’re allocated and released. Because these operations can occur in any order, the heap doesn’t grow and shrink in an orderly way like the stack. After a program has been running for a while, the heap tends to become fragmented into a patchwork of allocated and free blocks, as shown in Figure 2.
Figure 2–A Fragmented Heap
As a result of heap fragmentation, when the program asks to allocate a new block of a certain size, it may be impossible to satisfy the request even though there’s enough free space available, because the space is broken up into blocks smaller than the requested size. When this happens, the Memory Manager will try to create the needed space by compacting the heap: moving allocated blocks together in order to collect the free space into a single larger block
(see Figure 3).
Figure 3–Heap Compaction
There’s a system heap that’s used by the Operating System and an application heap that’s used by the Toolbox and the application program.
_______________________________________________________________________________
»POINTERS AND HANDLES
_______________________________________________________________________________
The Memory Manager contains a few fundamental routines for allocating and releasing heap space. The NewPtr function allocates a block in the heap of a requested size and returns a pointer to the block. You can then make as many copies of the pointer as you need and use them in any way your program requires. When you’re finished with the block, you can release the memory it occupies
(returning it to available free space) with the DisposPtr procedure.
Once you’ve called DisposPtr, any pointers you may have to the block become invalid, since the block they’re supposed to point to no longer exists. You have to be careful not to use such “dangling” pointers. This type of bug can be very difficult to diagnose and correct, since its effects typically aren’t discovered until long after the pointer is left dangling.
Another way a pointer can be left dangling is for its underlying block to be moved to a different location within the heap. To avoid this problem, blocks that are referred to through simple pointers, as in Figure 4, are nonrelocatable. The Memory Manager will never move a nonrelocatable block, so you can rely on all pointers to it to remain correct for as long as the block remains allocated.
Figure 4–A Pointer to a Nonrelocatable Block
If all blocks in the heap were nonrelocatable, there would be no way to prevent the heap’s free space from becoming fragmented. Since the Memory Manager needs to be able to move blocks around in order to compact the heap, it also uses relocatable blocks. (All the allocated blocks shown above in Figure 3, the illustration of heap compaction, are relocatable.) To keep from creating dangling pointers, the Memory Manager maintains a single master pointer to each relocatable block. Whenever a relocatable block is created, a master pointer is allocated from the heap at the same time and set to point to the block. All references to the block are then made by double indirection, through a pointer to the master pointer, called a handle to the block (see Figure 5). If the Memory Manager needs to move the block during compaction, it has only to update the master pointer to point to the block’s new location; the master pointer itself is never moved. Since all copies of the handle point to this same master pointer, they can be relied on not to dangle, even after the block has been moved.
Figure 5–A Handle to a Relocatable Block
Relocatable blocks are moved only by the Memory Manager, and only at well-defined, predictable times. In particular, only the routines listed in Appendix B can cause blocks to move, and these routines can never be called from within an interrupt. If your program doesn’t call these routines, you can rely on blocks not being moved.
The NewHandle function allocates a block in the heap of a requested size and returns a handle to the block. You can then make as many copies of the handle as you need and use them in any way your program requires. When you’re finished with the block, you can free the space it occupies with the DisposHandle procedure.
Note: Toolbox routines that create new objects of various kinds, such as
NewWindow and NewControl, implicitly call the NewPtr and NewHandle
routines to allocate the space they need. There are also analogous
routines for releasing these objects, such as DisposeWindow and
DisposeControl.
If the Memory Manager can’t allocate a block of a requested size even after compacting the entire heap, it can try to free some space by purging blocks from the heap. Purging a block removes it from the heap and frees the space it occupies. The block’s master pointer is set to NIL, but the space occupied by the master pointer itself remains allocated. Any handles to the block now point to a NIL master pointer, and are said to be empty. If your program later needs to refer to the purged block, it can detect that the handle has become empty and ask the Memory Manager to reallocate the block. This operation updates the original master pointer, so that all handles to the block are left referring correctly to its new location (see Figure 6).
Warning: Reallocating a block recovers only the space it occupies, not its
contents. Any information the block contains is lost when the block
is purged. It’s up to your program to reconstitute the block’s
contents after reallocating it.
Relocatable and nonrelocatable are permanent properties of a block that can never be changed once the block is allocated. A relocatable block can also be locked or unlocked, purgeable or unpurgeable; your program can set and change these attributes as necessary. Locking a block temporarily prevents it from being moved, even if the heap is compacted. The block can later be unlocked, again allowing the Memory Manager to move it during compaction. A block can be purged only if it’s relocatable, unlocked, and purgeable. A newly allocated relocatable block is initially unlocked and unpurgeable.
Figure 6–Purging and Reallocating a Block
_______________________________________________________________________________
»General-Purpose Data Types
_______________________________________________________________________________
The Memory Manager includes a number of type definitions for general-purpose use. For working with pointers and handles, there are the following definitions:
TYPE SignedByte = -128..127;
Byte = 0..255;
Ptr = ^SignedByte;
Handle = ^Ptr;
SignedByte stands for an arbitrary byte in memory, just to give Ptr and Handle something to point to. You can define a buffer of, say, bufSize untyped memory bytes as a PACKED ARRAY[1..bufSize] OF SignedByte. Byte is an alternative definition that treats byte-length data as unsigned rather than signed quantities.
For working with strings, pointers to strings, and handles to strings, the Memory Manager includes the following definitions:
TYPE Str255 = STRING[255];
StringPtr = ^Str255;
StringHandle = ^StringPtr;
For treating procedures and functions as data objects, there’s the ProcPtr data type:
TYPE ProcPtr = Ptr;
For example, after the declarations
VAR aProcPtr: ProcPtr;
. . .
PROCEDURE MyProc;
BEGIN
. . .
END;
you can make aProcPtr point to MyProc by using Lisa Pascal’s @ operator, as follows:
aProcPtr := @MyProc
With the @ operator, you can assign procedures and functions to variables of type ProcPtr, embed them in data structures, and pass them as arguments to other routines. Notice, however, that the data type ProcPtr technically points to an arbitrary byte (SignedByte), not an actual routine. As a result, there’s no way in Pascal to access the underlying routine via this pointer in order to call it. Only routines written in assembly language (such as those in the Operating System and the Toolbox) can actually call the routine designated by a pointer of type ProcPtr.
Warning: You can’t use the @ operator with procedures or functions
whose declarations are nested within other routines.
Finally, for treating long integers as fixed-point numbers, there’s the following data type:
TYPE Fixed = LONGINT;
As illustrated in Figure 7, a fixed-point number is a 32-bit signed quantity containing an integer part in the high-order word and a fractional part in the low-order word. Negative numbers are the two’s complement; they’re formed by treating the fixed-point number as a long integer, inverting each bit, and adding 1 to the least significant bit.
Figure 7–Fixed-Point Number
_______________________________________________________________________________
»Type Coercion
Because of Pascal’s strong typing rules, you can’t directly assign a value of type Ptr to a variable of some other pointer type, or pass it as a parameter of some other pointer type. Instead, you have to coerce the pointer from one type to another. For example, assume the following declarations have been made:
TYPE Thing = RECORD
. . .
END;
ThingPtr = ^Thing;
ThingHandle = ^ThingPtr;
VAR aPtr: Ptr;
aThingPtr: ThingPtr;
aThingHandle: ThingHandle;
In the Lisa Pascal statement
aThingPtr := ThingPtr(NewPtr(SIZEOF(Thing)))
NewPtr allocates heap space for a new record of type Thing and returns a pointer of type Ptr, which is then coerced to type ThingPtr so it can be assigned to aThingPtr. The statement
DisposPtr(Ptr(aThingPtr))
disposes of the record pointed to by aThingPtr, first coercing the pointer to type Ptr (as required by the DisposPtr procedure). Similar calls to NewHandle and DisposHandle would require coercion between the data types Handle and ThingHandle. Given a pointer aPtr of type Ptr, you can make aThingPtr point to the same object as aPtr with the assignment
aThingPtr := ThingPtr(aPtr)
or you can refer to a field of a record of type Thing with the expression
ThingPtr(aPtr)^.field
In fact, you can use this same syntax to equate any two variables of the same length. For example:
VAR aChar: CHAR;
aByte: Byte;
. . .
aByte := Byte(aChar)
You can also use the Lisa Pascal functions ORD, ORD4, and POINTER, to coerce variables of different length from one type to another. For example:
VAR anInteger: INTEGER;
aLongInt: LONGINT;
aPointer: Ptr;
. . .
anInteger := ORD(aLongInt); {two low-order bytes only}
anInteger := ORD(aPointer); {two low-order bytes only}
aLongInt := ORD(anInteger); {packed into high-order bytes}
aLongInt := ORD4(anInteger); {packed into low-order bytes}
aLongInt := ORD(aPointer);
aPointer := POINTER(anInteger);
aPointer := POINTER(aLongInt)
Assembly-language note: Of course, assembly-language programmers needn’t
bother with type coercion.
_______________________________________________________________________________
»SUMMARY
_______________________________________________________________________________
TYPE SignedByte = -128..127;
Byte = 0..255;
Ptr = ^SignedByte;
Handle = ^Ptr;
Str255 = STRING[255];
StringPtr = ^Str255;
StringHandle = ^StringPtr;
ProcPtr = Ptr;
Fixed = LONGINT
Further Reference:
_______________________________________________________________________________
Memory Manager
Technical Note #18, TextEdit Conversion Utility
Technical Note #42, Pascal Routines Passed by Pointer
Using Assembly Language
_______________________________________________________________________________
USING ASSEMBLY LANGUAGE
_______________________________________________________________________________
About This Chapter
Definition Files
Pascal Data Types
The Trap Dispatch Table
The Trap Mechanism
Format of Trap Words
Trap Macros
Calling Conventions
Stack-Based Routines
Register-Based Routines
Macro Arguments
Result Codes
Register-Saving Conventions
Pascal Interface to the Toolbox and Operating System
Mixing Pascal and Assembly Language
Summary
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
This chapter gives you general information that you’ll need to write all or part of your Macintosh application program in assembly language. It assumes you already know how to write assembly-language programs for the Motorola MC68000, the microprocessor in the Macintosh.
_______________________________________________________________________________
»DEFINITION FILES
_______________________________________________________________________________
The primary aids to assembly-language programmers are a set of definition files for symbolic names used in assembly-language programs. The definition files include equate files, which equate symbolic names with values, and macro files, which define the macros used to call Toolbox and Operating System routines from assembly language. The equate files define a variety of symbolic names for various purposes, such as:
• useful numeric quantities
• masks and bit numbers
• offsets into data structures
• addresses of global variables (which in turn often contain addresses)
It’s a good idea to always use the symbolic names defined in an equate file in place of the corresponding numeric values (even if you know them), since some of these values may change. Note that the names of the offsets for a data structure don’t always match the field names in the corresponding Pascal definition. In the documentation, the definitions are normally shown in their Pascal form; the corresponding offset constants for assembly language use are listed in the summary at the end of each chapter.
Some generally useful global variables defined in the equate files are as follows:
Name Contents
OneOne $00010001
MinusOne $FFFFFFFF
Lo3Bytes $00FFFFFF
Scratch20 20-byte scratch area
Scratch8 8-byte scratch area
ToolScratch 8-byte scratch area
ApplScratch 12-byte scratch area reserved for use by applications
Scratch20, Scratch8, and ToolScratch will not be preserved across calls to the routines in the Macintosh ROM. ApplScratch will be preserved; it should be used only by application programs and not by desk accessories or other drivers.
Other global variables are described where relevant in Inside Macintosh. A list of all the variables described is given in Appendix D.
_______________________________________________________________________________
»PASCAL DATA TYPES
_______________________________________________________________________________
Pascal’s strong typing ability lets Pascal programmers write programs without really considering the size of variables. But assembly language programmers must keep track of the size of every variable. The sizes of the standard Pascal data types, and some of the basic types defined in the Memory Manager, are listed below. (See the Apple Numerics Manual for more information about SINGLE, DOUBLE, EXTENDED, and COMP.)
Type Size Contents
INTEGER 2 bytes Two’s complement integer
LONGINT 4 bytes Two’s complement integer
BOOLEAN 1 byte Boolean value in bit 0
CHAR 2 bytes Extended ASCII code in low-order byte
SINGLE (or REAL)
4 bytes IEEE standard single format
DOUBLE 8 bytes IEEE standard double format
EXTENDED 10 bytes IEEE standard extended format
COMP (or COMPUTATIONAL)
8 bytes Two’s complement integer with reserved value
STRING[n] n+1 bytes Byte containing string length (not counting
length byte) followed by bytes containing
ASCII codes of characters in string
SignedByte 1 byte Two’s complement integer
Byte 2 bytes Value in low-order byte
Ptr 4 bytes Address of data
Handle 4 bytes Address of master pointer
Other data types are constructed from these. For some commonly used data types, the size in bytes is available as a predefined constant.
Before allocating space for any variable whose size is greater than one byte, Pascal adds “padding” to the next word boundary, if it isn’t already at a word boundary. It does this not only when allocating variables declared successively in VAR statements, but also within arrays and records. As you would expect, the size of a Pascal array or record is the sum of the sizes of all its elements or fields (which are stored with the first one at the lowest address). For example, the size of the data type
TYPE TestRecord = RECORD
testHandle: Handle;
testBoolA: BOOLEAN;
testBoolB: BOOLEAN;
testChar: CHAR
END;
is eight bytes: four for the handle, one each for the Booleans, and two for the character. If the testBoolB field weren’t there, the size would be the same, because of the byte of padding Pascal would add to make the character begin on a word boundary.
In a packed record or array, type BOOLEAN is stored as a bit, and types CHAR and Byte are stored as bytes. The padding rule described above still applies. For example, if the TestRecord data type shown above were declared as PACKED RECORD, it would occupy only six bytes: four for the handle, one for the Booleans (each stored in a bit), and one for the character. If the last field were INTEGER rather than CHAR, padding before the two byte integer field would cause the size to be eight bytes.
Note: The packing algorithm may not be what you expect. If you need to know
exactly how data is packed, or if you have questions about the size of
a particular data type, the best thing to do is write a test program
in Pascal and look at the results. (You can use the SIZEOF function to
get the size.)
_______________________________________________________________________________
»THE TRAP DISPATCH TABLE
_______________________________________________________________________________
The Toolbox and Operating System reside in ROM. However, to allow flexibility for future development, application code must be kept free of any specific ROM addresses. So all references to Toolbox and Operating System routines are made indirectly through the trap dispatch table in RAM, which contains the addresses of the routines. As long as the location of the trap dispatch table is known, the routines themselves can be moved to different locations in ROM without disturbing the operation of programs that depend on them.
Information about the locations of the various Toolbox and Operating System routines is encoded in compressed form in the ROM itself. When the system starts up, this encoded information is expanded to form the trap dispatch table. Because the trap dispatch table resides in RAM, individual entries can be
“patched” to point to addresses other than the original ROM address. This allows changes to be made in the ROM code by loading corrected versions of individual routines into RAM at system startup and patching the trap dispatch table to point to them. It also allows an application program to replace specific Toolbox and Operating System routines with its own “custom” versions. A pair of utility routines for manipulating the trap dispatch table, GetTrapAddress and SetTrapAddress, are described in the Operating System Utilities chapter.
In the 64K ROM, references to both Toolbox and Operating System routines are made through a single trap dispatch table. For compactness, entries in that table are encoded into one word each. The high-order bit of each entry tells whether the routine resides in ROM (0) or RAM (1). The remaining 15 bits give the offset of the routine relative to a base address. For routines in ROM, this base address is the beginning of the ROM; for routines in RAM, it’s the beginning of the system heap. The two base addresses are kept in a pair of global variables named ROMBase and RAMBase. Using 15-bit unsigned word offsets, the range of locations that the trap dispatch table can address is limited to 64K bytes. Also, the interleaving of Operating System and Toolbox trap numbers limits the total number of traps to 512 and means that no two traps can be represented by the same number.
In the 128K ROM, the Toolbox and Operating System traps have separate dispatch tables. Instead of a packed format, entries in these dispatch tables are stored as full long-word addresses so the dispatcher makes no distinction between ROM and RAM addresses. The Operating System dispatch table consists of 256 long words, from address $400 through $7FF; this replaces the old dispatch table of 512 words. The Toolbox table consists of 512 long words, from address $C00 through $13FF.
Warning: The format of the trap dispatch tables may be different in future
versions of Macintosh system software. If it’s absolutely necessary
that you manipulate the trap dispatch tables, use the Operating
System Utility routines NGetTrapAddress and NSetTrapAddress (or with
the 64K ROM, GetTrapAddress and SetTrapAddress); they’re described
in the Operating System Utilities chapter.
The offset in a trap dispatch table entry is expressed in words instead of bytes, taking advantage of the fact that instructions must always fall on word boundaries (even byte addresses). As illustrated in Figure 1, the system does the following to find the absolute address of the routine:
1. checks the high-order bit of the trap dispatch table entry to find
out which base address to use
2. doubles the offset to convert it from words to bytes (by left shifting
one bit)
3. adds the result to the designated base address
Figure 1–Trap Dispatch Table Entry
Using 15-bit word offsets, the trap dispatch table can address locations within a range of 32K words, or 64K bytes, from the base address. Starting from ROMBase, this range is big enough to cover the entire ROM; but only slightly more than half of the 128K RAM lies within range of RAMBase. RAMBase is set to the beginning of the system heap to maximize the amount of useful space within range; locations below the start of the heap are used to hold global system data and can never contain executable code. If the heap is big enough, however, it’s possible for some of the application’s code to lie beyond the upper end of the trap dispatch table’s range. Any such code is inaccessible through the trap dispatch table.
Note: This problem is particularly acute on the Macintosh 512K and
Macintosh XL. To make sure they lie within range of RAMBase,
patches to Toolbox and Operating System routines are typically
placed in the system heap rather than the application heap.
_______________________________________________________________________________
»THE TRAP MECHANISM
_______________________________________________________________________________
Calls to the Toolbox and Operating System via the trap dispatch table are implemented by means of the MC68000’s “1010 emulator” trap. To issue such a call in assembly language, you use one of the trap macros defined in the macro files. When you assemble your program, the macro generates a trap word in the machine language code. A trap word always begins with the hexadecimal digit $A
(binary 1010); the rest of the word identifies the routine you’re calling, along with some additional information pertaining to the call.
Note: A list of all Macintosh trap words is given in Appendix C.
Instruction words beginning with $A or $F (“A-line” or “F-line” instructions) don’t correspond to any valid machine language instruction, and are known as unimplemented instructions. They’re used to augment the processor’s native instruction set with additional operations that are “emulated” in software instead of being executed directly by the hardware. A-line instructions are reserved for use by Apple; on a Macintosh, they provide access to the Toolbox and Operating System routines. Attempting to execute such an instruction causes a trap to the trap dispatcher, which examines the bit pattern of the trap word to determine what operation it stands for, looks up the address of the corresponding routine in the trap dispatch table, and jumps to the routine.
Note: F-line instructions are reserved by Motorola for use in future
processors.
_______________________________________________________________________________
»Format of Trap Words
As noted above, a trap word always contains $A in bits 12-15. Bit 11 determines how the remainder of the word will be interpreted; usually it’s 0 for Operating System calls and 1 for Toolbox calls, though there are some exceptions.
Figure 2 shows the Toolbox trap word format. Bits 0-8 form the trap number (an index into the trap dispatch table), identifying the particular routine being called. Bit 9 is reserved for future use. Bit 10 is the “auto-pop” bit; this bit is used by language systems that, rather than directly invoking the trap like Lisa Pascal, do a JSR to the trap word followed immediately by a return to the calling routine. In this case, the return addresses for the both the JSR and the trap get pushed onto the stack, in that order. The auto-pop bit causes the trap dispatcher to pop the trap’s return address from the stack and return directly to the calling program.
Figure 2–Toolbox Trap Word (Bit 11=1)
For Operating System calls, only the low order eight bits (bits 0-7) are used for the trap number (see Figure 3). Thus of the 512 entries in the trap dispatch table, only the first 256 can be used for Operating System traps. Bit 8 of an Operating System trap has to do with register usage and is discussed below under “Register Saving Conventions”. Bits 9 and 10 have specialized meanings depending on which routine you’re calling, and are covered where relevant in other chapters.
Figure 3–Operating System Trap Word (Bit 11=0)
As described above, a trap word begins with the hexadecimal digit $A (binary 1010); the rest of the word identifies the routine you’re calling, along with additional information pertaining to the call.
In the 64K ROM, an Operating System trap and a Toolbox trap cannot have the same trap number; the GetTrapAddress and SetTrapAddress routines do not distinguish between Toolbox and Operating System traps.
Since each group has its own dispatch table in the 128K ROM, there can be a Toolbox trap and an Operating System trap with the same trap number. Two new routines—NGetTrapAddress and NSetTrapAddress—have been added; they use bits 9 and 10 of their trap word for specifying the group to which a routine belongs.
_______________________________________________________________________________
»Trap Macros
The names of all trap macros begin with the underscore character (_), followed by the name of the corresponding routine. As a rule, the macro name is the same as the name used to call the routine from Pascal, as given in the Toolbox and Operating System documentation. For example, to call the Window Manager routine NewWindow, you would use an instruction with the macro name _NewWindow in the opcode field. There are some exceptions, however, in which the spelling of the macro name differs from the name of the Pascal routine itself; these are noted in the documentation for the individual routines.
Note: The reason for the exceptions is that assembler names must be unique
to eight characters. Since one character is taken up by the underscore,
special macro names must be used for Pascal routines whose names aren’t
unique to seven characters.
Trap macros for Toolbox calls take no arguments; those for Operating System calls may have as many as three optional arguments. The first argument, if present, is used to load a register with a parameter value for the routine
you’re calling, and is discussed below under “Register Based Routines”. The remaining arguments control the settings of the various flag bits in the trap word. The form of these arguments varies with the meanings of the flag bits, and is described in the chapters on the relevant parts of the Operating System.
_______________________________________________________________________________
»CALLING CONVENTIONS
_______________________________________________________________________________
The calling conventions for Toolbox and Operating System routines fall into two categories: stack based and register based. As the terms imply, stack based routines communicate via the stack, following the same conventions used by the Pascal Compiler for routines written in Lisa Pascal, while register based routines receive their parameters and return their results in registers. Before calling any Toolbox or Operating System routine, you have to set up the parameters in the way the routine expects.
Note: As a general rule, Toolbox routines are stack based and Operating
System routines register based, but there are exceptions on both sides.
Throughout Inside Macintosh, register based calling conventions are
given for all routines that have them; if none is shown, then the
routine is stack based.
_______________________________________________________________________________
»Stack-Based Routines
To call a stack based routine from assembly language, you have to set up the parameters on the stack in the same way the compiled object code would if your program were written in Pascal. If the routine you’re calling is a function, its result is returned on the stack. The number and types of parameters, and the type of result returned by a function, depend on the routine being called. The number of bytes each parameter or result occupies on the stack depends on its type:
Type of parameter
or function
result Size Contents
INTEGER 2 bytes Two’s complement integer
LONGINT 4 bytes Two’s complement integer
BOOLEAN 2 bytes Boolean value in bit 0 of high-order byte
CHAR 2 bytes Extended ASCII code in low-order byte
SINGLE (or REAL), DOUBLE, COMP (or COMPUTATIONAL)
4 bytes Pointer to value converted to EXTENDED
EXTENDED 4 bytes Pointer to value
STRING[n] 4 bytes Pointer to string (first byte
pointed to is length byte)
SignedByte 2 bytes Value in low-order byte
Byte 2 bytes Value in low-order byte
Ptr 4 bytes Address of data
Handle 4 bytes Address of master pointer
Record or array 2 or 4 Contents of structure (padded to
bytes word boundary) if <= 4 bytes,
otherwise pointer to structure
VAR parameter 4 bytes Address of variable, regardless of type
The steps to take to call the routine are as follows:
1. If it’s a function, reserve space on the stack for the result.
2. Push the parameters onto the stack in the order they occur in
the routine’s Pascal definition.
3. Call the routine by executing the corresponding trap macro.
The trap pushes the return address onto the stack, along with an extra word of processor status information. The trap dispatcher removes this extra status word, leaving the stack in the state shown in Figure 4 on entry to the routine. The routine itself is responsible for removing its own parameters from the stack before returning. If it’s a function, it leaves its result on top of the stack in the space reserved for it; if it’s a procedure, it restores the stack to the same state it was in before the call.
Figure 4–Stack Format for Stack Based Routines
For example, the Window Manager function GrowWindow is defined in Pascal as follows:
FUNCTION GrowWindow (theWindow: WindowPtr; startPt: Point;
sizeRect: Rect) : LONGINT;
To call this function from assembly language, you’d write something like the following:
SUBQ.L #4,SP ;make room for LONGINT result
MOVE.L theWindow,-(SP) ;push window pointer
MOVE.L startPt,-(SP) ;a Point is a 4-byte record,
;so push actual contents
PEA sizeRect ;a Rect is an 8-byte record,
;so push a pointer to it
_GrowWindow ;trap to routine
MOVE.L (SP)+,D3 ;pop result from stack
Although the MC68000 hardware provides for separate user and supervisor stacks, each with its own stack pointer, the Macintosh maintains only one stack. All application programs run in supervisor mode and share the same stack with the system; the user stack pointer isn’t used.
Warning: For compatibility with future versions of the Macintosh, your
program should not rely on capabilities available only in
supervisor mode (such as the instruction RTE).
Remember that the stack pointer must always be aligned on a word boundary. This is why, for example, a Boolean parameter occupies two bytes; it’s actually the Boolean value followed by a byte of padding. Because all Macintosh application code runs in the MC68000’s supervisor mode, an odd stack pointer will cause a
“double bus fault”: an unrecoverable system failure that causes the system to restart.
To keep the stack pointer properly aligned, the MC68000 automatically adjusts the pointer by 2 instead of 1 when you move a byte length value to or from the stack. This happens only when all of the following three conditions are met:
• A one byte value is being transferred.
• Either the source or the destination is specified by
predecrement or postincrement addressing.
• The register being decremented or incremented is the stack pointer (A7).
An extra, unused byte will automatically be added in the low order byte to keep the stack pointer even. (Note that if you need to move a character to or from the stack, you must explicitly use a full word of data, with the character in the low order byte.)
Warning: If you use any other method to manipulate the stack pointer, it’s
your responsibility to make sure the pointer stays properly aligned.
Note: Some Toolbox and Operating System routines accept the address of
one of your own routines as a parameter, and call that routine under
certain circumstances. In these cases, you must set up your routine
to be stack based.
_______________________________________________________________________________
»Register-Based Routines
By convention, register based routines normally use register A0 for passing addresses (such as pointers to data objects) and D0 for other data values (such as integers). Depending on the routine, these registers may be used to pass parameters to the routine, result values back to the calling program, or both. For routines that take more than two parameters (one address and one data
value), the parameters are normally collected in a parameter block in memory and a pointer to the parameter block is passed in A0. However, not all routines obey these conventions; for example, some expect parameters in other registers, such as A1. See the description of each individual routine for details.
Whatever the conventions may be for a particular routine, it’s up to you to set up the parameters in the appropriate registers before calling the routine. For instance, the Memory Manager procedure BlockMove, which copies a block of consecutive bytes from one place to another in memory, expects to find the address of the first source byte in register A0, the address of the first destination location in A1, and the number of bytes to be copied in D0. So you might write something like
LEA src(A5),A0 ;source address in A0
LEA dest(A5),A1 ;destination address in A1
MOVEQ #20,D0 ;byte count in D0
_BlockMove ; trap to routine
»Macro Arguments
The following information applies to the Lisa Workshop Assembler. If you’re using some other assembler, you should check its documentation to find out whether this information applies.
Many register based routines expect to find an address of some sort in register A0. You can specify the contents of that register as an argument to the macro instead of explicitly setting up the register yourself. The first argument you supply to the macro, if any, represents an address to be passed in A0. The macro will load the register with an LEA (Load Effective Address) instruction before trapping to the routine. So, for instance, to perform a Read operation on a file, you could set up the parameter block for the operation and then use the instruction
_Read paramBlock ;trap to routine with pointer to
; parameter block in A0
This feature is purely a convenience, and is optional: If you don’t supply any arguments to a trap macro, or if the first argument is null, the LEA to A0 will be omitted from the macro expansion. Notice that A0 is loaded with the address denoted by the argument, not the contents of that address.
Note: You can use any of the MC68000’s addressing modes to specify this
address, with one exception: You can’t use the two register indexing
mode (“address register indirect with index and displacement”). An
instruction such as
_Read offset(A3,D5)
won’t work properly, because the comma separating the two registers
will be taken as a delimiter marking the end of the macro argument.
»Result Codes
Many register-based routines return a result code in the low order word of register D0 to report successful completion or failure due to some error condition. A result code of 0 indicates that the routine was completed successfully. Just before returning from a register based call, the trap dispatcher tests the low order word of D0 with a TST.W instruction to set the processor’s condition codes. You can then check for an error by branching directly on the condition codes, without any explicit test of your own. For example:
_PurgeMem ;trap to routine
BEQ NoError ;branch if no error
. . . ;handle error
Warning: Not all register based routines return a result code. Some leave
the contents of D0 unchanged; others use the full 32 bits of the
register to return a long word result. See the descriptions of
individual routines for details.
_______________________________________________________________________________
»Register-Saving Conventions
All Toolbox and Operating System routines preserve the contents of all registers except A0, A1, and D0-D2 (and of course A7, which is the stack pointer). In addition, for register based routines, the trap dispatcher saves registers A1, D1, and D2 before dispatching to the routine and restores them before returning to the calling program. A7 and D0 are never restored; whatever the routine leaves in these registers is passed back unchanged to the calling program, allowing the routine to manipulate the stack pointer as appropriate and to return a result code.
Whether the trap dispatcher preserves register A0 for a register based trap depends on the setting of bit 8 of the trap word: If this bit is 0, the trap dispatcher saves and restores A0; if it’s 1, the routine passes back A0 unchanged. Thus bit 8 of the trap word should be set to 1 only for those routines that return a result in A0, and to 0 for all other routines. The trap macros automatically set this bit correctly for each routine, so you never have to worry about it yourself.
Stack based traps preserve only registers A2-A6 and D3-D7. If you want to preserve any of the other registers, you have to save them yourself before trapping to the routine - typically on the stack with a MOVEM (Move Multiple) instruction - and restore them afterward.
Warning: When an application starts up, register A5 is set to point to the
boundary between the application globals and the application
parameters (see the memory map in the Memory Manager chapter for
details). Certain parts of the system rely on finding A5 set up
properly (for instance, the first application parameter is a
pointer to the first QuickDraw global variable), so you have to
be a bit more careful about preserving this register. The safest
policy is never to touch A5 at all. If you must use it for your
own purposes, just saving its contents at the beginning of a
routine and restoring them before returning isn’t enough: You
have to be sure to restore it before any call that might depend
on it. The correct setting of A5 is always available in the global
variable CurrentA5.
Note: Any routine in your application that may be called as the result
of a Toolbox or Operating System call shouldn’t rely on the value
of any register except A5, which shouldn’t change.
_______________________________________________________________________________
»Pascal Interface to the Toolbox and Operating System
When you call a register based Toolbox or Operating System routine from Pascal, you’re actually calling an interface routine that fetches the parameters from the stack where the Pascal calling program left them, puts them in the registers where the routine expects them, and then traps to the routine. On return, it moves the routine’s result, if any, from a register to the stack and then returns to the calling program. (For routines that return a result code, the interface routine may also move the result code to a global variable, where it can later be accessed.)
For stack-based calls, there’s no interface routine; the trap word is inserted directly into the compiled code.
_______________________________________________________________________________
»MIXING PASCAL AND ASSEMBLY LANGUAGE
_______________________________________________________________________________
You can mix Pascal and assembly language freely in your own programs, calling routines written in either language from the other. The Pascal and assembly language portions of the program have to be compiled and assembled separately, then combined with a program such as the Lisa Workshop Linker. For convenience in this discussion, such separately compiled or assembled portions of a program will be called “modules”. You can divide a program into any number of modules, each of which may be written in either Pascal or assembly language.
References in one module to routines defined in another are called external references, and must be resolved by a program like the Linker that matches them up with their definitions in other modules. You have to identify all the external references in each module so they can be resolved properly. For more information, and for details about the actual process of linking the modules together, see the documentation for the development system you’re using.
In addition to being able to call your own Pascal routines from assembly language, you can call certain routines in the Toolbox and Operating System that were created expressly for Lisa Pascal programmers and aren’t part of the Macintosh ROM. (These routines may also be available to users of other development systems, depending on how the interfaces have been set up on those systems.) They’re marked with the notation
[Not in ROM]
in Inside Macintosh. There are no trap macros for these routines (though they may call other routines for which there are trap macros). Some of them were created just to allow Pascal programmers access to assembly language information, and so won’t be useful to assembly language programmers. Others, however, contain code that’s executed before a trap macro is invoked, and you may want to perform the operations they provide.
All calls from one language to the other, in either direction, must obey
Pascal’s stack based calling conventions (see “Stack Based Routines”, above). To call your own Pascal routine from assembly language, or one of the Toolbox or Operating System routines that aren’t in ROM, push the parameters onto the stack before the call and (if the routine is a function) look for the result on the stack on return. In an assembly language routine to be called from Pascal, look for the parameters on the stack on entry and leave the result (if any) on the stack before returning.
Under stack based calling conventions, a convenient way to access a routine’s parameters on the stack is with a frame pointer, using the MC68000’s LINK and UNLK (Unlink) instructions. You can use any address register for the frame pointer (except A7, which is reserved for the stack pointer), but register A6 is conventionally used for this purpose on the Macintosh. The instruction
LINK A6,#-12
at the beginning of a routine saves the previous contents of A6 on the stack and sets A6 to point to it. The second operand specifies the number of bytes of stack space to be reserved for the routine’s local variables: in this case, 12 bytes. The LINK instruction offsets the stack pointer by this amount after copying it into A6.
Warning: The offset is added to the stack pointer, not subtracted from it.
So to allocate stack space for local variables, you have to give
a negative offset; the instruction won’t work properly if the
offset is positive. Also, to keep the stack pointer correctly
aligned, be sure the offset is even. For a routine with no local
variables on the stack, use an offset of #0.
Register A6 now points within the routine’s stack frame; the routine can locate its parameters and local variables by indexing with respect to this register
(see Figure 5). The register itself points to its own saved contents, which are often (but needn’t necessarily be) the frame pointer of the calling routine. The parameters and return address are found at positive offsets from the frame pointer.
Figure 5–Frame Pointer
Since the saved contents of the frame pointer register occupy a long word (four bytes) on the stack, the return address is located at 4(A6) and the last parameter at 8(A6). This is followed by the rest of the parameters in reverse order, and finally by the space reserved for the function result, if any. The proper offsets for these remaining parameters and for the function result depend on the number and types of the parameters, according to the table above under “Stack Based Routines”. If the LINK instruction allocated stack space for any local variables, they can be accessed at negative offsets from the frame pointer, again depending on their number and types.
At the end of the routine, the instruction
UNLK A6
reverses the process: First it releases the local variables by setting the stack pointer equal to the frame pointer (A6), then it pops the saved contents back into register A6. This restores the register to its original state and leaves the stack pointer pointing to the routine’s return address.
A routine with no parameters can now just return to the caller with an RTS instruction. But if there are any parameters, it’s the routine’s responsibility to pop them from the stack before returning. The usual way of doing this is to pop the return address into an address register, increment the stack pointer to remove the parameters, and then exit with an indirect jump through the register.
Remember that any routine called from Pascal must preserve registers A2-A6 and D3-D7. This is usually done by saving the registers that the routine will be using on the stack with a MOVEM instruction, and then restoring them before returning. Any routine you write that will be accessed via the trap mechanism - for instance, your own version of a Toolbox or Operating System routine that you’ve patched into the trap dispatch table - should observe the same conventions.
Putting all this together, the routine should begin with a sequence like
MyRoutine LINK A6,#-dd ;set up frame pointer--
; dd = number of bytes
; of local variables
MOVEM.L A2-A4/D3-D7,-(SP) ;...or whatever
; registers you use
and end with something like
MOVEM.L (SP)+,A2-A4/D3-D7 ;restore registers
UNLK A6 ;restore frame pointer
MOVE.L (SP)+,A1 ;save return address in an
; available register
ADD.W #pp,SP ;pop parameters--
; pp = number of bytes
; of parameters
JMP (A1) ;return to caller
Notice that A6 doesn’t have to be included in the MOVEM instructions, since
it’s saved and restored by the LINK and UNLK.
_______________________________________________________________________________
»SUMMARY
_______________________________________________________________________________
Variables
OneOne $00010001
MinusOne $FFFFFFFF
Lo3Bytes $00FFFFFF
Scratch20 20-byte scratch area
Scratch8 8-byte scratch area
ToolScratch 8-byte scratch area
ApplScratch 12-byte scratch area reserved for use by applications
ROMBase Base address of ROM
RAMBase Trap dispatch table's base address for routines in RAM
CurrentA5 Address of boundary between application globals
and application parameters
Further Reference:
_______________________________________________________________________________
Technical Note #21, QuickDraw’s Internal Picture Definition
Technical Note #88, Signals
Technical Note #103, MaxApplZone & MoveHHi from Assembly Language
Technical Note #156, Checking for Specific Functionality
Technical Note #164, MPW C Functions: To declare or not to declare…
QuickDraw
_______________________________________________________________________________
QUICKDRAW
_______________________________________________________________________________
About This Chapter
About QuickDraw
The Mathematical Foundation of QuickDraw
The Coordinate Plane
Points
Rectangles
Regions
Graphic Entities
Bit Images
Bit Maps
Patterns
Cursors
Graphic Entities as Resources
The Drawing Environment: GrafPort
Pen Characteristics
Text Characteristics
Coordinates in GrafPorts
General Discussion of Drawing
Transfer Modes
Drawing in Color
Pictures and Polygons
Pictures
Polygons
Using QuickDraw
QuickDraw Routines
GrafPort Routines
Cursor-Handling Routines
Pen and Line-Drawing Routines
Text-Drawing Routines
Drawing in Color
Calculations with Rectangles
Graphic Operations on Rectangles
Graphic Operations on Ovals
Graphic Operations on Rounded-Corner Rectangles
Graphic Operations on Arcs and Wedges
Calculations with Regions
Graphic Operations on Regions
Bit Map Operations
Pictures
Calculations with Polygons
Graphic Operations on Polygons
Calculations with Points
Miscellaneous Routines
Advanced Routine
Customizing QuickDraw Operations
Summary of QuickDraw
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
This chapter describes QuickDraw, the part of the Toolbox that allows Macintosh programmers to perform highly complex graphic operations very easily and very quickly. It describes the data types used by QuickDraw and gives details of the procedures and functions available in QuickDraw.
_______________________________________________________________________________
»ABOUT QUICKDRAW
_______________________________________________________________________________
QuickDraw allows you to draw many different things on the Macintosh screen; some of these are illustrated in Figure 1.
Figure 1–Samples of QuickDraw’s Abilities
You can draw:
• text characters in a number of proportionally-spaced fonts, with
variations that include boldfacing, italicizing, underlining, and
outlining
• straight lines of any length, width, and pattern
• a variety of shapes, including rectangles, rounded-corner rectangles,
circles and ovals, and polygons, all either outlined and hollow or
filled in with a pattern
• arcs of ovals, or wedge-shaped sections filled in with a pattern
• any other arbitrary shape or collection of shapes
• a picture composed of any combination of the above, drawn with just
a single procedure call
QuickDraw also has some other abilities that you won’t find in many other graphics packages. These abilities take care of most of the “housekeeping”—the trivial but time-consuming overhead that’s necessary to keep things in order. They include:
• The ability to define many distinct “ports” on the screen. Each port
has its own complete drawing environment—its own coordinate system,
drawing location, character set, location on the screen, and so on.
You can easily switch from one drawing port to another.
• Full and complete “clipping” to arbitrary areas, so that drawing will
occur only where you want. It’s like an electronic coloring book that
won’t let you color outside the lines. You don’t have to worry about
accidentally drawing over something else on the screen, or drawing off
the screen and destroying memory.
• Off-screen drawing. Anything you can draw on the screen, you can also
draw into an off-screen buffer, so you can prepare an image for an
output device without disturbing the screen, or you can prepare a
picture and move it onto the screen very quickly.
And QuickDraw lives up to its name: It’s very fast. The speed and responsiveness of the Macintosh user interface are due primarily to the speed of QuickDraw. You can do good-quality animation, fast interactive graphics, and complex yet speedy text displays using the full features of QuickDraw. This means you don’t have to bypass the general-purpose QuickDraw routines by writing a lot of special routines to improve speed.
In addition to its routines and data types, QuickDraw provides global variables that you can use from your Pascal program. For example, there’s a variable named thePort that points to the current drawing port.
Assembly-language note: See the discussion of InitGraf in the “QuickDraw
Routines” section for details on how to access the
QuickDraw global variables from assembly language.
In conjunction with the Font Manager, QuickDraw supports font families, fractional character widths, and the disabling of font scaling; these features are described in the Font Manager chapter section.
The 128K ROM version of QuickDraw supports all eight transfer modes for text drawing, instead of just srcOr, srcBic, and scrXor.
The size of a picture is a long word with a range of over four gigabytes. To get the size of a picture, use GetHandleSize instead of looking at the picSize field, which for compatibility contains the low 16 bits of the real size. Old code will work fine for pictures up to 32767 bytes. To check whether you have run out of memory during picture creation, test EmptyRect(picFrame); it returns TRUE if you have.
The following bugs have been fixed in the 128K ROM:
• RectInRgn used to return TRUE occasionally when the rectangle intersected
the region’s enclosing rectangle but not the actual region.
• SectRgn, DiffRgn, UnionRgn, XorRgn, and FrameRgn used to cause a stack
overflow for regions with more than 25 rectangles in one scan line.
• PtToAngle didn’t work correctly when the angle was 90 and the aspect
ratio was a power of two.
• In some cases where the CopyBits source bitmap overlapped its destination,
the transfer would destroy the source bitmap before it was used.
• If you tried to draw a long piece of shadowed text with a tall font,
QuickDraw would cause a stack overflow if there wasn’t enough stack
space for the required off-screen buffer. Now it detects the potential
stack overflow and recurses on the left and right halves of the text.
• DrawText did not work correctly in pictures if the character count
was greater than 255.
The original QuickDraw described in this chapter has been expanded in two significant areas: color capabilities with Color QuickDraw, which gives each pixel color information, and direct RGB colors with 32-Bit QuickDraw, where every pixel can contain a full RGB record with up to eight bits per component. Refer to the Color QuickDraw chapter and the 32-Bit QuickDraw documentation for more details on both of these enhancements to QuickDraw.
_______________________________________________________________________________
»THE MATHEMATICAL FOUNDATION OF QUICKDRAW
_______________________________________________________________________________
To create graphics that are both precise and pretty requires not supercharged features but a firm mathematical foundation for the features you have. If the mathematics that underlie a graphics package are imprecise or fuzzy, the graphics will be, too. QuickDraw defines some clear mathematical constructs that are widely used in its procedures, functions, and data types: the coordinate plane, the point, the rectangle, and the region.
_______________________________________________________________________________
»The Coordinate Plane
All information about location or movement is given to QuickDraw in terms of coordinates on a plane. The coordinate plane is a two-dimensional grid, as illustrated in Figure 2.
Note the following features of the QuickDraw coordinate plane:
• All grid coordinates are integers (in the range –32767 to 32767).
• All grid lines are infinitely thin.
Figure 2–The Coordinate Plane
These concepts are important. First, they mean that the QuickDraw plane is finite, not infinite (although it’s very large). Second, they mean that all elements represented on the coordinate plane are mathematically pure. Mathematical calculations using integer arithmetic will produce intuitively correct results. If you keep in mind that grid lines are infinitely thin,
you’ll never have “endpoint paranoia”—the confusion that results from not knowing whether that last dot is included in the line.
_______________________________________________________________________________
»Points
There are 4,294,836,224 unique points on the coordinate plane. Each point is at the intersection of a horizontal grid line and a vertical grid line. As the grid lines are infinitely thin, so a point is infinitely small. Of course, there are many more points on this grid than there are dots on the Macintosh screen: When using QuickDraw you associate small parts of the grid with areas on the screen, so that you aren’t bound into an arbitrary, limited coordinate system.
The coordinate origin (0,0) is in the middle of the grid. Horizontal coordinates increase as you move from left to right, and vertical coordinates increase as you move from top to bottom. This is the way both a TV screen and a page of English text are scanned: from the top left to the bottom right.
Figure 3 shows the relationship between points, grid lines, and pixels, the physical dots on the screen. (Pixels correspond to bits in memory, as described in the next section.)
You can store the coordinates of a point into a Pascal variable of type Point, defined by QuickDraw as a record of two integers:
TYPE VHSelect = (v,h);
Point = RECORD CASE INTEGER OF
0: (v: INTEGER: {vertical coordinate}
h: INTEGER); {horizontal coordinate}
1: (vh: ARRAY[VHSelect] OF INTEGER)
END;
Figure 3–Points and Pixels
The variant part of this record lets you access the vertical and horizontal coordinates of a point either individually or as an array. For example, if the variable goodPt is declared to be of type Point, the following will all refer to the coordinates of the point:
goodPt.v goodPt.h
goodPt.vh[v] goodPt.vh[h]
_______________________________________________________________________________
»Rectangles
Any two points can define the top left and bottom right corners of a rectangle. As these points are infinitely small, the borders of the rectangle are infinitely thin (see Figure 4).
Figure 4–A Rectangle
Rectangles are used to define active areas on the screen, to assign coordinate systems to graphic entities, and to specify the locations and sizes for various drawing commands. QuickDraw also allows you to perform many mathematical calculations on rectangles—changing their sizes, shifting them around, and so on.
Note: Remember that rectangles, like points, are mathematical concepts
that have no direct representation on the screen. The association
between these conceptual elements and their physical representations
is made by the BitMap data type, described in the following section.
The data type for rectangles is called Rect, and consists of four integers or two points:
TYPE Rect = RECORD CASE INTEGER OF
0: (top: INTEGER;
left: INTEGER;
bottom: INTEGER;
right: INTEGER);
1: (topLeft: Point;
botRight: Point)
END;
Again, the record variant allows you to access a variable of type Rect either as four boundary coordinates or as two diagonally opposite corner points. Combined with the record variant for points, all of the following references to the rectangle named aRect are legal:
aRect {type Rect}
aRect.topLeft aRect.botRight {type Point}
aRect.top aRect.left {type INTEGER}
aRect.topLeft.v aRect.topLeft.h {type INTEGER}
aRect.topLeft.vh[v] aRect.topLeft.vh[h] {type INTEGER}
aRect.bottom aRect.right {type INTEGER}
aRect.botRight.v aRect.botRight.h {type INTEGER}
aRect.botRight.vh[v] aRect.botRight.vh[h] {type INTEGER}
Note: If the bottom coordinate of a rectangle is equal to or less than
the top, or the right coordinate is equal to or less than the left,
the rectangle is an empty rectangle (that is, one that contains no bits).
_______________________________________________________________________________
»Regions
Unlike most graphics packages that can manipulate only simple geometric structures (usually rectilinear, at that), QuickDraw has the ability to gather an arbitrary set of spatially coherent points into a structure called a region, and perform complex yet rapid manipulations and calculations on such structures. Regions not only make your programs simpler and faster, but will let you perform operations that would otherwise be nearly impossible.
You define a region by calling routines that draw lines and shapes (even other regions). The outline of a region should be one or more closed loops. A region can be concave or convex, can consist of one area or many disjoint areas, and can even have “holes” in the middle. In Figure 5, the region on the left has a hole in the middle, and the region on the right consists of two disjoint areas.
Figure 5–Regions
The data structure for a region consists of two fixed-length fields followed by a variable-length field:
TYPE Region = RECORD
rgnSize: INTEGER; {size in bytes}
rgnBBox: Rect; {enclosing rectangle}
{more data if not rectangular}
END;
The rgnSize field contains the size, in bytes, of the region variable. The maximum size of a region is 32K bytes. The rgnBBox field is a rectangle that completely encloses the region.
The simplest region is a rectangle. In this case, the rgnBBox field defines the entire region, and there’s no optional region data. For rectangular regions (or empty regions), the rgnSize field contains 10.
The region definition data for nonrectangular regions is stored in a compact way that allows for highly efficient access by QuickDraw routines.
All regions are accessed through handles:
TYPE RgnPtr = ^Region;
RgnHandle = ^RgnPtr;
Many calculations can be performed on regions. A region can be “expanded” or
“shrunk” and, given any two regions, QuickDraw can find their union, intersection, difference, and exclusive-OR; it can also determine whether a given point intersects a region, and so on.
_______________________________________________________________________________
»GRAPHIC ENTITIES
_______________________________________________________________________________
Points, rectangles, and regions are all mathematical models rather than actual graphic elements—they’re data types that QuickDraw uses for drawing, but they don’t actually appear on the screen. Some entities that do have a direct graphic interpretation are the bit image, bit map, pattern, and cursor. This section describes these graphic entities and relates them to the mathematical constructs described above.
_______________________________________________________________________________
»Bit Images
A bit image is a collection of bits in memory that have a rectilinear representation. Take a collection of words in memory and lay them end to end so that bit 15 of the lowest-numbered word is on the left and bit 0 of the highest-numbered word is on the far right. Then take this array of bits and divide it, on word boundaries, into a number of equal-size rows. Stack these rows vertically so that the first row is on the top and the last row is on the bottom. The result is a matrix like the one shown in Figure 6—rows and columns of bits, with each row containing the same number of bytes. The number of bytes in each row of the bit image is called the row width of that image. A bit image can be any length that’s a multiple of the row width.
Figure 6–A Bit Image
The screen itself is one large visible bit image. On a Macintosh 128K or 512K, for example, the screen is a 342-by-512 bit image, with a row width of 64 bytes. These 21,888 bytes of memory are displayed as a matrix of 175,104 pixels on the screen, each bit corresponding to one pixel. If a bit’s value is 0, its pixel is white; if the bit’s value is 1, the pixel is black.
Warning: The numbers given here apply only to the Macintosh 128K and 512K
systems. To allow for your application running on any version of
the Macintosh, you should never use explicit numbers for screen
dimensions. The QuickDraw global variable screenBits (a bit map,
described below) gives you access to a rectangle whose dimensions
are those of the main screen, whatever version of the Macintosh is
being used.
On a Macintosh 128K or 512K, each pixel on the screen is square, and there are 72 pixels per inch in each direction. On an unmodified Macintosh XL, each pixel is one and a half times taller than it is wide, meaning a rectangle 30 pixels wide by 20 tall looks square; there are 90 pixels per inch horizontally, and 60 per inch vertically. A Macintosh XL may be modified to have square pixels. You can get the the screen resolution by calling the Toolbox Utility procedure ScreenRes.
Note: The values given for pixels per inch may not be exactly the measurement
on the screen, but they’re the values you should use when calculating
the size of printed output.
Note: Since each pixel on the screen represents one bit in a bit image,
wherever this chapter says “bit”, you can substitute “pixel” if the
bit image is the screen. Likewise, this chapter often refers to pixels
on the screen where the discussion applies equally to bits in an off-
screen bit image.
_______________________________________________________________________________
»Bit Maps
A bit map in QuickDraw is a data structure that defines a physical bit image in terms of the coordinate plane. A bit map has three parts: a pointer to a bit image, the row width of that image, and a boundary rectangle that gives the bit map both its dimensions and a coordinate system.
There can be several bit maps pointing to the same bit image, each imposing a different coordinate system on it. This important feature is explained in
“Coordinates in GrafPorts”, below.
As shown in Figure 7, the structure of a bit map is as follows:
TYPE BitMap = RECORD
baseAddr: Ptr; {pointer to bit image}
rowBytes: INTEGER; {row width}
bounds: Rect {boundary rectangle}
END;
Figure 7–A Bit Map
BaseAddr is a pointer to the beginning of the bit image in memory. RowBytes is the row width in bytes. Both of these must always be even: A bit map must always begin on a word boundary and contain an integral number of words in each row.
The bounds field is the bit map’s boundary rectangle, which both encloses the active area of the bit image and imposes a coordinate system on it. The top left corner of the boundary rectangle is aligned around the first bit in the bit image.
The relationship between the boundary rectangle and the bit image in a bit map is simple yet very important. First, some general rules:
• Bits in a bit image fall between points on the coordinate plane.
• A rectangle that is H points wide and V points tall encloses
exactly (H–1)*(V–1) bits.
The coordinate system assigns integer values to the lines that border and separate bits, not to the bit positions themselves. For example, if a bit map is assigned the boundary rectangle with corners (10,–8) and (34,8), the bottom right bit in the image will be between horizontal coordinates 33 and 34, and between vertical coordinates 7 and 8 (see Figure 8).
Figure 8–Coordinates and Bit Maps
The width of the boundary rectangle determines how many bits of one row are logically owned by the bit map. This width must not exceed the number of bits in each row of the bit image. The height of the boundary rectangle determines how many rows of the image are logically owned by the bit map. The number of rows enclosed by the boundary rectangle must not exceed the number of rows in the bit image.
Normally, the boundary rectangle completely encloses the bit image. If the rectangle is smaller than the dimensions of the image, the least significant bits in each row, as well as the last rows in the image, aren’t affected by any operations on the bit map.
There’s a QuickDraw global variable, named screenBits, that contains a bit map corresponding to the screen of the Macintosh being used. Wherever your program needs the exact dimensions of the screen, it should get them from the boundary rectangle of this variable.
_______________________________________________________________________________
»Patterns
A pattern is a 64-bit image, organized as an 8-by-8-bit square, that’s used to define a repeating design (such as stripes) or tone (such as gray). Patterns can be used to draw lines and shapes or to fill areas on the screen.
When a pattern is drawn, it’s aligned so that adjacent areas of the same pattern in the same graphics port will blend with it into a continuous, coordinated pattern. QuickDraw provides predefined patterns in global variables named white, black, gray, ltGray, and dkGray. Any other 64-bit variable or constant can also be used as a pattern. The data type definition for a pattern is as follows:
TYPE Pattern = PACKED ARRAY[0..7] OF 0..255;
The row width of a pattern is one byte.
_______________________________________________________________________________
»Cursors
A cursor is a small image that appears on the screen and is controlled by the mouse. (It appears only on the screen, and never in an off-screen bit image.)
Note: Macintosh user manuals call this image a “pointer”, since it points
to a location on the screen. To avoid confusion with other meanings
of “pointer” in Inside Macintosh, we use the alternate term “cursor”.
A cursor is defined as a 256-bit image, a 16-by-16-bit square. The row width of a cursor is two bytes. Figure 9 illustrates four cursors.
Figure 9–Cursors
A cursor has three fields: a 16-word data field that contains the image itself, a 16-word mask field that contains information about the screen appearance of each bit of the cursor, and a hotSpot point that aligns the cursor with the mouse location.
TYPE Bits16 = ARRAY[0..15] OF INTEGER;
Cursor = RECORD
data: Bits16; {cursor image}
mask: Bits16; {cursor mask}
hotSpot: Point {point aligned with mouse}
END;
The data for the cursor must begin on a word boundary.
The cursor appears on the screen as a 16-by-16-bit square. The appearance of each bit of the square is determined by the corresponding bits in the data and mask and, if the mask bit is 0, by the pixel “under” the cursor (the pixel already on the screen in the same position as this bit of the cursor):
Data Mask Resulting pixel on screen
0 1 White
1 1 Black
0 0 Same as pixel under cursor
1 0 Inverse of pixel under cursor
Notice that if all mask bits are 0, the cursor is completely transparent, in that the image under the cursor can still be viewed: Pixels under the white part of the cursor appear unchanged, while under the black part of the cursor, black pixels show through as white.
The hotSpot aligns a point (not a bit) in the image with the mouse location. Imagine the rectangle with corners (0,0) and (16,16) framing the image, as in each of the examples in Figure 9; the hotSpot is defined in this coordinate system. A hotSpot of (0,0) is at the top left of the image. For the arrow in Figure 9 to point to the mouse location, (1,1) would be its hotSpot. A hotSpot of (8,8) is in the exact center of the image; the center of the plus sign or circle in Figure 9 would coincide with the mouse location if (8,8) were the hotSpot for that cursor. Similarly, the hotSpot for the pointing hand would be
(16,9).
Whenever you move the mouse, the low-level interrupt-driven mouse routines move the cursor’s hotSpot to be aligned with the new mouse location.
QuickDraw supplies a predefined cursor in the global variable named arrow; this is the standard arrow cursor (illustrated in Figure 9).
_______________________________________________________________________________
»Graphic Entities as Resources
You can create cursors and patterns in your program code, but it’s usually simpler and more convenient to store them in a resource file and read them in when you need them. Standard cursors and patterns are available not only through the global variables provided by QuickDraw, but also as system resources stored in the system resource file. QuickDraw itself operates independently of the Resource Manager, so it doesn’t contain routines for accessing graphics-related resources; instead, these routines are included in the Toolbox Utilities (see the Toolbox Utilities chapter for more information).
Besides patterns and cursors, two other graphic entities that may be stored in resource files (and accessed via Toolbox Utility routines) are a QuickDraw picture, described later in this chapter, and an icon, a 32-by-32 bit image that’s used to graphically represent an object, concept, or message.
_______________________________________________________________________________
»THE DRAWING ENVIRONMENT: GRAFPORT
_______________________________________________________________________________
A grafPort is a complete drawing environment that defines where and how graphic operations will take place. You can have many grafPorts open at once, and each one will have its own coordinate system, drawing pattern, background pattern, pen size and location, character font and style, and bit map in which drawing takes place. You can instantly switch from one port to another. GrafPorts are the structures upon which a program builds windows, which are fundamental to the Macintosh “overlapping windows” user interface. Besides being used for windows on the screen, grafPorts are used for printing and for off-screen drawing.
A grafPort is defined as follows:
TYPE GrafPtr = ^GrafPort;
GrafPort = RECORD
device: INTEGER; {device-specific information}
portBits: BitMap; {grafPort's bit map}
portRect: Rect; {grafPort's rectangle}
visRgn: RgnHandle; {visible region}
clipRgn: RgnHandle; {clipping region}
bkPat: Pattern; {background pattern}
fillPat: Pattern; {fill pattern}
pnLoc: Point; {pen location}
pnSize: Point; {pen size}
pnMode: INTEGER; {pen's transfer mode}
pnPat: Pattern; {pen pattern}
pnVis: INTEGER; {pen visibility}
txFont: INTEGER; {font number for text}
txFace: Style; {text's character style}
txMode: INTEGER; {text's transfer mode}
txSize: INTEGER; {font size for text}
spExtra: Fixed; {extra space}
fgColor: LONGINT; {foreground color}
bkColor: LONGINT; {background color}
colrBit: INTEGER; {color bit}
patStretch: INTEGER; {used internally}
picSave: Handle; {picture being saved}
rgnSave: Handle; {region being saved}
polySave: Handle; {polygon being saved}
grafProcs: QDProcsPtr {low-level drawing routines}
END;
Note that picSave is a Handle used internally by QuickDraw while it is saving a picture, and rgnSave and polySave are used by QuickDraw as flags; they are set to “1” when the corresponding action is taking place.
All QuickDraw operations refer to grafPorts via grafPtrs. (For historical reasons, grafPort is one of the few objects in the Macintosh system software that’s referred to by a pointer rather than a handle.)
Warning: You can access all fields and subfields of a grafPort normally,
but you should not store new values directly into them. QuickDraw
has routines for altering all fields of a grafPort, and using
these routines ensures that changing a grafPort produces no
unusual side effects.
The device field of a grafPort contains device-specific information that’s used by the Font Manager to achieve the best possible results when drawing text in the grafPort. There may be physical differences in the same logical font for different output devices, to ensure the highest-quality printing on the device being used. The default value of the device field is 0, for best results on output to the screen. For more information, see the Font Manager chapter.
The portBits field is the bit map that points to the bit image to be used by the grafPort. The default bit map uses the entire screen as its bit image. The bit map may be changed to indicate a different structure in memory: All graphics routines work in exactly the same way regardless of whether their effects are visible on the screen. A program can, for example, prepare an image to be printed on a printer without ever displaying the image on the screen, or develop a picture in an off-screen bit map before transferring it to the screen. The portBits.bounds rectangle determines the coordinate system of the grafPort; all other coordinates in the grafPort are expressed in this system.
The portRect field is a rectangle that defines a subset of the bit map that will be used for drawing: All drawing done by the application occurs inside the portRect. Its coordinates are in the coordinate system defined by the portBits.bounds rectangle. The portRect usually falls within the portBits.bounds rectangle, but it’s not required to do so. The portRect usually defines the “writable” interior area of a window, document, or other object on the screen.
The visRgn field is manipulated by the Window Manager; you will normally never change a grafPort’s visRgn. It indicates the region of the grafPort that’s actually visible on the screen, that is, the part of the window that’s not covered by other windows. For example, if you move one window in front of another, the Window Manager logically removes the area of overlap from the visRgn of the window in back. When you draw into the back window, whatever’s being drawn is clipped to the visRgn so that it doesn’t run over onto the front window. The default visRgn is set to the portRect.
The clipRgn is the grafPort’s clipping region, an arbitrary region that you can use to limit drawing to any region within the portRect. If, for example, you want to draw a half circle on the screen, you can set the clipRgn to half the square that would enclose the whole circle, and then draw the whole circle. Only the half within the clipRgn will actually be drawn in the grafPort. The default clipRgn is set arbitrarily large, you have full control over its setting; as a matter of recommended programming practice, it is advisable to make the default clipRgn rectangle smaller.
Figure 10 illustrates a typical bit map (as defined by portBits), portRect, visRgn, and clipRgn.
Figure 10–GrafPort Regions
The bkPat and fillPat fields of a grafPort contain patterns used by certain QuickDraw routines. BkPat is the “background” pattern that’s used when an area is erased or when bits are scrolled out of it. When asked to fill an area with a specified pattern, QuickDraw stores the given pattern in the fillPat field and then calls a low-level drawing routine that gets the pattern from that field. The various graphic operations are discussed in detail later in the descriptions of individual QuickDraw routines.
Of the next ten fields, the first five determine characteristics of the graphics pen and the last five determine characteristics of any text that may be drawn; these are described in separate sections below.
The fgColor, bkColor, and colrBit fields contain values related to drawing in color. FgColor is the grafPort’s foreground color and bkColor is its background color. ColrBit tells the color imaging software which plane of the color picture to draw into. For more information, see “Drawing in Color” in the section “General Discussion of Drawing”.
The patStretch field is used during output to a printer to expand patterns if necessary. The application should not change its value.
The picSave, rgnSave, and polySave fields reflect the state of picture, region, and polygon definition, respectively. The application shouldn’t be concerned about exactly what information the handle, if any, leads to; you may, however, save the current value of rgnSave, set the field to NIL to disable the region definition, and later restore it to the saved value to resume the region definition. The picSave and polySave fields work similarly for pictures and polygons.
Finally, the grafProcs field may point to a special data structure that the application stores into if it wants to customize QuickDraw drawing routines or use QuickDraw in other advanced, highly specialized ways (see “Customizing QuickDraw Operations”). If grafProcs is NIL, QuickDraw responds in the standard ways described in this chapter.
_______________________________________________________________________________
»Pen Characteristics
The pnLoc, pnSize, pnMode, pnPat, and pnVis fields of a grafPort deal with the graphics “pen”. Each grafPort has one and only one such pen, which is used for drawing lines, shapes, and text. The pen has four characteristics: a location, a size (height and width), a drawing mode, and a drawing pattern (see Figure
11).
Figure 11–A Graphics Pen
The pnLoc field specifies the point where QuickDraw will begin drawing the next line, shape, or character. It can be anywhere on the coordinate plane: There are no restrictions on the movement or placement of the pen. Remember that the pen location is a point in the grafPort’s coordinate system, not a pixel in a bit image. The top left corner of the pen is at the pen location; the pen hangs below and to the right of this point.
The pen is rectangular in shape, and its width and height are specified by pnSize. The default size is a 1-by-1-bit square; the width and height can range from (0,0) to (30000,30000). If either the pen width or the pen height is less than 1, the pen will not draw.
The pnMode and pnPat fields of a grafPort determine how the bits under the pen are affected when lines or shapes are drawn. The pnPat is a pattern that’s used like the “ink” in the pen. This pattern, like all other patterns drawn in the grafPort, is always aligned with the port’s coordinate system: The top left corner of the pattern is aligned with the top left corner of the portRect, so that adjacent areas of the same pattern will blend into a continuous, coordinated pattern.
The pnMode field determines how the pen pattern is to affect what’s already in the bit image when lines or shapes are drawn. When the pen draws, QuickDraw first determines what bits in the bit image will be affected and finds their corresponding bits in the pattern. It then does a bit-by-bit comparison based on the pen mode, which specifies one of eight Boolean operations to perform. The resulting bit is stored into its proper place in the bit image. The pen modes are described under “Transfer Modes” in the section “General Discussion of Drawing”.
The pnVis field determines the pen’s visibility, that is, whether it draws on the screen. For more information, see the descriptions of HidePen and ShowPen under “Pen and Line-Drawing Routines” in the “QuickDraw Routines” section.
_______________________________________________________________________________
»Text Characteristics
The txFont, txFace, txMode, txSize, and spExtra fields of a grafPort determine how text will be drawn—the font, style, and size of characters and how they will be placed in the bit image. QuickDraw can draw characters as quickly and easily as it draws lines and shapes, and in many prepared fonts. Font means the complete set of characters of one typeface. The characters may be drawn in any size and character style (that is, with stylistic variations such as bold, italic, and underline). Figure 12 shows two characters drawn by QuickDraw and some terms associated with drawing text.
Figure 12–QuickDraw Characters
Text is drawn with the base line positioned at the pen location.
The txFont field is a font number that identifies the character font to be used in the grafPort. The font number 0 represents the system font. For more information about the system font, the other font numbers recognized by the Font Manager, and the construction, layout, and loading of fonts, see the Font Manager chapter.
A character font is defined as a collection of images that make up the individual characters of the font. The characters can be of unequal widths, and they’re not restricted to their “cells”: The lower curl of a lowercase j, for example, can stretch back under the previous character (typographers call this kerning). A font can consist of up to 255 distinct characters, yet not all characters need to be defined in a single font. In addition, each font contains a missing symbol to be drawn in case of a request to draw a character that’s missing from the font.
The txFace field controls the character style of the text with values from the set defined by the Style data type:
TYPE StyleItem = (bold,italic,underline,outline,shadow,condense,extend);
Style = SET OF StyleItem;
Assembly-language note: In assembly language, this set is stored as a word
whose low-order byte contains bits representing the
style. The bit numbers are specified by the following
global constants:
boldBit .EQU 0
italicBit .EQU 1
ulineBit .EQU 2
outlineBit .EQU 3
shadowBit .EQU 5
extendBit .EQU 6
If all bits are 0, it represents the plain character
style.
You can apply stylistic variations either alone or in combination; Figure 13 illustrates some as applied to the Geneva font. Most combinations usually look good only for large font sizes.
Figure 13–Stylistic Variations
If you specify bold, each character is repeatedly drawn one bit to the right an appropriate number of times for extra thickness.
Italic adds an italic slant to the characters. Character bits above the base line are skewed right; bits below the base line are skewed left.
Underline draws a line below the base line of the characters. If part of a character descends below the base line (as “y” in Figure 13), the underline
isn’t drawn through the pixel on either side of the descending part.
Outline makes a hollow, outlined character rather than a solid one. Shadow also makes an outlined character, but the outline is thickened below and to the right of the character to achieve the effect of a shadow. If you specify bold along with outline or shadow, the hollow part of the character is widened.
Condense and extend affect the horizontal distance between all characters, including spaces. Condense decreases the distance between characters and extend increases it, by an amount that the Font Manager determines is appropriate.
The txMode field controls the way characters are placed in the bit image. It functions much like a pnMode: When a character is drawn, QuickDraw determines which bits in the bit image will be affected, does a bit-by-bit comparison based on the mode, and stores the resulting bits into the bit image. These modes are described under “Transfer Modes” in the section “General Discussion of Drawing”. Only three of them—srcOr, srcXor, and srcBic—should be used for drawing text.
Note: If you use scrCopy, some extra blank space will be appended at the
end of the text.
The txSize field specifies the font size in points (where “point” is a typographical term meaning approximately 1/72 inch). Any size from 1 to 127 points may be specified. If the Font Manager doesn’t have the font in a specified size, it will scale a size it does have as necessary to produce the size desired. A value of 0 in this field represents the system font size (12 points).
Finally, the spExtra field is useful when a line of characters is to be drawn justified such that it’s aligned with both a left and a right margin (sometimes called “full justification”). SpExtra contains a fixed-point number equal to the average number of pixels by which each space character should be widened to fill out the line. The Fixed data type is described in the Macintosh Memory Management: An Introduction chapter.
_______________________________________________________________________________
»COORDINATES IN GRAFPORTS
_______________________________________________________________________________
Each grafPort has its own local coordinate system. All fields in the grafPort are expressed in these coordinates, and all calculations and actions performed in QuickDraw use the local coordinate system of the currently selected port.
Two things are important to remember:
• Each grafPort maps a portion of the coordinate plane into a similarly-
sized portion of a bit image.
• The portBits.bounds rectangle defines the local coordinates for a grafPort.
The top left corner of portBits.bounds is always aligned around the first bit in the bit image; the coordinates of that corner “anchor” a point on the grid to that bit in the bit image. This forms a common reference point for multiple grafPorts that use the same bit image (such as the screen); given a portBits.bounds rectangle for each port, you know that their top left corners coincide.
The relationship between the portBits.bounds and portRect rectangles is very important: The portBits.bounds rectangle establishes a coordinate system for the port, and the portRect rectangle indicates the section of the coordinate plane (and thus the bit image) that will be used for drawing. The portRect usually falls inside the portBits.bounds rectangle, but it’s not required to do so.
When a new grafPort is created, its bit map is set to point to the entire screen, and both the portBits.bounds and the portRect are set to rectangles enclosing the screen. The point (0,0) corresponds to the screen’s top left corner.
You can redefine the local coordinates of the top left corner of the grafPort’s portRect, using the SetOrigin procedure. This offsets the coordinates of the grafPort’s portBits.bounds rectangle, recalculating the coordinates of all points in the grafPort to be relative to the new corner coordinates. For example, consider these procedure calls:
SetPort(gamePort);
SetOrigin(90,80)
The call to SetPort sets the current grafPort to gamePort; the call to SetOrigin changes the local coordinates of the top left corner of that port’s portRect to (90,80) (see Figure 14).
Figure 14–Changing Local Coordinates
This offsets the coordinates of the following elements:
gamePort^.portBits.bounds
gamePort^.portRect
gamePort^.visRgn
These three elements are always kept “in sync”.
Notice that when the local coordinates of a grafPort are offset, the grafPort’s clipRgn and pen location are not offset. A good way to think of it is that the port’s structure “sticks” to the screen, while the document in the grafPort
(along with the pen and clipRgn) “sticks” to the coordinate system. For example, in Figure 14, before SetOrigin, the visRgn and clipRgn are the same as the portRect. After the SetOrigin call, the locations of portBits.bounds, portRect, and visRgn do not change on the screen; their coordinates are simply offset. As always, the top left corner of portBits.bounds remains “anchored” around the first bit in the bit image (the first pixel on the screen); the image on the screen doesn’t move as a result of SetOrigin. However, the pen location and clipRgn do move on the screen; the top left corner of the clipRgn is still
(100,100), but this location has moved down and to the right, and the pen has similarly moved.
If you’re moving, comparing, or otherwise dealing with mathematical items in different grafPorts (for example, finding the intersection of two regions in two different grafPorts), you must adjust to a common coordinate system before you perform the operation. A QuickDraw procedure, LocalToGlobal, lets you convert a point’s local coordinates to a global coordinate system where the top left corner of the bit image is (0,0); by converting the various local coordinates to global coordinates, you can compare and mix them with confidence. For more information, see the description of LocaltoGlobal under “Calculations with Points” in the “QuickDraw Routines” section.
_______________________________________________________________________________
»GENERAL DISCUSSION OF DRAWING
_______________________________________________________________________________
Drawing occurs:
• always inside a grafPort, in the bit image and coordinate system
defined by the grafPort’s bit map
• always within the intersection of the grafPort’s portBits.bounds
and portRect, and clipped to its visRgn and clipRgn
• always at the grafPort’s pen location
• usually with the grafPort’s pen size, pattern, and mode
With QuickDraw routines, you can draw lines, shapes, and text. Shapes include rectangles, ovals, rounded-corner rectangles, wedge-shaped sections of ovals, regions, and polygons.
Lines are defined by two points: the current pen location and a destination location. When drawing a line, QuickDraw moves the top left corner of the pen along the mathematical trajectory from the current location to the destination. The pen hangs below and to the right of the trajectory (see Figure 15).
Figure 15–Drawing Lines
Note: No mathematical element (such as the pen location) is ever affected
by clipping; clipping only determines what appears where in the bit
image. If you draw a line to a location outside the intersection of
the portRect, visRgn and clipRgn, the pen location will move there,
but only the portion of the line that’s inside that area will actually
be drawn. This is true for all drawing routines.
Rectangles, ovals, and rounded-corner rectangles are defined by two corner points. The shapes always appear inside the mathematical rectangle defined by the two points. A region is defined in a more complex manner, but also appears only within the rectangle enclosing it. Remember, these enclosing rectangles have infinitely thin borders and are not visible on the screen.
As illustrated in Figure 16, shapes may be drawn either solid (filled in with a pattern) or framed (outlined and hollow).
Figure 16–Solid Shapes and Framed Shapes
In the case of framed shapes, the outline appears completely within the enclosing rectangle—with one exception—and the vertical and horizontal thickness of the outline is determined by the pen size. The exception is polygons, as discussed in the section “Pictures and Polygons” below.
The pen pattern is used to fill in the bits that are affected by the drawing operation. The pen mode defines how those bits are to be affected by directing QuickDraw to apply one of eight Boolean operations to the bits in the shape and the corresponding pixels on the screen.
Text drawing doesn’t use the pnSize, pnPat, or pnMode, but it does use the pnLoc. QuickDraw starts drawing each character from the current pen location, with the character’s base line at the pen location. After a character is drawn, the pen moves to the right to the location where it will draw the next character. No wraparound or carriage return is performed automatically. Clipping of text is performed in exactly the same manner as all other clipping in QuickDraw.
_______________________________________________________________________________
»Transfer Modes
When lines or shapes are drawn, the pnMode field of the grafPort determines how the drawing is to appear in the port’s bit image; similarly, the txMode field determines how text is to appear. There’s also a QuickDraw procedure that transfers a bit image from one bit map to another, and this procedure has a mode parameter that determines the appearance of the result. In all these cases, the mode, called a transfer mode, specifies one of eight Boolean operations: For each bit in the item to be drawn, QuickDraw finds the corresponding bit in the destination bit map, performs the Boolean operation on the pair of bits, and stores the resulting bit into the bit image.
There are two types of transfer mode:
• pattern transfer modes, for drawing lines or shapes with a pattern
• source transfer modes, for drawing text or transferring any bit
image between two bit maps
For each type of mode, there are four basic operations—Copy, Or, Xor, and Bic
(“bit clear”). The Copy operation simply replaces the pixels in the destination with the pixels in the pattern or source, “painting” over the destination without regard for what’s already there. The Or, Xor, and Bic operations leave the destination pixels under the white part of the pattern or source unchanged, and differ in how they affect the pixels under the black part: Or replaces those pixels with black pixels, thus “overlaying” the destination with the black part of the pattern or source; Xor inverts the pixels under the black part; and Bic erases them to white.
Each of the basic operations has a variant in which every pixel in the pattern or source is inverted before the operation is performed, giving eight operations in all. Each mode is defined by name as a constant in QuickDraw (see Figure 17).
Figure 17–Transfer Modes
Pattern Source Action on each pixel in destination:
transfer transfer If black pixel in If white pixel in
mode mode pattern or source pattern or source
patCopy srcCopy Force black Force white
patOr srcOr Force black Leave alone
patXor srcXor Invert Leave alone
patBic srcBic Force white Leave alone
notPatCopy notSrcCopy Force white Force black
notPatOr notSrcOr Leave alone Force black
notPatXor notSrcXor Leave alone Invert
notPatBic notSrcBic Leave alone Force white
_______________________________________________________________________________
»Drawing in Color
Your application can draw on color output devices by using QuickDraw procedures to set the foreground color and the background color. Eight standard colors may be specified with the following predefined constants:
CONST blackColor = 33;
whiteColor = 30;
redColor = 209;
greenColor = 329;
blueColor = 389;
cyanColor = 269;
magentaColor = 149;
yellowColor = 89;
Initially, the foreground color is blackColor and the background color is whiteColor. If you specify a color other than whiteColor, it will appear as black on a black-and-white output device.
To apply the table in the “Transfer Modes” section above to drawing in color, make the following translation: Where the table shows “Force black”, read
“Force foreground color”, and where it shows “Force white”, read “Force background color”. The effect of inverting a color depends on the device being used.
Note: QuickDraw can support output devices that have up to 32 bits of color
information per pixel. A color picture may be thought of, then, as
having up to 32 planes. At any one time, QuickDraw draws into only
one of these planes. A QuickDraw routine called by the color-imaging
software specifies which plane.
X-Ref: Technical Note #73
_______________________________________________________________________________
»PICTURES AND POLYGONS
_______________________________________________________________________________
QuickDraw lets you save a sequence of drawing commands and “play them back” later with a single procedure call. There are two such mechanisms: one for drawing any picture to scale in a destination rectangle that you specify, and another for drawing polygons in all the ways you can draw other shapes in QuickDraw.
_______________________________________________________________________________
»»Pictures
A picture in QuickDraw is a transcript of calls to routines that draw something—anything—in a bit image. Pictures make it easy for one program to draw something defined in another program, with great flexibility and without knowing the details about what’s being drawn.
For each picture you define, you specify a rectangle that surrounds it; this rectangle is called the picture frame. When you later call the procedure that plays back the saved picture, you supply a destination rectangle, and QuickDraw scales the picture so that its frame is completely aligned with the destination rectangle. Thus, the picture may be expanded or shrunk to fit its destination rectangle. For example, if the picture is a circle inside a square picture frame, and the destination rectangle is not square, the picture will be drawn as an oval.
Since a picture may include any sequence of drawing commands, its data structure is a variable-length entity. It consists of two fixed-length fields followed by a variable-length field:
TYPE Picture = RECORD
picSize: INTEGER; {size in bytes}
picFrame: Rect; {picture frame}
{picture definition data}
END;
The picSize field contains the size, in bytes, of the picture variable. The picFrame field is the picture frame that surrounds the picture and gives a frame of reference for scaling when the picture is played back. The rest of the structure contains a compact representation of the drawing commands that define the picture.
All pictures are accessed through handles:
TYPE PicPtr = ^Picture;
PicHandle = ^PicPtr;
To define a picture, you call a QuickDraw function that returns a picHandle, and then call the drawing routines that define the picture.
QuickDraw also allows you to intersperse picture comments with the definition of a picture. These comments, which do not affect the picture’s appearance, may be used to provide additional information about the picture when it’s played back. This is especially valuable when pictures are transmitted from one application to another. There are two standard types of comments which, like parentheses, serve to group drawing commands together (such as all the commands that draw a particular part of a picture):
CONST picLParen = 0;
picRParen = 1;
The application defining the picture can use these standard comments as well as comments of its own design.
X-Ref: Technical Note #21
X-Ref: Technical Note #91
X-Ref: Technical Note #154
_______________________________________________________________________________
»Polygons
Polygons are similar to pictures in that you define them by a sequence of calls to QuickDraw routines. They’re also similar to other shapes that QuickDraw knows about, since there’s a set of procedures for performing graphic operations and calculations on them.
A polygon is simply any sequence of connected lines (see Figure 18). You define a polygon by moving to the starting point of the polygon and drawing lines from there to the next point, from that point to the next, and so on.
The data structure for a polygon consists of two fixed-length fields followed by a variable-length array:
TYPE Polygon = RECORD
polySize: INTEGER; {size in bytes}
polyBBox: Rect; {enclosing rectangle}
polyPoints: ARRAY[0..0] OF Point
END;
Figure 18–Polygons
The polySize field contains the size, in bytes, of the polygon variable. The maximum size of a polygon is 32K bytes. The polyBBox field is a rectangle that just encloses the entire polygon. The polyPoints array expands as necessary to contain the points of the polygon—the starting point followed by each successive point to which a line is drawn.
Like pictures and regions, polygons are accessed through handles:
TYPE PolyPtr = ^Polygon;
PolyHandle = ^PolyPtr;
To define a polygon, you call a routine that returns a polyHandle, and then call the line-drawing routines that define the polygon.
Just as for other shapes that QuickDraw knows about, there’s a set of graphic operations to draw polygons on the screen. QuickDraw draws a polygon by moving to the starting point and then drawing lines to the remaining points in succession, just as when the routines were called to define the polygon. In this sense it “plays back” those routine calls. As a result, polygons are not treated exactly the same as other QuickDraw shapes. For example, the procedure that frames a polygon draws outside the actual boundary of the polygon, because QuickDraw line-drawing routines draw below and to the right of the pen location. The procedures that fill a polygon with a pattern, however, stay within the boundary of the polygon; if the polygon’s ending point isn’t the same as its starting point, these procedures add a line between them to complete the shape.
QuickDraw also scales a polygon differently from a similarly-shaped region if it’s being drawn as part of a picture: When stretched, a slanted line is drawn more smoothly if it’s part of a polygon rather than a region. You may find it helpful to keep in mind the conceptual difference between polygons and regions: A polygon is treated more as a continuous shape, a region more as a set of bits.
_______________________________________________________________________________
»USING QUICKDRAW
_______________________________________________________________________________
Call the InitGraf procedure to initialize QuickDraw at the beginning of your program, before initializing any other parts of the Toolbox.
When your application starts up, the cursor will be a wristwatch; the Finder sets it to this to indicate that a lengthy operation is in progress. Call the InitCursor procedure when the application is ready to respond to user input, to change the cursor to the standard arrow. Each time through the main event loop, you should call SetCursor to change the cursor as appropriate for its screen location.
All graphic operations are performed in grafPorts. Before a grafPort can be used, it must be allocated and initialized with the OpenPort procedure. Normally, you don’t call OpenPort yourself—in most cases your application will draw into a window you’ve created with Window Manager routines, and these routines call OpenPort to create the window’s grafPort. Likewise, a grafPort’s regions are disposed of with ClosePort, and the grafPort itself is disposed of with the Memory Manager procedure DisposPtr—but when you call the Window Manager to close or dispose of a window, it calls these routines for you.
In an application that uses multiple windows, each is a separate grafPort. If your application draws into more than one grafPort, you can call SetPort to set the grafPort that you want to draw in. At times you may need to preserve the current grafPort; you can do this by calling GetPort to save the current port, SetPort to set the port you want to draw in, and then SetPort again when you need to restore the previous port.
Each grafPort has its own local coordinate system. Some Toolbox routines return or expect points that are expressed in a common, global coordinate system, while others use local coordinates. For example, when the Event Manager reports an event, it gives the mouse location in global coordinates; but when you call the Control Manager to find out whether the user clicked in a control in one of your windows, you pass the mouse location in local coordinates. The GlobalToLocal procedure lets you convert global coordinates to local coordinates, and the LocalToGlobal procedure lets you do the reverse.
The SetOrigin procedure will adjust a grafPort’s local coordinate system. If your application performs scrolling, you’ll use ScrollRect to shift the bits of the image, and then SetOrigin to readjust the coordinate system after this shift.
You can redefine a grafPort’s clipping region with the SetClip or ClipRect procedure. Just as GetPort and SetPort are used to preserve the current grafPort, GetClip and SetClip are useful for saving the grafPort’s clipRgn while you temporarily perform other clipping functions. This is useful, for example, when you want to reset the clipRgn to redraw the newly displayed portion of a document that’s been scrolled.
When drawing text in a grafPort, you can set the font characteristics with TextFont, TextFace, TextMode, and TextSize. CharWidth, StringWidth, or TextWidth will tell you how much horizontal space the text will require, and GetFontInfo will tell you how much vertical space. You can draw text with DrawChar, DrawString, and DrawText.
The LineTo procedure draws a line from the current pen location to a given point, and the Line procedure draws a line between two given points. You can set the pen location with the MoveTo or Move procedure, and set other pen characteristics with PenSize, PenMode, and PenPat.
In addition to drawing text and lines, you can use QuickDraw to draw a variety of shapes. Most of them are defined simply by a rectangle that encloses the shape. Others require you to call a series of routines to define them:
• To define a region, call the NewRgn function to allocate space for it,
then call OpenRgn, and then specify the outline of the region by calling
routines that draw lines and shapes. End the region definition by calling
CloseRgn. When you’re completely done with the region, call DisposeRgn
to release the memory it occupies.
• To define a polygon, call the OpenPoly function and then form the polygon
by calling procedures that draw lines. Call ClosePoly when you’re finished
defining the polygon, and KillPoly when you’re completely done with it.
You can perform the following graphic operations on rectangles, rounded-corner rectangles, ovals, arcs/wedges, regions, and polygons:
• frame, to outline the shape using the current pen pattern and size
• paint, to fill the shape using the current pen pattern
• erase, to paint the shape using the current background pattern
• invert, to invert the pixels in the shape
• fill, to fill the shape with a specified pattern
QuickDraw pictures let you record and play back complex drawing sequences. To define a picture, call the OpenPicture function and then the drawing routines that form the picture. Call ClosePicture when you’re finished defining the picture. To draw a picture, call DrawPicture. When you’re completely done with a picture, call KillPicture (or the Resource Manager procedure ReleaseResource, if the picture’s a resource).
You’ll use points, rectangles, and regions not only when drawing with QuickDraw, but also when using other parts of the Toolbox and Operating System. At times, you may find it useful to perform calculations on these entities. You can, for example, add and subtract points, and perform a number of calculations on rectangles and regions, such as offsetting them, rescaling them, calculating their union or intersection, and so on.
Note: When performing a calculation on entities in different grafPorts,
you need to adjust to a common coordinate system first, by calling
LocalToGlobal to convert to global coordinates.
To transfer a bit image from one bit map to another, you can use the CopyBits procedure. For example, you can call SetPortBits to change the bit map of the current grafPort to an off-screen buffer, draw into that grafPort, and then call CopyBits to transfer the image from the off-screen buffer onto the screen.
The SeedFill and CalcMask procedures operate on a portion of a bitmap. In both routines, srcPtr and dstPtr point to the beginning of the data to be filled or calculated, not to the beginning of the bitmap; both parameters must point to word boundaries in memory. SrcRow and dstRow specify the row width in bytes (in other words, the rowBytes field of the BitMap record) of the source and destination bitmaps respectively. Height and words determine the number of bits to be filled or calculated; words is the width of the rectangle in words and height is the height of the rectangle in pixels. Figure 19 illustrates the use of these parameters.
Figure 19–Parameters Used by SeedFill and CalcMask
_______________________________________________________________________________
»QUICKDRAW ROUTINES
_______________________________________________________________________________
»GrafPort Routines
PROCEDURE InitGraf (globalPtr: Ptr);
Call InitGraf once and only once at the beginning of your program to initialize QuickDraw. It initializes the global variables listed below (as well as some private global variables for its own internal use).
Variable Type Initial setting
thePort GrafPtr NIL
white Pattern An all-white pattern
black Pattern An all-black pattern
gray Pattern A 50% gray pattern
ltGray Pattern A 25% gray pattern
dkGray Pattern A 75% gray pattern
arrow Cursor The standard arrow cursor
screenBits BitMap The entire screen
randSeed LONGINT 1
You must pass, in the globalPtr parameter, a pointer to the first QuickDraw global variable, thePort. From Pascal programs, you should always pass @thePort for globalPtr.
Assembly-language note: The QuickDraw global variables are stored in reverse
order, from high to low memory, and require the number
of bytes specified by the global constant grafSize.
Most development systems (including the Lisa Workshop)
preallocate space for these globals immediately below
the location pointed to by register A5. Since thePort
is four bytes, you would pass the globalPtr parameter
as follows:
PEA -4(A5)
_InitGraf
InitGraf stores this pointer to thePort in the
location pointed to by A5. This value is used as a
base address when accessing the other QuickDraw global
variables, which are accessed using negative offsets
(the offsets have the same names as the Pascal global
variables). For example:
MOVE.L (A5),A0 ;point to first
; QuickDraw global
MOVE.L randSeed(A0),A1 ;get global variable
; randSeed
Note: To initialize the cursor, call InitCursor (described under
“Cursor-Handling Routines” below).
PROCEDURE OpenPort (port: GrafPtr);
OpenPort allocates space for the given grafPort’s visRgn and clipRgn, initializes the fields of the grafPort as indicated below, and makes the grafPort the current port (by calling SetPort). OpenPort is called by the Window Manager when you create a window, and you normally won’t call it yourself. If you do call OpenPort, you can create the grafPtr with the Memory Manager procedure NewPtr or reserve the space on the stack (with a variable of type GrafPort).
Field Type Initial setting
device INTEGER 0 (the screen)
portBits BitMap screenBits
portRect Rect screenBits.bounds
visRgn RgnHandle handle to a rectangular region coincident
with screenBits.bounds
clipRgn RgnHandle handle to the rectangular region
(–32767,–32767) (32767,32767)
bkPat Pattern white
fillPat Pattern black
pnLoc Point (0,0)
pnSize Point (1,1)
pnMode INTEGER patCopy
pnPat Pattern black
pnVis INTEGER 0 (visible)
txFont INTEGER 0 (system font)
txFace Style plain
txMode INTEGER srcOr
txSize INTEGER 0 (system font size)
spExtra Fixed 0
fgColor LONGINT blackColor
bkColor LONGINT whiteColor
colrBit INTEGER 0
patStretch INTEGER 0
picSave Handle NIL
rgnSave Handle NIL
polySave Handle NIL
grafProcs QDProcsPtr NIL
PROCEDURE InitPort (port: GrafPtr);
Given a pointer to a grafPort that’s been opened with OpenPort, InitPort reinitializes the fields of the grafPort and makes it the current port. It’s unlikely that you’ll ever have a reason to call this procedure.
Note: InitPort does everything OpenPort does except allocate space for
the visRgn and clipRgn.
PROCEDURE ClosePort (port: GrafPtr);
ClosePort releases the memory occupied by the given grafPort’s visRgn and clipRgn. When you’re completely through with a grafPort, call this procedure and then dispose of the grafPort with the Memory Manager procedure DisposPtr
(if it was allocated with NewPtr). This is normally done for you when you call the Window Manager to close or dispose of a window.
Warning: If ClosePort isn’t called before a grafPort is disposed of, the
memory used by the visRgn and clipRgn will be unrecoverable.
PROCEDURE SetPort (port: GrafPtr);
SetPort makes the specified grafPort the current port.
Note: Only SetPort (and OpenPort and InitPort, which call it) changes the
current port. All the other routines in the Toolbox and Operating
System (even those that call SetPort, OpenPort, or InitPort) leave
the current port set to what it was when they were called.
The global variable thePort always points to the current port. All QuickDraw drawing routines affect the bit map thePort^.portBits and use the local coordinate system of thePort^.
Each port has its own pen and text characteristics, which remain unchanged when the port isn’t selected as the current port.
PROCEDURE GetPort (VAR port: GrafPtr);
GetPort returns a pointer to the current grafPort. This pointer is also available through the global variable thePort, but you may prefer to use GetPort for better readability of your program text. For example, a procedure could do a GetPort(savePort) before setting its own grafPort and a
SetPort(savePort) afterwards to restore the previous port.
PROCEDURE GrafDevice (device: INTEGER);
GrafDevice sets the device field of the current grafPort to the given value, which consists of device-specific information that’s used by the Font Manager to achieve the best possible results when drawing text in the grafPort. The initial value of the device field is 0, for best results on output to the screen. For more information, see the Font Manager chapter.
Note: This field is used for communication between QuickDraw and the Font
Manager; normally you won’t set it yourself.
PROCEDURE SetPortBits (bm: BitMap);
Assembly-language note: The macro you invoke to call SetPortBits from
assembly language is named _SetPBits.
SetPortBits sets the portBits field of the current grafPort to any previously defined bit map. This allows you to perform all normal drawing and calculations on a buffer other than the screen—for example, a small off-screen image for later “stamping” onto the screen (with the CopyBits procedure, described under
“Bit Transfer Operations” below).
Remember to prepare all fields of the bit map before you call SetPortBits.
PROCEDURE PortSize (width,height: INTEGER);
PortSize changes the size of the current grafPort’s portRect. This does not affect the screen; it merely changes the size of the “active area” of the grafPort.
Note: This procedure is normally called only by the Window Manager.
The top left corner of the portRect remains at its same location; the width and height of the portRect are set to the given width and height. In other words, PortSize moves the bottom right corner of the portRect to a position relative to the top left corner.
PortSize doesn’t change the clipRgn or the visRgn, nor does it affect the local coordinate system of the grafPort: It changes only the portRect’s width and height. Remember that all drawing occurs only in the intersection of the portBits.bounds and the portRect, clipped to the visRgn and the clipRgn.
PROCEDURE MovePortTo (leftGlobal,topGlobal: INTEGER);
MovePortTo changes the position of the current grafPort’s portRect. This does not affect the screen; it merely changes the location at which subsequent drawing inside the port will appear.
Note: This procedure is normally called only by the Window Manager
and the System Error Handler.
The leftGlobal and topGlobal parameters set the distance between the top left corner of portBits.bounds and the top left corner of the new portRect.
Like PortSize, MovePortTo doesn’t change the clipRgn or the visRgn, nor does it affect the local coordinate system of the grafPort.
PROCEDURE SetOrigin (h,v: INTEGER);
SetOrigin changes the local coordinate system of the current grafPort. This does not affect the screen; it does, however, affect where subsequent drawing inside the port will appear.
The h and v parameters set the coordinates of the top left corner of the portRect. All other coordinates are calculated from this point; SetOrigin also offsets the coordinates of the portBits.bounds rectangle and the visRgn. Relative distances among elements in the port remain the same; only their absolute local coordinates change. All subsequent drawing and calculation routines use the new coordinate system.
Note: SetOrigin does not offset the coordinates of the clipRgn or the pen;
the pen and clipRgn “stick” to the coordinate system, and therefore
change position on the screen (unlike the portBits.bounds, portRect,
and visRgn, which “stick” to the screen, and don’t change position).
See the “Coordinates in GrafPorts” section for an illustration.
SetOrigin is useful for readjusting the coordinate system after a scrolling operation. (See ScrollRect under “Bit Transfer Operations” below.)
Note: All other routines in the Toolbox and Operating System preserve the
local coordinate system of the current grafPort.
PROCEDURE SetClip (rgn: RgnHandle);
SetClip changes the clipping region of the current grafPort to a region that’s equivalent to the given region. Note that this doesn’t change the region handle, but affects the clipping region itself. Since SetClip makes a copy of the given region, any subsequent changes you make to that region will not affect the clipping region of the port.
You can set the clipping region to any arbitrary region, to aid you in drawing inside the grafPort. The initial clipRgn is an arbitrarily large rectangle.
Note: All routines in the Toolbox and Operating System preserve the
current clipRgn.
PROCEDURE GetClip (rgn: RgnHandle);
GetClip changes the given region to a region that’s equivalent to the clipping region of the current grafPort. This is the reverse of what SetClip does. Like SetClip, it doesn’t change the region handle. GetClip and SetClip are used to preserve the current clipRgn (they’re analogous to GetPort and SetPort).
PROCEDURE ClipRect (r: Rect);
ClipRect changes the clipping region of the current grafPort to a rectangle that’s equivalent to the given rectangle. Note that this doesn’t change the region handle, but affects the clipping region itself.
PROCEDURE BackPat (pat: Pattern);
BackPat sets the background pattern of the current grafPort to the given pattern. The background pattern is used in ScrollRect and in all QuickDraw routines that perform an “erase” operation.
_______________________________________________________________________________
»Cursor-Handling Routines
PROCEDURE InitCursor;
InitCursor sets the current cursor to the standard arrow and sets the cursor level to 0, making the cursor visible. The cursor level keeps track of the number of times the cursor has been hidden to compensate for nested calls to HideCursor and ShowCursor, explained below.
PROCEDURE SetCursor (crsr: Cursor);
SetCursor sets the current cursor to the given cursor. If the cursor is hidden, it remains hidden and will attain the new appearance when it’s uncovered; if the cursor is already visible, it changes to the new appearance immediately.
The cursor image is initialized by InitCursor to the standard arrow, visible on the screen.
Note: You’ll normally get a cursor from a resource file, by calling the
Toolbox Utility function GetCursor, and then doubly dereference the
handle it returns.
PROCEDURE HideCursor;
HideCursor removes the cursor from the screen, restoring the bits under it, and decrements the cursor level (which InitCursor initialized to 0). Every call to HideCursor should be balanced by a subsequent call to ShowCursor.
Note: See also the description of the Toolbox Utility procedure ShieldCursor.
PROCEDURE ShowCursor;
ShowCursor increments the cursor level, which may have been decremented by HideCursor, and displays the cursor on the screen if the level becomes 0. A call to ShowCursor should balance each previous call to HideCursor. The level isn’t incremented beyond 0, so extra calls to ShowCursor have no effect.
The low-level interrupt-driven routines link the cursor with the mouse position, so that if the cursor level is 0 (visible), the cursor automatically follows the mouse. You don’t need to do anything but a ShowCursor to have the cursor track the mouse.
If the cursor has been changed (with SetCursor) while hidden, ShowCursor presents the new cursor.
PROCEDURE ObscureCursor;
ObscureCursor hides the cursor until the next time the mouse is moved. It’s normally called when the user begins to type. Unlike HideCursor, it has no effect on the cursor level and must not be balanced by a call to ShowCursor.
_______________________________________________________________________________
»Pen and Line-Drawing Routines
The pen and line-drawing routines all depend on the coordinate system of the current grafPort. Remember that each grafPort has its own pen; if you draw in one grafPort, change to another, and return to the first, the pen will remain in the same location.
PROCEDURE HidePen;
HidePen decrements the current grafPort’s pnVis field, which is initialized to 0 by OpenPort; whenever pnVis is negative, the pen doesn’t draw on the screen. PnVis keeps track of the number of times the pen has been hidden to compensate for nested calls to HidePen and ShowPen (below). Every call to HidePen should be balanced by a subsequent call to ShowPen. HidePen is called by OpenRgn, OpenPicture, and OpenPoly so that you can define regions, pictures, and polygons without drawing on the screen.
PROCEDURE ShowPen;
ShowPen increments the current grafPort’s pnVis field, which may have been decremented by HidePen; if pnVis becomes 0, QuickDraw resumes drawing on the screen. Extra calls to ShowPen will increment pnVis beyond 0, so every call to ShowPen should be balanced by a call to HidePen. ShowPen is called by CloseRgn, ClosePicture, and ClosePoly.
PROCEDURE GetPen (VAR pt: Point);
GetPen returns the current pen location, in the local coordinates of the current grafPort.
PROCEDURE GetPenState (VAR pnState: PenState);
GetPenState saves the pen location, size, pattern, and mode in pnState, to be restored later with SetPenState. This is useful when calling subroutines that operate in the current port but must change the graphics pen: Each such procedure can save the pen’s state when it’s called, do whatever it needs to do, and restore the previous pen state immediately before returning. The PenState data type is defined as follows:
TYPE PenState = RECORD
pnLoc: Point; {pen location}
pnSize: Point; {pen size}
pnMode: INTEGER; {pen's transfer mode}
pnPat: Pattern {pen pattern}
END;
PROCEDURE SetPenState (pnState: PenState);
SetPenState sets the pen location, size, pattern, and mode in the current grafPort to the values stored in pnState. This is usually called at the end of a procedure that has altered the pen parameters and wants to restore them to their state at the beginning of the procedure. (See GetPenState, above.)
PROCEDURE PenSize (width,height: INTEGER);
PenSize sets the dimensions of the graphics pen in the current grafPort. All subsequent calls to Line, LineTo, and the procedures that draw framed shapes in the current grafPort will use the new pen dimensions.
The pen dimensions can be accessed in the variable thePort^.pnSize, which is of type Point. If either of the pen dimensions is set to a negative value, the pen assumes the dimensions (0,0) and no drawing is performed. For a discussion of how the pen draws, see the “General Discussion of Drawing” section.
PROCEDURE PenMode (mode: INTEGER);
PenMode sets the transfer mode through which the pen pattern is transferred onto the bit map when lines or shapes are drawn in the current grafPort. The mode may be any one of the pattern transfer modes:
patCopy notPatCopy
patOr notPatOr
patXor notPatXor
patBic notPatBic
If the mode is one of the source transfer modes (or negative), no drawing is performed. The current pen mode can be accessed in the variable thePort^.pnMode. The initial pen mode is patCopy, in which the pen pattern is copied directly to the bit map.
PROCEDURE PenPat (pat: Pattern);
PenPat sets the pattern that’s used by the pen in the current grafPort. The standard patterns white, black, gray, ltGray, and dkGray are predefined; the initial pen pattern is black. The current pen pattern can be accessed in the variable thePort^.pnPat, and this value can be assigned to any other variable of type Pattern.
PROCEDURE PenNormal;
PenNormal resets the initial state of the pen in the current grafPort, as follows:
Field Setting
pnSize (1,1)
pnMode patCopy
pnPat black
The pen location is not changed.
PROCEDURE MoveTo (h,v: INTEGER);
MoveTo moves the pen to location (h,v) in the local coordinates of the current grafPort. No drawing is performed.
PROCEDURE Move (dh,dv: INTEGER);
This procedure moves the pen a distance of dh horizontally and dv vertically from its current location; it calls MoveTo(h+dh,v+dv), where (h,v) is the current location. The positive directions are to the right and down. No drawing is performed.
PROCEDURE LineTo (h,v: INTEGER);
LineTo draws a line from the current pen location to the location specified (in local coordinates) by h and v. The new pen location is (h,v) after the line is drawn. See the “General Discussion of Drawing” section.
If a region or polygon is open and being formed, its outline is infinitely thin and is not affected by the pnSize, pnMode, or pnPat. (See OpenRgn and OpenPoly.)
PROCEDURE Line (dh,dv: INTEGER);
This procedure draws a line to the location that’s a distance of dh horizontally and dv vertically from the current pen location; it calls
LineTo(h+dh,v+dv), where (h,v) is the current location. The positive directions are to the right and down. The pen location becomes the coordinates of the end of the line after the line is drawn. See the “General Discussion of Drawing” section.
If a region or polygon is open and being formed, its outline is infinitely thin and is not affected by the pnSize, pnMode, or pnPat. (See OpenRgn and OpenPoly.)
_______________________________________________________________________________
»Text-Drawing Routines
Each grafPort has its own text characteristics, and all these procedures deal with those of the current port.
PROCEDURE TextFont (font: INTEGER);
TextFont sets the current grafPort’s font (thePort^.txFont) to the given font number. The initial font number is 0, which represents the system font.
PROCEDURE TextFace (face: Style);
TextFace sets the current grafPort’s character style (thePort^.txFace). The Style data type allows you to specify a set of one or more of the following predefined constants: bold, italic, underline, outline, shadow, condense, and extend. For example:
TextFace([bold]); {bold}
TextFace([bold,italic]); {bold and italic}
TextFace(thePort^.txFace+[bold]); {whatever it was plus bold}
TextFace(thePort^.txFace-[bold]); {whatever it was but not bold}
TextFace([]); {plain text}
PROCEDURE TextMode (mode: INTEGER);
TextMode sets the current grafPort’s transfer mode for drawing text
(thePort^.txMode). The mode should be srcOr, srcXor, or srcBic. The initial transfer mode for drawing text is srcOr.
PROCEDURE TextSize (size: INTEGER);
TextSize sets the current grafPort’s font size (thePort^.txSize) to the given number of points. Any size may be specified, but the result will look best if the Font Manager has the font in that size (otherwise it will scale a size it does have). The next best result will occur if the given size is an even multiple of a size available for the font. If 0 is specified, the system font size (12 points) will be used. The initial txSize setting is 0.
PROCEDURE SpaceExtra (extra: Fixed);
SpaceExtra sets the current grafPort’s spExtra field, which specifies the average number of pixels by which to widen each space in a line of text. This is useful when text is being fully justified (that is, aligned with both a left and a right margin). The initial spExtra setting is 0.
SpaceExtra will also accept a negative parameter, but be careful not to narrow spaces so much that the text is unreadable.
PROCEDURE DrawChar (ch: CHAR);
DrawChar places the given character to the right of the pen location, with the left end of its base line at the pen’s location, and advances the pen accordingly. If the character isn’t in the font, the font’s missing symbol is drawn.
Note: If you’re drawing a series of characters, it’s faster to make one
DrawString or DrawText call rather than a series of DrawChar calls.
PROCEDURE DrawString (s: Str255);
DrawString calls DrawChar for each character in the given string. The string is placed beginning at the current pen location and extending right. No formatting (such as carriage returns and line feeds) is performed by QuickDraw. The pen location ends up to the right of the last character in the string.
Warning: QuickDraw temporarily stores on the stack all of the text you
ask it to draw, even if the text will be clipped. When drawing
large font sizes or complex style variations, it’s best to draw
only what will be visible on the screen. You can determine how
many characters will actually fit on the screen by calling the
StringWidth function before calling DrawString.
PROCEDURE DrawText (textBuf: Ptr; firstByte,byteCount: INTEGER);
DrawText calls DrawChar for each character in the arbitrary structure in memory specified by textBuf, starting firstByte bytes into the structure and continuing for byteCount bytes (firstByte starts at 0). The text is placed beginning at the current pen location and extending right. No formatting (such as carriage returns and line feeds) is performed by QuickDraw. The pen location ends up to the right of the last character in the string.
Warning: Inside a picture definition, DrawText can’t have a byteCount
greater than 255.
Note: You can determine how many characters will actually fit on the
screen by calling the TextWidth function before calling DrawText.
(See the warning under DrawString above.)
FUNCTION CharWidth (ch: CHAR) : INTEGER;
CharWidth returns the character width of the specified character, that is, the value that will be added to the pen horizontal coordinate if the specified character is drawn. CharWidth includes the effects of the stylistic variations set with TextFace; if you change these after determining the character width but before actually drawing the character, the predetermined width may not be correct. If the character is a space, CharWidth also includes the effect of SpaceExtra.
FUNCTION StringWidth (s: Str255) : INTEGER;
StringWidth returns the width of the given text string, which it calculates by adding the CharWidths of all the characters in the string (see above).
FUNCTION TextWidth (textBuf: Ptr; firstByte,byteCount: INTEGER) : INTEGER;
TextWidth returns the width of the text stored in the arbitrary structure in memory specified by textBuf, starting firstByte bytes into the structure and continuing for byteCount bytes (firstByte starts at 0). TextWidth calculates the width by adding the CharWidths of all the characters in the text. (See CharWidth, above.)
PROCEDURE MeasureText (count: INTEGER; textAddr,charLocs: Ptr);
This procedure is designed to improve performance in specialized applications such as word processors by providing an array version of the TextWidth function; it’s like calling TextWidth repeatedly for a given set of characters. TextAddr points to an arbitrary piece of text in memory, and count specifies how many characters are to be measured.
MeasureText moves along the string and, for each character, computes the distance from TextAddr to the right edge of the character. CharLocs should point to an array of count + 1 integers. Upon return, the first element in the array will always contain 0; the other elements will contain pixel positions on the screen for all of the specified characters.
Note: MeasureText only works with text displayed on the screen; since it
doesn’t go through the QuickDraw procedure StdText, it can’t be used
to measure text to be printed.
PROCEDURE GetFontInfo (VAR info: FontInfo);
GetFontInfo returns the following information about the current grafPort’s character font, taking into consideration the style and size in which the characters will be drawn: the ascent, descent, maximum character width (the greatest distance the pen will move when a character is drawn), and leading
(the vertical distance between the descent line and the ascent line below it), all in pixels. The FontInfo data type is defined as follows:
TYPE FontInfo = RECORD
ascent: INTEGER; {ascent}
descent: INTEGER; {descent}
widMax: INTEGER; {maximum character width}
leading: INTEGER {leading}
END;
The line height (in pixels) can be determined by adding the ascent, descent, and leading.
_______________________________________________________________________________
»Drawing in Color
These routines enable applications to do color drawing on color output devices. All nonwhite colors will appear as black on black-and-white output devices.
PROCEDURE ForeColor (color: LONGINT);
ForeColor sets the foreground color for all drawing in the current grafPort
(thePort^.fgColor) to the given color. The following standard colors are predefined: blackColor, whiteColor, redColor, greenColor, blueColor, cyanColor, magentaColor, and yellowColor. The initial foreground color is blackColor.
PROCEDURE BackColor (color: LONGINT);
BackColor sets the background color for all drawing in the current grafPort
(thePort^.bkColor) to the given color. Eight standard colors are predefined
(see ForeColor above). The initial background color is whiteColor.
PROCEDURE ColorBit (whichBit: INTEGER);
ColorBit is called by printing software for a color printer, or other color-imaging software, to set the current grafPort’s colrBit field to whichBit; this tells QuickDraw which plane of the color picture to draw into. QuickDraw will draw into the plane corresponding to bit number whichBit. Since QuickDraw can support output devices that have up to 32 bits of color information per pixel, the possible range of values for whichBit is 0 through 31. The initial value of the colrBit field is 0.
_______________________________________________________________________________
»Calculations with Rectangles
Calculation routines are independent of the current coordinate system; a calculation will operate the same regardless of which grafPort is active.
Remember that if the parameters to a calculation procedure were defined in different grafPorts, you must first adjust them to global coordinates.
PROCEDURE SetRect (VAR r: Rect; left,top,right,bottom: INTEGER);
SetRect assigns the four boundary coordinates to the given rectangle. The result is a rectangle with coordinates (left,top) (right,bottom).
This procedure is supplied as a utility to help you shorten your program text. If you want a more readable text at the expense of length, you can assign integers (or points) directly into the rectangle’s fields. There’s no significant code size or execution speed advantage to either method.
PROCEDURE OffsetRect (VAR r: Rect; dh,dv: INTEGER);
OffsetRect moves the given rectangle by adding dh to each horizontal coordinate and dv to each vertical coordinate. If dh and dv are positive, the movement is to the right and down; if either is negative, the corresponding movement is in the opposite direction. The rectangle retains its shape and size; it’s merely moved on the coordinate plane. This doesn’t affect the screen unless you subsequently call a routine to draw within the rectangle.
PROCEDURE InsetRect (VAR r: Rect; dh,dv: INTEGER);
InsetRect shrinks or expands the given rectangle. The left and right sides are moved in by the amount specified by dh; the top and bottom are moved toward the center by the amount specified by dv. If dh or dv is negative, the appropriate pair of sides is moved outward instead of inward. The effect is to alter the size by 2*dh horizontally and 2*dv vertically, with the rectangle remaining centered in the same place on the coordinate plane.
If the resulting width or height becomes less than 1, the rectangle is set to the empty rectangle (0,0)(0,0).
FUNCTION SectRect (src1,src2: Rect; VAR dstRect: Rect) : BOOLEAN;
SectRect calculates the rectangle that’s the intersection of the two given rectangles, and returns TRUE if they indeed intersect or FALSE if they don’t. Rectangles that “touch” at a line or a point are not considered intersecting, because their intersection rectangle (actually, in this case, an intersection line or point) doesn’t enclose any bits in the bit image.
If the rectangles don’t intersect, the destination rectangle is set to (0,0)
(0,0). SectRect works correctly even if one of the source rectangles is also the destination.
PROCEDURE UnionRect (src1,src2: Rect; VAR dstRect: Rect);
UnionRect calculates the smallest rectangle that encloses both of the given rectangles. It works correctly even if one of the source rectangles is also the destination.
FUNCTION PtInRect (pt: Point; r: Rect) : BOOLEAN;
PtInRect determines whether the pixel below and to the right of the given coordinate point is enclosed in the specified rectangle, and returns TRUE if so or FALSE if not.
PROCEDURE Pt2Rect (pt1,pt2: Point; VAR dstRect: Rect);
Pt2Rect returns the smallest rectangle that encloses the two given points.
PROCEDURE PtToAngle (r: Rect; pt: Point; VAR angle: INTEGER);
PtToAngle calculates an integer angle between a line from the center of the rectangle to the given point and a line from the center of the rectangle pointing straight up (12 o’clock high). The angle is in degrees from 0 to 359, measured clockwise from 12 o’clock, with 90 degrees at 3 o’clock, 180 at
6 o’clock, and 270 at 9 o’clock. Other angles are measured relative to the rectangle: If the line to the given point goes through the top right corner of the rectangle, the angle returned is 45 degrees, even if the rectangle isn’t square; if it goes through the bottom right corner, the angle is 135 degrees, and so on (see Figure 20).
Figure 20–PtToAngle
The angle returned might be used as input to one of the procedures that manipulate arcs and wedges, as described below under “Graphic Operations on Arcs and Wedges”.
FUNCTION EqualRect (rect1,rect2: Rect) : BOOLEAN;
EqualRect compares the two given rectangles and returns TRUE if they’re equal or FALSE if not. The two rectangles must have identical boundary coordinates to be considered equal.
FUNCTION EmptyRect (r: Rect) : BOOLEAN;
EmptyRect returns TRUE if the given rectangle is an empty rectangle or FALSE if not. A rectangle is considered empty if the bottom coordinate is less than or equal to the top or the right coordinate is less than or equal to the left.
_______________________________________________________________________________
»Graphic Operations on Rectangles
See also the ScrollRect procedure under “Bit Transfer Operations”.
PROCEDURE FrameRect (r: Rect);
FrameRect draws an outline just inside the specified rectangle, using the current grafPort’s pen pattern, mode, and size. The outline is as wide as the pen width and as tall as the pen height. It’s drawn with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
If a region is open and being formed, the outside outline of the new rectangle is mathematically added to the region’s boundary.
PROCEDURE PaintRect (r: Rect);
PaintRect paints the specified rectangle with the current grafPort’s pen pattern and mode. The rectangle is filled with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
PROCEDURE EraseRect (r: Rect);
EraseRect paints the specified rectangle with the current grafPort’s background pattern bkPat (in patCopy mode). The grafPort’s pnPat and pnMode are ignored; the pen location is not changed.
PROCEDURE InvertRect (r: Rect);
Assembly-language note: The macro you invoke to call InvertRect from
assembly language is named _InverRect.
InvertRect inverts the pixels enclosed by the specified rectangle: Every white pixel becomes black and every black pixel becomes white. The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
PROCEDURE FillRect (r: Rect; pat: Pattern);
FillRect fills the specified rectangle with the given pattern (in patCopy mode). The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
_______________________________________________________________________________
»Graphic Operations on Ovals
Ovals are drawn inside rectangles that you specify. If you specify a square rectangle, QuickDraw draws a circle.
PROCEDURE FrameOval (r: Rect);
FrameOval draws an outline just inside the oval that fits inside the specified rectangle, using the current grafPort’s pen pattern, mode, and size. The outline is as wide as the pen width and as tall as the pen height. It’s drawn with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
If a region is open and being formed, the outside outline of the new oval is mathematically added to the region’s boundary.
PROCEDURE PaintOval (r: Rect);
PaintOval paints an oval just inside the specified rectangle with the current grafPort’s pen pattern and mode. The oval is filled with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
PROCEDURE EraseOval (r: Rect);
EraseOval paints an oval just inside the specified rectangle with the current grafPort’s background pattern bkPat (in patCopy mode). The grafPort’s pnPat and pnMode are ignored; the pen location is not changed.
PROCEDURE InvertOval (r: Rect);
InvertOval inverts the pixels enclosed by an oval just inside the specified rectangle: Every white pixel becomes black and every black pixel becomes white. The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
PROCEDURE FillOval (r: Rect; pat: Pattern);
FillOval fills an oval just inside the specified rectangle with the given pattern (in patCopy mode). The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
_______________________________________________________________________________
»Graphic Operations on Rounded-Corner Rectangles
PROCEDURE FrameRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER);
FrameRoundRect draws an outline just inside the specified rounded-corner rectangle, using the current grafPort’s pen pattern, mode, and size. OvalWidth and ovalHeight specify the diameters of curvature for the corners (see Figure 21). The outline is as wide as the pen width and as tall as the pen height.
It’s drawn with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
Figure 21–Rounded-Corner Rectangle
If a region is open and being formed, the outside outline of the new rounded-corner rectangle is mathematically added to the region’s boundary.
PROCEDURE PaintRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER);
PaintRoundRect paints the specified rounded-corner rectangle with the current grafPort’s pen pattern and mode. OvalWidth and ovalHeight specify the diameters of curvature for the corners.
The rounded-corner rectangle is filled with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
PROCEDURE EraseRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER);
EraseRoundRect paints the specified rounded-corner rectangle with the current grafPort’s background pattern bkPat (in patCopy mode).
OvalWidth and ovalHeight specify the diameters of curvature for the corners. The grafPort’s pnPat and pnMode are ignored; the pen location is not changed.
PROCEDURE InvertRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER);
Assembly-language note: The macro you invoke to call InvertRoundRect from
assembly language is named _InverRoundRect.
InvertRoundRect inverts the pixels enclosed by the specified rounded-corner rectangle: Every white pixel becomes black and every black pixel becomes white. OvalWidth and ovalHeight specify the diameters of curvature for the corners. The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
PROCEDURE FillRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER;
pat: Pattern);
FillRoundRect fills the specified rounded-corner rectangle with the given pattern (in patCopy mode). OvalWidth and ovalHeight specify the diameters of curvature for the corners. The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
_______________________________________________________________________________
»Graphic Operations on Arcs and Wedges
These procedures perform graphic operations on arcs and wedge-shaped sections of ovals. See also PtToAngle under “Calculations with Rectangles”.
PROCEDURE FrameArc (r: Rect; startAngle,arcAngle: INTEGER);
FrameArc draws an arc of the oval that fits inside the specified rectangle, using the current grafPort’s pen pattern, mode, and size. StartAngle indicates where the arc begins and is treated MOD 360. ArcAngle defines the extent of the arc. The angles are given in positive or negative degrees; a positive angle goes clockwise, while a negative angle goes counterclockwise. Zero degrees is at 12 o’clock high, 90 (or –270) is at 3 o’clock, 180 (or –180) is at 6 o’clock, and 270 (or –90) is at 9 o’clock. Other angles are measured relative to the enclosing rectangle: A line from the center of the rectangle through its top right corner is at 45 degrees, even if the rectangle isn’t square; a line through the bottom right corner is at 135 degrees, and so on (see Figure 22).
Figure 22–Operations on Arcs and Wedges
The arc is as wide as the pen width and as tall as the pen height. It’s drawn with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
Warning: FrameArc differs from other QuickDraw routines that frame shapes
in that the arc is not mathematically added to the boundary of a
region that’s open and being formed.
Note: QuickDraw doesn’t provide a routine for drawing an outlined wedge
of an oval.
PROCEDURE PaintArc (r: Rect; startAngle,arcAngle: INTEGER);
PaintArc paints a wedge of the oval just inside the specified rectangle with the current grafPort’s pen pattern and mode. StartAngle and arcAngle define the arc of the wedge as in FrameArc. The wedge is filled with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
PROCEDURE EraseArc (r: Rect; startAngle,arcAngle: INTEGER);
EraseArc paints a wedge of the oval just inside the specified rectangle with the current grafPort’s background pattern bkPat (in patCopy mode). StartAngle and arcAngle define the arc of the wedge as in FrameArc. The grafPort’s pnPat and pnMode are ignored; the pen location is not changed.
PROCEDURE InvertArc (r: Rect; startAngle,arcAngle: INTEGER);
InvertArc inverts the pixels enclosed by a wedge of the oval just inside the specified rectangle: Every white pixel becomes black and every black pixel becomes white. StartAngle and arcAngle define the arc of the wedge as in FrameArc. The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
PROCEDURE FillArc (r: Rect; startAngle,arcAngle: INTEGER; pat: Pattern);
FillArc fills a wedge of the oval just inside the specified rectangle with the given pattern (in patCopy mode). StartAngle and arcAngle define the arc of the wedge as in FrameArc. The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
_______________________________________________________________________________
»Calculations with Regions
Remember that if the parameters to a calculation procedure were defined in different grafPorts, you must first adjust them to global coordinates.
FUNCTION NewRgn : RgnHandle;
NewRgn allocates space for a new, variable-size region, initializes it to the empty region defined by the rectangle (0,0)(0,0), and returns a handle to the new region.
Warning: Only this function creates new regions; all other routines just
alter the size and shape of existing regions. Before a region’s
handle can be passed to any drawing or calculation routine, space
must already have been allocated for the region.
PROCEDURE OpenRgn;
OpenRgn tells QuickDraw to allocate temporary space and start saving lines and framed shapes for later processing as a region definition. While a region is open, all calls to Line, LineTo, and the procedures that draw framed shapes
(except arcs) affect the outline of the region. Only the line endpoints and shape boundaries affect the region definition; the pen mode, pattern, and size do not affect it. In fact, OpenRgn calls HidePen, so no drawing occurs on the screen while the region is open (unless you called ShowPen just after OpenRgn, or you called ShowPen previously without balancing it by a call to HidePen). Since the pen hangs below and to the right of the pen location, drawing lines with even the smallest pen will change bits that lie outside the region you define.
The outline of a region is mathematically defined and infinitely thin, and separates the bit image into two groups of bits: Those within the region and those outside it. A region should consist of one or more closed loops. Each framed shape itself constitutes a loop. Any lines drawn with Line or LineTo should connect with each other or with a framed shape. Even though the on-screen presentation of a region is clipped, the definition of a region is not; you can define a region anywhere on the coordinate plane with complete disregard for the location of various grafPort entities on that plane.
When a region is open, the current grafPort’s rgnSave field contains a handle to information related to the region definition. If you want to temporarily disable the collection of lines and shapes, you can save the current value of this field, set the field to NIL, and later restore the saved value to resume the region definition. Also, calling SetPort while a region is being formed will discontinue formation of the region until another call to SetPort resets the region’s original grafPort.
Warning: Do not call OpenRgn while another region or polygon is already
open. All open regions but the most recent will behave strangely.
Note: Regions are limited to 32K bytes.
PROCEDURE CloseRgn (dstRgn: RgnHandle);
CloseRgn stops the collection of lines and framed shapes, organizes them into a region definition, and saves the resulting region in the region indicated by dstRgn. CloseRgn does not create the destination region; space must already have been allocated for it. You should perform one and only one CloseRgn for every OpenRgn. CloseRgn calls ShowPen, balancing the HidePen call made by OpenRgn.
Here’s an example of how to create and open a region, define a barbell shape, close the region, draw it, and dispose of it:
barbell := NewRgn; {create a new region}
OpenRgn; {begin collecting stuff}
SetRect(tempRect,20,20,30,50); {form the left weight}
FrameOval(tempRect);
SetRect(tempRect,25,30,85,40); {form the bar}
FrameRect(tempRect);
SetRect(tempRect,80,20,90,50); {form the right weight}
FrameOval(tempRect);
CloseRgn(barbell); {we're done; save in barbell}
FillRgn(barbell,black); {draw it on the screen}
DisposeRgn(barbell) {dispose of the region}
PROCEDURE DisposeRgn (rgn: RgnHandle);
Assembly-language note: The macro you invoke to call DisposeRgn from
assembly language is named _DisposRgn.
DisposeRgn releases the memory occupied by the given region. Use this only after you’re completely through with a temporary region.
PROCEDURE CopyRgn (srcRgn,dstRgn: RgnHandle);
CopyRgn copies the mathematical structure of srcRgn into dstRgn; that is, it makes a duplicate copy of srcRgn. Once this is done, srcRgn may be altered (or even disposed of) without affecting dstRgn. CopyRgn does not create the destination region; space must already have been allocated for it.
PROCEDURE SetEmptyRgn (rgn: RgnHandle);
SetEmptyRgn destroys the previous structure of the given region, then sets the new structure to the empty region defined by the rectangle (0,0)(0,0).
PROCEDURE SetRectRgn (rgn: RgnHandle; left,top,right,bottom: INTEGER);
Assembly-language note: The macro you invoke to call SetRectRgn from
assembly language is named _SetRecRgn.
SetRectRgn destroys the previous structure of the given region, and then sets the new structure to the rectangle specified by left, top, right, and bottom.
If the specified rectangle is empty (that is, right<=left or bottom<=top), the region is set to the empty region defined by the rectangle (0,0)(0,0).
PROCEDURE RectRgn (rgn: RgnHandle; r: Rect);
RectRgn destroys the previous structure of the given region, and then sets the new structure to the rectangle specified by r. This is the same as SetRectRgn, except the given rectangle is defined by a rectangle rather than by four boundary coordinates.
PROCEDURE OffsetRgn (rgn: RgnHandle; dh,dv: INTEGER);
Assembly-language note: The macro you invoke to call OffsetRgn from
assembly language is named _OfsetRgn.
OffsetRgn moves the region on the coordinate plane, a distance of dh horizontally and dv vertically. This doesn’t affect the screen unless you subsequently call a routine to draw the region. If dh and dv are positive, the movement is to the right and down; if either is negative, the corresponding movement is in the opposite direction. The region retains its size and shape.
Note: OffsetRgn is an especially efficient operation, because most of
the data defining a region is stored relative to rgnBBox and so
isn’t actually changed by OffsetRgn.
PROCEDURE InsetRgn (rgn: RgnHandle; dh,dv: INTEGER);
InsetRgn shrinks or expands the region. All points on the region boundary are moved inwards a distance of dv vertically and dh horizontally; if dh or dv is negative, the points are moved outwards in that direction. InsetRgn leaves the region “centered” at the same position, but moves the outline in (for positive values of dh and dv) or out (for negative values of dh and dv). InsetRgn of a rectangular region works just like InsetRect.
Note: InsetRgn temporarily uses heap space that’s twice the size of
the original region.
PROCEDURE SectRgn (srcRgnA,srcRgnB,dstRgn: RgnHandle);
SectRgn calculates the intersection of two regions and places the intersection in a third region. This does not create the destination region; space must already have been allocated for it. The destination region can be one of the source regions, if desired.
If the regions do not intersect, or one of the regions is empty, the destination is set to the empty region defined by the rectangle (0,0)(0,0).
Note: SectRgn may temporarily use heap space that’s twice the size of
the two input regions.
PROCEDURE UnionRgn (srcRgnA,srcRgnB,dstRgn: RgnHandle);
UnionRgn calculates the union of two regions and places the union in a third region. This does not create the destination region; space must already have been allocated for it. The destination region can be one of the source regions, if desired.
If both regions are empty, the destination is set to the empty region defined by the rectangle (0,0)(0,0).
Note: UnionRgn may temporarily use heap space that’s twice the size of
the two input regions.
PROCEDURE DiffRgn (srcRgnA,srcRgnB,dstRgn: RgnHandle);
DiffRgn subtracts srcRgnB from srcRgnA and places the difference in a third region. This does not create the destination region; space must already have been allocated for it. The destination region can be one of the source regions, if desired.
If the first source region is empty, the destination is set to the empty region defined by the rectangle (0,0)(0,0).
Note: DiffRgn may temporarily use heap space that’s twice the size of
the two input regions.
PROCEDURE XorRgn (srcRgnA,srcRgnB,dstRgn: RgnHandle);
XorRgn calculates the difference between the union and the intersection of srcRgnA and srcRgnB and places the result in dstRgn. This does not create the destination region; space must already have been allocated for it. The destination region can be one of the source regions, if desired.
If the regions are coincident, the destination is set to the empty region defined by the rectangle (0,0)(0,0).
Note: XorRgn may temporarily use heap space that’s twice the size of
the two input regions.
FUNCTION PtInRgn (pt: Point; rgn: RgnHandle) : BOOLEAN;
PtInRgn checks whether the pixel below and to the right of the given coordinate point is within the specified region, and returns TRUE if so or FALSE if not.
FUNCTION RectInRgn (r: Rect; rgn: RgnHandle) : BOOLEAN;
RectInRgn checks whether the given rectangle intersects the specified region, and returns TRUE if the intersection encloses at least one bit or FALSE if not.
Note: RectInRgn will sometimes return TRUE when the rectangle merely
intersects the region’s enclosing rectangle. If you need to know
exactly whether a given rectangle intersects the actual region,
you can use RectRgn to set the rectangle to a region, and call
SectRgn to see whether the two regions intersect: If the result
of SectRgn is an empty region, then the rectangle doesn’t intersect
the region.
FUNCTION EqualRgn (rgnA,rgnB: RgnHandle) : BOOLEAN;
EqualRgn compares the two given regions and returns TRUE if they’re equal or FALSE if not. The two regions must have identical sizes, shapes, and locations to be considered equal. Any two empty regions are always equal.
FUNCTION EmptyRgn (rgn: RgnHandle) : BOOLEAN;
EmptyRgn returns TRUE if the region is an empty region or FALSE if not. Some of the circumstances in which an empty region can be created are: a NewRgn call; a CopyRgn of an empty region; a SetRectRgn or RectRgn with an empty rectangle as an argument; CloseRgn without a previous OpenRgn or with no drawing after an OpenRgn; OffsetRgn of an empty region; InsetRgn with an empty region or too large an inset; SectRgn of nonintersecting regions; UnionRgn of two empty regions; and DiffRgn or XorRgn of two identical or nonintersecting regions.
_______________________________________________________________________________
»Graphic Operations on Regions
These routines all depend on the coordinate system of the current grafPort. If a region is drawn in a different grafPort than the one in which it was defined, it may not appear in the proper position in the port.
PROCEDURE FrameRgn (rgn: RgnHandle);
FrameRgn draws an outline just inside the specified region, using the current grafPort’s pen pattern, mode, and size. The outline is as wide as the pen width and as tall as the pen height. It’s drawn with the pnPat, according to the pattern transfer mode specified by pnMode. The outline will never go outside the region boundary. The pen location is not changed by this procedure.
If a region is open and being formed, the outside outline of the region being framed is mathematically added to that region’s boundary.
Note: FrameRgn actually does a CopyRgn, an InsetRgn, and a DiffRgn;
it may temporarily use heap space that’s three times the size
of the original region.
PROCEDURE PaintRgn (rgn: RgnHandle);
PaintRgn paints the specified region with the current grafPort’s pen pattern and pen mode. The region is filled with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
PROCEDURE EraseRgn (rgn: RgnHandle);
EraseRgn paints the specified region with the current grafPort’s background pattern bkPat (in patCopy mode). The grafPort’s pnPat and pnMode are ignored; the pen location is not changed.
PROCEDURE InvertRgn (rgn: RgnHandle);
Assembly-language note: The macro you invoke to call InvertRgn from
assembly language is named _InverRgn.
InvertRgn inverts the pixels enclosed by the specified region: Every white pixel becomes black and every black pixel becomes white. The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
PROCEDURE FillRgn (rgn: RgnHandle; pat: Pattern);
FillRgn fills the specified region with the given pattern (in patCopy mode). The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
_______________________________________________________________________________
»Bit Map Operations
PROCEDURE ScrollRect (r: Rect; dh,dv: INTEGER; updateRgn: RgnHandle);
ScrollRect shifts (“scrolls”) the bits that are inside the intersection of the specified rectangle and the visRgn, clipRgn, portRect, and portBits.bounds of the current grafPort. No other bits are affected. The bits are shifted a distance of dh horizontally and dv vertically. The positive directions are
to the right and down. Bits that are shifted out of the scroll area are
lost—they’re neither placed outside the area nor saved. The space created by the scroll is filled with the grafPort’s background pattern (thePort^.bkPat), and the updateRgn is changed to this filled area (see Figure 23).
Figure 23–Scrolling
ScrollRect doesn’t change the coordinate system of the grafPort, it simply moves the entire document to different coordinates. Notice that ScrollRect doesn’t move the pen and the clipRgn. However, since the document has moved, they’re in a different position relative to the document.
To restore the coordinates of the document to what they were before the ScrollRect, you can use the SetOrigin procedure. In Figure 23, suppose that before the ScrollRect the top left corner of the document was at coordinates
(100,100). After ScrollRect(r,10,20...), the coordinates of the document are offset by the specified values. You could call SetOrigin(90,80) to offset the coordinate system to compensate for the scroll (see Figure 14 in the
“Coordinates in GrafPorts” section for an illustration). The document itself doesn’t move as a result of SetOrigin, but the pen and clipRgn move down and to the right, and are restored to their original position relative to the document. Notice that updateRgn will still need to be redrawn.
PROCEDURE CopyBits (srcBits,dstBits: BitMap; srcRect,dstRect: Rect;
mode: INTEGER; maskRgn: RgnHandle);
CopyBits transfers a bit image between any two bit maps and clips the result to the area specified by the maskRgn parameter. The transfer may be performed in any of the eight source transfer modes. The result is always clipped to the maskRgn and the boundary rectangle of the destination bit map; if the destination bit map is the current grafPort’s portBits, it’s also clipped to the intersection of the grafPort’s clipRgn and visRgn. If you don’t want to clip to a maskRgn, just pass NIL for the maskRgn parameter. The dstRect and maskRgn coordinates are in terms of the dstBits.bounds coordinate system, and the srcRect coordinates are in terms of the srcBits.bounds coordinates.
Warning: If you perform a CopyBits between two grafPorts that overlap,
you must first convert to global coordinates, and then specify
screenBits for both srcBits and dstBits.
The bits enclosed by the source rectangle are transferred into the destination rectangle according to the rules of the chosen mode. The source transfer modes are as follows:
srcCopy notSrcCopy
srcOr notSrcXor
srcXor notSrcOr
srcBic notSrcBic
The source rectangle is completely aligned with the destination rectangle; if the rectangles are of different sizes, the bit image is expanded or shrunk as necessary to fit the destination rectangle. For example, if the bit image is a circle in a square source rectangle, and the destination rectangle is not square, the bit image appears as an oval in the destination (see Figure 24).
Figure 24–Operation of CopyBits
PROCEDURE SeedFill (srcPtr,dstPtr: Ptr;
srcRow,dstRow,height,words,seedH,seedV: INTEGER);
Given a source bit image, SeedFill computes a destination bit image with 1’s only in the pixels where paint can leak from the starting seed point, like the MacPaint paint-bucket tool. SeedH and seedV specify horizontal and vertical offsets, in pixels, from the beginning of the data pointed to by dstPtr, determining how far into the destination bit image filling should begin. Calls to SeedFill are not clipped to the current port and are not stored into QuickDraw pictures.
PROCEDURE CalcMask (srcPtr,dstPtr: Ptr; srcRow,dstRow,height, words: INTEGER);
Given a source bit image, CalcMask computes a destination bit image with 1’s only in the pixels where paint could not leak from any of the outer edges, like the MacPaint lasso tool. Calls to CalcMask are not clipped to the current port and are not stored into QuickDraw pictures.
PROCEDURE CopyMask (srcBits,maskBits,dstBits: BitMap;
srcRect, maskRect,dstRect: Rect);
CopyMask is a new version of the CopyBits procedure; it transfers a bit image from the source bitmap to the destination bitmap only where the corresponding bit of the mask rectangle is a 1. (Note that the mask is specified as a rectangle instead of as a handle to a region.) It can be used along with CalcMask to implement the lasso copy as in MacPaint; it’s also useful for drawing icons. CopyMask doesn’t check for overlap between the source and destination bitmaps, doesn’t stretch the bit image, and doesn’t store into QuickDraw pictures. CopyMask does, however, respect the current port’s visRgn and clipRgn if dstBits is the portBits of the current grafPort.
_______________________________________________________________________________
»Pictures
FUNCTION OpenPicture (picFrame: Rect) : PicHandle;
OpenPicture returns a handle to a new picture that has the given rectangle as its picture frame, and tells QuickDraw to start saving as the picture definition all calls to drawing routines and all picture comments (if any).
OpenPicture calls HidePen, so no drawing occurs on the screen while the picture is open (unless you call ShowPen just after OpenPicture, or you called ShowPen previously without balancing it by a call to HidePen).
When a picture is open, the current grafPort’s picSave field contains a handle to information related to the picture definition. If you want to temporarily disable the collection of routine calls and picture comments, you can save the current value of this field, set the field to NIL, and later restore the saved value to resume the picture definition.
Warning: Do not call OpenPicture while another picture is already open.
Warning: A grafPort’s clipRgn is initialized to an arbitrarily large
region. You should always change the clipRgn to a smaller
region before calling OpenPicture, or no drawing may occur
when you call DrawPicture.
PROCEDURE ClosePicture;
ClosePicture tells QuickDraw to stop saving routine calls and picture comments as the definition of the currently open picture. You should perform one and only one ClosePicture for every OpenPicture. ClosePicture calls ShowPen, balancing the HidePen call made by OpenPicture.
PROCEDURE PicComment (kind,dataSize: INTEGER; dataHandle: Handle);
PicComment inserts the specified comment into the definition of the currently open picture. The kind parameter identifies the type of comment. DataHandle is a handle to additional data if desired, and dataSize is the size of that data in bytes. If there’s no additional data for the comment, dataHandle should be NIL and dataSize should be 0. An application that processes the comments must include a procedure to do the processing and store a pointer to it in the data structure pointed to by the grafProcs field of the grafPort (see “Customizing QuickDraw Operations”).
Note: The standard low-level procedure for processing picture comments
simply ignores all comments.
PROCEDURE DrawPicture (myPicture: PicHandle; dstRect: Rect);
DrawPicture takes the part of the given picture that’s inside the picture frame and draws it in dstRect, expanding or shrinking it as necessary to align the borders of the picture frame with dstRect. DrawPicture passes any picture comments to a low-level procedure accessed indirectly through the grafProcs field of the grafPort (see PicComment above).
Warning: If you call DrawPicture with the initial, arbitrarily large
clipRgn and the destination rectangle is offset from the
picture frame, you may end up with an empty clipRgn, and no
drawing will take place.
PROCEDURE KillPicture (myPicture: PicHandle);
KillPicture releases the memory occupied by the given picture. Use this only when you’re completely through with a picture (unless the picture is a resource, in which case use the Resource Manager procedure ReleaseResource).
_______________________________________________________________________________
»Calculations with Polygons
FUNCTION OpenPoly : PolyHandle;
OpenPoly returns a handle to a new polygon and tells QuickDraw to start saving the polygon definition as specified by calls to line-drawing routines. While a polygon is open, all calls to Line and LineTo affect the outline of the polygon. Only the line endpoints affect the polygon definition; the pen mode, pattern, and size do not affect it. In fact, OpenPoly calls HidePen, so no drawing occurs on the screen while the polygon is open (unless you call ShowPen just after OpenPoly, or you called ShowPen previously without balancing it by a call to HidePen).
A polygon should consist of a sequence of connected lines. Even though the on-screen presentation of a polygon is clipped, the definition of a polygon is not; you can define a polygon anywhere on the coordinate plane.
When a polygon is open, the current grafPort’s polySave field contains a handle to information related to the polygon definition. If you want to temporarily disable the polygon definition, you can save the current value of this field, set the field to NIL, and later restore the saved value to resume the polygon definition.
Warning: Do not call OpenPoly while a region or another polygon is
already open.
Note: Polygons are limited to 32K bytes; you can determine the polygon
size while it’s being formed by calling the Memory Manager function
GetHandleSize.
PROCEDURE ClosePoly;
Assembly-language note: The macro you invoke to call ClosePoly from
assembly language is named _ClosePgon.
ClosePoly tells QuickDraw to stop saving the definition of the currently open polygon and computes the polyBBox rectangle. You should perform one and only one ClosePoly for every OpenPoly. ClosePoly calls ShowPen, balancing the HidePen call made by OpenPoly.
Here’s an example of how to open a polygon, define it as a triangle, close it, and draw it:
triPoly := OpenPoly; {save handle and begin collecting stuff}
MoveTo(300,100); {move to first point and }
LineTo(400,200); { form }
LineTo(200,200); { the }
LineTo(300,100); { triangle }
ClosePoly; {stop collecting stuff}
FillPoly(triPoly,gray); {draw it on the screen}
KillPoly(triPoly) {we're all done}
PROCEDURE KillPoly (poly: PolyHandle);
KillPoly releases the memory occupied by the given polygon. Use this only when you’re completely through with a polygon.
PROCEDURE OffsetPoly (poly: PolyHandle; dh,dv: INTEGER);
OffsetPoly moves the polygon on the coordinate plane, a distance of dh horizontally and dv vertically. This doesn’t affect the screen unless you subsequently call a routine to draw the polygon. If dh and dv are positive, the movement is to the right and down; if either is negative, the corresponding movement is in the opposite direction. The polygon retains its shape and size.
Note: OffsetPoly is an especially efficient operation, because the data
defining a polygon is stored relative to the first point of the
polygon and so isn’t actually changed by OffsetPoly.
_______________________________________________________________________________
»Graphic Operations on Polygons
Four of the operations described here—PaintPoly, ErasePoly, InvertPoly, and FillPoly—temporarily convert the polygon into a region to perform their operations. The amount of memory required for this temporary region may be far greater than the amount required by the polygon alone. You can estimate the size of this region by scaling down the polygon with MapPoly, converting it into a region, checking the region’s size with the Memory Manager function GetHandleSize, and multiplying that value by the factor by which you scaled down the polygon.
Warning: If any horizontal or vertical line drawn through the polygon
would intersect the polygon’s outline more than 50 times, the
results of these graphic operations are undefined.
PROCEDURE FramePoly (poly: PolyHandle);
FramePoly plays back the line-drawing routine calls that define the given polygon, using the current grafPort’s pen pattern, mode, and size. The pen will hang below and to the right of each point on the boundary of the polygon;
thus, the polygon drawn will extend beyond the right and bottom edges of
poly^^.polyBBox by the pen width and pen height, respectively. All other graphic operations occur strictly within the boundary of the polygon, as for other shapes. You can see this difference in Figure 25, where each of the polygons is shown with its polyBBox.
Figure 25–Drawing Polygons
If a polygon is open and being formed, FramePoly affects the outline of the polygon just as if the line-drawing routines themselves had been called. If a region is open and being formed, the outside outline of the polygon being framed is mathematically added to the region’s boundary.
PROCEDURE PaintPoly (poly: PolyHandle);
PaintPoly paints the specified polygon with the current grafPort’s pen pattern and pen mode. The polygon is filled with the pnPat, according to the pattern transfer mode specified by pnMode. The pen location is not changed by this procedure.
PROCEDURE ErasePoly (poly: PolyHandle);
ErasePoly paints the specified polygon with the current grafPort’s background pattern bkPat (in patCopy mode). The pnPat and pnMode are ignored; the pen location is not changed.
PROCEDURE InvertPoly (poly: PolyHandle);
InvertPoly inverts the pixels enclosed by the specified polygon: Every white pixel becomes black and every black pixel becomes white. The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
PROCEDURE FillPoly (poly: PolyHandle; pat: Pattern);
FillPoly fills the specified polygon with the given pattern (in patCopy mode). The grafPort’s pnPat, pnMode, and bkPat are all ignored; the pen location is not changed.
_______________________________________________________________________________
»Calculations with Points
PROCEDURE AddPt (srcPt: Point; VAR dstPt: Point);
AddPt adds the coordinates of srcPt to the coordinates of dstPt, and returns the result in dstPt.
PROCEDURE SubPt (srcPt: Point; VAR dstPt: Point);
SubPt subtracts the coordinates of srcPt from the coordinates of dstPt, and returns the result in dstPt.
Note: To get the results of coordinate subtraction returned as a function
result, you can use the Toolbox Utility function DeltaPoint.
PROCEDURE SetPt (VAR pt: Point; h,v: INTEGER);
SetPt assigns the two given coordinates to the point pt.
FUNCTION EqualPt (pt1,pt2: Point) : BOOLEAN;
EqualPt compares the two given points and returns TRUE if they’re equal or FALSE if not.
PROCEDURE LocalToGlobal (VAR pt: Point);
LocalToGlobal converts the given point from the current grafPort’s local coordinate system into a global coordinate system with the origin (0,0) at the top left corner of the port’s bit image (such as the screen). This global point can then be compared to other global points, or be changed into the local coordinates of another grafPort.
Since a rectangle is defined by two points, you can convert a rectangle into global coordinates by performing two LocalToGlobal calls. You can also convert a rectangle, region, or polygon into global coordinates by calling OffsetRect, OffsetRgn, or OffsetPoly. For examples, see GlobalToLocal below.
PROCEDURE GlobalToLocal (VAR pt: Point);
GlobalToLocal takes a point expressed in global coordinates (with the top left corner of the bit image as coordinate (0,0)) and converts it into the local coordinates of the current grafPort. The global point can be obtained with the LocalToGlobal call (see above). For example, suppose a game draws a “ball” within a rectangle named ballRect, defined in the grafPort named gamePort (as illustrated in Figure 26). If you want to draw that ball in the grafPort named selectPort, you can calculate the ball’s selectPort coordinates like this:
SetPort(gamePort); {start in origin port}
selectBall := ballRect; {make a copy to be moved}
LocalToGlobal(selectBall.topLeft); {put both corners into }
LocalToGlobal(selectBall.botRight); { global coordinates}
SetPort(selectPort); {switch to destination port}
GlobalToLocal(selectBall.topLeft); {put both corners into }
GlobalToLocal(selectBall.botRight); { these local coordinates}
FillOval(selectBall,ballColor) {draw the ball}
Figure 26–Converting between Coordinate Systems
You can see from Figure 26 that LocalToGlobal and GlobalToLocal simply offset the coordinates of the rectangle by the coordinates of the top left corner of the local grafPort’s portBits.bounds rectangle. You could also do this with OffsetRect. In fact, the way to convert regions and polygons from one coordinate system to another is with OffsetRgn or OffsetPoly rather than LocalToGlobal and GlobalToLocal. For example, if myRgn were a region enclosed by a rectangle having the same coordinates as ballRect in gamePort, you could convert the region to global coordinates with
OffsetRgn(myRgn,-20,-40)
and then convert it to the coordinates of the selectPort grafPort with
OffsetRgn(myRgn,15,-30)
_______________________________________________________________________________
»Miscellaneous Routines
FUNCTION Random : INTEGER;
This function returns a pseudo-random integer, uniformly distributed in the range –32767 through 32767. The value the sequence starts from depends on the global variable randSeed, which InitGraf initializes to 1. To start the sequence over again from where it began, reset randSeed to 1. To start a new sequence each time, you must reset randSeed to a random number.
Note: You can start a new sequence by storing the current date and time
in randSeed; see GetDateTime in the Operating System Utilities chapter.
Assembly-language note: From assembly language, it’s better to start a new
sequence by storing the value of the system global
variable RndSeed in randSeed.
FUNCTION GetPixel (h,v: INTEGER) : BOOLEAN;
GetPixel looks at the pixel associated with the given coordinate point and returns TRUE if it’s black or FALSE if it’s white. The selected pixel is immediately below and to the right of the point whose coordinates are given in h and v, in the local coordinates of the current grafPort. There’s no guarantee that the specified pixel actually belongs to the port, however; it may have been drawn by a port overlapping the current one. To see if the point indeed belongs to the current port, you could call PtInRgn(pt, thePort^.visRgn).
Note: To find out which window’s grafPort a point lies in, you call the
Window Manager function FindWindow, as described in the Window
Manager chapter.
PROCEDURE StuffHex (thingPtr: Ptr; s: Str255);
StuffHex stores bits (expressed as a string of hexadecimal digits) into any data structure. You can easily create a pattern in your program with StuffHex
(though more likely, you’ll store patterns in a resource file). For example,
StuffHex(@stripes,'0102040810204080')
places a striped pattern into the pattern variable named stripes.
Warning: There’s no range checking on the size of the destination variable.
It’s easy to overrun the variable and destroy something if you
don’t know what you’re doing.
PROCEDURE ScalePt (VAR pt: Point; srcRect,dstRect: Rect);
A width and height are passed in pt; the horizontal component of pt is the width, and its vertical component is the height. ScalePt scales these measurements as follows and returns the result in pt: It multiplies the given width by the ratio of dstRect’s width to srcRect’s width, and multiplies the given height by the ratio of dstRect’s height to srcRect’s height.
ScalePt can be used, for example, for scaling the pen dimensions. In Figure 27, where dstRect’s width is twice srcRect’s width and its height is three times srcRect’s height, the pen width is scaled from 3 to 6 and the pen height is scaled from 2 to 6.
Note: The minimum value ScalePt will return is (1,1).
Figure 27–ScalePt and MapPt
PROCEDURE MapPt (VAR pt: Point; srcRect,dstRect: Rect);
Given a point within srcRect, MapPt maps it to a similarly located point within dstRect (that is, to where it would fall if it were part of a drawing being expanded or shrunk to fit dstRect). The result is returned in pt. A corner point of srcRect would be mapped to the corresponding corner point of dstRect, and the center of srcRect to the center of dstRect. In Figure 27, the point
(3,2) in srcRect is mapped to (18,7) in dstRect. SrcRect and dstRect may overlap, and pt need not actually be within srcRect.
Note: Remember, if you’re going to draw inside the destination rectangle,
you’ll probably also want to scale the pen size accordingly with ScalePt.
PROCEDURE MapRect (VAR r: Rect; srcRect,dstRect: Rect);
Given a rectangle within srcRect, MapRect maps it to a similarly located rectangle within dstRect by calling MapPt to map the top left and bottom right corners of the rectangle. The result is returned in r.
PROCEDURE MapRgn (rgn: RgnHandle; srcRect,dstRect: Rect);
Given a region within srcRect, MapRgn maps it to a similarly located region within dstRect by calling MapPt to map all the points in the region.
Note: MapRgn is useful for determining whether a region operation will
exceed available memory: By mapping a large region into a smaller
one and performing the operation (without actually drawing), you
can estimate how much memory will be required by the anticipated
operation.
PROCEDURE MapPoly (poly: PolyHandle; srcRect,dstRect: Rect);
Given a polygon within srcRect, MapPoly maps it to a similarly located polygon within dstRect by calling MapPt to map all the points that define the polygon.
Note: Like MapRgn, MapPoly is useful for determining whether a polygon
operation will succeed.
_______________________________________________________________________________
»Advanced Routine
The function GetMaskTable, accessible only from assembly language, returns in register A0 a pointer to a ROM table containing the following useful masks:
.WORD $0000,$8000,$C000,$E000 ;Table of 16 right masks
.WORD $F000,$F800,$FC00,$FE00
.WORD $FF00,$FF80,$FFC0,$FFE0
.WORD $FFF0,$FFF8,$FFFC,$FFFE
.WORD $FFFF,$7FFF,$3FFF,$1FFF ;Table of 16 left masks
.WORD $0FFF,$07FF,$03FF,$01FF
.WORD $00FF,$007F,$003F,$001F
.WORD $000F,$0007,$0003,$0001
.WORD $8000,$4000,$2000,$1000 ;Table of 16 bit masks
.WORD $0800,$0400,$0200,$0100
.WORD $0080,$0040,$0020,$0010
.WORD $0008,$0004,$0002,$0001
_______________________________________________________________________________
»CUSTOMIZING QUICKDRAW OPERATIONS
_______________________________________________________________________________
For each shape that QuickDraw knows how to draw, there are procedures that perform these basic graphic operations on the shape: frame, paint, erase, invert, and fill. Those procedures in turn call a low-level drawing routine for the shape. For example, the FrameOval, PaintOval, EraseOval, InvertOval, and FillOval procedures all call a low-level routine that draws the oval. For each type of object QuickDraw can draw, including text and lines, there’s a pointer to such a routine. By changing these pointers, you can install your own routines, and either completely override the standard ones or call them after your routines have modified parameters as necessary.
Other low-level routines that you can install in this way are:
• The procedure that does bit transfer and is called by CopyBits.
• The function that measures the width of text and is called by
CharWidth, StringWidth, and TextWidth.
• The procedure that processes picture comments and is called by
DrawPicture. The standard such procedure ignores picture comments.
• The procedure that saves drawing commands as the definition of a
picture, and the one that retrieves them. This enables the application
to draw on remote devices, print to the disk, get picture input from
the disk, and support large pictures.
The grafProcs field of a grafPort determines which low-level routines are called; if it contains NIL, the standard routines are called, so that all operations in that grafPort are done in the standard ways described in this chapter. You can set the grafProcs field to point to a record of pointers to routines. The data type of grafProcs is QDProcsPtr:
TYPE QDProcsPtr = ^QDProcs;
QDProcs = RECORD
textProc: Ptr; {text drawing}
lineProc: Ptr; {line drawing}
rectProc: Ptr; {rectangle drawing}
rRectProc: Ptr; {roundRect drawing}
ovalProc: Ptr; {oval drawing}
arcProc: Ptr; {arc/wedge drawing}
polyProc: Ptr; {polygon drawing}
rgnProc: Ptr; {region drawing}
bitsProc: Ptr; {bit transfer}
commentProc: Ptr; {picture comment processing}
txMeasProc: Ptr; {text width measurement}
getPicProc: Ptr; {picture retrieval}
putPicProc: Ptr {picture saving}
END;
To assist you in setting up a QDProcs record, QuickDraw provides the following procedure:
PROCEDURE SetStdProcs (VAR procs: QDProcs);
This procedure sets all the fields of the given QDProcs record to point to the standard low-level routines. You can then change the ones you wish to point to your own routines. For example, if your procedure that processes picture comments is named MyComments, you’ll store @MyComments in the commentProc field of the QDProcs record.
You can either write your own routines to completely replace the standard ones, or do preprocessing and then call the standard routines. The routines you install must of course have the same calling sequences as the standard routines, which are described below.
Note: These low-level routines should be called only from your
customized routines.
The standard drawing routines tell which graphic operation to perform from a parameter of type GrafVerb:
TYPE GrafVerb = (frame,paint,erase,invert,fill);
When the grafVerb is fill, the pattern to use during filling is passed in the fillPat field of the grafPort.
PROCEDURE StdText (byteCount: INTEGER; textBuf: Ptr; numer,denom: Point);
StdText is the standard low-level routine for drawing text. It draws text from the arbitrary structure in memory specified by textBuf, starting from the first byte and continuing for byteCount bytes. Numer and denom specify the scaling factor: numer.v over denom.v gives the vertical scaling, and numer.h over denom.h gives the horizontal scaling.
PROCEDURE StdLine (newPt: Point);
StdLine is the standard low-level routine for drawing a line. It draws a line from the current pen location to the location specified (in local coordinates) by newPt.
PROCEDURE StdRect (verb: GrafVerb; r: Rect);
StdRect is the standard low-level routine for drawing a rectangle. It draws the given rectangle according to the specified grafVerb.
PROCEDURE StdRRect (verb: GrafVerb; r: Rect; ovalwidth, ovalHeight: INTEGER)
StdRRect is the standard low-level routine for drawing a rounded-corner rectangle. It draws the given rounded-corner rectangle according to the specified grafVerb. OvalWidth and ovalHeight specify the diameters of curvature for the corners.
PROCEDURE StdOval (verb: GrafVerb; r: Rect);
StdOval is the standard low-level routine for drawing an oval. It draws an oval inside the given rectangle according to the specified grafVerb.
PROCEDURE StdArc (verb: GrafVerb; r: Rect; startAngle,arcAngle: INTEGER);
StdArc is the standard low-level routine for drawing an arc or a wedge. It draws an arc or wedge of the oval that fits inside the given rectangle, beginning at startAngle and extending to arcAngle. The grafVerb specifies the graphic operation; if it’s the frame operation, an arc is drawn; otherwise, a wedge is drawn.
PROCEDURE StdPoly (verb: GrafVerb; poly: PolyHandle);
StdPoly is the standard low-level routine for drawing a polygon. It draws the given polygon according to the specified grafVerb.
PROCEDURE StdRgn (verb: GrafVerb; rgn: RgnHandle);
StdRgn is the standard low-level routine for drawing a region. It draws the given region according to the specified grafVerb.
PROCEDURE StdBits (VAR srcBits: BitMap; VAR srcRect,dstRect: Rect;
mode: INTEGER; maskRgn: RgnHandle);
StdBits is the standard low-level routine for doing bit transfer. It transfers a bit image between the given bit map and thePort^.portBits, just as if CopyBits were called with the same parameters and with a destination bit map equal to thePort^.portBits.
PROCEDURE StdComment (kind,dataSize: INTEGER; dataHandle: Handle);
StdComment is the standard low-level routine for processing a picture comment. The kind parameter identifies the type of comment. DataHandle is a handle to additional data, and dataSize is the size of that data in bytes. If there’s no additional data for the comment, dataHandle will be NIL and dataSize will be 0. StdComment simply ignores the comment.
FUNCTION StdTxMeas (byteCount: INTEGER; textAddr: Ptr;
VAR numer, denom: Point; VAR info: FontInfo) : INTEGER;
StdTxMeas is the standard low-level routine for measuring text width. It returns the width of the text stored in the arbitrary structure in memory specified by textAddr, starting with the first byte and continuing for byteCount bytes. Numer and denom specify the scaling as in the StdText procedure; note that StdTxMeas may change them.
PROCEDURE StdGetPic (dataPtr: Ptr; byteCount: INTEGER);
StdGetPic is the standard low-level routine for retrieving information from the definition of a picture. It retrieves the next byteCount bytes from the definition of the currently open picture and stores them in the data structure pointed to by dataPtr.
PROCEDURE StdPutPic (dataPtr: Ptr; byteCount: INTEGER);
StdPutPic is the standard low-level routine for saving information as the definition of a picture. It saves as the definition of the currently open picture the drawing commands stored in the data structure pointed to by dataPtr, starting with the first byte and continuing for the next byteCount bytes.
_______________________________________________________________________________
»SUMMARY OF QUICKDRAW
_______________________________________________________________________________
Constants
CONST
{ Source transfer modes }
srcCopy = 0;
srcOr = 1;
srcXor = 2;
srcBic = 3;
notSrcCopy = 4;
notSrcOr = 5;
notSrcXor = 6;
notSrcBic = 7;
{ Pattern transfer modes }
patCopy = 8;
patOr = 9;
patXor = 10;
patBic = 11;
notPatCopy = 12;
notPatOr = 13;
notPatXor = 14;
notPatBic = 15;
{ Standard colors for ForeColor and BackColor }
blackColor = 33;
whiteColor = 30;
redColor = 209;
greenColor = 329;
blueColor = 389;
cyanColor = 269;
magentaColor = 149;
yellowColor = 89;
{ Standard picture comments }
picLParen = 0;
picRParen = 1;
_______________________________________________________________________________
Data Types
TYPE
StyleItem = (bold,italic,underline,outline,shadow,condense,extend);
Style = SET OF StyleItem;
VHSelect = (v,h);
Point = RECORD CASE INTEGER OF
0: (v: INTEGER; {vertical coordinate}
h: INTEGER); {horizontal coordinate}
1: (vh: ARRAY[VHSelect] OF INTEGER)
END;
Rect = RECORD CASE INTEGER OF
0: (top: INTEGER;
left: INTEGER;
bottom: INTEGER;
right: INTEGER);
1: (topLeft: Point;
botRight: Point)
END;
RgnHandle = ^RgnPtr;
RgnPtr = ^Region;
Region = RECORD
rgnSize: INTEGER; {size in bytes}
rgnBBox: Rect; {enclosing rectangle}
{more data if not rectangular}
END;
BitMap = RECORD
baseAddr: Ptr; {pointer to bit image}
rowBytes: INTEGER; {row width}
bounds: Rect {boundary rectangle}
END;
Pattern = PACKED ARRAY[0..7] OF 0..255;
Bits16 = ARRAY[0..15] OF INTEGER;
Cursor = RECORD
data: Bits16; {cursor image}
mask: Bits16; {cursor mask}
hotSpot: Point {point aligned with mouse}
END;
QDProcsPtr = ^QDProcs;
QDProcs = RECORD
textProc: Ptr; {text drawing}
lineProc: Ptr; {line drawing}
rectProc: Ptr; {rectangle drawing}
rRectProc: Ptr; {roundRect drawing}
ovalProc: Ptr; {oval drawing}
arcProc: Ptr; {arc/wedge drawing}
rgnProc: Ptr; {region drawing}
bitsProc: Ptr; {bit transfer}
commentProc: Ptr; {picture comment processing}
txMeasProc: Ptr; {text width measurement}
getPicProc: Ptr; {picture retrieval}
putPicProc: Ptr {picture saving}
END;
GrafPtr = ^GrafPort;
GrafPort = RECORD
device: INTEGER; {device-specific information}
portBits: BitMap; {grafPort's bit map}
portRect: Rect; {grafPort's rectangle}
visRgn: RgnHandle; {visible region}
clipRgn: RgnHandle; {clipping region}
bkPat: Pattern; {background pattern}
fillPat: Pattern; {fill pattern}
pnLoc: Point; {pen location}
pnSize: Point; {pen size}
pnMode: INTEGER; {pen's transfer mode}
pnPat: Pattern; {pen pattern}
pnVis: INTEGER; {pen visibility}
txFont: INTEGER; {font number for text}
txFace: Style; {text's character style}
txMode: INTEGER; {text's transfer mode}
txSize: INTEGER; {font size for text}
spExtra: Fixed; {extra space}
fgColor: LONGINT; {foreground color}
bkColor: LONGINT; {background color}
colrBit: INTEGER; {color bit}
patStretch: INTEGER; {used internally}
picSave: Handle; {picture being saved}
rgnSave: Handle; {region being saved}
polySave: Handle; {polygon being saved}
grafProcs: QDProcsPtr {low-level drawing routines}
END;
PicHandle = ^PicPtr;
PicPtr = ^Picture;
Picture = RECORD
picSize: INTEGER; {size in bytes}
picFrame: Rect; {picture frame}
{picture definition data}
END;
PolyHandle = ^PolyPtr;
PolyPtr = ^Polygon;
Polygon = RECORD
polySize: INTEGER; {size in bytes}
polyBBox: Rect; {enclosing rectangle}
polyPoints: ARRAY[0..0] OF Point
END;
PenState = RECORD
pnLoc: Point; {pen location}
pnSize: Point; {pen size}
pnMode: INTEGER; {pen's transfer mode}
pnPat: Pattern {pen pattern}
END;
FontInfo = RECORD
ascent: INTEGER; {ascent}
descent: INTEGER; {descent}
widMax: INTEGER; {maximum character width}
leading: INTEGER {leading}
END;
GrafVerb = (frame,paint,erase,invert,fill);
_______________________________________________________________________________
Variables
VAR
thePort: GrafPtr; {pointer to current grafPort}
white: Pattern; {all-white pattern}
black: Pattern; {all-black pattern}
gray: Pattern; {50% gray pattern}
ltGray: Pattern; {25% gray pattern}
dkGray: Pattern; {75% gray pattern}
arrow: Cursor; {standard arrow cursor}
screenBits: BitMap; {the entire screen}
randSeed: LONGINT; {determines where Random sequence begins}
_______________________________________________________________________________
Routines
GrafPort Routines
PROCEDURE InitGraf (globalPtr: Ptr);
PROCEDURE OpenPort (port: GrafPtr);
PROCEDURE InitPort (port: GrafPtr);
PROCEDURE ClosePort (port: GrafPtr);
PROCEDURE SetPort (port: GrafPtr);
PROCEDURE GetPort (VAR port:GrafPtr);
PROCEDURE GrafDevice (device: INTEGER);
PROCEDURE SetPortBits (bm: BitMap);
PROCEDURE PortSize (width,height: INTEGER);
PROCEDURE MovePortTo (leftGlobal,topGlobal: INTEGER);
PROCEDURE SetOrigin (h,v: INTEGER);
PROCEDURE SetClip (rgn: RgnHandle);
PROCEDURE GetClip (rgn: RgnHandle);
PROCEDURE ClipRect (r: Rect);
PROCEDURE BackPat (pat: Pattern);
Cursor Handling
PROCEDURE InitCursor;
PROCEDURE SetCursor (crsr: Cursor);
PROCEDURE HideCursor;
PROCEDURE ShowCursor;
PROCEDURE ObscureCursor;
Pen and Line Drawing
PROCEDURE HidePen;
PROCEDURE ShowPen;
PROCEDURE GetPen (VAR pt: Point);
PROCEDURE GetPenState (VAR pnState: PenState);
PROCEDURE SetPenState (pnState: PenState);
PROCEDURE PenSize (width,height: INTEGER);
PROCEDURE PenMode (mode: INTEGER);
PROCEDURE PenPat (pat: Pattern);
PROCEDURE PenNormal;
PROCEDURE MoveTo (h,v: INTEGER);
PROCEDURE Move (dh,dv: INTEGER);
PROCEDURE LineTo (h,v: INTEGER);
PROCEDURE Line (dh,dv: INTEGER);
Text Drawing
PROCEDURE TextFont (font: INTEGER);
PROCEDURE TextFace (face: Style);
PROCEDURE TextMode (mode: INTEGER);
PROCEDURE TextSize (size: INTEGER);
PROCEDURE SpaceExtra (extra: Fixed);
PROCEDURE DrawChar (ch: CHAR);
PROCEDURE DrawString (s: Str255);
PROCEDURE DrawText (textBuf: Ptr; firstByte,byteCount: INTEGER);
FUNCTION CharWidth (ch: CHAR) : INTEGER;
FUNCTION StringWidth (s: Str255) : INTEGER;
FUNCTION TextWidth (textBuf: Ptr;
firstByte,byteCount: INTEGER) : INTEGER;
PROCEDURE MeasureText (count: INTEGER; textAddr,charLocs: Ptr);
PROCEDURE GetFontInfo (VAR info: FontInfo);
Drawing in Color
PROCEDURE ForeColor (color: LONGINT);
PROCEDURE BackColor (color: LONGINT);
PROCEDURE ColorBit (whichBit: INTEGER);
Calculations with Rectangles
PROCEDURE SetRect (VAR r: Rect; left,top,right,bottom: INTEGER);
PROCEDURE OffsetRect (VAR r: Rect; dh,dv: INTEGER);
PROCEDURE InsetRect (VAR r: Rect; dh,dv: INTEGER);
FUNCTION SectRect (src1,src2: Rect; VAR dstRect: Rect) : BOOLEAN;
PROCEDURE UnionRect (src1,src2: Rect; VAR dstRect: Rect);
FUNCTION PtInRect (pt: Point; r: Rect) : BOOLEAN;
PROCEDURE Pt2Rect (pt1,pt2: Point; VAR dstRect: Rect);
PROCEDURE PtToAngle (r: Rect; pt: Point; VAR angle: INTEGER);
FUNCTION EqualRect (rect1,rect2: Rect) : BOOLEAN;
FUNCTION EmptyRect (r: Rect) : BOOLEAN;
Graphic Operations on Rectangles
PROCEDURE FrameRect (r: Rect);
PROCEDURE PaintRect (r: Rect);
PROCEDURE EraseRect (r: Rect);
PROCEDURE InvertRect (r: Rect);
PROCEDURE FillRect (r: Rect; pat: Pattern);
Graphic Operations on Ovals
PROCEDURE FrameOval (r: Rect);
PROCEDURE PaintOval (r: Rect);
PROCEDURE EraseOval (r: Rect);
PROCEDURE InvertOval (r: Rect);
PROCEDURE FillOval (r: Rect; pat: Pattern);
Graphic Operations on Rounded-Corner Rectangles
PROCEDURE FrameRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER);
PROCEDURE PaintRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER);
PROCEDURE EraseRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER);
PROCEDURE InvertRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER);
PROCEDURE FillRoundRect (r: Rect; ovalWidth,ovalHeight: INTEGER;
pat: Pattern);
Graphic Operations on Arcs and Wedges
PROCEDURE FrameArc (r: Rect; startAngle,arcAngle: INTEGER);
PROCEDURE PaintArc (r: Rect; startAngle,arcAngle: INTEGER);
PROCEDURE EraseArc (r: Rect; startAngle,arcAngle: INTEGER);
PROCEDURE InvertArc (r: Rect; startAngle,arcAngle: INTEGER);
PROCEDURE FillArc (r: Rect; startAngle,arcAngle: INTEGER; pat: Pattern);
Calculations with Regions
FUNCTION NewRgn : RgnHandle;
PROCEDURE OpenRgn;
PROCEDURE CloseRgn (dstRgn: RgnHandle);
PROCEDURE DisposeRgn (rgn: RgnHandle);
PROCEDURE CopyRgn (srcRgn,dstRgn: RgnHandle);
PROCEDURE SetEmptyRgn (rgn: RgnHandle);
PROCEDURE SetRectRgn (rgn: RgnHandle; left,top,right,bottom: INTEGER);
PROCEDURE RectRgn (rgn: RgnHandle; r: Rect);
PROCEDURE OffsetRgn (rgn: RgnHandle; dh,dv: INTEGER);
PROCEDURE InsetRgn (rgn: RgnHandle; dh,dv: INTEGER);
PROCEDURE SectRgn (srcRgnA,srcRgnB,dstRgn: RgnHandle);
PROCEDURE UnionRgn (srcRgnA,srcRgnB,dstRgn: RgnHandle);
PROCEDURE DiffRgn (srcRgnA,srcRgnB,dstRgn: RgnHandle);
PROCEDURE XorRgn (srcRgnA,srcRgnB,dstRgn: RgnHandle);
FUNCTION PtInRgn (pt: Point; rgn: RgnHandle) : BOOLEAN;
FUNCTION RectInRgn (r: Rect; rgn: RgnHandle) : BOOLEAN;
FUNCTION EqualRgn (rgnA,rgnB: RgnHandle) : BOOLEAN;
FUNCTION EmptyRgn (rgn: RgnHandle) : BOOLEAN;
Graphic Operations on Regions
PROCEDURE FrameRgn (rgn: RgnHandle);
PROCEDURE PaintRgn (rgn: RgnHandle);
PROCEDURE EraseRgn (rgn: RgnHandle);
PROCEDURE InvertRgn (rgn: RgnHandle);
PROCEDURE FillRgn (rgn: RgnHandle; pat: Pattern);
Bit Map Operations
PROCEDURE ScrollRect (r: Rect; dh,dv: INTEGER; updateRgn: RgnHandle);
PROCEDURE CopyBits (srcBits,dstBits: BitMap; srcRect,dstRect: Rect;
mode: INTEGER; maskRgn: RgnHandle);
PROCEDURE SeedFill (srcPtr,dstPtr: Ptr;
srcRow,dstRow,height,words, seedH,seedV: INTEGER);
PROCEDURE CalcMask (srcPtr,dstPtr: Ptr;
srcRow,dstRow,height,words: INTEGER);
PROCEDURE CopyMask (srcBits,maskBits,dstBits: BitMap;
srcRect, maskRect,dstRect: ect);
Pictures
FUNCTION OpenPicture (picFrame: Rect) : PicHandle;
PROCEDURE PicComment (kind,dataSize: INTEGER; dataHandle: Handle);
PROCEDURE ClosePicture;
PROCEDURE DrawPicture (myPicture: PicHandle; dstRect: Rect);
PROCEDURE KillPicture (myPicture: PicHandle);
Calculations with Polygons
FUNCTION OpenPoly : PolyHandle;
PROCEDURE ClosePoly;
PROCEDURE KillPoly (poly: PolyHandle);
PROCEDURE OffsetPoly (poly: PolyHandle; dh,dv: INTEGER);
Graphic Operations on Polygons
PROCEDURE FramePoly (poly: PolyHandle);
PROCEDURE PaintPoly (poly: PolyHandle);
PROCEDURE ErasePoly (poly: PolyHandle);
PROCEDURE InvertPoly (poly: PolyHandle);
PROCEDURE FillPoly (poly: PolyHandle; pat: Pattern);
Calculations with Points
PROCEDURE AddPt (srcPt: Point; VAR dstPt: Point);
PROCEDURE SubPt (srcPt: Point; VAR dstPt: Point);
PROCEDURE SetPt (VAR pt: Point; h,v: INTEGER);
FUNCTION EqualPt (pt1,pt2: Point) : BOOLEAN;
PROCEDURE LocalToGlobal (VAR pt: Point);
PROCEDURE GlobalToLocal (VAR pt: Point);
Miscellaneous Routines
FUNCTION Random : INTEGER;
FUNCTION GetPixel (h,v: INTEGER) : BOOLEAN;
PROCEDURE StuffHex (thingPtr: Ptr; s: Str255);
PROCEDURE ScalePt (VAR pt: Point; srcRect,dstRect: Rect);
PROCEDURE MapPt (VAR pt: Point; srcRect,dstRect: Rect);
PROCEDURE MapRect (VAR r: Rect; srcRect,dstRect: Rect);
PROCEDURE MapRgn (rgn: RgnHandle; srcRect,dstRect: Rect);
PROCEDURE MapPoly (poly: PolyHandle; srcRect,dstRect: Rect);
Customizing QuickDraw Operations
PROCEDURE SetStdProcs (VAR procs: QDProcs);
PROCEDURE StdText (byteCount: INTEGER; textBuf: Ptr;
numer,denom: Point);
PROCEDURE StdLine (newPt: Point);
PROCEDURE StdRect (verb: GrafVerb; r: Rect);
PROCEDURE StdRRect (verb: GrafVerb; r: Rect;
ovalwidth,ovalHeight: INTEGER);
PROCEDURE StdOval (verb: GrafVerb; r: Rect);
PROCEDURE StdArc (verb: GrafVerb; r: Rect;
startAngle,arcAngle: INTEGER);
PROCEDURE StdPoly (verb: GrafVerb; poly: PolyHandle);
PROCEDURE StdRgn (verb: GrafVerb; rgn: RgnHandle);
PROCEDURE StdBits (VAR srcBits: BitMap; VAR srcRect,dstRect: Rect;
mode: INTEGER; maskRgn: RgnHandle);
PROCEDURE StdComment (kind,dataSize: INTEGER; dataHandle: Handle);
FUNCTION StdTxMeas (byteCount: INTEGER; textAddr: Ptr;
VAR numer, denom: Point;
VAR info: FontInfo) : INTEGER;
PROCEDURE StdGetPic (dataPtr: Ptr; byteCount: INTEGER);
PROCEDURE StdPutPic (dataPtr: Ptr; byteCount: INTEGER);
_______________________________________________________________________________
Assembly-Language Information
Constants
; Size in bytes of QuickDraw global variables
grafSize .EQU 206
; Source transfer modes
srcCopy .EQU 0
srcOr .EQU 1
srcXor .EQU 2
srcBic .EQU 3
notSrcCopy .EQU 4
notSrcOr .EQU 5
notSrcXor .EQU 6
notSrcBic .EQU 7
; Pattern transfer modes
patCopy .EQU 8
patOr .EQU 9
patXor .EQU 10
patBic .EQU 11
notPatCopy .EQU 12
notPatOr .EQU 13
notPatXor .EQU 14
notPatBic .EQU 15
; Standard colors for ForeColor and BackColor
blackColor .EQU 33
whiteColor .EQU 30
redColor .EQU 205
greenColor .EQU 341
blueColor .EQU 409
cyanColor .EQU 273
magentaColor .EQU 137
yellowColor .EQU 69
; Standard picture comments
picLParen .EQU 0
picRParen .EQU 1
; Character style
boldBit .EQU 0
italicBit .EQU 1
ulineBit .EQU 2
outlineBit .EQU 3
shadowBit .EQU 4
condenseBit .EQU 5
extendBit .EQU 6
; Graphic operations
frame .EQU 0
paint .EQU 1
erase .EQU 2
invert .EQU 3
fill .EQU 4
Point Data Structure
v Vertical coordinate (word)
h Horizontal coordinate (word)
Rectangle Data Structure
top Vertical coordinate of top left corner (word)
left Horizontal coordinate of top left corner (word)
bottom Vertical coordinate of bottom right corner (word)
right Horizontal coordinate of bottom right corner (word)
topLeft Top left corner (point; long)
botRight Bottom right corner (point; long)
Region Data Structure
rgnSize Size in bytes (word)
rgnBBox Enclosing rectangle (8 bytes)
rgnData More data if not rectangular
Bit Map Data Structure
baseAddr Pointer to bit image
rowBytes Row width (word)
bounds Boundary rectangle (8 bytes)
bitMapRec Size in bytes of bit map data structure
Cursor Data Structure
data Cursor image (32 bytes)
mask Cursor mask (32 bytes)
hotSpot Point aligned with mouse (long)
cursRec Size in bytes of cursor data structure
Structure of QDProcs Record
textProc Address of text-drawing routine
lineProc Address of line-drawing routine
rectProc Address of rectangle-drawing routine
rRectProc Address of roundRect-drawing routine
ovalProc Address of oval-drawing routine
arcProc Address of arc/wedge-drawing routine
polyProc Address of polygon-drawing routine
rgnProc Address of region-drawing routine
bitsProc Address of bit-transfer routine
commentProc Address of routine for processing picture comments
txMeasProc Address of routine for measuring text width
getPicProc Address of picture-retrieval routine
putPicProc Address of picture-saving routine
qdProcsRec Size in bytes of QDProcs record
GrafPort Data Structure
device Font-specific information (word)
portBits GrafPort's bit map (bitMapRec bytes)
portBounds Boundary rectangle of grafPort's bit map (8 bytes)
portRect GrafPort's rectangle (8 bytes)
visRgn Handle to visible region
clipRgn Handle to clipping region
bkPat Background pattern (8 bytes)
fillPat Fill pattern (8 bytes)
pnLoc Pen location (point; long)
pnSize Pen size (point; long)
pnMode Pen's transfer mode (word)
pnPat Pen pattern (8 bytes)
pnVis Pen visibility (word)
txFont Font number for text (word)
txFace Text's character style (word)
txMode Text's transfer mode (word)
txSize Font size for text (word)
spExtra Extra space (long)
fgColor Foreground color (long)
bkColor Background color (long)
colrBit Color bit (word)
picSave Picture being saved
rgnSave Region being saved
polySave Polygon being saved
grafProcs Pointer to QDProcs record
Picture Data Structure
picSize Size in bytes (word)
picFrame Picture frame (rectangle; 8 bytes)
picData Picture definition data
Polygon Data Structure
polySize Size in bytes (word)
polyBBox Enclosing rectangle (8 bytes)
polyPoints Polygon points
Pen State Data Structure
psLoc Pen location (point; long)
psSize Pen size (point; long)
psMode Pen's transfer mode (word)
psPat Pen pattern (8 bytes)
psRec Size in bytes of pen state data structure
Font Information Data Structure
ascent Ascent (word)
descent Descent (word)
widMax Maximum character width (word)
leading Leading (word)
Special Macro Names
Pascal name Macro name
SetPortBits _SetPBits
InvertRect _InverRect
InvertRoundRect _InverRoundRect
DisposeRgn _DisposRgn
SetRectRgn _SetRecRgn
OffsetRgn _OfSetRgn
InvertRgn _InverRgn
ClosePoly _ClosePgon
Variables
RndSeed Random number seed (long)
Routine
Trap macro On entry On exit
_GetMaskTable A0: ptr to mask table in ROM
Further Reference:
_______________________________________________________________________________
Font Manager
Color QuickDraw
Technical Note #21, QuickDraw’s Internal Picture Definition
Technical Note #26, Character vs. String Operations in QuickDraw
Technical Note #41, Drawing Into an Offscreen Bitmap
Technical Note #55, Drawing Icons
Technical Note #59, Pictures and Clip Regions
Technical Note #60, Drawing Characters into a Narrow GrafPort
Technical Note #72, Optimizing for the LaserWriter — Techniques
Technical Note #73, Color Printing
Technical Note #86, MacPaint Document Format
Technical Note #91, Optimizing for the LaserWriter—Picture Comments
Technical Note #92, The Appearance of Text
Technical Note #120, Drawing Into an Off-Screen Pixel Map
Technical Note #154, Displaying Large PICT Files
Technical Note #155, Handles and Pointers—Identity Crisis
Technical Note #163, Adding Color With CopyBits
Technical Note #171, _PackBits Data Format
Technical Note #181, Every Picture [Comment] Tells Its Story, Don’t It?
Technical Note #183, Position-Independent PostScript
Technical Note #193, So Many Bitmaps, So Little Time
Technical Note #194, WMgrPortability
Technical Note #198, Font/DA Mover, Styled Fonts, and 'NFNT's
Technical Note #223, Assembly Language Use of _InitGraf with MPW
Technical Note #244, A Leading Cause of Color Cursor Cursing
Technical Note #252, Plotting Small Icons
Technical Note #259, Old Style Colors
32-Bit QuickDraw Documentation
Color QuickDraw
_______________________________________________________________________________
COLOR QUICKDRAW
_______________________________________________________________________________
About This Chapter
Color Representation
RGB Space
Other Color Spaces
Using Color on the Macintosh II
From Color to Pixel
About Color QuickDraw
Drawing Color in a GrafPort
Drawing Color in a CGrafPort
The Color Graphics Port
Pixel Images
Pixel Maps
Pixel Patterns
Relative Patterns
Transfer Modes
Arithmetic Drawing Modes
Replace With Transparency
The Hilite Mode
The Color Cursor
Color Icons
Using Color QuickDraw
Color QuickDraw Routines
Operations on CGrafPorts
Setting the Foreground and Background Colors
Color Drawing Operations
Creating Pixel Maps
Operations on Pixel Maps
Operations on Pixel Patterns
Creating a PixPat
Operations on Color Cursors
Operations on Color Icons
Operations on CGrafPort Fields
Operations on Color Tables
Color QuickDraw Resource Formats
'crsr' (Color Cursor)
'ppat' (Pixel Pattern)
'cicn' (Color Icon)
'clut' (Color Table)
Using Text with QuickDraw
Text Mask Mode
Drawing with Multibit Fonts
Fractional Character Positioning
Color Picture Format
Differences between Version 1 and Version 2 Pictures
Drawing with Version 2 Pictures in Old GrafPorts
Picture Representation
Picture Parsing
Picture Record Structure
Picture Spooling
Spooling a Picture From Disk
Spooling a Picture to a File
Drawing to an Offscreen Pixel Map
New GrafProcs Record
Picture Compatibility
Picture Format
Picture Definition: Version 1
Picture Definition: Version 2
PicComments
Sample PICT File
Color Picture Routines
PICT Opcodes
The New Opcodes: Expanded Format
Summary of Color QuickDraw
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
Warning: This chapter has not been updated to reflect changes and improvements
that are available on systems using 32-Bit QuickDraw. For further
information on 32-Bit QuickDraw, please refer to the 32-Bit QuickDraw
documentation (available on “Phil & Dave’s Excellent CD: The Release
Version).
A new version of QuickDraw has been created to take advantage of the capabilities of the Macintosh II. Color QuickDraw is able to use a very large number of colors and can take advantage of systems that have one or more screens of any size. This chapter describes the use of color with one screen. The following chapter, “Graphics Devices”, explains what your program should do to support more than one screen.
The features of Color QuickDraw implemented for the Macintosh Plus, the Macintosh SE, and the Macintosh II are
• Text drawing modes are enhanced, and now include a text mask mode,
drawing with multibit fonts, and fractional character positioning.
• The QuickDraw picture format (PICT) has been enhanced, and includes
a number of new opcodes.
Some of the features of Color QuickDraw for the Macintosh II are
• All drawing operations supported by old QuickDraw can now be
performed in color.
• Color QuickDraw supports the use of as many as 2^48 colors; however,
current hardware can only support 2^24 colors, and this assumes the
presence of 32-Bit QuickDraw. In addition, Color QuickDraw’s color
model is hardware-independent, allowing programs to operate
independently of the display device.
• Color QuickDraw includes several new data types: color tables,
color icons, color patterns, and color cursors. These types can
be stored as resources that are easily used by your program.
• A new set of transfer modes has been added. These modes allow colors
to be blended with or added to the colors that are already on the screen.
• Most Toolbox Managers have been enhanced to use color. Thus you can
now add color to windows, menus, controls, dialog boxes, and TextEdit
text. Refer to the appropriate chapters for more information.
• The QuickDraw picture format (PICT) has been extended so that Color
QuickDraw images can be recorded in pictures.
This chapter introduces the basic concepts, terminology, and data structures underlying the Macintosh II approach to graphics. The material presented here assumes familiarity with the QuickDraw concepts described in the QuickDraw chapter, such as bit maps, graphics ports, patterns, cursors, and transfer modes. You should also be familiar with the use of resources, as presented in the Resource Manager chapter.
_______________________________________________________________________________
»COLOR REPRESENTATION
_______________________________________________________________________________
The following sections introduce the basic concepts and terminology used in Color QuickDraw. It’s important to keep in mind that Color QuickDraw is designed to be device-independent. The range of colors available is the result of the system configuration: the screen resolution, the graphics hardware used to produce color, and the software used to select and store color values. Color QuickDraw provides a consistent way of dealing with color, regardless of the characteristics of the video card or display device.
The original QuickDraw represents each dot on the screen (known as a pixel) as a single bit in memory. Each bit can have two values, zero or one. This allows two colors, usually black and white, to be displayed.
To produce color graphics, more than one bit of memory per pixel displayed is needed. If two bits per pixel are available, four colors can be displayed. Four bits per pixel provides a display of 16 colors, and eight bits per pixel provides a display of 256 colors. The bits in a pixel, taken together, form a number known as the pixel value.
The number of possible colors is related to the amount of memory used to store each pixel. Since displayed pixels are stored in RAM on the video card, rather than in the RAM in the Macintosh, the quality of the graphics depends on capabilities of the video card used.
_______________________________________________________________________________
»RGB Space
Color QuickDraw represents colors in RGB space. Each color has a red, a green, and a blue component, hence the name RGB. These components may be visualized as being mapped into a color cube, as shown in Figures 1 and 2. (Figure 1 is a color representation of Figure 2.)
Figure 1–RGB Color Cube (Color Version)
Figure 2–RGB Color Cube (B/W Version)
The data structures used within Color Quickdraw express each RGB component as an unsigned integer value. Each R, G, and B can have a value from $0000 to $FFFF (or 0 to 65,535). RGB color is additive; that is, as the value of a component is increased, the amount of that component in the total color increases. An RGB color is black if all three components are set to 0, or white if each component is set to 65,535. Pixel values between these two extremes can be combined to represent all the possible colors. For instance, pixel values that lie along the diagonal between black and white, and for which R = G = B, are all perceived as shades of gray.
_______________________________________________________________________________
»Other Color Spaces
In addition to RGB, several other color models are commonly used to represent colors. These other models include HSV (hue, saturation, value), HLS (hue, lightness, saturation), and CMY (cyan, magenta, yellow). If you wish to work in a different color space in your program, you can use the conversion routines provided in the Color Picker Package to convert colors to their RGB equivalents before passing them to Color QuickDraw. Please refer to the Color Picker Package chapter for more details.
_______________________________________________________________________________
»USING COLOR ON THE MACINTOSH II
_______________________________________________________________________________
Before you read about the details of how to use Color QuickDraw, it’s useful to understand the various components of the color system and how they interact with each other. This section, through a series of rules and examples, attempts to illustrate these interactions.
Rule 1: The user selects the depth of the screen using the Control Panel.
This rule is mentioned first to convey the fundamental need for device independence. Your application shouldn’t change the depth of the screen, because it must avoid conflicts with desk accessories or other applications that are using the screen at the same time. Let the user decide how many colors should be displayed.
Rule 2: Work with colors in RGB space, not with the colors on the screen.
Whenever possible, your application should assume that it’s drawing to a screen that has 2^48 colors. Let Color QuickDraw determine what colors to actually display on the screen. This lets your program work better when drawing to devices that support more colors.
The easiest way to follow this rule is for a program to call the Color Picker Package to select colors. The Color Picker returns an RGB value, which can then be used as the current color. When Color QuickDraw draws using that color, it selects the color that best matches the specified RGB.
Rule 3: To ensure good color matching, and to avoid conflict with other applications and desk accessories, use the Palette Manager.
If your program requires a very specific set of colors not found in the default selection of colors, for instance 128 levels of gray, then you should use the Palette Manager. The Palette Manager lets you specify the set of colors that is to be used by a particular window. When that window is brought to the front, its set of colors is switched in (with a minimal amount of impact on the rest of the screen).
You should also use the Palette Manager if your application needs to animate colors (that is, to change the colors of pixels that are already displayed).
The Palette Manager is a powerful tool because it makes sure that your application gets the best selection of colors across multiple screen devices and multiple screen depths. You don’t have to worry about interactions with desk accessories or other applications. Please refer to the chapter on the Palette Manager for more information on using the Palette Manager routines.
Rule 4: Be aware that systems may have multiple video devices.
Since the Macintosh II is able to support multiple screen devices, make sure your application takes into account the variable-sized desktop. For instance, a document may have been dragged to an alternate screen on one system, and then copied and used on another system. You should leave the document positioned where it is if it lies within the desktop, but move it to the main screen if it doesn’t. Please refer to the Graphics Devices chapter for more details.
Figure 3 helps to illustrate the relationships between the various parts of the color system.
Figure 3–The Macintosh II Color System
_______________________________________________________________________________
»From Color to Pixel
To help illustrate the interconnections of the color system, let’s examine the steps from the specification of a color to the display of that color on the screen. This is an oversimplified explanation that you should use for conceptual understanding only.
First, you specify the color that you want to display. Color QuickDraw stores the RGB components so that it knows the exact color that you specified. Let’s assume that the screen is set to eight bits per pixel. This means that each pixel is able to have 2^8, or 256, different values. Associated with the screen is a structure called a color table, which is a list of all the colors that the screen is currently able to display. So in this case the color table has 256 RGB values in it, one for each possible pixel value. The first entry in the color table specifies the color of all pixels that have value 0, the second entry specifies the color of pixels that have value 1, and so on. Thus the color’s position in the table determines the pixel value that produces that color.
When you use Color QuickDraw to draw something, it retrieves the stored RGB, and asks the Color Manager to return the pixel value that best represents that color. The Color Manager effectively searches through the color table for the RGB that most closely matches your color. The position in the table of the best match determines the pixel value to be placed on the screen. Color QuickDraw then places that pixel value on the screen.
But how does this pixel cause the assigned color to be displayed? Color QuickDraw has placed this pixel into the RAM on the video card. While your Macintosh II is turned on, the video card is continuously redisplaying every pixel that is stored in its RAM (very, very quickly). Internal to the video card is another color table, the Color Look-Up Table (CLUT). It is organized exactly like the first one, but is used the other way around. The video card takes the pixel value and uses it to determine what RGB value that pixel represents. It then uses that RGB to send off three signals (red, green, and blue) to the video monitor, indicating exactly what color the current pixel should be.
Some video cards allow you to change the set of colors displayed at a given time. Although this is normally done transparently through the Palette Manager, it actually happens when both the screen’s color table and the one that is internal to the video card are changed to reflect the new set of colors.
A very slight variation of this is used to support the monochrome mode that you can set from the control panel. When you set monochrome mode, the screen’s color table doesn’t change: from the application’s point of view, the same set of colors is still available. Instead, when the video card is told to use monochrome mode, it replaces each entry in the video card’s internal color table with a level of gray (R=G=B) that matches the luminance of the color it is replacing. Because of this, the switch between color and monochrome modes has no effect on a running program.
_______________________________________________________________________________
»ABOUT COLOR QUICKDRAW
_______________________________________________________________________________
The most fundamental difference between the original QuickDraw and Color QuickDraw is the environment in which drawing takes place. In the original QuickDraw, all drawing is performed in a grafPort, the structure that defines the coordinate system, drawing pattern, background pattern, pen size and location, character font and style, and bit map in which drawing takes place. In Color QuickDraw, drawing takes place in a color grafPort (cGrafPort) instead. As described in later sections, most of the fields in a cGrafPort are the same as fields in a grafPort; however, a few fields have been changed to hold color information.
When you’re using a grafPort in your application, you can specify up to eight colors. When drawing to a color screen or printing, these colors will actually be displayed. When drawing to an offscreen bitmap, the colors will be lost
(since an offscreen bitmap only has one bit for each pixel).
When you’re using a cGrafPort, however, you can specify up to 2^48 colors. The number of colors that are displayed depends on the setting of the screen, the capability of the printer, or the depth of the offscreen pixmap. There is more information about offscreen pixmaps in the “Drawing to Offscreen Devices” section of the next chapter.
Color grafPorts are used by the system in the same way as grafPorts. They are the same size as grafPorts, and they are the structures upon which a program builds color windows. As with a grafPort, you set thePort to be a cGrafPort using the SetPort command.
You can use all old drawing commands when drawing into a cGrafPort, and you can use all new drawing commands when drawing into a grafPort. However, since new drawing commands that are used in a grafPort don’t take advantage of any of the features of Color QuickDraw, it’s not recommended.
_______________________________________________________________________________
»Drawing Color in a GrafPort
Although the QuickDraw graphics routines were designed mainly for monochrome drawing, they also included some rudimentary color capabilities. A pair of fields in the grafPort record, fgColor and bkColor, allow a foreground and background color to be specified. The color values used in these fields are based on a planar model: each bit position corresponds to a different color plane, and the value of each bit indicates whether a particular color plane should be activated. (The term color plane refers to a logical plane, rather than a physical plane.) The individual color planes combine to produce the
full-color image.
The standard QuickDraw color values consist of one bit for normal monochrome drawing (black on white), one bit for inverted monochrome (white on black), three bits for the additive primary colors (red, green, blue) used in video display, and four bits for the subtractive primary colors (cyan, magenta, yellow, black) used in hardcopy printing. The original QuickDraw interface includes a set of predefined constants for the standard colors:
CONST
blackColor = 33;
whiteColor = 30;
redColor = 209;
greenColor = 329;
blueColor = 389;
cyanColor = 269;
magentaColor = 149;
yellowColor = 89;
These are the only colors available in the original QuickDraw. All programs that draw into grafPorts are limited to these eight colors. When these colors are drawn to the screen on the Macintosh II, Color QuickDraw automatically draws them in color, if the screen is set to a color mode.
_______________________________________________________________________________
»Drawing Color in a CGrafPort
Color QuickDraw represents color using the RGBColor record type, which specifies the red, blue, and green components of the color. Three 16-bit unsigned integers give the intensity values for the three additive primary colors:
TYPE
RGBColor = RECORD
red: INTEGER; {red component}
green: INTEGER; {green component}
blue: INTEGER {blue component}
END;
A color of this form is referred to as an RGB value and is the form in which an application specifies the colors it needs. The translation from the RGB value to the pixel value is performed at the time the color is drawn. At times the pixel value is stored in the fgColor or bkColor fields. Refer to the Graphics Devices chapter for more details.
When drawing is actually performed, QuickDraw calls the Color Manager to supply the color that most closely matches the requested color for the current device. As described in the Color Manager chapter, you can replace the method used for color matching if necessary. Normally pixel values are handled entirely by Color QuickDraw and the Color Manager; applications only refer to colors as RGB values.
A set of colors is grouped into a structure called a color table:
TYPE
CTabHandle = ^CTabPtr;
CTabPtr = ^ColorTable;
ColorTable = RECORD
ctSeed: LONGINT; {unique identifier from table}
ctFlags: INTEGER; {contains flags describing the }
{ specArray; clear for a pixMap}
ctSize: INTEGER; {number of entries -1 }
{ in ctTable}
ctTable: cSpecArray
END;
The fields of a color table are fully described in the Color Manager chapter. The ctFlags field contains flags that differentiate between a device color table and an image color table. The ctTable field is composed of a cSpecArray, which contains an array of ColorSpec entries. Notice that each entry in the color table is a ColorSpec, not simply an RGBColor. The type ColorSpec is composed of a value field and an RGB value, as shown below.
TYPE
cSpecArray : ARRAY [0..0] of ColorSpec;
ColorSpec = RECORD
value: INTEGER; {pixel value}
rgb: RGBColor {RGB value}
END;
Color tables are used to represent the set of colors that a device is capable of displaying, and they are used to describe the desired colors in an image. If the color table describes an image’s colors, then a ColorSpec determines the desired RGB for the pixel value stored in the value field. This is the most common usage, and most of the routines described in this chapter work with a ColorSpec in this manner.
If the color table describes a device’s colors, then the value field in a ColorSpec is reserved for use by the Color Manager. In most cases your application won’t change the device color table. If you want to know more about the device color table, refer to the Color Manager chapter for more details.
_______________________________________________________________________________
»THE COLOR GRAPHICS PORT
_______________________________________________________________________________
As described above, programs designed to take advantage of the more powerful new color facilities available on the Macintosh II must use a new form of graphics port, the color graphics port (type cGrafPort). Color grafPorts will generally be created indirectly, as a result of opening a color window with the new routines NewCWindow, GetNewCWindow, and NewCDialog.
In addition, the old routines GetNewWindow, GetNewDialog, Alert, StopAlert, NoteAlert, and CautionAlert will open a color grafPort if certain resources
(types 'wctb', 'dctb', or 'actb') are present. Refer to the chapters on the Window and Dialog Managers for more details.
The new cGrafPort structure is the same size as the old-style grafPort and most of its fields are unchanged. The old portBits field, which formerly held a complete 14-byte BitMap record embedded within the grafPort, has been replaced by a 4-byte PixMapHandle (portPixMap), freeing 10 bytes for other uses. (In particular, the new portVersion field, in the position previously occupied by the bit map’s rowBytes field, always has its two high
bits set; these bits are used to distinguish cGrafPorts from grafPorts, in which the two high bits of rowBytes are always clear. See Figure 4.) Similarly, the old bkPat, pnPat, and fillPat fields, which previously held 8-byte patterns, have been replaced by three 4-byte handles. The resulting 12 bytes of additional space are taken up by two 6-byte RGBColor records.
The structure of the color graphics port is as follows:
CGrafPtr = ^CGrafPort;
CGrafPort = RECORD
device: INTEGER; {device ID for font }
{ selection}
portPixMap: PixMapHandle; {port's pixel map}
portVersion: INTEGER; {highest 2 bits always }
{ set}
grafVars: Handle; {handle to more fields}
chExtra: INTEGER; {extra characters}
pnLocHFrac: INTEGER; {pen fraction}
portRect: Rect; {port rectangle}
visRgn: RgnHandle; {visible region}
clipRgn: RgnHandle; {clipping region}
bkPixPat: PixPatHandle; {background pattern}
rgbFgColor: RGBColor; {requested foreground }
{ color}
rgbBkColor: RGBColor; {requested background }
{ color}
pnLoc: Point; {pen location}
pnSize: Point; {pen size}
pnMode: INTEGER; {pen transfer mode}
pnPixPat: PixPatHandle; {pen pattern}
fillPixPat: PixPatHandle; {fill pattern}
pnVis: INTEGER; {pen visibility}
txFont: INTEGER; {font number for text}
txFace: Style; {text's character style}
txMode: INTEGER; {text's transfer mode}
txSize: INTEGER; {font size for text}
spExtra: Fixed; {extra space}
fgColor: LONGINT; {actual foreground color}
bkColor: LONGINT; {actual background color}
colrBit: INTEGER; {plane being drawn}
patStretch: INTEGER; {used internally}
picSave: Handle; {picture being saved}
rgnSave: Handle; {region being saved}
polySave: Handle; {polygon being saved}
grafProcs: CQDProcsPtr {low-level drawing }
{ routines}
END;
Field descriptions
portPixMap The portPixMap field contains a handle to the port’s pixel
map. This is the structure that describes the cGrafPort’s pixels.
portVersion The two high bits of the portVersion field are always set.
This allows Color QuickDraw to tell the difference between a
grafPort and a cGrafPort. The remainder of the field gives
the version number of Color QuickDraw that created this port.
(Initial release is version 0.)
grafVars The grafVars field contains a handle to additional fields.
chExtra The chExtra field is used in proportional spacing. It specifies
a fixed point number by which to widen every character,
excluding the space character, in a line of text. (The number
is in 4.12 fractional notation: four bits of signed integer
followed by 12 bits of fraction. This number is multiplied by
txSize before it is used.) Default chExtra is 0.
pnLocHFrac The pnLocHFrac field contains the fractional horizontal pen
position used when drawing text. The initial pen fraction is 1/2.
bkPixPat The bkPixPat field contains a handle to the background pixel
pattern.
rgbFgColor The rgbFgColor field contains the requested foreground color.
rgbBkColor The rgbBkColor field contains the requested background color.
pnPixPat The pnPixPat field contains a handle to the pixel pattern for
pen drawing.
fillPixPat The fillPixPat field contains a handle to the pixel pattern for
area fill; for internal use only. Notice that this is not in
the same location as old fillPat.
fgColor The fgColor field contains the pixel value of the foreground
color supplied by the Color Manager. This is the best available
approximation to rgbFgColor.
bkColor The bkColor field contains the pixel value of the background
color supplied by the Color Manager. This is the best available
approximation to rgbBkColor.
colrBit The colrBit field is reserved: not for use by applications.
grafProc The grafProc field used with a cGrafPort contains a CQDProcsPtr,
instead of the QDProcsPtr used with a grafPort.
All remaining fields have the same meanings as in the old-style grafPort.
Figure 4–Color QuickDraw Fields
_______________________________________________________________________________
»Pixel Images
The representation of a color image in memory is a pixel image, analogous to the bit image used by the original QuickDraw. The number of bits per pixel is called the depth of the image; a pixel image one bit deep is equivalent to a bit image. On the Macintosh II, the pixel image that appears on a video screen is normally stored on a graphics card rather than in main memory. To increase speed, your program can build additional images in RAM for rapid transfer to the display device. This technique, called drawing to an offscreen bitmap, is described in the Graphics Devices chapter.
There are several possible arrangements of a pixel image in memory. The size and structure of a pixel image is described by the pixel map data structure; this structure and its various forms are discussed below. See Figure 5 for a representation of a pixel image on a system with screen depth set to eight.
_______________________________________________________________________________
»Pixel Maps
Just as the original QuickDraw does all of its drawing in a bit map, Color QuickDraw uses an extended data structure called a pixel map (pixMap). In addition to the dimensions and contents of a pixel image, the pixel map also includes information on the image’s storage format, depth, resolution, and color usage:
TYPE
PixMapHandle = ^PixMapPtr;
PixMapPtr = ^PixMap;
PixMap = RECORD
baseAddr: Ptr; {pointer to pixMap data}
rowBytes: INTEGER; {offset to next row}
bounds: Rect; {boundary rectangle}
pmVersion: INTEGER; {color QuickDraw version }
{ number}
packType: INTEGER; {packing format}
packSize: LONGINT; {size of data in packed }
{ state}
hRes: Fixed; {horizontal resolution}
vRes: Fixed; {vertical resolution}
pixelType: INTEGER; {format of pixel image}
pixelSize: INTEGER; {physical bits per pixel}
cmpCount: INTEGER; {logical components per }
{ pixel}
cmpSize: INTEGER; {logical bits per component}
planeBytes: LONGINT; {offset to next plane}
pmTable: CTabHandle; {absolute colors for this }
{ image}
pmReserved: LONGINT {reserved for future }
{ expansion}
END;
Field descriptions
baseAddr The baseAddr field contains a pointer to first byte of the
pixel image, the same as in a bitMap. For optimal performance
this should be a multiple of four.
rowBytes The rowBytes field contains the offset in bytes from one row of
the image to the next, the same as in a bitMap. As before,
rowBytes must be even. The high three bits of rowBytes are used
as flags. If bit 15 = 1, the data structure is a pixMap;
otherwise it is a bitMap. Bits 14 and 13 are not used and must
be 0.
bounds The bounds field is the boundary rectangle, which defines the
coordinate system and extent of the pixel map; it’s similar to
a bitMap. This rectangle is in pixels, so depth has no effect
on its values.
pmVersion The pmVersion is the version number of Color QuickDraw that
created this pixel map, which is provided for future
compatibility. (Initial release is version 0.)
packType The packType field identifies the packing algorithm used to
compress image data. Color QuickDraw currently supports only
packType = 0, which means no packing.
packSize The packSize field contains the size of the packed image in
bytes. When packType = 0, this field should be set to 0.
hRes The hRes is the horizontal resolution of pixMap data in pixels
per inch.
vRes The vRes is the vertical resolution of pixMap data in pixels
per inch. By default, hRes = vRes = 72 pixels per inch.
pixelType The pixelType field specifies the storage format for a pixel
image. 0 = chunky, 1 = chunky/planar, 2 = planar. Only chunky
is used in the Macintosh II.
pixelSize The pixelSize is the physical bits per pixel; it’s always a
power of 2.
cmpCount The cmpCount is the number of color components per pixel. For
chunky pixel images, this is always 1.
cmpSize The cmpSize field contains the logical bits per RGBColor
component. Note that (cmpCount*cmpSize) doesn’t necessarily
equal pixelSize. For chunky pixel images, cmpSize = pixelSize.
planeBytes The planeBytes field is the offset in bytes from one plane to
the next. If only one plane is used, as is the case with chunky
pixel images, this field is set to 0.
pmTable The pmTable field is a handle to table of colors used in the
pixMap. This may be a device color table or an image color table.
pmReserved The pmReserved field is reserved for future expansion; it must
be set to 0 for future compatibility.
The data in a pixel image can be organized several ways, depending on the characteristics of the device or image. The pixMap data structure supports three pixel image formats: chunky, planar, and chunky/planar.
In a chunky pixel image, all of a pixel’s bits are stored consecutively in memory, all of a row’s pixels are stored consecutively, and rowBytes indicates the offset in memory from one row to the next. This is the only one of the three formats that’s supported by this implementation of Color QuickDraw. The pixel depths that are currently supported are 1, 2, 4, and 8 bits per pixel. In a chunky pixMap cmpCount = 1 and cmpSize = pixelSize. Figure 5 shows a chunky pixel image for a system with screen depth set to eight.
A planar pixel image is a pixel image separated into distinct bit images in memory, one for each color plane. Within the bit image, rowBytes indicates the offset in memory from one row to the next. PlaneBytes indicates the offset in memory from one plane to the next. The planar format isn’t supported by this implementation of Color QuickDraw.
A chunky/planar pixel image is separated into distinct pixel images in memory, typically one for each color component. Within the pixel image, rowBytes indicates the offset in memory from one row to the next. PlaneBytes indicates the offset in memory from one plane to the next. The chunky/planar format isn’t supported by this implementation of Color QuickDraw.
Figure 5–A Pixel Image
_______________________________________________________________________________
»Pixel Patterns
With Color QuickDraw, monochrome patterns are replaced by a new form of pattern structure, the pixel pattern, which offers greater flexibility in the use of color. The three pattern fields in a grafPort—pnPat, bkPat, and fillPat—have been replaced by the pnPixPat, bkPixPat, and fillPixPat fields in a cGrafPort. The format for a pixel pattern is shown below:
TYPE
PixPatHandle = ^PixPatPtr;
PixPatPtr = ^PixPat;
PixPat = RECORD
patType: INTEGER; {pattern type}
patMap: PixMapHandle; {pattern characteristics}
patData: Handle; {pixel image defining }
{ pattern}
patXData: Handle; {expanded pixel image}
patXValid: INTEGER; {flags for expanded }
{ pattern data}
patXMap: Handle; {handle to expanded }
{ pattern data}
pat1Data: Pattern; {old-style pattern/RGB }
{ color}
END;
Field descriptions
patType The patType field specifies the pattern’s type. The possible
values include: 0 = old-style pattern, 1 = full-color pixel
pattern, 2 = RGB pattern.
patMap The patMap field is a handle to the pixel map describing the
pattern’s pixel image.
patData The patData field is a handle to the pattern’s pixel image.
patXData The patXData field is a handle to an expanded pixel image used
internally by Color QuickDraw.
patXValid When the pattern’s data or color table change, you can
invalidate the expanded data by setting the patXValid field to –1.
patXMap The patXMap field is a handle that is reserved for use by Color
QuickDraw.
pat1Data The pat1Data field contains an old-style 8-by-8 pattern to be
used when this pattern is drawn into old grafPort. NewPixPat
sets this field to 50% gray.
Old-style patterns are still supported. When used in a cGrafPort, the QuickDraw routines PenPat and BackPat store the pattern within pnPixPat and bkPixPat, respectively, and set the patType to 0 to indicate that the structure contains old pattern data. Such patterns are limited to the original 8-by-8 dimensions and are always drawn using the values in the cGrafPort’s rgbFgColor and rgbBkColor fields. Similarly, filled drawing operations, such as FillRect, are also supported.
In a pixel pattern (patType = 1), the pattern’s dimensions, depth, resolution
(only 72 pixels per inch is supported), set of colors, and other characteristics are defined by a pixel map, referenced by the patMap handle. Since the pixel map has its own color table, pixel patterns can consist of any number of colors, and don’t usually use the foreground and background colors. The section on relative patterns, below, describes an exception to this rule.
Furthermore, patType = 1 patterns are not limited to a fixed size: their height and width can be any power of 2, as specified by the height and width of
patMap^^.bounds. (Notice that a pattern eight bits wide—the original QuickDraw size—has a row width of just one byte, contrary to the usual rule that the rowBytes field must be even.) This pattern type is generally read into memory using the GetPixPat routine, or set using the PenPixPat or BackPixPat routines.
Although the patMap defines the pattern’s characteristics, its baseAddr field is ignored; for a type1 pattern, the actual pixel image defining the pattern is stored in the handle in the pattern’s patData field. The pattern’s depth need not match that of the pixel map it’s painted into; the depth will be adjusted automatically when the pattern is drawn. Color QuickDraw maintains a private copy of the pattern’s pixel image, expanded to the current screen depth, and aligned to the current grafPort or cGrafPort, in the patXData field.
The third pattern type is RGBPat (patType = 2). Using the MakeRGBPat routine, the application can specify the exact color it wants to use. QuickDraw selects a pattern to approximate that color. In this way, an application can effectively increase the color resolution of the screen. Pixel patterns are particularly useful for dithering: mixing existing colors together to create the illusion of a third color that’s unavailable on a particular device. The MakeRGBPat routine aids in this process by constructing a dithered pattern to approximate a given absolute color. (See the description of MakeRGBPat in the
“Color QuickDraw Routines” section for more details.) In the current implementation of Color QuickDraw, an RGBPat can display 125 different patterns on a 4-bit-deep screen, or 2197 different patterns on an 8-bit-deep screen.
For an RGBPat, the RGB defines the image; there is no image data. An RGBPat has an 8-by-8, 2-bit-deep pattern.
A program that creates a pixMap must initialize the pixMap’s color table to describe the pixels. GetCTable could be used to read such a table from a resource file; you could then dispose of the pixMap’s color table and replace it with the one returned by GetCTable.
»Relative Patterns
Type1 pixel patterns contain color tables that describe the colors they use. Generally such a color table contains one entry for each color used in the pattern. For instance, if your pattern has five colors in it, you would probably create a four-bit-per-pixel pattern that uses pixel values 0–4, and a color table with five entries, numbered 0–4, that contain the RGB specifications for those pixel values.
When the pattern is drawn, each possible pixel value that isn’t specified in the color table is assigned a color. The largest unassigned pixel value becomes the foreground color; the smallest unassigned pixel value is assigned the background color. Remaining unassigned pixel values are given colors that are evenly distributed between the foreground and background.
For instance, in the color table mentioned above, pixel values 5–15 are unused. Assume that the foreground color is black and the background color is white. Pixel value 15 is assigned the foreground color, black; pixel value 5 is assigned the background color, white; the nine pixel values between them are assigned evenly distributed shades of gray. If the pixMap’s color table is set to NIL, all pixel values are determined by blending the foreground and background colors.
_______________________________________________________________________________
»Transfer Modes
A transfer mode is a method of placing information on the display devices. It involves an interaction between what your application is drawing (the source) and what’s already there (the destination). The original QuickDraw offered eight basic transfer modes:
• completely replacing the destination with the source (Copy), and its
inverse (NotCopy)
• combining the destination with the source (Or), and its inverse (NotOr)
• selectively clearing the destination with the source (Bic, for “bit
clear”), and its inverse (NotBic)
• selectively inverting the destination with the source (Xor), and its
inverse (NotXor)
This is how color affects these eight transfer modes when the source pixels are either black (all 1’s) or white (all 0’s):
Copy The Copy mode applies the foreground color to the black part of
the source (the part containing 1’s) and the background color to
the white part of the source (the part containing 0’s), and
replaces the destination with the colored source.
NotCopy The NotCopy mode applies the foreground color to the white part
of the source and the background color to the black part of the
source, and replaces the destination with the colored source. It
thus has the effect of reversing the foreground and background
colors.
Or The Or mode applies the foreground color to the black part of the
source and replaces the destination with the colored source. The
white part of the source isn’t transferred to the destination. If
the foreground is black, the drawing will be faster.
NotOr The NotOr mode applies the foreground color to the white part of
the source and replaces the destination with the colored source.
The black part of the source isn’t transferred to the destination.
If the foreground is black, the drawing will be faster.
Bic The Bic mode applies the background color to the black part of
the source and replaces the destination with the colored source.
The white part of the source isn’t transferred to the destination.
NotBic The NotBic mode applies the background color to the white part
of the source and replaces the destination with the colored source.
The black part of the source isn’t transferred to the destination.
Xor The Xor mode complements the bits in the destination corresponding
to the bits equal to 1 in the source. When used on a colored
destination, the color of the inverted destination isn’t defined.
NotXor The NotXor mode inverts the bits that are 0 in the source. When
used on a colored destination, the color of the inverted
destination isn’t defined.
Pixels of colors other than black and white aren’t all 1’s or all 0’s, so the application of a foreground color or a background color to the pixel produces an undefined result. For this reason, and because a pixPat already contains color, the foreground and background colors are ignored when your application is drawing with a pixPat. When your program draws a pixMap the foreground and background colors are not ignored. Make sure that the foreground is black and the background is white before you call CopyBits or the result will be undefined.
If you intend to draw with pixMaps or pixPats, you will probably want to use the Copy mode or one of the arithmetic modes described in the following section.
To help make color work well on different screen depths, Color QuickDraw does some validity checking of the foreground and background colors. If your application is drawing to a cGrafPort with a depth equal to 1 or 2, and if the RGB values of the foreground and background colors aren’t the same, but both of them map to the same pixel value, then the foreground color is inverted. This ensures that, for instance, red text drawn on a green background doesn’t map to black on black.
_______________________________________________________________________________
»Arithmetic Drawing Modes
Color QuickDraw uses a set of arithmetic drawing modes designed specifically for use with color. These modes change the destination pixels by performing arithmetic operations on the source and destination pixels. These drawing modes are most useful in 8-bit color, but work on 4-bit and 2-bit color as well. If the destination bitmap is one bit deep, the mode reverts to one of the old transfer modes that approximates the arithmetic mode requested.
Each drawing routine converts the source and destination pixels to their RGB components, performs an operation on each pair of components to provide a new RGB value for the destination, and then assigns the destination a pixel value close to the calculated RGB value. The arithmetic modes listed below can be used for all drawing operations; your application can pass them as a parameter to TextMode, PenMode, or CopyBits.
addOver This mode assigns to the destination pixel the color closest to
the sum of the source and destination RGB values. If the sum of
any of the RGB components exceeds the maximum allowable value,
65,535, the RGB value wraps around to the value less 65,536.
AddOver is slightly faster than addPin. If the destination bitmap
is one bit deep, addOver reverts to Xor.
addPin This mode assigns to the destination pixel the color closest to
the sum of the destination RGB values, pinned to a maximum allowable
RGB value. For grafPorts, the pin value is always white. For
cGrafPorts, the pin value is assigned using OpColor. If the
destination bitmap is one bit deep, addPin reverts to Bic.
subOver This mode assigns to the destination pixel the color closest to
the difference of the source and destination RGB values. If the
result is less than 0, the RGB value wraps around to 65,536 less
the result. SubOver is slightly faster than subPin. If the
destination bitmap is one bit deep, subOver reverts to Xor.
subPin This mode assigns to the destination pixel the color closest to
the difference of the sum and the destination RGB values, pinned
to a minimum allowable RGB value. For grafPorts, the pin value is
always black. In a cGrafPort, the pin value is assigned by using
OpColor. If the destination bitmap is one bit deep, subPin reverts
to Or.
adMax (Arithmetic Drawing Max) This mode compares the source and
destination pixels, and replaces the destination pixel with the
color containing the greater saturation of each of the RGB
components. Each RGB component comparison is done independently,
so the resulting color isn’t necessarily either the source or the
destination color. If the destination bitmap is one bit deep,
adMax reverts to Bic.
adMin (Arithmetic Drawing Min) This mode compares the source and
destination pixels, and replaces the destination pixel with
the color containing the lesser saturation of each of the RGB
components. Each RGB component is compared independently, so
the resulting color isn’t necessarily the source or the
destination color. If the destination bitmap is one bit deep,
adMin reverts to Or.
blend This mode replaces the destination pixel with a weighted average
of the colors of the source and destination pixels. The formula
used to calculate the destination is:
dest = source*weight/65,536 + destination*(1-weight/65,536)
where weight is an unsigned value between 1 and 65,535. In a
grafPort, the weight is set to 50% gray, so that equal weights
of the source and destination RGB components are combined to
produce the destination color. In a cGrafPort, the weight is an
RGBColor that individually specifies the weights of the red,
green, and blue components. The weight is assigned using OpColor.
If the destination bitmap is one bit deep, blend reverts to Copy.
Because drawing with the arithmetic modes uses the closest matching pixel values, and not necessarily exact matches, these modes might not produce the results you expect. For instance, suppose srcCopy mode is used to paint a green pixel on the screen in 4-bit mode. Of the 16 colors available, the closest green may contain a small amount of red, as in RGB components of 300 red, 65,535 green, and 0 blue. AddOver is then used to paint a red pixel on top of the green pixel, ideally resulting in a yellow pixel. The red pixel’s RGB components are 65,535 red, 0 green, and 0 blue. Adding the red components together wraps to 300, since the largest representable value is 65,535. In this case, AddOver would cause no visible change at all. Using AddPin with an opColor of white would produce the desired results.
On the Macintosh II the rules for setting the pen mode and the text mode have been relaxed slightly. It’s no longer necessary to specify a pattern mode or a source mode (patCopy as opposed to srcCopy) to perform a particular operation. QuickDraw will choose the correct drawing mode automatically. However, to be compatible with earlier versions of QuickDraw, you application must specify the correct drawing mode. Text and bitmaps should always use a source mode; rectangles, regions, polygons, arcs, ovals, round rectangles, and lines should always use a pattern mode.
The constants used for the arithmetic transfer modes are as follows:
CONST
blend = 32;
addPin = 33;
addOver = 34;
subPin = 35;
adMax = 37;
subOver = 38;
adMin = 39;
Warning: Unlike the rest of QuickDraw, the arithmetic modes don’t call the
Color Manager when mapping a requested RGB value to a pixel value.
If your application replaces the color matching routines, you must
either not use these modes, or you must maintain the inverse table
using the Color Manager routines.
_______________________________________________________________________________
»Replace with Transparency
The transparent mode replaces the destination pixel with the source pixel if the source pixel isn’t equal to the background color. This mode is most useful in 8-bit, 4-bit, or 2-bit color modes. To specify a transparent pattern, use the drawing mode transparent+patCopy. If the destination pixMap is one bit deep, the mode is translated to Or. Transparency can be specified as a parameter to TextMode, PenMode, or CopyBits.
Transparent mode is optimized to handle source bitmaps with large transparent holes, as an alternative to specifying an unusual clipping region or mask parameter to CopyMask. Patterns aren’t optimized, and may not draw as quickly.
The constant used for transparent mode is
CONST
transparent = 36;
_______________________________________________________________________________
»The Hilite Mode
This new method of highlighting exchanges the background color and the highlight color in the destination. This has the visual effect of using a highlighting pen to select the object. For instance, TextEdit uses the hilite mode to select text: if the highlight color is yellow, selected text appears on a yellow background. In general, highlighting should be used in place of inversion when selecting and deselecting objects such as text or graphics.
There are two ways to use hilite mode. The easiest is to call
BitClr (Ptr(HiliteMode,pHiliteBit));
just before calling InvertRect, InvertRgn, InvertArc, InvertRoundRct, or InvertPoly or any drawing using srcXor mode. On a one-bit-deep destination, this will work exactly like inversion, and is compatible with all versions of QuickDraw. Color QuickDraw resets the hilite bit after performing each drawing operation, so the hilite bit should be cleared immediately before calling a routine that is to do highlighting. Routines that formerly used Xor inversion, such as the Invert routines, Paint, Frame, LineTo, text drawing, and CopyBits, will now use hilite mode if the hilite bit is clear.
Assembly language note: You can use
BCLR #hiliteBit, hiliteMode
Do not alter the other bits in HiliteMode.
The second way to use hilite mode is to pass it directly to TextMode, PenMode, or CopyBits as a parameter.
Hilite mode uses the source or pattern to decide which bits to exchange; only bits that are on in the source or pattern can be highlighted in the destination.
A very small inversion should probably not use hilite mode, because a small selection in the hilite color might be too hard to see. TextEdit, for instance, uses hilite mode to select and deselect text, but not to blink the insertion point.
Hilite mode is optimized to look for consecutive pixels in either the hilite or background colors. For example, if the source is an all black pattern, the highlighting will be especially fast, operating internally on a long word at a time instead of a pixel at a time. Highlighting a large area without such consecutive pixels (a gray pattern, for instance) can be slow.
The global variable HiliteRGB is read from parameter RAM when the machine starts. Old grafPorts use the RGB values in the global HiliteRGB as the highlight color. Color grafPorts default to the global HiliteRGB, but can be overridden by the HiliteColor procedure.
The constants used with hilite mode are listed below:
CONST
hilite = 50;
pHiliteBit = 0; {this is the correct value for use when calling }
{ the BitClear trap. BClr must use the assembly }
{ language equate hiliteBit}
_______________________________________________________________________________
»THE COLOR CURSOR
_______________________________________________________________________________
Color QuickDraw supports the use of color cursors. The size of a cursor is still 16-by-16 pixels. The new CCrsr data structure is substantially different from the Cursor data structure used with the original QuickDraw: the CCrsr fields crsr1Data, crsrMask, and crsrHotSpot are the only fields that have counterparts in the Cursor record.
The structure of the color cursor is as follows:
TYPE
CCrsrHandle = ^CCrsrPtr;
CCrsrPtr = ^CCrsr;
CCrsr = RECORD
crsrType: INTEGER; {type of cursor}
crsrMap: PixMapHandle; {the cursor's pixmap}
crsrData: Handle; {cursor's data}
crsrXData: Handle; {expanded cursor data}
crsrXValid: INTEGER; {depth of expanded data}
crsrXHandle: Handle; {Reserved for future }
{ use}
crsr1Data: Bits16; {one-bit cursor}
crsrMask: Bits16; {cursor's mask}
crsrHotSpot: Point; {cursor's hotspot}
crsrXTable: LONGINT; {private}
crsrID: LONGINT; {ctSeed for expanded }
{ cursor}
END;
You will not normally need to manipulate the fields of a color cursor. Your application can load in a color cursor using the GetCCursor routine, and display it using the SetCCursor routine. When the application is finished using a color cursor, it should dispose of it using the DisposCCursor routine. These routines are discussed below in the section “Color QuickDraw Routines”.
Color cursors are stored in resources of type 'crsr'. The format of the 'crsr' resource is given in the section “Color QuickDraw Resource Formats”.
Field descriptions
crsrType The crsrType field specifies the type of cursor. Possible
values are: $8000 = old cursor, $8001 = new cursor.
crsrMap The crsrMap field is a handle to the pixel map defining the
cursor’s characteristics.
crsrData The crsrData field is a handle to the cursor’s pixel data.
crsrXData The crsrXData field is a handle to the expanded pixel image
used internally by Color QuickDraw (private).
crsrXValid The crsrXValid field contains the depth of the expanded cursor
image. If you change the cursor’s data or color table, you
should set this field to 0 to cause the cursor to be reexpanded.
You should never set it to any other values.
crsrXHandle The crsrXHandle field is reserved for future use.
crsr1Data The crsr1Data field contains a 16-by-16 one-bit image to be
displayed when the cursor is on 1-bit or 2-bit per pixel screens.
crsrMask The crsrMask field contains the cursor’s mask data. The same
1-bit-deep mask is used with crsrData and crsr1Data.
crsrHotSpot The crsrHotSpot field contains the cursor’s hot spot.
crsrXTable The crsrXTable field is reserved for future use.
crsrID The crsrID field contains the ctSeed for the cursor.
The first four fields of the CCrsr record are similar to the first four fields of the PixPat record, and are used in the same manner by Color QuickDraw. See the discussion of the patMap field under the section titled “Pixel Patterns” for more information on how the crsrMap is used.
The display of a cursor involves a relationship between a mask, stored in the crsrMask field with the same format used for old cursor masks, and an image. There are two possible sources for a color cursor’s image. When the cursor is on a screen whose depth is one or two bits per pixel, the image for the cursor is taken from Crsr1Data, which contains old-style cursor data. In this case, the relationship between data and mask is exactly as before. When the screen depth is greater than two bits per pixel, the image for the cursor is taken from crsrMap and crsrData; the relationship between mask and data is described in the following paragraph.
The data pixels within the mask replace the destination pixels. The data pixels outside the mask are displayed using an XOR with the destination pixels. If data pixels outside the mask are 0 (white), the destination pixels aren’t changed. If data pixels outside the mask are all 1’s (black), the destination pixels are complemented. All other values outside of the mask cause unpredictable results.
To work properly, a color cursor’s image should contain white pixels
(R = G = B = $FFFF) for the transparent part of the image, and black pixels
(R = G = B = $0000) for the inverting part of the image, in addition to the other colors in the cursor’s image. Thus, to define a cursor that contains two colors, it’s necessary to use a 2-bit-per-pixel cursor image (that is, a four-color image).
If your application changes the value of your cursor data or its color table, it should set the crsrXValid field to 0 to indicate that the cursor’s data needs to be reexpanded, and assign a new unique value to crsrID (unique values can be obtained using the GetCTSeed routine); then it should call SetCCursor to display the changed cursor.
_______________________________________________________________________________
»COLOR ICONS
_______________________________________________________________________________
A new data structure, known as CIcon, supports the use of color icons. The structure of the color icon is as follows:
TYPE
CIconHandle = ^CIconPtr;
CIconPtr = ^CIcon;
CIcon = RECORD
iconPMap: PixMap; {the icon's pixMap}
iconMask: BitMap; {the icon's mask bitmap}
iconBMap: BitMap; {the icon's bitMap}
iconData: Handle; {the icon's data}
iconMaskData: ARRAY[0..0] OF INTEGER;
{icon's mask and bitmap }
{ data}
END;
You won’t normally need to manipulate the fields of color icons. Your application can load a color icon into memory using the routine GetCIcon. To draw a color icon that’s already in memory, use PlotCIcon. When your application is through with a color icon, it can dispose of it using the DisposCIcon routine. These routines are discussed below in the section “Color QuickDraw Routines”.
Color icons are stored in a resource file as resource type 'cicn'. The format of the 'cicn' resource is given in the section “Using Color QuickDraw
Resources”.
Field descriptions
iconPMap The iconPMap field contains the pixel map describing the
icon. Note that pixMap is inline, not a handle.
iconMask The iconMask field contains a bit map for the icon’s mask.
iconBMap The iconBMap field contains a bit map for the icon.
iconData The iconData field contains a handle to the icon’s pixel image.
iconMaskData The iconMaskData field is an array containing the icon’s mask
data followed by the icon’s bitmap data. This is only used
when the icon is stored as a resource.
You can use color icons in menus in the same way that you could use old icons in menus. The menu definition procedure first tries to load in a 'cicn' with the specified resource ID. If it doesn’t find one, then it tries to load in an
'ICON' with that ID. The Dialog Manager will also use a 'cicn' in place of an
'ICON' if there is one with the ID specified in the item list. For more information, see the Menu Manager and Dialog Manager chapters.
_______________________________________________________________________________
»USING COLOR QUICKDRAW
_______________________________________________________________________________
This section gives an overview of routines that you will typically call while using Color QuickDraw. All routines are discussed below in the section “Color QuickDraw Routines”.
Using a color graphics port is much like using an old-style grafPort. The old routines SetPort and GetPort operate on grafPorts or cGrafPorts, and the global variable ThePort points to either to a grafPort or a cGrafPort. Color QuickDraw examines the two high bits of the portBits.rowBytes field (the portVersion field in a cGrafPort). If these bits equal 0, then it is a grafPort; if they are both 1, then it is a cGrafPort. In Pascal, use type coercion to convert between GrafPtr and cGrafPtr. For example:
VAR myPort: CGrafPtr;
SetPort (GrafPtr(myPort));
There’s still a graphics pen for line drawing, with a current size, location, pattern, and transfer mode; all of the old line- and shape-drawing operations, such as Move, LineTo, FrameRect, and PaintPoly, still work just as before. However, colors should be set with the new routines RGBForeColor and RGBBackColor (described below) instead of the old ForeColor and BackColor routines. If your application is using the Palette Manager, use the routines PMForeColor and PMBackColor instead.
PenPat and BackPat are still supported, and will construct a pixel pattern equivalent to the specified bit pattern. The patType field of this pattern is set to 0; thus it will always use the port’s current foreground and background colors at the time of drawing.
To read a multicolored pattern from a resource file, use the GetPixPat routine. Set these patterns using PenPixPat and BackPixPat, or pass them as parameters to Color QuickDraw’s color fill routines (such as FillCRect). These patterns have their own color tables and are generally not affected by the port’s foreground and background colors (refer to the earlier discussion of relative patterns).
Most routines that accept bitMaps as parameters also accept pixMaps (not PixMapHandles). Likewise, any new routine that has a pixMap as a parameter will also accept a bitMap. This allows one set of routines to work for all operations on images; the high bit of the rowBytes field distinguishes whether the parameter is a bitMap or a pixMap.
It’s worth noting here that resources are used slightly differently by Color QuickDraw than they were used by QuickDraw. For instance, with old QuickDraw, your application could call GetCursor before each SetCursor; the same handle would be passed back to the application each time. With Color QuickDraw, the color cursor is a compound structure, more complex than a simple resource handle. Color QuickDraw reads the requested resource, copies it, and then alters the copy before passing it to the application. Each time your application calls GetCCursor, it gets a new copy of the cursor. This means that your program should only call GetCCursor once, even if it does multiple SetCCursor calls. The new resource types should be marked as purgeable if you are concerned about memory space. This discussion holds true for color cursor, color pattern, color icon, and color table resources.
_______________________________________________________________________________
»COLOR QUICKDRAW ROUTINES
_______________________________________________________________________________
Color QuickDraw continues to support all the original QuickDraw calls described in the QuickDraw chapter. The following sections describe in detail the new Color QuickDraw routines, as well as changes to existing routines.
_______________________________________________________________________________
»Operations on CGrafPorts
PROCEDURE OpenCPort (port: CGrafPtr);
The OpenCPort procedure is analogous to OpenPort, except it opens a cGrafPort instead of a grafPort. You will rarely need to use this call, since OpenCPort is called by NewCWindow and GetNewCWindow, as well as by the Dialog Manager when the appropriate color resources are present. OpenCPort allocates storage for all the structures in the cGrafPort, and then calls InitCPort to initialize them. The new structures allocated are the portPixMap, the pnPixPat, the fillPixPat, the bkPixPat, and the grafVars handle. The GrafVars record structure is shown below:
TYPE
GrafVars = RECORD
rgbOpColor: RGBColor; {color for addPin, subPin, and }
{ blend}
rgbHiliteColor: RGBColor; {color for highlighting}
pmFgColor: Handle; {Palette handle for foreground }
{ color}
pmFgIndex: INTEGER; {index value for foreground}
pmBkColor: Handle; {Palette handle for background }
{ color}
pmBkIndex: INTEGER; {index value for background}
pmFlags: INTEGER; {Flags for Palette Manager}
END;
The rgbOpColor field is initialized as black, and the rgbHiliteColor field is initialized as the default HiliteRGB. All the rest of the GrafVars fields are initially zero.
The portPixMap is not allocated a color table of its own. When InitCPort is called, the handle to the current device’s color table is copied into the portPixMap.
PROCEDURE InitCPort (port: CGrafPtr);
The InitCPort procedure does not allocate any storage. It merely initializes all the fields in the cGrafPort to their default values. All old fields are initialized to the same values as a grafPort’s fields. New fields are given the following values:
portPixMap: copied from theGDevice^^.GDPMap
portVersion: $C000
grafVars: opColor initialized to black, rgbHiliteColor
initialized as default HiliteRGB. All other
fields are initialized as 0.
chExtra: 0
pnLocHFrac: 1/2
bkPixPat: white
rgbFgColor: black
rgbBkColor: white
pnPixPat: black
fillPixPat: black
The default portPixMap is set to be the same as the current device’s pixMap. This allows you to create an offscreen port that is identical to the screen’s grafPort or cGrafPort for drawing offscreen. If you want to use a different set of colors for offscreen drawing, you should create a new gDevice, and set it as the current gDevice before opening the cGrafPort. Refer to the section on offscreen bitMaps in the Graphics Devices chapter for more details.
As mentioned above, InitCPort does not copy the data from the current device’s color table to the portPixMap’s color table. It simply replaces whatever is in the pmTable field with a copy of the handle to the current device’s color table.
If you try to initialize a grafPort using InitCPort, it will simply return without doing anything.
PROCEDURE CloseCPort (port: CGrafPtr);
CloseCPort releases the memory allocated to the cGrafPort. It disposes of the visRgn, the clipRgn, the bkPixPat, the pnPixPat, the fillPixPat, and the grafVars handle. It also disposes of the portPixMap, but doesn’t dispose of the portPixMap’s color table (which is really owned by the gDevice). If you have placed your own color table into the portPixMap, either dispose of it before calling CloseCPort, or store another reference to it for other uses.
_______________________________________________________________________________
»Setting the Foreground and Background Colors
PROCEDURE RGBForeColor (color : RGBColor);
PROCEDURE RGBBackColor (color : RGBColor);
These two calls set the foreground and background colors to the best available match for the current device. The only drawing operations that aren’t affected by these colors are PlotCIcon, and drawing using the new color patterns. Before you call CopyBits with a pixMap as the source, you should set the foreground to black and the background to white.
If the current port is a cGrafPort, the specified RGB is placed in the rgbFgColor or rgbBkColor field (and the pixel value most closely matching that color is placed in the fgColor or bkColor field). If the current port is a grafPort, fgColor or bkColor is set to the old QuickDraw color determined by taking the high bit of each of the R, G, and B components, and using that three-bit number to select one of the eight QuickDraw colors. The ordering of the QuickDraw colors is shown in the GetForeColor description.
PROCEDURE GetForeColor (VAR color : RGBColor);
PROCEDURE GetBackColor (VAR color : RGBColor);
These two calls return the RGB components of the foreground and background colors set in the current port. The calls work for both grafPorts and cGrafPorts. If the current port is a cGrafPort, the returned value is taken directly from the rgbFgColor or rgbBkColor field. If the current port is a grafPort, then only eight possible RGB values can be returned. These eight values are determined by the values in a global variable named QDColors, which is a pointer to a color table containing the current QuickDraw colors.
The colors are stored in the following order:
Value Color Red Green Blue
0 black $0000 $0000 $0000
1 yellow $FC00 $F37D $052F
2 magenta $F2D7 $0856 $84EC
3 red $DD6B $08C2 $06A2
4 cyan $0241 $AB54 $EAFF
5 green $0000 $8000 $11B0
6 blue $0000 $0000 $D400
7 white $FFFF $FFFF $FFFF
This is the set of colors that Color QuickDraw uses to determine precisely what colors should be displayed by an old grafPort that is using color. The default set of colors has been adjusted to match the colors produced on the ImageWriter II printer.
_______________________________________________________________________________
»Color Drawing Operations
PROCEDURE FillCRect (r: Rect; ppat: PixPatHandle);
PROCEDURE FillCOval (r: Rect; ppat: PixPatHandle);
PROCEDURE FillCRoundRect (r: Rect; ovWd,ovHt: INTEGER; ppat: PixPatHandle);
PROCEDURE FillCArc (r: Rect; startAngle,arcAngle: INTEGER; ppat: PixPatHandle);
PROCEDURE FillCRgn (rgn: RgnHandle; ppat: PixPatHandle);
PROCEDURE FillCPoly (poly: PolyHandle; ppat: PixPatHandle);
These calls are analogous to their similarly named counterparts in QuickDraw. They allow a multicolored pattern to be used for filling.
PROCEDURE GetCPixel (h,v: INTEGER; VAR cPix: RGBColor);
The GetCPixel function returns the RGB of the pixel at the specified position in the current port.
PROCEDURE SetCPixel (h,v: INTEGER; cPix: RGBColor);
The SetCPixel function sets the pixel at the specified position to the pixel value that most closely matches the specified RGB.
_______________________________________________________________________________
»Creating Pixel Maps
FUNCTION NewPixMap : PixMapHandle;
The NewPixMap function creates a new, initialized pixMap data structure and returns a handle to it. All fields of the pixMap are copied from the current device’s pixMap except the color table. A handle to the color table is allocated but not initialized.
PROCEDURE DisposPixMap (pm: PixMapHandle);
The DisposPixMap procedure releases all storage allocated by NewPixMap. It disposes of the pixMap’s color table, and of the pixMap itself. Be careful not to dispose of a pixMap whose color table is the same as the current device’s color table.
PROCEDURE CopyPixMap (srcPM,dstPM: PixMapHandle);
The CopyPixMap routine is used for duplicating the pixMap data structure. CopyPixMap copies the contents of the source pixMap data structure to the destination pixMap data structure. The contents of the color table are copied, so the destination pixMap has its own copy of the color table. Since the baseAddr field of the pixMap is a pointer, the pointer, but not the image itself, is copied.
_______________________________________________________________________________
»Operations on Pixel Maps
PROCEDURE CopyBits (srcBits,dstBits: BitMap; srcRect, dstRect: Rect;
mode: INTEGER; maskRgn: RgnHandle);
CopyBits now accepts either bitMaps or pixMaps as parameters. For convenience, just as you could pass the current port^.portBits as a parameter to CopyBits, you can now pass GrafPtr(cPort)^.portBits. (Recall that in a cGrafPort the high two bits of the portVersion field are set. This field, in the same position in the port as portBits.rowBytes, indicates to QuickDraw that it has been passed a portPixMap handle.)
This call transfers an image from one bitMap or pixMap to another bitMap or pixMap. The source and destination may be of different depths, of different sizes, and they may have different color tables. Note, however, that the destination pixMap is assumed to use the same color table as the gDevice.
(This is because an inverse table is required for translation to the destination’s color table.)
During a CopyBits call, the foreground and background colors are applied to the image. To avoid unwanted coloring of the image, set the foreground to black and the background to white before calling this routine.
PROCEDURE CopyMask (srcBits,maskBits,dstBits: BitMap;
srcRect,maskRect,dstRect: Rect);
CopyMask is a new version of the CopyBits procedure, introduced in the Macintosh Plus. It transfers an image from the source to the destination only where the corresponding bit of the mask equals 1. The Macintosh II version will accept either a bitMap or pixMap as the srcBits or dstBits parameters. The maskBits parameter must be a bitMap.
Like the Macintosh Plus version, CopyMask doesn’t send any of its drawing commands through grafProc routines; thus CopyMask calls are not recorded in pictures. Unlike the Macintosh Plus version, the Macintosh II version of CopyMask is able to stretch the source and mask to fit the dstRect. The srcRect and maskRect should be the same size. CopyMask uses the same low-level code as CopyBits, so all the same rules regarding depth translation and color table translation apply.
During a CopyMask call, the foreground and background colors are applied to the image. To avoid unwanted coloring, set the foreground to black and the background to white before calling this routine.
PROCEDURE SeedCFill (srcBits, dstBits: BitMap; srcRect, dstRect: Rect;
seedH, seedV: INTEGER; matchProc: ProcPtr;
matchData: LONGINT);
The SeedCFill procedure generates a mask for use with CopyMask or CopyBits, with bits equal to 1 only in those positions where paint can leak from the starting seed point, like the MacPaint® bucket tool.
Given a rectangle within a source bitMap or pixMap (srcBits), SeedCFill returns a mask (dstBits) that contains 1’s in place of all pixels to which paint can leak from the specified seed position (seedH, seedV), expressed in the local coordinate system of the source pixMap. By default, paint can leak to all adjacent pixels whose RGB value exactly match that of the seed. To use this default, set matchProc and matchData to zero.
In generating the mask, SeedCFill performs CopyBits to convert srcBits to a
one-bit mask. It installs a default searchProc into the gDevice that returns 0 if the RGB value matches that of the seed; all other RGB values return 1’s.
If you want to customize SeedCFill, your application can specify a matchProc that is used instead of the default searchProc. It should return 0’s for RGB values that you want to be filled, and 1’s for values that shouldn’t be filled. When the matchProc is called, the GDRefCon field of the current gDevice contains a pointer to a record having the following structure:
MatchRec = RECORD
red: INTEGER;
green: INTEGER;
blue: INTEGER;
matchData: LONGINT
END;
In this record the red, green, and blue fields are the RGB of the pixel at the specified seed location. MatchData is simply whatever value you passed to SeedCFill as a parameter. For instance, your application could pass a handle to a color table whose entries should all be filled, and then, in the matchProc, check to see if the specified RGB matches any of the colors in the table.
No automatic scaling is performed: the source and destination rectangles must be the same size. Calls to SeedCFill are not clipped to the current port and are not stored into QuickDraw pictures.
PROCEDURE CalcCMask (srcBits, dstBits: BitMap; srcRect, dstRect: Rect;
seedRGB: RGBColor; matchProc: ProcPtr; matchData: LONGINT);
This routine generates a mask (dstBits) corresponding to the area in a pixMap
(srcBits) to which paint cannot leak from outside of the srcRect. The size of srcRect must be the same as the size of dstRect. By default, paint can leak to all adjacent pixels whose RGB values don’t match that of the seedRGB. To use this default, set matchProc and matchData to 0.
For instance, if srcBits contains a blue rectangle on a red background, and your application calls CalcCMask with the seedRGB equal to blue, then the returned mask has ones in the positions corresponding to the edges and interior of the rectangle, and zeros outside of the rectangle.
If you want to customize CalcCMask, your application can specify a matchProc that is used instead of the default searchProc. It should return 1’s for RGB values that define the edges of the mask, and 0’s for values that don’t.
When the matchProc is called, the GDRefCon field of the gDevice contains a pointer to a MatchRec record (the structure shown in the SeedCFill description). The red, green, and blue fields are the RGB of the pixel at the specifed seed location. MatchData is simply whatever value your application passed to CalcCMask as a parameter. For instance, your program could pass a handle to a color table whose entries should all be within the mask, and then, in the matchProc, check to see if the specified RGB matches any of the colors in the table.
No automatic scaling is performed: the source and destination rectangles must be the same size. Calls to CalcCMask are not clipped to the current port and are not stored into QuickDraw pictures.
_______________________________________________________________________________
»Operations on Pixel Patterns
FUNCTION NewPixPat: PixPatHandle;
The NewPixPat function creates a new pixPat data structure, and returns a handle to it. It calls NewPixMap to allocate and initialize the pattern’s pixMap to the same settings as theGDevice^^.GDPMap, and it sets the type of the pixPat to be a color pattern. The pat1Data field is initialized to a 50% gray pattern. New handles for data, expanded data, expanded map, and color table are allocated but not initialized. Including the pixPat itself, it allocates a total of six handles. You will generally not need to use this routine since the GetPixPat routine can be used to read in a pattern from a resource file.
The sizes of the pixMap and pixPat handles are the size of their respective data structures (see the type declarations in the “Summary” section). The other three handles are initially small in size. Once the pattern is drawn, the size of the expanded data is proportional to the size of the pattern data, but adjusted to the depth of the screen. The color table size is the size of the record structure plus eight bytes times the number of colors in the table.
»Creating a PixPat
To create a color pattern, use NewPixPat to allocate a new PixPatHandle. Set the rowBytes, bounds, and pixelSize of the pattern’s pixMap to the dimensions of the desired pattern. The rowBytes should be equal to (width of bounds)*pixelSize/8; it need not be even. The width and height of the bounds must be a power of two. Each scanline of the pattern must be at least one byte in length—that is, (width of bounds)*pixelSize must be at least eight. Set the other fields in the pattern’s pixMap as described in the section on the pixMap data structure.
Your application can explicitly specify the color corresponding to each pixel value with the color table. The color table for the pattern must be placed in the pmTable in the pixPat’s pixMap. Patterns may also contain colors that are relative to the foreground and background at the time that they are drawn. Refer to the section on the pixPat data structure for more information on relative patterns.
PROCEDURE DisposPixPat (ppat: PixPatHandle);
The DisposPixPat procedure releases all storage allocated by NewPixPat. It disposes of the pixPat’s data handle, expanded data handle, and pixMap handle.
PROCEDURE CopyPixPat (srcPP,dstPP: PixPatHandle);
The CopyPixPat procedure copies the contents of the source pixPat to the destination pixPat. It entirely copies all fields in the source pixPat, including the contents of the data handle, expanded data handle, expanded map, pixMap handle, and color table.
FUNCTION GetPixPat (patID: INTEGER): PixPatHandle;
The GetPixPat call creates a new pixPat data structure, and then uses the information in the resource of type 'ppat' and the specified ID to initialize the pixPat. The 'ppat' resource format is described in the section “Color QuickDraw Resource Formats”. If the resource with the specified ID is not found, then this routine returns a NIL handle.
PROCEDURE MakeRGBPat (ppat: PixPatHandle; myColor: RGBColor);
The MakeRGBPat procedure is a new call which generates a pixPat that approximates the specified color when drawn. For example, if your application is drawing to a device that has 4 bits per pixel, you will only get 16 colors if you simply set the foreground color and draw. If you use MakeRGBPat to select a pattern, and then draw using that pattern, you will effectively get 125 different colors. More colors are theoretically possible; this implementation opted for a fast pattern selection rather than the best possible pattern selection. If the device has 8 bits per pixel, you will effectively get 2197 colors.
Note that these patterns aren’t usually solid; they provide a wide selection of colors by alternating between colors with up to four colors in a pattern. For this reason lines that are one pixel wide may not look good using these patterns. For an RGB pattern, the patMap^^.bounds always contains (0, 0, 8, 8), and the patMap^^.rowbytes equals 2. Figure 6 shows how these colors are arranged.
When MakeRGBPat creates a color table, it only fills in the last colorSpec field: the other colorSpec values are computed at the time the drawing actually takes place, using the current pixel depth for the system.
Value RGB
0 computed RGB color
1 computed RGB color
2 computed RGB color
3 computed RGB color
4 RGBColor passed to MakeRGBPat routine
Figure 6–RGB Pattern
PROCEDURE PenPixPat (ppat: PixPatHandle);
PROCEDURE BackPixPat (ppat: PixPatHandle);
The PenPixPat and BackPixPat calls are analogous to PenPat and BackPat, but use multicolor pixel patterns instead of old-style patterns. If you try to use a pixel pattern in a grafPort, the data in the pat1Data field is placed into pnPat, bkPat, or fillPat.
When your application sets a pixel pattern, the handle you provide is actually placed into the grafPort or cGrafPort. In this way, QuickDraw can expand the pattern once (saving it in the patXData field) when the pattern is first set, and won’t have to reexpand it each time you set the pattern.
Since your handle is actually stored in the grafPort or cGrafPort, it’s considered bad form to dispose of a PixPatHandle that is currently set as the pnPixPat or bkPixPat. (Just in case you forget, QuickDraw will remove all references to your pattern from existing grafPorts or cGrafPorts when you dispose of it.)
Using the old calls PenPat and BackPat, you can still set old-style patterns in a cGrafPort. If necessary, it creates a new pixPatHandle in which to store the pattern (because, as described above, pixPatHandles are owned by the application). As in old grafPorts, old-style patterns are drawn using the foreground and background colors at the time of drawing, not at the time the pattern is set.
_______________________________________________________________________________
»Operations on Color Cursors
FUNCTION GetCCursor (crsrID: INTEGER): CCrsrHandle;
The GetCCursor call creates a new CCrsr data structure, then initializes it using the information in the resource of type 'crsr' with the specified ID. The 'crsr' resource format is described in the section “Color QuickDraw Resource Formats”. If the resource with the specified ID isn’t found, then this routine returns a NIL handle.
Since GetCCursor creates a new CCrsr data structure each time it is called, your application shouldn’t call GetCCursor before each call to SetCCursor
(unlike the way GetCursor/SetCursor were normally used). GetCCursor doesn’t dispose or detach the resource, so resources of type 'crsr' should typically be purgeable.
PROCEDURE SetCCursor (cCrsr: CCrsrHandle);
The SetCCursor procedure allows your application to set a multicolor cursor. At the time the cursor is set, it’s expanded to the current screen depth so that it can be drawn rapidly.
If your application has changed the cursor’s data or its color table, it must also invalidate the fields crsrXValid and crsrID (described in the section on the Color Cursor data structure), before calling SetCCursor.
PROCEDURE DisposCCursor(cCrsr: CCrsrHandle);
The DisposCCursor procedure disposes all structures allocated by GetCCursor.
PROCEDURE AllocCursor;
The AllocCursor procedure reallocates cursor memory. Under normal circumstances, you should never need to use this call, since reallocation of cursor memory is only necessary after the depth of one of the screens has been changed.
_______________________________________________________________________________
»Operations on Color Icons
FUNCTION GetCIcon(id: INTEGER): CIconHandle;
The GetCIcon function allocates a CIcon data structure and initializes it using the information in the resource of type 'cicn' with the specified ID. It returns the handle to the icon’s data structure. If the specified resource
isn’t found, a NIL handle is returned.
The format of the 'cicn' resource is described in the section “Color QuickDraw Resource Formats”.
Since GetCIcon creates a new CIcon data structure each time it is called, your application shouldn’t call GetCIcon before each call to PlotCIcon. GetCIcon doesn’t dispose or detach the resource, so resources of type 'cicn' should typically be purgeable.
PROCEDURE DisposCIcon(theIcon: CIconHandle);
The DisposCIcon procedure disposes all structures allocated by GetCIcon.
PROCEDURE PlotCIcon(theRect: Rect; theIcon: CIconHandle);
The PlotCIcon procedure draws the specified icon in the specified rectangle. The iconMask field of the CIcon determines which pixels of the iconPMap are drawn and which are not. Only pixels with 1’s in corresponding positions in the iconMask are drawn; all other pixels don’t affect the destination. If the screen depth is one or two bits per pixel, the iconBMap is used as the source instead of the iconPMap (unless the rowBytes field of iconBMap is 0, indicating that there is no iconBMap.
When the icon is drawn, the boundsRect of the iconPMap is used as the image’s source rectangle. The icon and its mask are both stretched to the destination rectangle. The icon’s pixels are remapped to the current depth and color table, if necessary. The bounds fields of the iconPMap, iconBMap, and iconMask are expected to be equal in size.
PlotCIcon is simply a structured call to CopyMask. As such, it doesn’t send any of its drawing commands through grafProc routines; thus, PlotCIcon calls are not recorded in pictures.
_______________________________________________________________________________
»Operations on CGrafPort Fields
PROCEDURE SetPortPix (pm: PixMapHandle);
The SetPortPix call is analogous to SetPortBits, and should be used instead of SetPortBits for cGrafPorts. It replaces the portPixMap field of the current cGrafPort with the specified handle. SetPortPix has no effect when used with an old grafPort. If SetPortBits is called when the current port is a cGrafPort, it does nothing.
PROCEDURE OpColor (color: RGBColor);
If the current port is a cGrafPort, the OpColor procedure sets the red, green, and blue values used by the AddPin, SubPin, and Blend drawing modes. This information is actually stored in the grafVars handle in the cGrafPort, but you should never need to reference it directly. If the current port is a grafPort, OpColor has no effect.
PROCEDURE HiliteColor (color:RGBColor);
The highlight color is used by all drawing operations that use the highlight transfer mode. When a cGrafPort is created, its highlight color is initialized from the global variable HiliteRGB. The HiliteColor procedure allows you to change the highlighting color used by the current port. This information is actually stored in the grafVars handle in the cGrafPort, but you should never need to reference it directly. If the current port is a grafPort, HiliteColor has no effect.
PROCEDURE CharExtra (extra:Fixed);
The CharExtra procedure sets the cGrafPort’s charExtra field, which specifies the number of pixels by which to widen every character excluding the space character in a line of text. The charExtra field is stored in a compressed format based on the txSize field, so you must set txSize before calling CharExtra. The initial charExtra setting is 0. CharExtra will accept a negative number. CharExtra has no effect on grafPorts.
PROCEDURE SetStdCProcs (VAR cProcs: CQDProcs);
This procedure sets all the fields of the given CQDProcs record to point to the standard low-level routines. You can then change the ones you wish to point to your own routines. For example, if your procedure that processes picture comments is named MyComments, you will store @MyComments in the commentProc field of the CQD Procs record.
When drawing in a cGrafPort, your application must always use SetStdCProcs instead of SetStdProcs.
_______________________________________________________________________________
»Operations on Color Tables
FUNCTION GetCTable (ctID: INTEGER): CTabHandle;
The GetCTable routine allocates a new color table data structure, and initializes it using the information in the resource of type 'clut' having the specified ID. If the specified resource is not found, a NIL handle is returned.
If you place this handle into a pixMap, you should first dispose of the handle that was already there.
The format of the 'clut' resource is given in the section “Color QuickDraw Resource Formats”. Resource ID values 0..127 are reserved for system use. Any
'clut' resources defined by your application should have IDs in the range
128..1023. This value must be in the ctSeed field in the resource, and will be placed in the ctSeed field of the color table (for color table identification). All other possible seed values are used to identify newly created color tables, and color tables that have been modified.
If you modify a color table, you should invalidate it by changing its ctSeed field. You can get a new unique value for ctSeed using the routine GetCTSeed, described in the Color Manager chapter.
PROCEDURE DisposCTable(cTable: CTabHandle);
The DisposCTable procedure disposes the handle allocated for a color table.
_______________________________________________________________________________
»COLOR QUICKDRAW RESOURCE FORMATS
_______________________________________________________________________________
Several new resource types have been defined for use with Color QuickDraw. They are
'crsr' Color cursor resource type
'ppat' Pixel Pattern resource type
'cicn' Color Icon resource type
'clut' Color Look-Up Table resource type
The precise formats of resources of these types are given below.
It is important to note that resources are used somewhat differently by Color QuickDraw. For instance, with old QuickDraw, you could do a GetCursor for each SetCursor, and the same handle would be passed back to the application each time. With Color QuickDraw, the color cursor, icon, and pattern are compound structures, more complex than a simple resource handle. Color QuickDraw reads the requested resource, copies it, and then alters the copy before passing it to the application. Each time you call GetCCursor, you get a new copy of the cursor. This means that you should do one GetCCursor call for a cursor, even if you do multiple SetCCursor calls. These new resource types should be marked as purgeable if you are concerned about memory space.
Here are the resource formats of the resources used by Color QuickDraw. All offsets are measured from the beginning of the resource’s data.
»'crsr' (Color Cursor)
CCrsr {data structure describing cursor}
crsrType: [2 bytes] = $8001
crsrMap: [4 bytes] = offset to pixMap structure
crsrData: [4 bytes] = offset to pixel data
crsrXData: [4 bytes] = 0
crsrXValid: [2 bytes] = 0
crsrXHandle: [4 bytes] = 0
crsr1Data: [32 bytes] = 1 bit image for cursor
crsrMask: [32 bytes] = cursor’s mask
crsrHotSpot: [4 bytes] = cursor’s hotSpot (v,h)
crsrXTable: [4 bytes] = 0
crsrID: [4 bytes] = 0
PixMap {pixMap describing cursor’s pixel image}
baseAddr: [4 bytes] = 0
rowBytes: [2 bytes] = rowBytes of image
bounds: [8 bytes] = boundary rectangle of image
pmVersion: [2 bytes] = 0
packType: [2 bytes] = 0
packSize: [4 bytes] = 0
hRes: [4 bytes] = $00480000
vRes: [4 bytes] = $00480000
pixelType: [2 bytes] = 0 = chunky
pixelSize: [2 bytes] = bits per pixel in image
cmpCount: [2 bytes] = 1
cmpSize: [2 bytes] = pixelsize
planeBytes: [4 bytes] = 0
pmTable: [4 bytes] = offset to color table data
pmReserved: [4 bytes] = 0
pixel data [see below] data for cursor
color table data [see below] data for color table
The crsrMap field of the CCrsr record contains an offset to the pixMap record from the beginning of the resource data. The crsrData field of the CCrsr record contains an offset to the pixel data from the beginning of the resource data. The pmTable field of the pixMap record contains an offset to the color table data from the beginning of the resource data. The size of the pixelData is calculated by subtracting the offset to the pixel data from the offset to the color table data. The color table data consists of a color table record
(ctSeed, ctFlags, ctSize) followed by ctSize+1 color table entries. Each entry in the color table connects a pixel value used in the pixel data to an actual RGB.
»'ppat' (Pixel Pattern)
PixPat record {data structure describing pattern}
patType [2 bytes] = 1 (full color pattern)
patMap [4 bytes] = offset to pixMap record
patData [4 bytes] = offset to pixel data
patXData [4 bytes] = 0
patXValid [2 bytes] = –1
patXMap [4 bytes] = 0
pat1Data [8 bytes] = 1 bit pattern data
PixMap { pixMap describing pattern’s pixel image }
baseAddr [4 bytes] = 0
rowBytes [2 bytes] = rowBytes of image
bounds [8 bytes] = boundary rectangle of image
pmVersion [2 bytes] = 0
packType [2 bytes] = 0
packSize [4 bytes] = 0
hRes [4 bytes] = $00480000
vRes [4 bytes] = $00480000
pixelType [2 bytes] = 0 = chunky
pixelSize [2 bytes] = bits per pixel in image
cmpCount [2 bytes] = 1
cmpSize [2 bytes] = pixelsize
planeBytes [4 bytes] = 0
pmTable [4 bytes] = offset to color table data
pmReserved [4 bytes] = 0
pixel data [see below] data for pattern
color table data [see below] data for color table
The patMap field of the pixPat record contains an offset to the pixMap record from the beginning of the resource data. The patData field of the pixPat record contains an offset to the pixel data from the beginning of the resource data. The pmTable field of the pixMap record contains an offset to the color table data from the beginning of the resource data. The size of the pixelData is calculated by subtracting the offset to the pixel data from the offset to the color table data. The color table data consists of a color table record
(ctSeed, ctFlags, ctSize) followed by ctSize+1 color table entries. Each entry in the color table connects a pixel value used in the pixel data to an actual RGB.
»'cicn' (Color Icon)
IconPMap {pixMap describing icon’s pixel image}
baseAddr [4 bytes] = 0
rowBytes [2 bytes] = rowBytes of image
bounds [8 bytes] = boundary rectangle of image
pmVersion [2 bytes] = 0
packType [2 bytes] = 0
packSize [4 bytes] = 0
hRes [4 bytes] = $00480000
vRes [4 bytes] = $00480000
pixelType [2 bytes] = 0 = chunky
pixelSize [2 bytes] = bits per pixel in image
cmpCount [2 bytes] = 1
cmpSize [2 bytes] = pixelsize
planeBytes [4 bytes] = 0
pmTable [4 bytes] = 0
pmReserved [4 bytes] = 0
IconMask {Mask used when drawing icon}
baseAddr [4 bytes] = 0
rowBytes [2 bytes] = rowBytes of image
bounds [8 bytes] = boundary rectangle of image
IconBMap {Image used when drawing to 1 bit screen}
baseAddr [4 bytes] = 0
rowBytes [2 bytes] = rowBytes of image
bounds [8 bytes] = boundary rectangle of image
IconData {placeholder for image’s handle}
[4 bytes] = 0
MaskData {the icon’s mask data }
[n bytes] n = IconMask.rowBytes*height
BMapData {the icon’s bitMap data }
[n bytes] n = IconBMap.rowBytes*height
PMapCTab {the icon’s color table }
[n bytes] n = 8+(ColorTable.ctSize+1)*CTEntrySize
PMapData {the icon’s image data }
[n bytes] n = IconPMap.rowBytes*height
In the calculations above:
height = IconPMap^^.bounds.bottom–IconPMap^^.bounds.top.
IconPMap is the pixMap describing the data in the IconData field. IconMask is the mask that is to be applied to the data when it is drawn. IconBMap is a bitMap to be drawn when the destination is only one or two pixels deep. If the rowbytes field of IconBMap is 0, then no data is loaded in for the IconBMap, and IconPMap is always used when drawing the icon. MaskData is the mask’s data. It is immediately followed by the bitMap’s data (which may be NIL). Next is the color table describing the IconPMap, as shown below. The final entry in the resource is the pixMap’s data.
»'clut' (Color Table)
ctSeed [4 bytes] = 0
ctFlags [2 bytes] = $0000 if pixMap color table
= $8000 if device color table
ctSize [2 bytes] = #entries – 1
table data [n bytes] n = 8*(ctSize+1)
The 'clut' resource format is an exact duplicate of a color table in memory. Each element in the table data is four integers (eight bytes): a value field followed by red, green, and blue values. If the color table is used to describe a pixMap, then ctFlags should be set to 0, and the value field of each entry contains the pixel value to be associated with the following RGB. If the color table is used to describe a device, then ctFlags should be set to $8000, and the value fields should be set to 0. In this case, the implicit values are based on each entry’s position in the table.
There are several default color tables that are in the Macintosh II ROMs. There is one for each of the standard pixel depths. The resource ID for each table is the same as the depth. For example, the default color table used when you switch your system to 8 bits per pixel mode is stored with resource ID = 8.
There is one other default color table. This color table defines the eight QuickDraw colors, the colors displayed by programs using the old QuickDraw model. This color table has ID = 127. Its values are given in the section
“Setting the Foreground and Background Colors”.
_______________________________________________________________________________
»USING TEXT WITH QUICKDRAW
_______________________________________________________________________________
This section explains those QuickDraw features which provide enhanced text handling for the Macintosh Plus, Macintosh SE, and Macintosh II. The drawing mode recommended for all applications is SrcOr, because it uses the least memory and will draw the entire character in all cases. The SrcOr mode will only affect other parts of existing characters if the characters overlap. In srcOr mode the color of the character is determined by the foreground color, although text drawing is fastest when the foreground color is black.
With QuickDraw, characters can kern to the left and to the right. QuickDraw begins drawing a series of characters at the specified pen position plus the kernMax field (part of the Font record), plus any kerning below the baseline caused by italicizing the font. (The kernMax field denotes the kerning allowed by a given font; since its value is normally negative, most fonts kern to the left. Italicizing also normally moves the pen to the left.) QuickDraw then draws through the ending pen position, plus any kerning above the baseline caused by italicizing the font (normally to the right), plus any space required to handle the outlined or shadowed part of the character.
To draw text in any mode, including the kerned part of the leading and trailing characters, it is best to draw the entire line of text at once. If the line must be drawn in pieces, it is best to end each piece with a space character, so that the succeeding piece can harmlessly kern left, and the last character drawn (a space) will not have any right kerning clipped.
Macintosh Plus and Macintosh SE Note: The Macintosh Plus and Macintosh SE
versions of QuickDraw clip a leading
left-kerning character, and do not take
italicizing into account when
positioning the pen. Also, it adds a
constant of 32 to the width of the
character imaging rectangle, causing
large italicized fonts to have the
rightmost character clipped in drawing
modes other than srcOr.
The outline and shadow styles cause the outline and shadow of the character to be drawn in the foreground color. The inside of the character, if drawn at all, is drawn in the background color. The center of shadowed or outlined text is drawn in a grafPort in scrBic mode if the text mode is srcOr, for compatibility with old applications. This allows black text with a white outline on an arbitrary background. If the text mode is srcBic, the center of shadowed or outlined text is drawn in srcOr.
The style underline draws the underline through the entire text line, from the pen starting position through the ending position, plus any offsets from font or italic kerning, as described above. If the underline is outlined or shadowed, the ends aren’t capped, that is, consecutively drawn pieces of text should maintain a continuous underline.
Macintosh Plus and Macintosh SE Note: QuickDraw clips the right edge of the
underline to the ending pen position,
causing outlined or shadowed underlines
to match imperfectly when text is drawn
in sections.
One of the reasons that SrcOr is recommended is that the maximum stack space required for a text font drawing operation can be considerable. Text drawing uses a minimum amount of stack if the mode is srcOr, the forecolor is black, the visRgn and clipRgn are rectangular (or at least the destination of the text is contained within a rectangular portion of the visRgn), the text is not scaled, and the text does not have to be italicized, boldfaced, outlined, or shadowed by QuickDraw. Otherwise, the amount of stack required to draw all of the text at once depends most on the size and width of the the text and the depth of the destination.
If QuickDraw can’t get enough stack space to draw an entire string at once, it will draw the string in pieces. This can produce disconcerting results in modes other than srcOr or srcBic if some of the characters overlap because of kerning or italicizing. If the mode is srcCopy, overlapping characters will be clipped by the last drawn character. If the mode is srcXor, pixels where the characters overlap are not drawn at all. If the mode is one of the arithmetic modes, the arithmetic rules are followed, ignoring that the destination may include part of the string being drawn.
The stack space required for a drawing operation on the Macintosh II is roughly given by this calculation:
(text width) * (text height) * (font depth) / (8 bits per byte ) + 3K
Font depth normally equals the screen depth. If the amount of stack space available is small (less than 3.5K), QuickDraw instead uses a font depth of 1, which is slow, but uses less stack space.
On the Macintosh Plus, the required stack space is roughly equal to
(text width) * (text height) / (8 bits per byte ) + 2K
_______________________________________________________________________________
»Text Mask Mode
For the Macintosh II, the maskConstant may be added to another drawing mode to cause just the character portion of the text to be applied in the current transfer mode to the destination. If the text font contains more than one color, or if the drawing mode is an arithmetic mode or hilite mode, the mask mode causes only the portion of the characters not equal to the background to be drawn.
The arithmetic drawing modes and hilite mode apply the character’s background to the destination; this can lead to undesirable results if the text is drawn in pieces. The leftmost part of a text piece is drawn on top of a previous text piece if the font kerns to the left. The maskMode supplied in addition to these modes causes only the foreground part of the character to be drawn. The only reasonable way to kern to the right in text mask mode is to use srcOr, or to add trailing characters. This is because the rightmost kern is clipped.
The constant used with maskMode is
CONST
mask = 64;
_______________________________________________________________________________
»Drawing with Multibit Fonts
Multibit fonts may have a specific color. The transfer modes may not produce the desired results with a multibit font. The arithmetic modes, transparent mode, and hilite mode work equally well with single bit and multibit fonts.
Unlike single bit fonts, multibit fonts draw quickly in srcOr only if the foreground is white. Single bit fonts draw quickly in srcOr only if the foreground is black. Grayscale fonts produce a spectrum of colors, rather than just the foreground and background colors.
_______________________________________________________________________________
»Fractional Character Positioning
CGrafPorts maintain the fractional horizontal pen position, so that a series of text drawing calls will accumulate the fractional position. The horizontal pen fraction is initially set to 1/2. InitPort, Move, MoveTo, Line and LineTo reset the pen position to 1/2. For an old grafPort, the pen fraction is hard-coded to 1/2.
_______________________________________________________________________________
»COLOR PICTURE FORMAT
_______________________________________________________________________________
With the introduction of the Macintosh II, the QuickDraw picture structure has been extended to include new color graphics opcodes. The new version 2 pictures and opcodes solve many of the major problems encountered by developers in using PICT files, and enable future expandibility. For example, it is now possible to specify the resolution of bitMap data. Color can also be specified, but only chunky pixels (contiguously stored pixel components) are currently recognized by Color QuickDraw. Your application only needs to generate or recognize the chunky pixel format. This format is indicated by an image or pixMap with a cmpCount = 1.
Most existing applications can use version 2 pictures without modification. On a Macintosh II, version 2 pictures will draw in color (if drawn directly to the screen). Currently, they will print using the old QuickDraw colors. Eventually, new print drivers will be able to take advantage of the new color information.
On a Macintosh 512K enhanced, Macintosh Plus, and Macintosh SE, a patch in the System file beginning with version 4.1 provides QuickDraw with the capability to convert and display version 2 pictures. The original Macintosh and Macintosh 512 can’t display version 2 pictures.
Applications that generate pictures in the QuickDraw picture format are free to use any or all available features to support their particular needs. Some will use only the imaging features. You may wish to include comments in the picture that are pertinent to the needs of your application. In general, put a minimal amount of information in your PICT files and avoid redundancy. It’s reasonable for receiving applications to ignore picture opcodes that aren’t needed.
_______________________________________________________________________________
»Differences Between Version 1 and Version 2 Pictures
The major differences between version 1 and version 2 pictures are listed below.
• Version 1 opcodes are a single byte; version 2 opcodes are 2 bytes in
length. This means that old opcodes in a version 2 picture take up two
bytes, not one.
• Version 1 data may start on byte boundaries; version 2 opcodes and data
are always word-aligned.
• In version 2, the high bit of the rowBytes field is used to indicate a
pixMap instead of a bitMap; pixData then replaces bitData.
• All unused version 2 opcodes, as well as the number of data bytes
associated with each, have been defined. This was done so that picture
parsing code can safely ignore unknown opcodes, enabling future use
of these opcodes in a backward-compatible manner.
_______________________________________________________________________________
»Drawing With Version 2 Pictures in Old GrafPorts
Enhancements to the DrawPicture routine allow pictures created with Color QuickDraw to be used in either a cGrafPort or an old-style grafPort. You can create a picture using the new drawing commands in a cGrafPort, cut it, and then paste it into an application that draws into an old grafPort. The picture will lose some of its detail when transferred in this way, but should be sufficient for most purposes. The following considerations apply to the use of this technique:
• The rgbFgColor and rbgBkColor fields are mapped to the old-style
Quickdraw constant (one of eight) that most closely approximates that
color. For a grafPort with depth greater than one, even old applications
will be able to draw color pictures.
• Patterns created using MakeRGBPat are drawn as old-style patterns having
approximately the same luminance as the original pattern.
• Other new patterns are replaced by the old-style pattern contained
in the pat1Data field of the PixPat data structure. This field is
initialized to 50% gray by the NewPixPat routine, and is initialized
from the resource in a GetPixPat call.
• PixMaps in the picture are drawn without interpretation. The CopyBits
call performs all necessary mapping to the destination screen. If
the picture is drawn on a Macintosh Plus or a Macintosh SE, or if the
BitsProc routine has been replaced by the application, the pixMap is
converted to a bitMap before it’s drawn.
• Changes to the ChExtra and pnLocHFrac fields, and the Hilite color
and OpColor, are ignored.
A new standard opcodeProc, SetStdCProc, is called by QuickDraw when it is playing back a color picture and it sees a new opcode that it doesn’t recognize. The default routine simply reads and ignores all undefined opcodes.
_______________________________________________________________________________
»Picture Representation
The PICT file is a data fork file with a 512-byte header, followed by a picture (see Figure 7). This data fork file contains a QuickDraw (and now, Color QuickDraw) data structure within which a graphic application, using standard QuickDraw calls, places drawing opcodes to represent an object or image graphic data. In the QuickDraw picture format, pictures consist of opcodes followed by picture data.
Figure 7–PICT file format.
_______________________________________________________________________________
»Picture Parsing
The first 512 bytes of a PICT data file contain application-specific header information. Each QuickDraw (and Color QuickDraw) picture definition consists of a fixed-size header containing information about the size, scaling, and version of the picture, followed by the opcodes and picture data defining the objects drawn between the OpenPicture and ClosePicture calls.
When the OpenPicture routine is called and the port is an old grafPort, a version 1 picture is opened. When the OpenPicture routine is called and the port is a cGrafPort, then a version 2 picture is opened. If any fields in the grafPort are different than the default entries, those fields that are different get recorded in the picture.
Version 4.1 of the Macintosh System file incorporates a patch to QuickDraw that will enable QuickDraw (on machines with 128K or larger ROMs) to parse a version 2 PICT file, read it completely, attempt to convert all Color QuickDraw color opcodes to a suitable black-and-white representation, and draw the picture in an old grafPort. If you are trying to display a version 2 picture on a Macintosh without the system patch, QuickDraw won’t be able to draw the picture.
_______________________________________________________________________________
»Picture Record Structure
The Pascal record structure of version 1 and version 2 pictures is exactly the same. In both, the picture begins with a picSize, then a picFrame (rect), followed by the picture definition data. Since a picture may include any sequence of drawing commands, its data structure is a variable-length entity. It consists of two fixed-length fields followed by a variable-length field:
TYPE Picture = RECORD
picSize: INTEGER; {low order 16 bits of picture }
{ size}
picFrame: Rect; {picture frame, used as }
{ reference for scaling when }
{ the picture is drawn }
{picture definition data}
END;
To maintain compatibility with the original picture format, the picSize field has not been changed in version 2 pictures. However, the information in this field is only useful if your application supports version 1 pictures not exceeding 32K bytes in size. Because pictures can be much larger than the 32K limit imposed by the 2-byte picSize field, use the GetHandleSize call to determine picture size if the picture is in memory or the file size returned in pBFGetInfo if the picture resides in a file.
The picFrame field is the picture frame that surrounds the picture and gives a frame of reference for scaling when the picture is played back. The rest of the structure contains a compact representation of the image defined by the opcodes. The picture definition data consists of a sequence of the opcodes listed in Table 3 in the Pict Opcodes section, each followed by zero or more bytes of data. Every opcode has an implicit or explicit size associated with it that indicates the number of data bytes following that opcode, ranging from 2 to
2^32 bytes (this maximum number of bytes applies to version 2 pictures only).
_______________________________________________________________________________
»Picture Spooling
In the past, images rarely exceeded the 32K practical limit placed on resources. Today, with the advent of scanners and other image input products, images may easily exceed this size. This increase in image size necessitates a means for handling pictures that are too large to reside entirely in memory. One solution is to place the picture in the data fork of a PICT file, and spool it in as needed. To read the file, an application can simply replace the QuickDraw default getPicProc routine with a procedure (getPICTData) that reads the picture data from a disk file; the disk access would be transparent. Note that this technique applies equally to version 1 (byte-opcode) and version 2
(word-opcode) pictures.
»Spooling a Picture From Disk
In order to display pictures of arbitrary size, an application must be able to import a QuickDraw picture from a file of type PICT. (This is the file type produced by a Save As command from MacDraw® with the PICT option selected.) What follows is a small program fragment that demonstrates how to spool in a picture from the data fork of a PICT file. The picture can be larger than the historical 32K resource size limitation.
{ The following variable and procedure must be at the }
{ main level of the program }
VAR
globalRef: INTEGER;
PROCEDURE GetPICTData(dataPtr: Ptr; byteCount: INTEGER);
{replacement for getPicProc routine}
VAR
err : INTEGER;
longCount: LONGINT;
BEGIN
longCount := byteCount;
{longCount is a Pascal VAR parameter and must be a LONGINT}
err := FSRead(globalRef,longCount,dataPtr);
{ignore errors here since it is unclear how to handle them}
END;
PROCEDURE GetandDrawPICTFile;
{procedure to draw in a picture from a PICT file selected by the user}
VAR
wher: Point; {where to display dialog}
reply: SFReply; {reply record}
myFileTypes: SFTypeList; {more of the Standard File goodies}
NumFileTypes: INTEGER;
err: OSErr;
myProcs: QDProcs; {use CQDProcs for a CGrafPort (a color }
{ window)}
PICTHand: PicHandle; {we need a picture handle for DrawPicture}
longCount: LONGINT;
myPB: ParamBlockRec;
BEGIN
wher.h := 20;
wher.v := 20;
NumFileTypes := 1; {Display PICT files}
myFileTypes[0] := 'PICT';
SFGetFile(wher,'',NIL,NumFileTypes,myFileTypes,NIL,reply);
IF reply.good THEN BEGIN
err := FSOpen(reply.fname,reply.vrefnum,globalRef);
SetStdProcs(myProcs); {use SetStdCProcs for a CGrafPort}
myWindow^.grafProcs := @myProcs;
myProcs.getPicProc := @GetPICTData;
PICTHand := PicHandle(NewHandle(SizeOf(Picture)));
{get one the size of (size word + frame rectangle)}
{skip (so to speak) the MacDraw header block}
err := SetFPos(globalRef,fsFromStart,512);
longCount := SizeOf(Picture);
{read in the (obsolete) size word and the picture frame}
err := FSRead(globalRef,longCount,Ptr(PICTHand^));
DrawPicture(PICTHand,PICTHand^^.picFrame);
{inside of DrawPicture, QD makes repeated calls to }
{ getPicProc to get actual picture opcodes and data. Since }
{ we have intercepted GetPicProc, QD will call myProcs to }
{ get getPicProc, instead of calling the default procedure}
err := FSClose(globalRef);
myWindow^.grafProcs := NIL;
DisposHandle(Handle(PICTHand));
END; {IF reply.good}
END;
»Spooling a Picture to a File
Spooling a picture out to a file is equally straightforward. By replacing the standard putPicProc with your own procedure, you can create a PICT file and spool the picture data out to the file.
Here is a sample of code to use as a guide:
{these variables and PutPICTData must be at the main program level}
VAR PICTcount: LONGINT; {the current size of the picture}
globalRef: INTEGER; {the file system reference number}
newPICThand: PicHandle;
{this is the replacement for the StdPutPic routine}
PROCEDURE PutPICTData(dataPtr: Ptr; byteCount: INTEGER);
VAR longCount: LONGINT;
err: INTEGER;
BEGIN {unfortunately, we don't know what to do with errors}
longCount := byteCount;
PICTCount := PICTCount + byteCount;
err := FSWrite(globalRef, longCount, dataPtr); {ignore error…}
IF newPICTHand <> NIL THEN newPICTHand^^.picSize := PICTCount;
{update so QD can track the size for oddness and pad out to full words}
END;
{Note that this assumes the picture is entirely in memory which wouldn't }
{ always be the case. You could (in effect) be feeding the StdGetPic }
{ procedure at the same time, or simply spooling while drawing.}
PROCEDURE SpoolOutPICTFile(PICTHand: PicHandle {the picture to spool});
VAR err: OSErr;
i: INTEGER;
wher: Point; { where to display dialog }
longCount, Zero: LONGINT;
pframe: Rect;
reply: SFReply; { reply record }
myProcs: QDProcs; {use CQDProcs for a CGrafPort (a color window)}
BEGIN
wher.h := 20;
wher.v := 20;
{get a file to output to}
SFPutFile(wher, 'Save the PICT as:', 'untitled', NIL, reply);
IF reply.good THEN
BEGIN
err := Create(reply.fname, reply.vrefnum, '????', 'PICT');
IF (err = noerr) | (err = dupfnerr) THEN
BEGIN
{now open the target file and prepare to spool to it}
signal(FSOpen(reply.fname, reply.vrefnum, globalRef));
SetStdProcs(myProcs); {use SetStdCProcs for a CGrafPort}
myWindow^.grafProcs := @myProcs;
myProcs.putPicProc := @putPICTdata;
Zero := 0;
longCount := 2;
PICTCount := SizeOf(Picture);
{now write out the 512 byte header and zero (initially) the}
{ Picture structure}
FOR i := 1 TO (512 + SizeOf(Picture)) DIV longCount DO
Signal(FSWrite(globalRef, longCount, @Zero));
{open a new picture and draw the old one to it; this will convert}
{ the old picture to fit the type of GrafPort to which we are}
{ currently set}
pFrame := PICThand^^.picFrame;
newPICTHand := NIL;
newPICTHand := OpenPicture(pFrame);
DrawPicture(PICTHand, pFrame); {draw the picture so the
bottleneck will be used. In real life you could be spooling while
doing drawing commands (you might not use DrawPicture)}
ClosePicture;
Signal(SetFPos(globalRef, fsFromStart, 512));
{skip the MacDraw header}
longCount := SizeOf(Picture);
{write out the correct (low word of the) size and the frame at}
{ the beginning}
Signal(FSWrite(globalRef, longCount, Ptr(newPICTHand^)));
Signal(FSClose(globalRef));
myWindow^.grafProcs := NIL;
KillPicture(newPICTHand);
END
ELSE
Signal(err);
END; {IF reply.good}
END; {OutPICT}
»Drawing to an Offscreen Pixel Map
With the advent of high resolution output devices such as laser printers, it has become necessary to support bitmap images at resolutions higher than those supported by the screen. To speed up the interactive manipulation of high-resolution pixel map images, developers may want to first draw them into an off screen pixel map at screen resolution and retain this screen version as long as the document is open.
Note: You can use the formula shown in the section “Sample PICT file” to
calculate the resolution of the source data. How to draw into an
offscreen pixmap is described in Macintosh Technical Note #120,
“Drawing Into an Off-Screen Pixel Map”; the Graphics Devices chapter
also contains a section describing how to draw to an offscreen device.
_______________________________________________________________________________
»New GrafProcs Record
The entire opcode space has been defined or reserved, as shown in the PICT Opcodes section in Table 3, and a new set of routines has been added to the grafProcs record. These changes provide support for anticipated future enhancements in a way that won’t cause old applications to crash. It works like this: when Color QuickDraw encounters an unused opcode, it calls the new opcodeProc routine to parse the opcode data. By default, this routine simply ignores the data, since no new opcodes are defined (other than HeaderOp, which is also ignored).
Color QuickDraw has replaced the QDProcs record with a CQDProcs record. In a new grafPort, you should never use the SetStdProcs routine. If you do, it will return the old QDProcs record, which won’t contain an entry for the stdOpcodeProc. If you don’t use the new SetStdCProcs routine, the first color picture that you try to display may crash your system.
The CQDProcs record structure is shown below. Only the last seven fields are new; the rest of the fields are the same as those in the QDProcs record.
CQDProcsPtr = ^CQDProcs
CQDProcs = RECORD
textProc: Ptr;
lineProc: Ptr;
rectProc: Ptr;
rRectProc: Ptr;
ovalProc: Ptr;
arcProc: Ptr;
polyProc: Ptr;
rgnProc: Ptr;
bitsProc: Ptr;
commentProc: Ptr;
txMeasProc: Ptr;
getPicProc: Ptr;
putPicProc: Ptr;
opcodeProc: Ptr; {fields added to QDProcs}
newProc1: Ptr; {reserved for future use}
newProc2: Ptr; {reserved for future use}
newProc3: Ptr; {reserved for future use}
newProc4: Ptr; {reserved for future use}
newProc5: Ptr; {reserved for future use}
newProc6: Ptr; {reserved for future use}
END;
_______________________________________________________________________________
»Picture Compatibility
Many applications already support PICT resources larger than 32K. The 128K ROMs (and later) allow pictures as large as memory (or spooling) will accommodate. This was made possible by having QuickDraw ignore the size word and simply read the picture until the end-of-picture opcode is reached.
Note: For maximum safety and convenience, let QuickDraw generate and
interpret your pictures.
While the PICT data formats described in this section allow you to read or write picture data directly, it’s best to let DrawPicture or OpenPicture and ClosePicture process the opcodes.
One reason to read a picture directly by scanning the opcodes is to disassemble it; for example, extracting a Color QuickDraw pixel map to store in a private data structure. This shouldn’t normally be necessary, unless your application is running on a CPU other than the Macintosh. You wouldn’t need to do it, of course, if you were using Color QuickDraw.
If your application does use the picture data, be sure it checks the version information. You may want to include an alert box in your application, indicating to users whether a picture was created using a later version of the picture format than is currently recognized by your application, and letting them know that some elements of the picture can’t be displayed. If the version information indicates a QuickDraw picture version later than the one recognized by your application, your program should skip over the new opcodes and only attempt to parse the opcodes it knows.
As with reading picture data directly, it’s best to use QuickDraw to create data in the PICT format. If you need to create PICT format data directly, it’s essential that you understand and follow the format presented in Table 3 and thoroughly test the data produced on both color and black and white Macintosh machines.
_______________________________________________________________________________
»Picture Format
This section describes the internal structure of the QuickDraw picture, consisting of a fixed-length header (which is different for version 1 and version 2 pictures), followed by variable-sized picture data. Your picture structure must follow the order shown in the examples below.
The two fixed-length fields, picSize and picFrame, are the same for version 1 and version 2 pictures.
picSize: INTEGER; {low-order 16 bits of picture size}
picFrame: RECT; {picture frame, used as scaling reference}
Following these fields is a variable amount of opcode-driven data. Opcodes represent drawing commands and parameters that affect those drawing commands in the picture. The first opcode in any picture must be the version opcode, followed by the version number of the picture.
»Picture Definition: Version 1
In a version 1 picture, the version opcode is $11, which is followed by version number $01. When parsing a version 1 picture, Color QuickDraw (or a patched QuickDraw) assumes it’s reading an old picture, fetching a byte at a time as opcodes. An end-of-picture byte ($FF) after the last opcode or data byte in the file signals the end of the data stream.
Picture Header (fixed size of 2 bytes):
$11 BYTE {version opcode}
$01 BYTE {version number of picture}
Picture Definition Data (variable sized):
opcode BYTE {one drawing command}
data . . .
opcode BYTE {one drawing command}
data . . .
$FF {end-of-picture opcode}
»Picture Definition: Version 2
In a version 2 picture, the first opcode is a two-byte version opcode ($0011). This is followed by a two-byte version number ($02FF). On machines without the 4.1 System file, the first $00 byte is skipped, then the $11 is interpreted as a version opcode. On a Macintosh II (or a Macintosh with System file 4.1 or later), this field identifies the picture as a version 2 picture, and all subsequent opcodes are read as words (which are word-aligned within the
picture). On a Macintosh without the 4.1 System patch, the $02 is read as the version number, then the $FF is read and interpreted as the end-of-picture opcode. For this reason, DrawPicture terminates without drawing anything.
Picture Header (fixed size of 30 bytes):
$0011 WORD {version opcode}
$02FF WORD {version number of new picture}
$0C00 WORD {reserved header opcode}
24 bytes of data {reserved for future Apple use}
Picture Definition Data (variable sized):
opcode WORD {one drawing command}
data . . .
opcode WORD {one drawing command}
data . . .
$00FF WORD {end-of-picture opcode}
For future expandibility, the second opcode in every version 2 picture must be a reserved header opcode, followed by 24 bytes of data that aren’t used by your application.
»PicComments
If your application requires capability beyond that provided by the picture opcodes, the picComment opcode allows data or commands to be passed directly to the output device. PicComments enable MacDraw, for example, to reconstruct graphics primitives not found in QuickDraw (such as rotated text) that are received either from the Clipboard or from another application. PicComments are also used as a means of communicating more effectively with the LaserWriter and with other applications via the scrap or the PICT data file.
Because some operations (like splines and rotated text) can be implemented more efficiently by the LaserWriter, some of the picture comments are designed to be issued along with QuickDraw commands that simulate the commented commands on the Macintosh screen. If the printer you are using has not implemented the comment commands, it ignores them and simulates the operations using the accompanying QuickDraw commands. Otherwise, it uses the comments to implement the desired effect and ignores the appropriate QuickDraw-simulated commands.
If you are going to produce or modify your own picture, the structure and use of these comments must be precise. The comments and the embedded QuickDraw commands must come in the correct sequence in order to work properly.
Note: Apple is currently investigating a method to register picComments.
If you intend to use new picComments in your application, you must
contact Apple’s Developer Technical Support to avoid conflict with
picComment numbers used by other developers.
»Sample PICT File
An example of a version 2 picture data file that can display a single image is shown in Table 1. Applications that generate picture data should set the resolution of the image source data in the hRes and vRes fields of the PICT file. It’s recommended, however, that you calculate the image resolution anyway, using the values for srcRect and dstRect according to the following formulas:
horizontal resolution (hRes) = width of srcRect x 72
________________
width of dstRect
vertical resolution (vRes) = height of srcRect x 72
_________________
height of dstRect
Table 1–PICT file example
_______________________________________________________________________________
Size Name Description
(in bytes)
2 picSize low word of picture size
8 picFrame rectangular bounding box of picture,
at 72 dpi
Picture Definition Data:
2 version op version opcode = $0011
2 version version number = $02FF
2 Header op header opcode = $0C00
4 size total size of picture in bytes
(–1 for version 2 pictures)
16 fBBox fixed-point bounding box
(–1 for version 2 pictures)
4 reserved reserved for future Apple use
(–1 for version 2 pictures)
2 opbitsRect bitMap opcode = $0090
2 rowBytes integer, must have high bit set to
signal pixMap
8 bounds rectangle, bounding rectangle at
source resolution
2 pmVersion integer, pixMap version number
2 packType integer, defines packing format
4 packSize LongInt, length of pixel data
4 hRes fixed, horizontal resolution (dpi) of
source data
4 vRes fixed, vertical resolution (dpi) of
source data
2 pixelType integer, defines pixel type
2 pixelSize integer, number of bits in pixel
2 cmpCount integer, number of components in pixel
2 cmpSize integer, number of bits per component
4 planeBytes LongInt, offset to next plane
pmTable color table = 0
pmReserved reserved = 0
4 ctSeed LongInt, color table seed
2 ctFlags integer, flags for color table
2 ctSize integer, number of entries in ctTable –1
(ctSize+1) * 8 ctTable color lookup table data
8 srcRect rectangle, source rectangle at source
resolution
8 dstRect rectangle, destination rectangle at 72 dpi
resolution
2 mode integer, transfer mode
see Table 5 pixData pixel data
2 endPICT op end-of-picture opcode = $00FF
_______________________________________________________________________________
_______________________________________________________________________________
»Color Picture Routines
FUNCTION OpenPicture (picFrame: Rect) : PicHandle;
The OpenPicture routine has been modified to take advantage of QuickDraw’s new color capabilities. If the current port is a cGrafPort, then OpenPicture automatically opens a version 2 picture, as described in the previous section. As before, you close the picture using ClosePicture and draw the picture using DrawPicture.
_______________________________________________________________________________
»PICT OPCODES
_______________________________________________________________________________
The opcode information in Table 3 is provided for the purpose of debugging application-generated PICT files. Your application should generate and read PICT files only by using standard QuickDraw or Color QuickDraw routines
(OpenPicture, ClosePicture).
The data types listed in Table 2 are used in the Table 3 opcode definitions. Data formats are described in Volume I.
Table 2–Data types
_______________________________________________________________________________
Type Size
v1 opcode 1 byte
v2 opcode 2 bytes
integer 2 bytes
long integer 4 bytes
mode 2 bytes
point 4 bytes
0..255 1 byte
–128..127 1 byte (signed)
rect 8 bytes (top, left, bottom, right: integer)
poly 10+ bytes
region 10+ bytes
fixed-point number 4 bytes
pattern 8 bytes
rowbytes 2 bytes (always an even quantity)
_______________________________________________________________________________
Valid picture opcodes are listed in Table 3. New opcodes or those altered for version 2 picture files are indicated by a leading asterisk (*). The unused opcodes found throughout the table are reserved for Apple use. The length of the data that follows these opcodes is pre-defined, so if they are encountered in pictures, they can simply be skipped. By default, Color QuickDraw reads and then ignores these opcodes.
Table 3–PICT opcodes
_______________________________________________________________________________
Opcode Name Description Data Size
(in bytes)
$0000 NOP nop 0
$0001 Clip clip region size
$0002 BkPat background pattern 8
$0003 TxFont text font (word) 2
$0004 TxFace text face (byte) 1
$0005 TxMode text mode (word) 2
$0006 SpExtra space extra (fixed point) 4
$0007 PnSize pen size (point) 4
$0008 PnMode pen mode (word) 2
$0009 PnPat pen pattern 8
$000A FillPat fill pattern 8
$000B OvSize oval size (point) 4
$000C Origin dh, dv (word) 4
$000D TxSize text size (word) 2
$000E FgColor foreground color (long) 4
$000F BkColor background color (long) 4
$0010 TxRatio numer (point), denom (point) 8
$0011 Version version (byte) 1
$0012 *BkPixPat color background pattern variable:
see Table 4
$0013 *PnPixPat color pen pattern variable:
see Table 4
$0014 *FillPixPat color fill pattern variable:
see Table 4
$0015 *PnLocHFrac fractional pen position 2
$0016 *ChExtra extra for each character 2
$0017 *reserved for Apple use opcode 0
$0018 *reserved for Apple use opcode 0
$0019 *reserved for Apple use opcode 0
$001A *RGBFgCol RGB foreColor variable:
see Table 4
$001B *RGBBkCol RGB backColor variable:
see Table 4
$001C *HiliteMode hilite mode flag 0
$001D *HiliteColor RGB hilite color variable:
see Table 4
$001E *DefHilite Use default hilite color 0
$001F *OpColor RGB OpColor for variable:
arithmetic modes see Table 4
$0020 Line pnLoc (point), newPt (point) 8
$0021 LineFrom newPt (point) 4
$0022 ShortLine pnLoc (point, dh, dv 6
(-128..127)
$0023 ShortLineFrom dh, dv (-128..127) 2
$0024 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0025 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0026 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0027 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0028 LongText txLoc (point), count 5 + text
(0..255), text
$0029 DHText dh (0..255), count 2 + text
(0..255), text
$002A DVText dv (0..255), count 2 + text
(0..255), text
$002B DHDVText dh, dv (0..255), count 3 + text
(0..255), text
$002C *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$002D *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$002E *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$002F *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0030 frameRect rect 8
$0031 paintRect rect 8
$0032 eraseRect rect 8
$0033 invertRect rect 8
$0034 fillRect rect 8
$0035 *reserved for Apple use opcode + 8 bytes data 8
$0036 *reserved for Apple use opcode + 8 bytes data 8
$0037 *reserved for Apple use opcode + 8 bytes data 8
$0038 frameSameRect rect 0
$0039 paintSameRect rect 0
$003A eraseSameRect rect 0
$003B invertSameRect rect 0
$003C fillSameRect rectangle 0
$003D *reserved for Apple use opcode 0
$003E *reserved for Apple use opcode 0
$003F *reserved for Apple use opcode 0
$0040 frameRRect rect (see Note # 5 ) 8
$0041 paintRRect rect (see Note # 5 ) 8
$0042 eraseRRect rect (see Note # 5 ) 8
$0043 invertRRect rect (see Note # 5 ) 8
$0044 fillRRect rect (see Note # 5 ) 8
$0045 *reserved for Apple use opcode + 8 bytes data 8
$0046 *reserved for Apple use opcode + 8 bytes data 8
$0047 *reserved for Apple use opcode + 8 bytes data 8
$0048 frameSameRRect rect 0
$0049 paintSameRRect rect 0
$004A eraseSameRRect rect 0
$004B invertSameRRect rect 0
$004C fillSameRRect rect 0
$004D *reserved for Apple use opcode 0
$004E *reserved for Apple use opcode 0
$004F *reserved for Apple use opcode 0
$0050 frameOval rect 8
$0051 paintOval rect 8
$0052 eraseOval rect 8
$0053 invertOval rect 8
$0054 fillOval rect 8
$0055 *reserved for Apple use opcode + 8 bytes data 8
$0056 *reserved for Apple use opcode + 8 bytes data 8
$0057 *reserved for Apple use opcode + 8 bytes data 8
$0058 frameSameOval rect 0
$0059 paintSameOval rect 0
$005A eraseSameOval rect 0
$005B invertSameOval rect 0
$005C fillSameOval rect 0
$005D *reserved for Apple use opcode 0
$005E *reserved for Apple use opcode 0
$005F *reserved for Apple use opcode 0
$0060 frameArc rect, startAngle, arcAngle 12
$0061 paintArc rect, startAngle, arcAngle 12
$0062 eraseArc rect, startAngle, arcAngle 12
$0063 invertArc rect, startAngle, arcAngle 12
$0064 fillArc rect, startAngle, arcAngle 12
$0065 *reserved for Apple use opcode + 12 bytes 12
$0066 *reserved for Apple use opcode + 12 bytes 12
$0067 *reserved for Apple use opcode + 12 bytes 12
$0068 frameSameArc rect 4
$0069 paintSameArc rect 4
$006A eraseSameArc rect 4
$006B invertSameArc rect 4
$006C fillSameArc rect 4
$006D *reserved for Apple use opcode + 4 bytes 4
$006E *reserved for Apple use opcode + 4 bytes 4
$006F *reserved for Apple use opcode + 4 bytes 4
size
$0070 framePoly poly polygon
size
$0071 paintPoly poly polygon
size
$0072 erasePoly poly polygon
size
$0073 invertPoly poly polygon
size
$0074 fillPoly poly polygon
size
$0075 *reserved for Apple use opcode + poly
$0076 *reserved for Apple use opcode + poly
$0077 *reserved for Apple use opcode word + poly
$0078 frameSamePoly (not yet implemented: 0
same as 70, etc)
$0079 paintSamePoly (not yet implemented) 0
$007A eraseSamePoly (not yet implemented) 0
$007B invertSamePoly (not yet implemented) 0
$007C fillSamePoly (not yet implemented) 0
$007D *reserved for Apple use opcode 0
$007E *reserved for Apple use opcode 0
$007F *reserved for Apple use opcode 0
$0080 frameRgn rgn region size
$0081 paintRgn rgn region size
$0082 eraseRgn rgn region size
$0083 invertRgn rgn region size
$0084 fillRgn rgn region size
$0085 *reserved for Apple use opcode + rgn region size
$0086 *reserved for Apple use opcode + rgn region size
$0087 *reserved for Apple use opcode + rgn region size
$0088 frameSameRgn (not yet implemented: 0
same as 80, etc.)
$0089 paintSameRgn (not yet implemented) 0
$008A eraseSameRgn (not yet implemented) 0
$008B invertSameRgn (not yet implemented) 0
$008C fillSameRgn (not yet implemented) 0
$008D *reserved for Apple use opcode 0
$008E *reserved for Apple use opcode 0
$008F *reserved for Apple use opcode 0
$0090 *BitsRect copybits, rect clipped variable:
see Table 4
$0091 *BitsRgn copybits, rgn clipped variable:
see Table 4
$0092 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0093 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0094 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0095 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0096 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$0097 *reserved for Apple use opcode word + 2 bytes 2+ data
data length + data length
$0098 *PackBitsRect packed copybits, rect variable:
clipped see Table 4
$0099 *PackBitsRgn packed copybits, rgn variable:
clipped see Table 4
$009A *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$009B *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$009C *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$009D *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$009E *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$009F *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$00A0 ShortComment kind (word) 2
$00A1 LongComment kind (word), size 4+data
(word), data
$00A2 *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
: : :
: : :
$00AF *reserved for Apple use opcode + 2 bytes data 2+ data
length + data length
$00B0 *reserved for Apple use opcode 0
: : :
: : :
$00CF *reserved for Apple use opcode 0
$00D0 *reserved for Apple use opcode + 4 bytes data 4+ data
length + data length
: : :
: : :
$00FE *reserved for Apple use opcode + 4 bytes data 4+ data
length + data length
$00FF opEndPic end of picture 2
$0100 *reserved for Apple use opcode + 2 bytes data 2
: : :
: : :
$01FF *reserved for Apple use opcode + 2 bytes data 2
$0200 *reserved for Apple use opcode + 4 bytes data 4
: : :
$0BFF *reserved for Apple use opcode + 4 bytes data 22
$0C00 HeaderOp opcode 24
$0C01 *reserved for Apple use opcode + 4 bytes data 24
: : :
$7F00 *reserved for Apple use opcode + 254 bytes data 254
: : :
$7FFF *reserved for Apple use opcode + 254 bytes data 254
$8000 *reserved for Apple use opcode 0
: : :
$80FF *reserved for Apple use opcode 0
$8100 *reserved for Apple use opcode + 4 bytes data 4+ data
length + data length
: : :
$FFFF *reserved for Apple use opcode + 4 bytes data 4+ data
length + data length
_______________________________________________________________________________
Notes to Table 3
1. The opcode value has been extended to a word for version 2 pictures.
Remember, opcode size = 1 byte for version 1.
2. Because opcodes must be word aligned in version 2 pictures, a byte
of 0 (zero) data is added after odd-size data.
3. The size of reserved opcodes has been defined. They can occur only
in version 2 pictures.
4. All unused opcodes are reserved for future Apple use and should
not be used.
5. For opcodes $0040–$0044: rounded-corner rectangles use the setting
of the ovSize point (refer to opcode $000B)
6. For opcodes $0090 and $0091: data is unpacked. These opcodes can
only be used for rowbytes less than 8.
7. For opcodes $0100–$7FFF: the amount of data for opcode
$nnXX = 2 * nn bytes.
_______________________________________________________________________________
»The New Opcodes: Expanded Format
The expanded format of the version 2 PICT opcodes is shown in Table 4 below.
Table 4–Data Format of Version 2 PICT Opcodes
_______________________________________________________________________________
Opcode Name Description Reference to Notes
$0012 BkPixPat color background pattern See Note 1
$0013 PnPixPat color pen pattern See Note 1
$0014 FillPixPat color fill pattern See Note 1
$0015 PnLocHFrac fractional pen If pnLocHFrac <> 1/2, it
position (word) is always put to the
picture before each
text drawing operation.
$0016 ChExtra extra for each After chExtra changes,
character (word) it is put to picture
before next text
drawing operation.
$001A RGBFgCol RGB foreColor (RBGColor) desired RGB for
foreground
$001B RGBBkCol RGB backColor (RGBColor) desired RGB for
background
$001D HiliteColor RGB hilite color
$001F OpColor RGB OpColor for
arithmetic modes
$001C HiliteMode hilite mode flag No data; this opcode is
sent before a drawing
operation that uses the
hilite mode.
$001E DefHilite Use default hilite No data; set hilite
color to default (from low
memory).
$0090 BitsRect copybits, rect See Note 2,4,5
clipped
$0091 BitsRgn copybits, rgn See Note 3,4,5
clipped
$0098 PackBitsRect packed copybits, See Note 2,4
rect clipped
$0099 PackBitsRgn packed copybits, See Note 3,4
rgn clipped
_______________________________________________________________________________
Notes to Table 4
1. if patType = ditherPat
then
PatType: word; {pattern type = 2}
Pat1Data: Pattern; {old pattern data}
RGB: RGBColor; {desired RGB for pattern}
else
PatType: word; {pattern type = 1}
Pat1Data: Pattern; {old pattern data}
pixMap: {described in Table 5}
colorTable: {described in Table 5}
pixData: {described in Table 5}
end;
2. pixMap: {described in Table 5}
colorTable: {described in Table 5}
srcRect: Rect; {source rectangle}
dstRect: Rect; {destination rectangle}
mode: Word; {transfer mode (may include new transfer }
{ modes)}
PixData: {described in Table 5}
3. pixMap: {described in Table 5 }
colorTable: {described in Table 5 }
srcRect: Rect; {source rectangle}
dstRect: Rect; {destination rectangle}
mode: Word; {transfer mode (may include new transfer }
{ modes)}
maskRgn: Rgn; {region for masking}
PixData: {described in Table 5}
4. These four opcodes ($0090, $0091, $0098, $0099) are modifications of
existing (version 1) opcodes. The first word following the opcode is
the rowBytes. If the high bit of the rowBytes is set, then it is a
pixMap containing multiple bits per pixel; if it is not set, it is a
bitMap containing one bit per pixel. In general, the difference between
version 1 and version 2 formats is that the pixMap replaces the bitMap,
a color table has been added, and pixData replaces the bitData.
5. Opcodes $0090 and $0091 are used only for rowbytes less than 8.
Table 5–Data Types Found Within New PICT Opcodes Listed in Table 4
_______________________________________________________________________________
Data Type Field Definitions Comments
pixMap = baseAddr: long; {unused = 0}
rowBytes: word; {rowBytes w/high byte set}
Bounds: rect; {bounding rectangle}
version: word; {version number = 0}
packType: word; {packing format = 0}
packSize: long; {packed size = 0}
hRes: fixed; {horizontal resolution (default = }
{ $0048.0000)}
vRes: fixed; {vertical resolution (default= }
{ $0048.0000)}
pixelType: word; {chunky format = 0}
pixelSize: word; {# bits per pixel (1,2,4,8)}
cmpCount: word; {# components in pixel = 1}
cmpSize: word; {size of each component = pixelSize }
{ for chunky}
planeBytes: long; {offset to next plane = 0}
pmTable: long; {color table = 0}
pmReserved: long; {reserved = 0}
end;
colorTable = ctSeed: long; {id number for color table = 0}
ctFlags: word; {flags word = 0}
ctSize: word; {number of ctTable entries-1 }
{ ctSize + 1 color table entries }
{ each entry = pixel value, red, }
{ green, blue: word}
end;
pixData: {the following pseudocode describes the pixData data type}
If rowBytes < 8 then data is unpacked
data size = rowBytes*(bounds.bottom-bounds.top);
If rowBytes >= 8 then data is packed.
Image contains (bounds.bottom-bounds.top) packed scanlines.
Packed scanlines are produced by the PackBits routine.
Each scanline consists of [byteCount] [data].
If rowBytes > 250 then byteCount is a word,
else it is a byte.
end;
_______________________________________________________________________________
»SUMMARY OF COLOR QUICKDRAW
_______________________________________________________________________________
Constants
CONST
{ Old-style grafPort colors }
blackColor = 33;
whiteColor = 30;
redColor = 209;
greenColor = 329;
blueColor = 389;
cyanColor = 269;
magentaColor = 149;
yellowColor = 89;
{ Arithmetic transfer modes }
blend = 32;
addPin = 33;
addOver = 34;
subPin = 35;
adMax = 37;
subOver = 38;
adMin = 39;
{ Transparent mode constant }
transparent = 36;
{ Text mask constant }
mask = 64;
{ Highlight constants }
hilite = 50;
pHiliteBit = 0; {this is the correct value for use when }
{ calling the BitClear trap. BClr must use }
{ the assembly language equate hiliteBit}
{ Constant for resource IDs }
defQDColors = 127;
_______________________________________________________________________________
Data Types
TYPE
RGBColor = RECORD
red: INTEGER; {red component}
green: INTEGER; {green component}
blue: INTEGER {blue component}
END;
ColorSpec = RECORD
value: INTEGER; {index or other value}
rgb: RGBColor {true color}
END;
cSpecArray : ARRAY [0..0] of ColorSpec;
CTabHandle = ^CTabPtr;
CTabPtr = ^ColorTable;
ColorTable = RECORD
ctSeed: LONGINT; {unique identifier from table}
ctFlags: INTEGER; {high bit is 1 for device, 0 }
{ for pixMap}
ctSize: INTEGER; {number of entries -1 in }
{ ctTable}
ctTable: cSpecArray
END;
CGrafPtr = ^CGrafPort;
CGrafPort = RECORD
device: INTEGER; {device ID for font selection}
portPixMap: PixMapHandle; {port's pixel map}
portVersion: INTEGER; {highest 2 bits always set}
grafVars: Handle; {handle to more fields}
chExtra: INTEGER; {extra characters placed}
{ on the end of a string}
pnLocHFrac: INTEGER; {pen fraction}
portRect: Rect; {port rectangle}
visRgn: RgnHandle; {visible region}
clipRgn: RgnHandle; {clipping region}
bkPixPat: PixPatHandle; {background pattern}
rgbFgColor: RGBColor; {requested foreground color}
rgbBkColor: RGBColor; {requested background color}
pnLoc: Point; {pen location}
pnSize: Point; {pen size}
pnMode: INTEGER; {pen transfer mode}
pnPixPat: PixPatHandle; {pen pattern}
fillPixPat: PixPatHandle; {fill pattern}
pnVis: INTEGER; {pen visibility}
txFont: INTEGER; {font number for text}
txFace: Style; {text's character style}
txMode: INTEGER; {text's transfer mode}
txSize: INTEGER; {font size for text}
spExtra: Fixed; {extra space}
fgColor: LONGINT; {actual foreground color}
bkColor: LONGINT; {actual background color}
colrBit: INTEGER; {plane being drawn}
patStretch: INTEGER; {used internally}
picSave: Handle; {picture being saved}
rgnSave: Handle; {region being saved}
polySave: Handle; {polygon being saved}
grafProcs: CQDProcsPtr {low-level drawing routines}
END;
GrafVars = RECORD
rgbOpColor: RGBColor; {color for addPin, }
{ subPin, and blend}
rgbHiliteColor: RGBColor; {color for hiliting}
pmFgColor: Handle; {palette handle for }
{ foreground color}
pmFgIndex: INTEGER; {index value for foreground}
pmBkColor: Handle; {palette handle for }
{ background color}
pmBkIndex: INTEGER; {index value for background}
pmFlags: INTEGER; {flags for Palette Manager}
END;
PixMapHandle = ^PixMapPtr;
PixMapPtr = ^PixMap;
PixMap = RECORD
baseAddr: Ptr; {pointer to pixMap data}
rowBytes: INTEGER; {offset to next row}
bounds: Rect; {boundary rectangle}
pmVersion: INTEGER; {color QuickDraw version number}
packType: INTEGER; {packing format}
packSize: LONGINT; {size of data in packed state}
hRes: Fixed; {horizontal resolution}
vRes: Fixed; {vertical resolution}
pixelType: INTEGER; {format of pixel image}
pixelSize: INTEGER; {physical bits per pixel}
cmpCount: INTEGER; {logical components per pixel}
cmpSize: INTEGER; {logical bits per component}
planeBytes: LONGINT; {offset to next plane}
pmTable: CTabHandle; {absolute colors for this image}
pmReserved: LONGINT {reserved for future expansion}
END;
PixPatHandle = ^PixPatPtr;
PixPatPtr = ^PixPat;
PixPat = RECORD
patType: INTEGER; {pattern type}
patMap: PixMapHandle; {pattern characteristics}
patData: Handle; {pixel image defining pattern}
patXData: Handle; {expanded pixel image}
patXValid: INTEGER; {flags for expanded pattern data}
patXMap: Handle; {handle to expanded pattern data}
pat1Data: Pattern; {old-style pattern/RGB color}
END;
CCrsrHandle = ^CCrsrPtr;
CCrsrPtr = ^CCrsr;
CCrsr = RECORD
crsrType: INTEGER; {type of cursor}
crsrMap: PixMapHandle; {the cursor's pixMap}
crsrData: Handle; {cursor's data}
crsrXData: Handle; {expanded cursor data}
crsrXValid: INTEGER; {depth of expanded data}
crsrXHandle: Handle; {reserved for future use}
crsr1Data: Bits16; {one-bit cursor}
crsrMask: Bits16; {cursor's mask}
crsrHotSpot: Point; {cursor's hotspot}
crsrXTable: LONGINT; {private}
crsrID: LONGINT; {ctSeed for expanded cursor}
END;
CIconHandle = ^CIconPtr;
CIconPtr = ^CIcon;
CIcon = RECORD
iconPMap: PixMap; {the icon's pixMap}
iconMask: BitMap; {the icon's mask bitMap}
iconBMap: BitMap; {the icon's bitMap}
iconData: Handle; {the icon's data}
iconMaskData: ARRAY[0..0] OF INTEGER; {icon's }
{ mask and bitMap data}
END;
MatchRec = RECORD
red: INTEGER; {red component}
green: INTEGER; {green component}
blue: INTEGER; {blue component}
matchData: LONGINT;
END;
CQDProcsPtr = ^CQDProcs
CQDProcs = RECORD
textProc: Ptr;
lineProc: Ptr;
rectProc: Ptr;
rRectProc: Ptr;
ovalProc: Ptr;
arcProc: Ptr;
polyProc: Ptr;
rgnProc: Ptr;
bitsProc: Ptr;
commentProc: Ptr;
txMeasProc: Ptr;
getPicProc: Ptr;
putPicProc: Ptr;
opcodeProc: Ptr; {fields added to QDProcs}
newProc1: Ptr; {reserved for future use}
newProc2: Ptr; {reserved for future use}
newProc3: Ptr; {reserved for future use}
newProc4: Ptr; {reserved for future use}
newProc5: Ptr; {reserved for future use}
newProc6: Ptr; {reserved for future use}
END;
_______________________________________________________________________________
Routines
Operations on cGrafPorts
PROCEDURE OpenCPort (port: CGrafPtr);
PROCEDURE InitCPort (port: CGrafPtr);
PROCEDURE CloseCPort (port: CGrafPtr);
Setting the Foreground and Background Colors
PROCEDURE RGBForeColor (color : RGBColor);
PROCEDURE RGBBackColor (color : RGBColor);
PROCEDURE GetForeColor (VAR color : RGBColor);
PROCEDURE GetBackColor (VAR color : RGBColor);
Creating Pixel Maps
FUNCTION NewPixMap : PixMapHandle;
PROCEDURE DisposPixMap (pm: PixMapHandle);
PROCEDURE CopyPixMap (srcPM,dstPM: PixMapHandle);
Operations on Pixel Maps
PROCEDURE CopyBits (srcBits, dstBits: BitMap; srcRect, dstRect: Rect;
mode: INTEGER; maskRgn: RgnHandle);
PROCEDURE CopyMask (srcBits,maskBits,dstBits: BitMap;
srcRect, maskRect, dstRect: Rect);
PROCEDURE SeedCFill (srcBits, dstBits: BitMap; srcRect, dstRect: Rect;
seedH, seedV: INTEGER; matchProc: ProcPtr;
matchData: LONGINT);
PROCEDURE CalcCMask (srcBits, dstBits: BitMap; srcRect, dstRect: Rect;
seedRGB: RGBColor; matchProc: ProcPtr; matchData: LONGINT);
Operations on Pixel Patterns
FUNCTION NewPixPat : PixPatHandle;
PROCEDURE DisposPixPat (ppat: PixPatHandle);
FUNCTION GetPixPat (patID: INTEGER): PixPatHandle;
PROCEDURE CopyPixPat (srcPP,dstPP: PixPatHandle);
PROCEDURE MakeRGBPat (ppat: PixPatHandle; myColor: RGBColor);
PROCEDURE PenPixPat (ppat: PixPatHandle);
PROCEDURE BackPixPat (ppat: PixPatHandle);
Color Drawing Operations
PROCEDURE FillCRect (r: Rect; ppat: PixPatHandle);
PROCEDURE FillCOval (r: Rect; ppat: PixPatHandle);
PROCEDURE FillCRoundRect (r: Rect; ovWd,ovHt: INTEGER; ppat: PixPatHandle);
PROCEDURE FillCArc (r: Rect; startAngle,arcAngle: INTEGER;
ppat: PixPatHandle);
PROCEDURE FillCRgn (rgn: RgnHandle; ppat: PixPatHandle);
PROCEDURE FillCPoly (poly: PolyHandle; ppat: PixPatHandle);
PROCEDURE GetCPixel (h,v: INTEGER; VAR cPix: RGBColor);
PROCEDURE SetCPixel (h,v: INTEGER; cPix: RGBColor);
Operations on Color Cursors
FUNCTION GetCCursor (crsrID: INTEGER): CCrsrHandle;
PROCEDURE SetCCursor (cCrsr: CCrsrHandle);
PROCEDURE DisposCCursor (cCrsr: CCrsrHandle);
PROCEDURE AllocCursor;
Operations on Icons
FUNCTION GetCIcon (id: INTEGER): CIconHandle;
PROCEDURE DisposCIcon (theIcon: CIconHandle);
PROCEDURE PlotCIcon (theRect: Rect; theIcon: CIconHandle);
Operations on cGrafPort Fields
PROCEDURE SetPortPix (pm: PixMapHandle);
PROCEDURE OpColor (color: RGBColor);
PROCEDURE HiliteColor (color:RGBColor);
PROCEDURE CharExtra (extra:Fixed);
PROCEDURE SetStdCProcs (VAR cProcs: CQDProcs);
Operations on Color Tables
FUNCTION GetCTable (ctID: INTEGER): CTabHandle;
PROCEDURE DisposCTable (ctTab: CTabHandle);
Color Picture Operations
FUNCTION OpenPicture (picFrame: Rect) : PicHandle;
_______________________________________________________________________________
Global Variables
HiliteMode {if the hilite mode is set, highlighting is on}
HiliteRGB {default highlight color for the system}
_______________________________________________________________________________
Assembly-Language Interface
HiLite Constant
hiliteBit EQU 7 ;flag bit in HiliteMode
; this is the correct value for use in assembler
; programs
Equates for Resource IDs
defQDColors EQU 127 ;resource ID of clut for default QDColors
RGBColor structure
red EQU $0 ;[word] red channel intensity
green EQU $2 ;[word] green channel intensity
blue EQU $4 ;[word] blue channel intensity
rgbColor EQU $6 ;size of record
ColorSpec structure
value EQU $0 ;[short] value field
rgb EQU $2 ;[rgbColor] rgb values
colorSpecSize EQU $8 ;size of record
Additional Offsets in a cGrafPort
portPixMap EQU portBits ;[long] pixelMap handle
portVersion EQU portPixMap+4 ;[word] port version number
grafVars EQU portVersion+2 ;[long] handle to new fields
chExtra EQU grafVars+4 ;[word] extra characters placed at
; the end of a string
pnLocHFrac EQU chExtra+2 ;[word] pen fraction
bkPixPat EQU bkPat ;[long] handle to bk pattern
rgbFgColor EQU bkPixPat+4 ;[6 bytes] RGB components of fg color
rgbBkColor EQU RGBFgColor+6 ;[6 bytes] RGB components of bk color
pnPixPat EQU $3A ;[long] handle to pen's pattern
fillPixPat EQU pnPixPat+4 ;[long] handle to fill pattern
Offsets Within GrafVars
rgbOpColor EQU 0 ;[6 bytes] color for addPin,
; subPin, and blend
rgbHiliteColor EQU rgbOpColor+6 ;[6 bytes] color for hiliting
pmFgColor EQU rgbHiliteColor+6 ;[4 bytes] Palette handle for
; foreground color
pmFgIndex EQU pmFgColor+4 ;[2 bytes] index value for
; foreground
pmBkColor EQU pmFgIndex+2 ;[4 bytes] Palette handle for
; background color
pmBkIndex EQU pmBkColor+4 ;[2 bytes] index value for
; background
pmFlags EQU pmBkIndex+2 ;[2 bytes] Flags for Palette
; manager
grafVarRec EQU pmFlags+2 ;size of grafVar record
PixMap field offsets
pmBaseAddr EQU $0 ;[long]
pmNewFlag EQU $4 ;[1 bit] upper bit of rowbytes is flag
pmRowBytes EQU $4 ;[word]
pmBounds EQU $6 ;[rect]
pmVersion EQU $E ;[word] pixMap version number
pmPackType EQU $10 ;[word] defines packing format
pmPackSize EQU $12 ;[long] size of pixel data
pmHRes EQU $16 ;[fixed] h. resolution (ppi)
pmVRes EQU $1A ;[fixed] v. resolution (ppi)
pmPixelType EQU $1E ;[word] defines pixel type
pmPixelSize EQU $20 ;[word] # bits in pixel
pmCmpCount EQU $22 ;[word] # components in pixel
pmCmpSize EQU $24 ;[word] # bits per field
pmPlaneBytes EQU $26 ;[long] offset to next plane
pmTable EQU $2A ;[long] color map
pmReserved EQU $2E ;[long] must be 0
pmRec EQU $32 ; size of pixMap record
PixPat field offsets
patType EQU $0 ;[word] type of pattern
patMap EQU $2 ;[long] handle to pixmap
patData EQU $6 ;[long] handle to data
patXData EQU $A ;[long] handle to expanded pattern data
patXValid EQU $E ;[word] flags whether expanded pattern valid
patXMap EQU $10 ;[long] handle to expanded pattern data
pat1Data EQU $14 ;[8 bytes] old-style pattern/RGB color
ppRec EQU $1C ; size of pixPat record
Pattern Types
oldPat EQU 0 ;foreground/background pattern
newPat EQU 1 ;self-contained color pattern
ditherPat EQU 2 ;rgb value to be dithered
oldCrsrPat EQU $8000 ;old-style cursor
CCrsrPat EQU $8001 ;new-style cursor
CCrsr (Color Cursor) field offsets
crsrType EQU 0 ;[word] cursor type
crsrMap EQU crsrType+2 ;[long] handle to cursor's pixmap
crsrData EQU crsrMap+4 ;[long] handle to cursor's color data
crsrXData EQU crsrData+4 ;[long] handle to expanded data
crsrXValid EQU crsrXData+4 ;[word] handle to expanded data (0 if none)
crsrXHandle EQU crsrXValid+2 ;[long] handle for future use
crsr1Data EQU crsrXHandle+4 ;[16 words] one-bit data
crsrMask EQU crsr1Data+32 ;[16 words] one-bit mask
crsrHotSpot EQU crsrMask+32 ;[point] hot-spot for cursor
crsrXTable EQU crsrHotSpot+4 ;[long] private
crsrID EQU crsrXTable+4 ;[long] color table seed for
; expanded cursor
crsrRec EQU crsrID+4 ;size of cursor save area
CIcon (Color Icon) field offsets
iconPMap EQU 0 ;[pixmap] icon's pixMap
iconMask EQU iconPMap+pmRec ;[bitmap] 1-bit version of icon
; 1-bit mask
iconBMap EQU iconMask+bitmapRec ;[bitmap] 1-bit version of icon
iconData EQU iconBMap+bitmapRec ;[long] Handle to pixMap data
; followed by bMap and mask data
iconRec EQU iconData+4 ;size of icon header
Extensions to the QDProcs record
opcodeProc EQU $34 ;[pointer]
newProc1 EQU $38 ;[pointer]
newProc2 EQU $3C ;[pointer]
newProc3 EQU $40 ;[pointer]
newProc4 EQU $44 ;[pointer]
newProc5 EQU $48 ;[pointer]
newProc6 EQU $4C ;[pointer]
cqdProcsRec EQU $50 ; size of QDProcs record
MatchRec structure
red EQU $0 ; [word] defined in RGBColor
green EQU $2 ; [word] defined in RGBColor
blue EQU $4 ; [word] defined in RGBColor
matchData EQU $6 ; [long]
matchRecSize EQU $A ;size of record
Global Variables
HiliteMode EQU $938 ;if the hilite bit is set, highlighting is on
HiliteRGB EQU $DA0 ;default highlight color for the system
Further Reference:
_______________________________________________________________________________
QuickDraw
Graphics Devices
Color Manager
Color Picker Package
Palette Manager
Resource Manager
Technical Note #21, QuickDraw’s Internal Picture Definition
Technical Note #27, MacDraw’s PICT File Format
Technical Note #120, Drawing Into an Off-Screen Pixel Map
Technical Note #163, Adding Color With CopyBits
Technical Note #171, _PackBits Data Format
Technical Note #244, A Leading Cause of Color Cursor Cursing
32-Bit QuickDraw Documentation
Graphics Devices
_______________________________________________________________________________
GRAPHICS DEVICES
_______________________________________________________________________________
About This Chapter
About Graphics Devices
Device Records
Multiple Screen Devices
Graphics Device Routines
Drawing to Offscreen Devices
Optimizing Visual Results
Optimizing Speed
Imaging for a Color Printer
Graphics Device Resources
Summary of Graphics Devices
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
Warning: This chapter has not been updated to reflect changes and improvements
that are available on systems using 32-Bit QuickDraw. For further
information on 32-Bit QuickDraw, please refer to the 32-Bit QuickDraw
documentation (available on “Phil & Dave’s Excellent CD: The Release
Version).
Because the Macintosh II supports a variable sized screen, different screen depths, and even multiple screens, a new set of data structures and routines has been introduced to support, in a general way, the use of graphics devices
(called gDevices). These data structures and routines are logically a part of Color QuickDraw, but because they are functionally quite independent of QuickDraw, they appear here in a separate chapter.
A graphics device is used to
• associate a driver with a particular graphics output device
• define the size and color capabilities of the device
• define the position of a video screen with respect to other screens
• change the default matching routine used by the Color Manager
• keep track of the cursor for that device
• allocate a set of colors used by an offscreen bitMap
Reader’s guide: Graphics devices are generally used only by the system. You
might need to use the information in this chapter, for example,
if your application needs explicit knowledge of the pixel depth
of the screen(s) it is drawing to, or if it wants to bring up a
window on a particular screen. You might also use the
information in this chapter if you want to allocate and
maintain an offscreen bitMap.
Before reading this chapter you should be familiar with the material in the chapter on Color QuickDraw. Some of the routine descriptions in this chapter also refer to the Color Manager, the Slot Manager, and the Device Manager chapters; you will only need to refer to those chapters if you are using those routines.
_______________________________________________________________________________
»ABOUT GRAPHICS DEVICES
_______________________________________________________________________________
When the system is started up, one handle to a gDevice record (described below) is allocated and initialized for each video card found by the system. These gDevice records are linked together in a linked list, which is called the DeviceList.
By default, the gDevice record corresponding to the first video card found is marked as an active device (a device your program can use for drawing); all other devices in the list are marked as inactive. The ways that other devices become active are described below. When drawing is being performed on a device, that device is stored as theGDevice.
If you want your application to write into an offscreen pixMap whose pixel depth or set of colors is different from that of the screen, your program must allocate a gDevice to describe the format of the offscreen pixMap. Your application could describe the set of colors that a printer can support, or represent an offscreen version of an image that spans multiple screens. More details of this technique are given below.
GDevices that correspond to video devices have drivers associated with them. These drivers are used, for example, to change the mode of the device from monochrome to color, or to change the pixel depth of the device. GDevices that your application creates won’t generally require drivers. The set of calls supported by a video driver is defined and described in “Designing Cards and Drivers for Macintosh II and Macintosh SE.”
_______________________________________________________________________________
»DEVICE RECORDS
_______________________________________________________________________________
All information that is needed to communicate with a graphics device is stored in a handle to a gDevice record, called a gdHandle. This information may describe many types of devices, including video displays, printers, or offscreen drawing environments.
The structure of the gDevice record is as follows:
TYPE
GDHandle = ^GDPtr;
GDPtr = ^GDevice;
GDevice = RECORD
gdRefNum: INTEGER; {reference number of driver}
gdID: INTEGER; {client ID for search procedure}
gdType: INTEGER; {device type}
gdITable: ITabHandle; {inverse table}
gdResPref: INTEGER; {preferred resolution}
gdSearchProc: SProcHndl; {list of search procedures}
gdCompProc: CProcHndl; {list of complement procedures}
gdFlags: INTEGER; {grafDevice flags word}
gdPMap: PixMapHandle; {pixel map for displayed image}
gdRefCon: LONGINT; {reference value}
gdnextGD: GDHandle; {handle of next gDevice}
gdRect: Rect; {device's global bounds}
gdMode: LONGINT; {device's current mode}
gdCCBytes: INTEGER; {rowBytes of expanded cursor data}
gdCCDepth: INTEGER; {rowBytes of expanded cursor data}
gdCCXData: Handle; {handle to cursor's expanded data}
gdCCXMask: Handle; {handle to cursor's expanded mask}
gdReserved: LONGINT {reserved for future expansion}
END;
Field descriptions
gdRefNum The gdRefNum is a reference number of the driver for the
display device associated with this card. For most display
devices, this information is set at system startup time.
gdID The gdID field contains an application-settable ID number
identifying the current client of the port. It is also used
for search and complement procedures (see “The Color Manager:
Search and Complement Procedures”).
gdType The gdType field specifies the general type of device.
Values include:
0 = CLUT device (mapped colors with lookup table)
1 = fixed colors (no lookup table)
2 = direct RGB
These device types are described in the Color Manager chapter.
gdITable The gdITable contains a handle to the inverse table for
color mapping (see “The Color Manager: Inverse Tables”).
gdResPref The gdResPref field contains the preferred resolution for
inverse tables (see “The Color Manager: Inverse Tables”).
gdSearchProc The gdSearchProc field is a pointer to the list of search
procedures (see “The Color Manager: Search and Complement
Procedures”); its value is NIL for a default procedure.
gdCompProc The gdCompProc field is a pointer to a list of complement
procedures (see “The Color Manager: Search and Complement
Procedures”; its value is NIL for a default procedure.
gdFlags The gdFlags field contains the gDevice’s attributes. Do not
set these flags directly; always use the procedures described
in this chapter.
gdPMap The gdPMap field is a handle to a pixel map giving
the dimension of the image buffer, along with the
characteristics of the device (resolution, storage format,
color depth, color table). For gDevices, the high bit of
theGDevice^^.gdPMap^^.pmTable^^.ctFlags is always set.
gdRefCon The gdRefCon is a field used to pass device-related
parameters (see SeedCFill and CalcCMask in the Color
QuickDraw chapter). Since a device is shared, you shouldn’t
store data here.
gdNextGD The gdNextGD field contains a handle to the next device in
the deviceList. If this is the last device in the deviceList,
this is set to zero.
gdRect The gdRect field contains the boundary rectangle of the
gDevice. The screen with the menu bar has topLeft = 0,0.
All other devices are relative to it.
gdMode The gdMode field specifies the current setting for the
device mode. This is the value passed to the driver to
set its pixel depth, etc.
gdCCBytes The gdCCBytes field contains the rowBytes of the expanded
cursor. Applications must not change this field.
gdCCDepth The gdCCDepth field contains the depth of the expanded
cursor. Applications must not change this field.
gdCCXData The gdCCXData field contains a handle to the cursor’s
expanded data. Applications must not change this field.
gdCCXMask The gdCCXMask field contains a handle to the cursor’s
expanded mask. Applications must not change this field.
gdReserved The gdReserved field is reserved for future expansion;
it must be set to zero for future compatibility.
_______________________________________________________________________________
»MULTIPLE SCREEN DEVICES
_______________________________________________________________________________
This section describes how multiple screen devices are supported by the system. It tells how they are initialized, and once initialized, how they’re used.
When the system is started up, one of the display devices is selected as the startup screen, the screen on which the “happy Macintosh” icon appears. If a startup screen has been indicated in parameter RAM, then that screen is used. Otherwise, the screen whose video card is in the lowest numbered slot is used as the startup screen. By default, the menu bar is placed on the startup screen. The screen with the menu bar is called the main screen.
The user can use the Control Panel to set the desired depth of each screen, whether it displays monochrome or color, and the position of that screen relative to the screen with the menu bar. Users can also select which screen should have the menu bar on it. See the Control Panel chapter for more information. All this information is stored in a resource of type 'scrn' (ID=0) in the system file.
When the InitGraf routine is called to initialize QuickDraw, it checks the System file for this resource. If it is found, the screens are organized according to the contents of this resource. If it is not found, then only the startup screen is used. The precise format of a 'scrn' resource is described in the “Graphics Device Resources” section.
When InitWindows is called, it scans through the device list and creates a region that is the union of all the active screen devices (minus the menu bar and the rounded corners on the outermost screens). It saves this region as the global variable GrayRgn, which describes and defines the desktop, the area on which windows can be dragged. Programs that paint the desktop should use FillRgn(GrayRgn,myPattern). Programs that move objects around on the desktop should pin to the GrayRgn, not to screenBits.bounds.
Since the Window Manager allows windows to be dragged anywhere within the GrayRgn, windows can span screen boundaries, or be moved to entirely different screens. Despite this fact, QuickDraw can draw to the window’s port as if it were all on one screen. In general terms, it works like this: when an application opens a window, the window’s port.portBits.baseAddr field is set to be equal to the base address of the main screen. When QuickDraw draws into a grafPort or cGrafPort, it compares the base address of the port to that of the main screen. If they are equal, then QuickDraw might need to draw to multiple screens.
If there are multiple screens, QuickDraw calculates the rectangle, in global coordinates, into which the drawing operation will write. For each active screen device in the device list, QuickDraw intersects the destination rectangle with the device’s rectangle (gdRect). If they intersect, the drawing command is issued to that device, with a new pixel value for the foreground and background colors if necessary. In addition, patterns and other structures may be reexpanded for each device.
_______________________________________________________________________________
»GRAPHICS DEVICE ROUTINES
_______________________________________________________________________________
The following set of routines allows an application to create and examine gDevice records. Since most device and driver information is automatically set at system startup time, these routines are not needed by most applications that simply draw to the screen.
FUNCTION NewGDevice(refNum: INTEGER; mode: LONGINT) GDHandle;
The NewGDevice function allocates a new gDevice data structure and all of its handles, then calls InitGDevice to initialize it for the specified device in the specified mode. If the request is unsuccessful, a NIL handle is returned. The new gDevice and all of its handles are allocated in the system heap. All attributes in the GDFlags word are set to FALSE.
If your application creates a gDevice without a driver, the mode parameter should be set to –1. In this case, InitGDevice is not called to initialize the gDevice. Your application must perform all initialization.
A graphics device’s default mode is defined as 128, as described in the Designing Cards and Drivers manual; this is assumed to be a monochrome mode. If the mode parameter is not the default mode, the gdDevType attribute is set TRUE, to indicate that the device is capable of displaying color (see the SetDeviceAttribute call).
This routine doesn’t automatically insert the gDevice into the device list. In general, your application shouldn’t add devices that it created to the device list.
PROCEDURE InitGDevice(gdRefNum: INTEGER; mode: LONGINT; gdh: GDHandle);
The InitGDevice routine sets the video device whose driver has the specified gdRefNum to the specified mode. It then fills out the gDevice record structure specified by the gdh parameter to contain all information describing that mode. The GDHandle should have been allocated by a call to NewGDevice.
The mode determines the configuration of the device; possible modes for a device can be determined by interrogating the video card’s ROM via calls to the Slot Manager (refer to the Slot Manager chapter and the Designing Cards and Drivers manual). Refer to the Device Manager chapter for more details about the interaction of devices and their drivers.
The information describing the new mode is primarily contained in the video card’s ROM. If the device has a fixed color table, then that table is read directly from the ROM. If the device has a variable color table, then the default color table for that depth is used (the 'clut' resource with ID=depth).
In general, your application should never need to call this routine. All video devices are initialized at start time and their modes are changed by the control panel. If your program is initializing a device without a driver, this call will do nothing; your application must initialize all fields of the gDevice. It is worth noting that after your program initializes the color table for the device, it needs to call MakeITable to build the inverse table for the device.
FUNCTION GetGDevice: GDHandle;
The GetGDevice routine returns a handle to the current gDevice. This is useful for determining the characteristics of the current output device (for instance its pixelSize or color table). Note that since a window can span screen boundaries, this call does not return the device that describes a port.
Assembly-language note: A handle to the currently active device is kept
in the global variable TheGDevice.
PROCEDURE SetGDevice(gdh: GDHandle);
The SetGDevice procedure sets the specified gDevice as the current device. Your application won’t generally need to use this call except to draw to offscreen gDevices.
FUNCTION DisposGDevice: GDHandle;
The DisposGDevice function disposes of the current gDevice and releases the space allocated for it, and all data structures allocated by NewGDevice.
FUNCTION GetDeviceList: GDHandle;
The GetDeviceList function returns a handle to the first device in the DeviceList.
Assembly-language note: A handle to the first element in the device
list is kept in the global variable DeviceList.
FUNCTION GetMainDevice: GDHandle;
The GetMainDevice function returns the handle of the gDevice that has the menu bar on it. Your application can examine this gDevice to determine the size or depth of the main screen.
Assembly-language note: A handle to the current main device is kept
in the global variable MainDevice.
FUNCTION GetNextDevice (gdh: GDHandle): GDHandle;
The GetnextDevice function returns the handle of the next gDevice in the DeviceList. If there are no more devices in the list, it returns NIL.
PROCEDURE SetDeviceAttribute: (gdh: GDHandle; attribute: INTEGER;
value: BOOLEAN);
The SetDeviceAttribute routine can be used to set a device’s attribute bits. The following attributes may be set using this call:
gdDevType = 0; {0 = monochrome, 1 = color}
ramInit = 10; {set if device has been initialized from RAM}
mainScreen = 11; {set if device is main screen}
allInit = 12; {set if devices were initialized from a 'scrn' resource}
screenDevice = 13; {set if device is a screen device}
noDriver = 14; {set if device has no driver}
screenActive = 15; {set if device is active}
FUNCTION TestDeviceAttribute (curDevice: GDHandle;
attribute: INTEGER) : BOOLEAN;
The TestDeviceAttribute function tests a single attribute to see if it is true or not. If your application is scanning through the device list, it would typically use this routine to test if a device is a screen device, and if so, test to see if it’s active. Then your application can draw to any active screen devices.
FUNCTION GetMaxDevice (globalRect: Rect):GDHandle;
The GetMaxDevice routine returns a handle to the deepest device that intersects the specified global rectangle. Your application might use this routine to allocate offscreen pixMaps, as described in the following section.
_______________________________________________________________________________
»DRAWING TO OFFSCREEN DEVICES
_______________________________________________________________________________
It’s sometimes desirable to perform drawing operations offscreen, and then use CopyBits to transfer the complete image to the screen. One reason to do this is to avoid the flicker that can happen when your program is drawing overlapping objects. Another reason might be to control the set of colors used in the drawing (for instance, if your application performs imaging for a printer that has a different set of colors than the screen). For both these examples, your application needs control of the color environment, and thus needs to make use of gDevices.
First, let’s look at the example of drawing a number of objects offscreen, and then transferring the completed image to the screen. In this case, the complicating factor is the possibility that your program may open a window that will span two (or more) screens with different depths. One way to approach the problem is to allocate the offscreen pixMap with a depth that is the same as the deepest screen touched by the window. This allows your program to perform offscreen drawing with the maximum number of colors that is used by any window, giving optimal visual results. Another approach is to allocate the offscreen pixMap with the depth of the screen that contains the largest portion of the window, so that transfers to the screen will be as fast as possible. You might want to alternate between these techniques depending on the position of the window.
_______________________________________________________________________________
»Optimizing Visual Results
When allocating a pixMap for the deepest screen, your application should first allocate an offscreen grafPort that has the depth of the deepest screen the window overlaps. To do this, your application must save the current gDevice
(GetGDevice), get the deepest screen (GetMaxDevice), set that to be the current gDevice (SetGDevice), create a new cGrafPort (OpenCPort), and then restore the saved gDevice (SetGDevice again). Since OpenCPort initializes its pixMap using TheGDevice, the current grafPort is the same as the deepest screen.
Next, your application must allocate storage for the pixels by setting portPixMap^^.bounds to define the height and width of the desired image, and setting rowBytes to ((width*portPixMap^^.pixelSize)+15)DIV 16*2. (Note that rowBytes must be even, and for optimal performance should be a multiple of
four. Your application can adjust portPixMap^^.bounds to achieve this.)
Next, define the interior of portPixMap.bounds to which your application can write by setting portRect. Now that the size of the pixMap is defined, the amount of storage is simply the height*portPixMap^^.rowBytes. It is generally better to allocate the storage as a handle. Before writing to it, your application should lock the handle, and place a pointer to the storage in
portPixMap^^.baseAddr.
All that remains is to draw to the grafPort. Before drawing, your program should save the current gDevice, and then set TheGDevice to be the maximum device (which was determined earlier). Your application can use SetPort to make this port the current port, and then perform all drawing operations. Remember to have your application restore TheGDevice after drawing is complete.
Keep in mind that all this preparation can be invalidated easily. If the user changes the depth of the screen or moves the window, all your carefully allocated storage may no longer be appropriate. Both changing the depth of the screen and moving the window across device boundaries will cause update events. In your application’s update routine, include a test to see if the environment has changed. One good test is to determine whether the color table has changed. Your application can compare the ctSeed field of the new maximum device with that of the old maximum device. (See the Color Manager chapter for more information on this technique.) If ctSeed has changed, your application should check the screen depth, and if it has changed, reallocate the pixMap (possibly repeating the entire process above). If the depth hasn’t changed, but the color table has, then your application can just redraw the objects into the offscreen pixMap.
_______________________________________________________________________________
»Optimizing Speed
If you decide to optimize for speed instead of appearance, then your application should examine each element in the device list to see how much of the window it intersects. Your application can do this by getting the device list (GetDeviceList), intersecting that device’s rectangle with your window’s rectangle, and then repeating the examination for each device by calling GetNextDevice. Before examining a device, your application can ensure that it is an active screen device using GetDeviceAttribute. The procedure for allocating the cGrafPort is the same as described above.
_______________________________________________________________________________
»Imaging for a Color Printer
Finally, let’s look briefly at the example of imaging into an offscreen device that isn’t the same as one of the screen devices, which you might do if you were imaging for a color printer. In this case the process is much the same, but instead of relying on an existing gDevice to define the drawing environment, your application must set up a new one. To do this, simply call NewGDevice to allocate a new gDevice data structure. Your application must initialize all fields of the pixMap and color table, as described in the Color QuickDraw chapter. It should call then MakeITable to build the device’s inverse table, as described in the Color Manager chapter. As with the example above, your application should set its gDevice as the current device before drawing to the offscreen pixMap. This will guarantee that drawing is done using the set of colors defined by your application’s gDevice.
_______________________________________________________________________________
»GRAPHICS DEVICE RESOURCES
_______________________________________________________________________________
A new resource type has been added to describe the setup of graphics devices:
'scrn' Screen resource type
The 'scrn' resource contains all the screen configuration information for a multiple screen system. Only the 'scrn' resource with ID = 0 is used by the system. Normally your application won’t have to alter or examine this resource. It’s created by the control panel, and is used by InitGraf.
The 'scrn' resource consists of a sequence of records, each describing one screen device. In the following description this sequence of records is represented by a Pascal FOR loop that repeats once for each screen device.
'scrn' (Screen configuration)
ScrnCount [word] number of devices in resource
FOR i := 1 to ScrnCount DO
spDrvrHw [word] Slot Manager hardware ID
slot [word] slot number
dCtlDevBase [long] dCtlDevBase from DCE
mode [word] Slot Manager ID for screen’s mode
flagMask [word] = $77FE
flags [word] indicates device state
bit 0 = 0 if monochrome; 1 if color
bit 11 = 1 if device is main screen
bit 15 = 1 if device is active
colorTable [word] resource id of desired 'clut'
gammaTable [word] resource id of desired 'gama'
global Rect [rect] device’s global rectangle
ctlCount [word] number of control calls
FOR j := 1 to ctlCount DO
csCode [word] control code for this call
length [word] number of bytes in param block
param blk [length] data to be passed in control call
END;
END;
The records in the 'scrn' resource must be in the same order as cards in the slots (starting with the lowest slot). InitGraf scans through the video cards in the slots, and compares them with the descriptors in the 'scrn' resource. If the spDrvrHw, slot, and dCtlDevBase fields all match for every screen device in the system, the 'scrn' resource is used to initialize the video devices. Otherwise the 'scrn' resource is simply ignored. Thus if you move a video card, or add or remove one, the 'scrn' resource will become invalid.
SpDrvrHw is a Slot Manager field that identifies the type of hardware on the card. (The spDrvrSw field on the card must identify it as an Apple-compatible video driver.) Slot is the number of the slot containing the card. DCtlDevBase is the beginning of the device’s address space, taken from the device’s DCE.
If all video devices match, the rest of the information in the 'scrn' resource is used to configure the video devices. The mode is actually the slot manager ID designating the descriptor for that mode. This same mode number is passed to the video driver to tell it which mode to use.
The flags bits are used to determine whether the device is active (that is, whether it will be used), whether it’s color or monochrome, and whether it’s the main screen (the one with the menu bar). The flagMask simply tells which bits in the flags word are used.
To use the default color table for a device, set the colorTable field to –1. To use the default gamma table for a device, set the gammaTable field to –1.
(Gamma correction is a technique used to select the appropriate intensities of the colors sent to a display device. The default gamma table is designed for the Macintosh II 13-inch color monitor; other manufacturers’ color monitors might incorporate their own gamma tables.)
The global rect specifies the coordinates of the device relative to other devices. The main device must have topLeft = 0,0. The coordinates of all other devices are specified relative to this device. Devices may not overlap, and must share at least part of an edge with another device. To support future device capabilities, a series of control calls may be specified. These are issued to the driver in the given order.
_______________________________________________________________________________
»SUMMARY OF GRAPHICS DEVICES
_______________________________________________________________________________
Constants
{ Values for GDFlags }
clutType = 0; {0 if lookup table}
fixedType = 1; {1 if fixed table}
directType = 2; {2 if direct values}
{ Bit assignments for GDFlags }
gdDevType = 0; {0 = monochrome, 1 = color}
ramInit = 10; {set if device has been initialized from RAM}
mainScreen = 11; {set if device is main screen}
allInit = 12; {set if devices were initialized from a 'scrn' resource}
screenDevice = 13; {set if device is a screen device}
noDriver = 14; {set if device has no driver}
screenActive = 15; {set if device is active}
_______________________________________________________________________________
Data Types
TYPE
GDHandle = ^GDPtr;
GDPtr = ^GDevice;
GDevice = RECORD
gdRefNum: INTEGER; {reference number of driver}
gdID: INTEGER; {client ID for search procedure}
gdType: INTEGER; {device type}
gdITable: ITabHandle; {inverse table}
gdResPref: INTEGER; {preferred resolution}
gdSearchProc: SProcHndl; {list of search procedures}
gdCompProc: CProcHndl; {list of complement procedures}
gdFlags: INTEGER; {grafDevice flags word}
gdPMap: PixMapHandle; {pixel map for displayed image}
gdRefCon: LONGINT; {reference value}
gdnextGD: GDHandle; {handle of next gDevice}
gdRect: Rect; {device's global bounds}
gdMode: LONGINT; {device's current mode}
gdCCBytes: INTEGER; {rowBytes of expanded cursor data}
gdCCDepth: INTEGER; {rowBytes of expanded cursor data}
gdCCXData: Handle; {handle to cursor's expanded data}
gdCCXMask: Handle; {handle to cursor's expanded mask}
gdReserved: LONGINT {reserved for future expansion}
END;
_______________________________________________________________________________
Routines
FUNCTION NewGDevice (refNum: INTEGER; mode: LONGINT) : GDHandle;
PROCEDURE InitGDevice (gdRefNum: INTEGER; mode: LONGINT; gdh: GDHandle);
FUNCTION GetGDevice: GDHandle;
PROCEDURE SetGDevice (gdh: GDHandle);
PROCEDURE DisposGDevice (gdh: GDHandle);
FUNCTION GetDeviceList: GDHandle;
FUNCTION GetMainDevice: GDHandle;
FUNCTION GetNextDevice (curDevice:GDHandle): GDHandle;
PROCEDURE SetDeviceAttribute (gdh: GDHandle; attribute: INTEGER;
value: BOOLEAN);
FUNCTION TestDeviceAttribute (gdh: GDHandle; attribute: INTEGER): BOOLEAN;
FUNCTION GetMaxDevice (globalRect:Rect): GDHandle;
_______________________________________________________________________________
Global Variables
DeviceList {handle to the first element in the device list}
GrayRgn {contains size and shape of current desktop}
TheGDevice {handle to current active device}
MainDevice {handle to the current main device}
_______________________________________________________________________________
Assembly Language Information
Values for GDTypes
clutType EQU 0 ;0 if lookup table
fixedType EQU 1 ;1 if fixed table
directType EQU 2 ;2 if direct values
Bit Assignments for GDFlags
gdDevType EQU 0 ;0 = monochrome, 1 = color
ramInit EQU 10 ;set if device has been initialized from RAM
mainScreen EQU 11 ;set if device is main screen
allInit EQU 12 ;set if devices were initialized from a
; 'scrn' resource
screenDevice EQU 13 ;set if device is a screen device
noDriver EQU 14 ;set if device has no driver
screenActive EQU 15 ;set if device is active
GDevice field offsets
gdRefNum EQU $0 ;[word] unitNum of driver
gdID EQU $2 ;[word] client ID for search procs
gdType EQU $4 ;[word] fixed/CLUT/direct
gdITable EQU $6 ;[long] handle to inverse table
gdResPref EQU $A ;[word] preferred resolution for inverse tables
gdSearchProc EQU $C ;[long] search proc (list?) pointer
gdCompProc EQU $10 ;[long] complement proc (list?) pointer
gdFlags EQU $14 ;[word] grafDevice flags word
gdPMap EQU $16 ;[long] handle to pixMap describing device
gdRefCon EQU $1A ;[long] reference value
gdNextGD EQU $1E ;handle of next gDevice
gdRect EQU $22 ;device's global bounds
gdMode EQU $2A ;device's current mode
gdCCBytes EQU $2E ;rowBytes of expanded cursor data
gdCCDepth EQU $30 ;handle to cursor’s expanded data
gdCCXData EQU $32 ;depth of expanded cursor data
gdCCXMask EQU $36 ;handle to cursor's expanded mask
gdReserved EQU $3A ;[long] MUST BE 0
gdRec EQU $3E ;size of GrafDevice record
Global Variables
DeviceList EQU $8A8 ;handle to the first element in the device list
GrayRgn EQU $9EE ;contains size and shape of current desktop
TheGDevice EQU $CC8 ;handle to current active device
MainDevice EQU $8A4 ;handle to the current main device
Further Reference:
_______________________________________________________________________________
Color QuickDraw
Color Manager
Slot Manager
Device Manager
32-Bit QuickDraw Documentation
TextEdit
_______________________________________________________________________________
TEXTEDIT
_______________________________________________________________________________
About This Chapter
About TextEdit
Data Structures
The Edit Record
The Destination and View Rectangles
The Selection Range
Justification
The TERec Data Type
The WordBreak Field
The ClikLoop Field
The Style Record
The Style Table
The Line-Height Table
The Null-Style Record
Text Styles
The Style Scrap
Using TextEdit
Cutting and Pasting
TextEdit Routines
Initialization and Allocation
Accessing the Text or Style Information of an Edit Record
Insertion Point and Selection Range
Editing
Text Display and Scrolling
Scrap Handling
Advanced Routines
Summary of TextEdit
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
TextEdit is the part of the Toolbox that handles basic text formatting and editing capabilities in a Macintosh application. This chapter describes the TextEdit routines and data types in detail as well as the enhanced version of TextEdit for the Macintosh Plus, the Macintosh SE and Macintosh II. The new TextEdit routines allow text attributes such as font, size, style, and color to vary from one character to another. The changes are backward compatible with earlier Macintosh versions: all existing programs using TextEdit routines should still work. The new TextEdit is also fully compatible with the Script Manager.
You should already be familiar with:
• the basic concepts and structures behind QuickDraw, particularly
points, rectangles, grafPorts, fonts, and character style
• the Toolbox Event Manager the Window Manager, particularly update
and activate events
_______________________________________________________________________________
»ABOUT TEXTEDIT
_______________________________________________________________________________
Note: The extensions to TextEdit described in this chapter were originally
documented in Inside Macintosh, Volumes IV and V. As such, the Volume
IV information refers to the 128K ROM and System file version 3.2 and
later, while the Volume V information refers to the Macintosh SE and
Macintosh II ROMs and System file version 4.1 and later. The sections
of this chapter that cover these extensions are so noted.
TextEdit is a set of routines and data types that provide the basic text editing and formatting capabilities needed in an application. These capabilities include:
• inserting new text
• deleting characters that are backspaced over
• translating mouse activity into text selection
• scrolling text within a window
• deleting selected text and possibly inserting it elsewhere, or
copying text without deleting it
The TextEdit routines follow the Macintosh User Interface Guidelines; using them ensures that your application will present a consistent user interface. The Dialog Manager uses TextEdit for text editing in dialog boxes.
TextEdit supports these standard features:
• Selecting text by clicking and dragging with the mouse, double-clicking
to select words. To TextEdit, a word is any series of printing characters,
excluding spaces (ASCII code $20) but including nonbreaking spaces
(ASCII code $CA).
• Extending or shortening the selection by Shift-clicking.
• Inverse highlighting of the current text selection, or display of a
blinking vertical bar at the insertion point.
• Word wraparound, which prevents a word from being split between lines
when text is drawn.
• Cutting (or copying) and pasting within an application via the
Clipboard. TextEdit puts text you cut or copy into the TextEdit scrap.
Note: The TextEdit scrap is used only by TextEdit; it’s not the same as
the “desk scrap” used by the Scrap Manager. To support cutting and
pasting between applications, or between applications and desk
accessories, you must transfer information between the two scraps.
Although TextEdit is useful for many standard text editing operations, there are some additional features that it doesn’t support. TextEdit does not support:
• the use of more than one font or stylistic variation in a single
block of text
• fully justified text (text aligned with both the left and right margins)
• “intelligent” cut and paste (adjusting spaces between words during
cutting and pasting)
• tabs
TextEdit also provides “hooks” for implementing some features such as automatic scrolling or a more precise definition of a word.
Note: The extensions to TextEdit described in the following paragraphs were
originally documented in Inside Macintosh, Volume IV. As such, this
information refers to the 128K ROMs and System file version 3.2 and
later.
When used with the System file version 3.0 or later, TextEdit also automatically supports the movement of the insertion point with the Macintosh Plus arrow keys; this is described the Macintosh User Interface Guidelines chapter.
Warning: Command–arrow key combinations are not supported by TextEdit
and must be handled by your application. Selection expansion
must also be handled by your application.
_______________________________________________________________________________
»DATA STRUCTURES
_______________________________________________________________________________
Note: The extensions to TextEdit described in the following paragraphs were
originally documented in Inside Macintosh, Volume V. As such, this
information refers to the Macintosh SE and Macintosh II ROMs and System
file version 4.1 and later.
The structure and size of the edit record are unchanged in the enhanced version of TextEdit, but a few of its fields are interpreted in different ways. All records have a 32K maximum size. A new data structure, the style record, has been introduced to carry the style information for the edit record’s text, along with various subsidiary data structures: the style run, the style table and its style elements, the line-height table and its line-height elements, and the null-style record. In addition, there is the text style record for passing style information to and from TextEdit routines, and the style scrap record for writing style information to the desk scrap.
_______________________________________________________________________________
»The Edit Record
Note: The information on the Edit Record described in the following
paragraphs was originally documented in Inside Macintosh, Volume I.
To edit text on the screen, TextEdit needs to know where and how to display the text, where to store the text, and other information related to editing. This display, storage, and editing information is contained in an edit record that defines the complete editing environment. The data type of an edit record is called TERec.
You prepare to edit text by specifying a destination rectangle in which to draw the text and a view rectangle in which the text will be visible. TextEdit incorporates the rectangles and the drawing environment of the current grafPort into an edit record, and returns a handle of type TEHandle to the record:
TYPE TEPtr = ^TERec;
TEHandle = ^TEPtr;
Most of the text editing routines require you to pass this handle as a parameter.
In addition to the two rectangles and a description of the drawing environment, the edit record also contains:
• a handle to the text to be edited
• a pointer to the grafPort in which the text is displayed
• the current selection range, which determines exactly which
characters will be affected by the next editing operation
• the justification of the text, as left, right, or center
The special terms introduced here are described in detail below.
For most operations, you don’t need to know the exact structure of an edit record; TextEdit routines access the record for you. However, to support some operations, such as automatic scrolling, you need to access the fields of the edit record directly. The structure of an edit record is given below.
Note: The extensions to TextEdit described in the following paragraph were
originally documented in Inside Macintosh, Volume IV. As such, this
information refers to the 128K ROMs and System file version 3.2 and
later.
TextEdit now installs a default click loop routine in the edit record that supports automatic scrolling; you still need, however, to update the scroll bars. If automatic scrolling is enabled, this routine checks to see if the mouse has been dragged out of the view rectangle; if it has, the routine scrolls the text using TEPinScroll. The amount by which the text is scrolled, whether horizontally or vertically, is determined by the lineHeight field of the edit record.
»The Destination and View Rectangles
Note: The information on the Destination and View Rectangles described in
the following paragraphs was originally documented in Inside Macintosh,
Volume I.
The destination rectangle is the rectangle in which the text is drawn. The view rectangle is the rectangle within which the text is actually visible. In other words, the view of the text drawn in the destination rectangle is clipped to the view rectangle (see Figure 1).
Figure 1–Destination and View Rectangles
You specify both rectangles in the local coordinates of the grafPort. To ensure that the first and last characters in each line are legible in a document window, you may want to inset the destination rectangle at least four pixels from the left and right edges of the grafPort’s portRect (20 pixels from the right edge if there’s a scroll bar or size box).
Edit operations may of course lengthen or shorten the text. If the text becomes too long to be enclosed by the destination rectangle, it’s simply drawn beyond the bottom. In other words, you can think of the destination rectangle as bottomless—its sides determine the beginning and end of each line of text, and its top determines the position of the first line.
Normally, at the right edge of the destination rectangle, the text automatically wraps around to the left edge to begin a new line. A new line also begins where explicitly specified by a Return character in the text. Word wraparound ensures that no word is ever split between lines unless it’s too long to fit entirely on one line, in which case it’s split at the right edge of the destination rectangle.
»The Selection Range
In the text editing environment, a character position is an index into the text, with position 0 corresponding to the first character. The edit record includes fields for character positions that specify the beginning and end of the current selection range, which is the series of characters where the next editing operation will occur. For example, the procedures that cut or copy from the text of an edit record do so to the current selection range.
The selection range, which is inversely highlighted when the window is active, extends from the beginning character position to the end character position. Figure 2 shows a selection range between positions 3 and 8, consisting of five characters (the character at position 8 isn’t included). The end position of a selection range may be 1 greater than the position of the last character of the text, so that the selection range can include the last character.
If the selection range is empty—that is, its beginning and end positions are the same—that position is the text’s insertion point, the position where characters will be inserted. By default, it’s marked with a blinking caret. If, for example, the insertion point is as illustrated in Figure 2 and the inserted characters are “edit ”, the text will read “the edit insertion point”.
Figure 2–Selection Range and Insertion Point
Note: We use the word caret here generically, to mean a symbol
indicating where something is to be inserted; the specific
symbol is a vertical bar ( | ).
If you call a procedure to insert characters when there’s a selection range of one or more characters rather than an insertion point, the editing procedure automatically deletes the selection range and replaces it with an insertion point before inserting the characters.
»Justification
TextEdit allows you to specify the justification of the lines of text, that is, their horizontal placement with respect to the left and right edges of the destination rectangle. The different types of justification supported by TextEdit are illustrated in Figure 3.
• Left justification aligns the text with the left edge of the
destination rectangle. This is the default type of justification.
• Center justification centers each line of text between the left
and right edges of the destination rectangle.
• Right justification aligns the text with the right edge of the
destination rectangle.
Figure 3–Justification
Note: Trailing spaces on a line are ignored for justification. For
example, “Fred” and “Fred ” will be aligned identically. (Leading
spaces are not ignored.)
TextEdit provides three predefined constants for setting the justification:
CONST teJustLeft = 0;
teJustCenter = 1;
teJustRight = -1;
»The TERec Data Type
The structure of an edit record is given here. Some TextEdit features are available only if you access fields of the edit record directly.
TYPE TERec = RECORD
destRect: Rect; {destination rectangle}
viewRect: Rect; {view rectangle}
selRect: Rect; {used from assembly language}
lineHeight: INTEGER; {for line spacing}
fontAscent: INTEGER; {caret/highlighting position}
selPoint: Point; {used from assembly language}
selStart: INTEGER; {start of selection range}
selEnd: INTEGER; {end of selection range}
active: INTEGER; {used internally}
wordBreak: ProcPtr; {for word break routine}
clikLoop: ProcPtr; {for click loop routine}
clickTime: LONGINT; {used internally}
clickLoc: INTEGER; {used internally}
caretTime: LONGINT; {used internally}
caretState: INTEGER; {used internally}
just: INTEGER; {justification of text}
teLength: INTEGER; {length of text}
hText: Handle; {text to be edited}
recalBack: INTEGER; {used internally}
recalLines: INTEGER; {used internally}
clikStuff: INTEGER; {used internally}
crOnly: INTEGER; {if <0, new line at Return only}
txFont: INTEGER; {text font}
txFace: Style; {character style}
txMode: INTEGER; {pen mode}
txSize: INTEGER; {font size}
inPort: GrafPtr; {grafPort}
highHook: ProcPtr; {used from assembly language}
caretHook: ProcPtr; {used from assembly language}
nLines: INTEGER; {number of lines}
lineStarts: ARRAY[0..16000] OF INTEGER
{positions of line starts}
END;
Warning: Don’t change any of the fields marked “used internally”—these
exist solely for internal use among the TextEdit routines.
The destRect and viewRect fields specify the destination and view rectangles.
The lineHeight and fontAscent fields have to do with the vertical spacing of the lines of text, and where the caret or highlighting of the selection range is drawn relative to the text. The fontAscent field specifies how far above the base line the pen is positioned to begin drawing the caret or highlighting. For single-spaced text, this is the ascent of the text in pixels (the height of the tallest characters in the font from the base line). The lineHeight field specifies the vertical distance from the ascent line of one line of text down to the ascent line of the next. For single-spaced text, this is the same as the font size, but in pixels. The values of the lineHeight and fontAscent fields for single-spaced text are shown in Figure 4. For more information on fonts, see the Font Manager chapter.
Figure 4–LineHeight and FontAscent
If you want to change the vertical spacing of the text, you should change both the lineHeight and fontAscent fields by the same amount, otherwise the placement of the caret or highlighting of the selection range may not look right. For example, to double the line spacing, add the value of lineHeight to both fields. (This doesn’t change the size of the characters; it affects only the spacing between lines.) If you change the size of the text, you should also change these fields; you can get font measurements you’ll need with the QuickDraw procedure GetFontInfo.
Assembly-language note: The selPoint field (whose assembly-language offset
is named teSelPoint) contains the point selected
with the mouse, in the local coordinates of the
current grafPort. You’ll need this for hit-testing
if you use the routine pointed to by the global
variable TEDoText (see “Advanced Routines” in the
“TextEdit Routines” section).
The selStart and selEnd fields specify the character positions of the beginning and end of the selection range. Remember that character position 0 refers to the first character, and that the end of a selection range can be 1 greater than the position of the last character of the text.
The wordBreak field lets you change TextEdit’s definition of a word, and the clikLoop field lets you implement automatic scrolling. These two fields are described in separate sections below.
The just field specifies the justification of the text. (See “Justification”, above.)
The teLength field contains the number of characters in the text to be edited
(the maximum length is 32K bytes). The hText field is a handle to the text. You can directly change the text of an edit record by changing these two fields.
The crOnly field specifies whether or not text wraps around at the right edge of the destination rectangle, as shown in Figure 5. If crOnly is positive, text does wrap around. If crOnly is negative, text does not wrap around at the edge of the destination rectangle, and new lines are specified explicitly by Return characters only. This is faster than word wraparound, and is useful in an application similar to a programming-language editor, where you may not want a single line of code to be split onto two lines.
Figure 5–New Lines
The txFont, txFace, txMode, and txSize fields specify the font, character style, pen mode, and font size, respectively, of all the text in the edit record. (See the QuickDraw chapter for details about these characteristics.) If you change one of these values, the entire text of this edit record will have the new characteristics when it’s redrawn. If you change the txSize field, remember to change the lineHeight and fontAscent fields, too.
The inPort field contains a pointer to the grafPort associated with this edit record.
Note: When printing, the inPort field must be set to the Printing Manager’s
grafPort (TPPrPort^.gPort).
Assembly-language note: The highHook and caretHook fields—at the offsets
teHiHook and teCarHook in assembly language—contain
the addresses of routines that deal with text
highlighting and the caret. These routines pass
parameters in registers; the application must save
and restore the registers.
If you store the address of a routine in teHiHook,
that routine will be used instead of the QuickDraw
procedure InvertRect whenever a selection range is
to be highlighted. The routine can destroy the
contents of registers A0, A1, D0, D1, and D2. On
entry, A3 is a pointer to a locked edit record; the
stack contains the rectangle enclosing the text being
highlighted. For example, if you store the address of
the following routine in teHiHook, selection ranges
will be underlined instead of inverted:
UnderHigh
MOVE.L (SP),A0 ;get address of
; rectangle to be
; highlighted
MOVE bottom(A0),top(A0) ;make the top
; coordinate equal
; to the bottom
SUBQ #1,top(A0) ; coordinate - 1
_InverRect ;invert the
; resulting
; rectangle
RTS
The routine whose address is stored in teCarHook acts
exactly the same way as the teHiHook routine, but on
the caret instead of the selection highlighting,
allowing you to change the appearance of the caret.
The routine is called with the stack containing the
rectangle that encloses the caret.
The nLines field contains the number of lines in the text. The lineStarts array contains the character position of the first character in each line. It’s declared to have 16001 elements to comply with Pascal range checking; it’s actually a dynamic data structure having only as many elements as needed. You shouldn’t change the elements of lineStarts.
Note: The extensions to TextEdit described in the following paragraphs were
originally documented in Inside Macintosh, Volume V. As such, this
information refers to the Macintosh SE and Macintosh II ROMs and System
file version 4.1 and later.
In the enhanced version of TextEdit, most fields of the edit record have the same meanings as in the old TextEdit, with the following exceptions:
txSize Used as a flag telling whether the edit record has style
information associated with it:
>0 Old-style edit record; all text set in a single font, size,
and face; all fields (including txSize itself) have their
old, natural meanings.
–1 Edit record has associated style information; the txFont and
txFace fields have new meanings as described below.
txFont, txFace Combine to hold a handle to the associated style record
(see “The Style Record” below). Use new routines
GetStylHandle and SetStylHandle to access or change this
handle in Pascal.
lineHeight Controls whether vertical spacing is fixed or may vary
fontAscent from line to line, depending on specific text styles:
>0 Fixed line height or font ascent, as before.
–1 Line height or font ascent calculated independently for
each line, based on maximum value for any individual style
on that line.
The new routine TEStylNew, which creates a new edit record with style information, sets txSize, lineHeight, and fontAscent to –1, allocates a style record, and stores a handle to the style record in the txFont and txFace fields. The old routine TENew still creates a new edit record without style information, initializing these fields from the current graphics port as before.
»The WordBreak Field
Note: The information on the WordBreak Field described in the following
paragraphs was originally documented in Inside Macintosh, Volume I.
The wordBreak field of an edit record lets you specify the record’s word break routine—the routine that determines the “word” that’s highlighted when the user double-clicks in the text, and the position at which text is wrapped around at the end of a line. The default routine breaks words at any character with an ASCII value of $20 or less (the space character or nonprinting control characters).
The word break routine must have two parameters and return a Boolean value. This is how you would declare one named MyWordBreak:
FUNCTION MyWordBreak (text: Ptr; charPos: INTEGER) : BOOLEAN;
The function should return TRUE to break a word at the character at position charPos in the specified text, or FALSE not to break there. From Pascal, you must call the SetWordBreak procedure to set the wordBreak field so that your routine will be used.
Assembly-language note: You can set this field to point to your own
assembly-language word break routine. The registers
must contain the following:
On entry A0: pointer to text
D0: character position (word)
On exit Z (zero) condition code:
0 to break at specified character
1 not to break there
»The ClikLoop Field
The clikLoop field of an edit record lets you specify a routine that will be called repeatedly (by the TEClick procedure, described below) as long as the mouse button is held down within the text. You can use this to implement the automatic scrolling of text when the user is making a selection and drags the cursor out of the view rectangle.
The click loop routine has no parameters and returns a Boolean value. You could declare a click loop routine named MyClikLoop like this:
FUNCTION MyClikLoop : BOOLEAN;
The function should return TRUE. From Pascal, you must call the SetClikLoop procedure to set the clikLoop field so that TextEdit will call your routine.
Warning: Returning FALSE from your click loop routine tells the TEClick
procedure that the mouse button has been released, which aborts
TEClick.
Assembly-language note: Your routine should set register D0 to 1, and
preserve register D2. (Returning 0 in register
D0 aborts TEClick.)
An automatic scrolling routine might check the mouse location, and call a scrolling routine if the mouse location is outside the view rectangle. (The scrolling routine can be the same routine that the Control Manager function TrackControl calls.) The handle to the current edit record should be kept as a global variable so the scrolling routine can access it.
_______________________________________________________________________________
»The Style Record
Note: The extensions to TextEdit described in the following paragraphs were
originally documented in Inside Macintosh, Volume V. As such, this
information refers to the Macintosh SE and Macintosh II ROMs and System
file version 4.1 and later.
The style record, located via a handle kept in the txFont and txFace fields of the edit record, specifies the styles for the edit record’s text. The text is divided into runs of consecutive characters in the same style, summarized in a table in the runs field of the style record. Each entry in this table gives the starting character position of a run and an index into the style table
(described in the next section). The length of the run is found by subtracting its start position from that of the next entry in the table. A dummy entry at the end of the table delimits the length of the last run; its start position is equal to the overall number of characters in the text, plus 1.
TYPE
TEStyleHandle = ^TEStylePtr;
TEStylePtr = ^TEStyleRec;
TEStyleRec = RECORD
nRuns: INTEGER; {number of style runs}
nStyles: INTEGER; {number of distinct styles }
{ stored in style table}
styleTab: STHandle; {handle to style table}
lhTab: LHHandle; {handle to line-height table}
teRefCon: LONGINT; {reserved for application use}
nullStyle: nullSTHandle; {handle to style set }
{ at null selection}
runs: ARRAY [0..0] OF StyleRun
END;
StyleRun = RECORD
startChar: INTEGER; {starting character position}
styleIndex: INTEGER {index in style table}
END;
Field descriptions
nRuns The nRuns field specifies the number of style runs in the text.
nStyles The nStyles field contains the number of distinct styles used
in the text; this forms the size of the style table.
styleTab The StyleTab field contains a handle to the style table (see
“The Style Table” below).
lhTab The lhTab field contains a handle to the line-height table
(see “The Line-Height Table” below).
teRefCon The teRefCon field is a reference constant for use by applications.
nullStyle The nullStyle field contains a handle to a data structure used
to store the style information for a null selection.
runs The runs field contains an indefinite-length array of style runs.
_______________________________________________________________________________
»The Style Table
The style table contains one entry for each distinct style used in an edit record’s text. The size of the table is given by the nStyles field of the style record. There is no duplication; each style appears exactly once in the table. A reference count tells how many times each style is used within the text.
TYPE
STHandle = ^STPtr;
STPtr = ^TEStyleTable;
TEStyleTable = ARRAY [0..0] OF STElement;
STElement = RECORD
stCount: INTEGER; {number of runs in this style}
stHeight: INTEGER; {line height}
stAscent: INTEGER; {font ascent}
stFont: INTEGER; {font (family) number}
stFace: Style; {character style}
stSize: INTEGER; {size in points}
stColor: RGBColor {absolute (RGB) color}
END;
Field descriptions
stCount The stCount field contains a reference count of character
runs using this style.
stHeight The stHeight field contains the line height for this style,
in points.
stAscent The stAscent field contains the font ascent for this style,
in points.
stFont The stFont field is the font (family) number.
stFace The stFace field is the character style (bold, italic, and
so forth).
stSize The stSize field is the text size in points.
stColor The stColor field is the RGB color; see the Color Manager
chapter for further information.
_______________________________________________________________________________
»The Line-Height Table
The line-height table holds vertical spacing information for an edit record’s text. This table parallels the lineStarts table in the edit record itself. Its length is given by the edit record’s nLines field plus 1 for a dummy entry at the end, just as the line starts array ends with a dummy entry that has the same value as the length of the text. The table’s contents are recalculated whenever the line starts themselves are recalculated with TECalText, or whenever an editing action causes recalibration.
The line-height table is used only if the lineHeight and fontAscent fields in the edit record are negative; positive values in those fields specify fixed vertical spacing, overriding the information in the table.
TYPE
LHHandle = ^LHPtr;
LHPtr = ^LHTable;
LHTable = ARRAY [0..0] OF LHElement;
LHElement = RECORD
lhHeight: INTEGER; {maximum height in line}
lhAscent: INTEGER {maximum ascent in line}
END;
Field descriptions
lhHeight The lhHeight field contains the line height in points;
this is the maximum value for any individual style in a line.
lhAscent The lhAscent field contains the font ascent in points;
this is the maximum value for any individual style in a line.
If you want, you can override TextEdit’s line-height calculation and store your own height and ascent values into the line-height table. Any table entry with the high bit set in the lhHeight field will be used as-is (both height and ascent), overriding whatever values TextEdit would have used. The high bit of lhHeight is masked out to arrive at the true line height, but the high bit of lhAscent is not masked, so you should never set it; the one in lhHeight serves as a flag for both fields. Notice that you can selectively set some lines for yourself and let TextEdit do the rest for you. This technique is intended to be used for static, unchanging text, such as in text boxes; if you use it on text that can change dynamically, be sure to readjust your line-height values whenever the line breaks in the text are recalculated. Otherwise, if new lines are created as a result of a text insertion, their line heights and ascents will be computed by TextEdit.
_______________________________________________________________________________
»The Null-Style Record
The null-style record is used to store the style information for a null selection. If TESetStyle is called when setStart equals setEnd, the input style information is stored in the nullStyle handle. The nStyles field of nullScrap is set to 1, and the style information is stored as the ScrpSTElement. If text is then entered (pasted, inserted, or typed), the style is entered into the runs array, and nStyles is reset to 0. The nStyles field is also reset if the selection offsets are changed (by TEClick, for example).
TYPE
NullSTHandle = ^NullSTPtr;
NullSTPtr = ^NullSTRec;
NullSTRec = RECORD
TEReserved: LONGINT; {reserved for future }
{ expansion}
nullScrap: STScrpHandle {handle to scrap style }
{ table}
END;
Field descriptions
teReserved The teReserved field is reserved for future expansion.
nullScrap The nullScrap field contains a handle to the scrap style table.
_______________________________________________________________________________
»Text Styles
Text style records are used for communicating style information between the application program and the TextEdit routines. They carry the same information as the STElement records in the style table, but without the reference count, line height, and font ascent:
TYPE
TextStyle = RECORD
tsFont: INTEGER; {Font (family) number}
tsFace: Style; {Character style}
tsSize: INTEGER; {Size in points}
tsColor: RGBColor {Absolute (RGB) color}
END;
Field descriptions
tsFont The tsFont field is the font (family) number.
tsFace The tsFace field is the character style (bold, italic, and so forth).
tsSize The tsSize field is the text size in points.
tsColor The tsColor field contains the RGB color; see the Color Manager
chapter for further information.
_______________________________________________________________________________
»The Style Scrap
A new scrap type, 'styl', is used for storing style information in the desk scrap along with the old 'TEXT' scrap. The format of the style scrap is defined by a style scrap record:
TYPE
StScrpHandle = ^StScrpPtr;
StScrpPtr = ^StScrpRec;
StScrpRec = RECORD
scrpNStyles: INTEGER; {number of distinct }
{ styles in scrap}
scrpStyleTab: ScrpSTTable {table of styles for scrap}
END;
Field descriptions
scrpNStyles The scrpNStyles field is the number of distinct styles
used in text; this forms the size of the style table.
scrpSTTable The scrpSTTable is the table of text styles: see the data
structure shown below.
Unlike the main style table for an edit record, the table in the style scrap may contain duplicate elements; the entries in the table correspond one-to-one with the character runs in the text. The scrpStartChar field of each entry gives the starting character position for the run.
The ScrpSTTable is a separate data structure defined for style records in the scrap. Its format is:
TYPE
ScrpSTTable = array [0..0] of ScrpSTElement;
ScrpSTElement = RECORD
scrpStartChar: LONGINT; {offset to start of style}
scrpHeight: INTEGER; {line height}
scrpAscent: INTEGER; {font ascent}
scrpFont: INTEGER; {font (family) number}
scrpFace: Style; {character style}
scrpSize: INTEGER; {size in points}
scrpColor: RGBColor; {absolute (RGB) color}
END;
Field descriptions
scrpStartChar The scrpStartChar field is the offset to the beginning
of a style record in the scrap.
scrpHeight The scrpHeight field contains the line height.
scrpAscent The scrpAscent field contains the font ascent.
scrpFont The scrpFont is the font’s family number.
scrpFace The scrpFace is the character style for the style scrap.
scrpSize The scrpSize field contains the size in points.
scrpColor The scrpColor field contains the RGB color for the style scrap.
_______________________________________________________________________________
»USING TEXTEDIT
_______________________________________________________________________________
Note: The information on Using TextEdit described in the following
paragraphs was originally documented in Inside Macintosh, Volume I.
Before using TextEdit, you must initialize QuickDraw, the Font Manager, and the Window Manager, in that order.
The first TextEdit routine to call is the initialization procedure TEInit. Call TENew to allocate an edit record; it returns a handle to the record. Most of the text editing routines require you to pass this handle as a parameter.
When you’ve finished working with the text of an edit record, you can get a handle to the text as a packed array of characters with the TEGetText function.
Note: To convert text from an edit record to a Pascal string, you can use
the Dialog Manager procedure GetIText, passing it the text handle
from the edit record.
When you’re completely done with an edit record and want to dispose of it, call TEDispose.
To make a blinking caret appear at the insertion point, call the TEIdle procedure as often as possible (at least once each time through the main event loop); if it’s not called often enough, the caret will blink irregularly.
Note: To change the cursor to an I-beam, you can call the Toolbox Utility
function GetCursor and the QuickDraw procedure SetCursor. The resource
ID for the I-beam cursor is defined in the Toolbox Utilities as the
constant iBeamCursor.
When a mouse-down event occurs in the view rectangle (and the window is active) call the TEClick procedure. TEClick controls the placement and highlighting of the selection range, including supporting use of the Shift key to make extended selections.
Key-down, auto-key, and mouse events that pertain to text editing can be handled by several TextEdit procedures:
• TEKey inserts characters and deletes characters backspaced over.
• TECut transfers the selection range to the TextEdit scrap, removing
the selection range from the text.
• TEPaste inserts the contents of the TextEdit scrap. By calling TECut,
changing the insertion point, and then calling TEPaste, you can perform
a “cut and paste” operation, moving text from one place to another.
• TECopy copies the selection range to the TextEdit scrap. By calling
TECopy, changing the insertion point, and then calling TEPaste, you
can make multiple copies of text.
• TEDelete removes the selection range (without transferring it to the
scrap). You can use TEDelete to implement the Clear command.
• TEInsert inserts specified text. You can use this to combine two or
more documents. TEDelete and TEInsert do not modify the scrap, so
they’re useful for implementing the Undo command.
After each editing procedure, TextEdit redraws the text if necessary from the insertion point to the end of the text. You never have to set the selection range or insertion point yourself; TEClick and the editing procedures leave it where it should be. If you want to modify the selection range directly,
however—to highlight an initial default name or value, for example—you can use the TESetSelect procedure.
To implement cutting and pasting of text between different applications, or between applications and desk accessories, you need to transfer the text between the TextEdit scrap (which is a private scrap used only by TextEdit) and the Scrap Manager’s desk scrap. You can do this using the functions TEFromScrap and TEToScrap. (See the Scrap Manager chapter for more information about scrap handling.)
When an update event is reported for a text editing window, call TEUpdate—along with the Window Manager procedures BeginUpdate and EndUpdate—to redraw the text.
Note: After changing any fields of the edit record that affect the
appearance of the text, you should call the Window Manager procedure
InvalRect(hTE^^.viewRect) so that the text will be updated.
The procedures TEActivate and TEDeactivate must be called each time GetNextEvent reports an activate event for a text editing window. TEActivate simply highlights the selection range or displays a caret at the insertion point; TEDeactivate unhighlights the selection range or removes the caret.
To specify the justification of the text, you can use TESetJust. If you change the justification, be sure to call InvalRect so the text will be updated.
To scroll text within the view rectangle, you can use the TEScroll procedure.
Note: The extensions to TextEdit described in the following paragraphs were
originally documented in Inside Macintosh, Volume IV. As such, this
information refers to the 128K ROMs and System file version 3.2 and
later.
Automatic scrolling of text (when the user is making a selection and drags the cursor out of the view rectangle) is now supported by TextEdit.
To enable and disable automatic scrolling, call the procedure TEAutoView. TESelView will, if automatic scrolling is enabled, automatically scroll the selection range into view. TEPinScroll scrolls text within the view rectangle but stops when the last line comes into view.
Note: When enabled, automatic scrolling can occur in response to
TESelView, TEKey, TEPaste, TEDelete, and TESetSelect.
Note: The information on TESetText described in the following
paragraphs was originally documented in Inside Macintosh, Volume I.
The TESetText procedure lets you change the text being edited. For example, if your application has several separate pieces of text that must be edited one at a time, you don’t have to allocate an edit record for each of them. Allocate a single edit record, and then use TESetText to change the text. (This is the method used in dialog boxes.)
Note: TESetText actually makes a copy of the text to be edited. Advanced
programmers can save space by storing a handle to the text in the
hText field of the edit record itself, then calling TECalText to
recalculate the beginning of each line.
If you ever want to draw noneditable text in any given rectangle, you can use the TextBox procedure.
If you’ve written your own word break or click loop routine in Pascal, you must call the SetWordBreak or SetClikLoop procedure to install your routine so TextEdit will use it.
_______________________________________________________________________________
»CUTTING AND PASTING
_______________________________________________________________________________
Note: The extensions to TextEdit described in the following paragraphs were
originally documented in Inside Macintosh, Volume V. As such, this
information refers to the Macintosh SE and Macintosh II ROMs and System
file version 4.1 and later.
For new TextEdit records created using TEStylNew, the routines TECut and TECopy will write both the text and its associated style information directly to the desk scrap, under scrap types 'TEXT' and 'styl', respectively. (For compatibility with existing applications, they also write a handle to the text to the old global TEScrapHandle.) For old TextEdit records, TECopy and TEPaste will work as they did before, copying and pasting via the private TextEdit scrap only.
A new routine, TEStylPaste, reads both text and style back from the desk scrap and pastes them into the document at the current selection range or insertion point. The old TEPaste reads the text only, ignoring any style information found in the scrap; instead it uses the style of the first character in the selection range being replaced, or that of the preceding character if the selection is an insertion point. (TEStylPaste defaults to the same behavior if it doesn’t find a 'styl' entry in the desk scrap.) The old routines TEFromScrap and TEToScrap, for transferring text between the desk and internal scraps, are no longer needed, but are still supported for backward compatibility. The GetStylScrap and TEStylInsert routines can now be used to access the text and style information associated with a given selection without destroying the current contents of the desk scrap.
_______________________________________________________________________________
»TEXTEDIT ROUTINES
_______________________________________________________________________________
Note: The information on TextEdit Routines described in the following
paragraphs was originally documented in Inside Macintosh, Volume I.
Those routines which were added with Styled TextEdit in Volume V
are marked as such.
The Macintosh Plus, Macintosh SE, and Macintosh II versions of TextEdit support all previous TextEdit routines, as well as the new routines described below.
Assembly-language note: All but two of the new routines share a single
trap, _TEDispatch ($A83D). The routines are
distinguished by an integer routine selector
passed on the stack, after the last argument:
TEStylPaste 0
TESetStyle 1
TEReplaceStyle 2
TEGetStyle 3
GetStylHandle 4
SetStylHandle 5
GetStylScrap 6
TEStylInsert 7
TEGetPoint 8
TEGetHeight 9
The Pascal interface supplies the routine selectors
automatically, as do the macros for calling these
routines from assembly language. The remaining two
new TextEdit routines have traps of their own:
_TEStylNew ($A83E) and _TEGetOffset ($A83C).
»Initialization and Allocation
PROCEDURE TEInit;
TEInit initializes TextEdit by allocating a handle for the TextEdit scrap. The scrap is initially empty. Call this procedure once and only once at the beginning of your program.
Note: You should call TEInit even if your application doesn’t use TextEdit,
so that desk accessories and dialog and alert boxes will work correctly.
FUNCTION TENew (destRect,viewRect: Rect) : TEHandle;
TENew allocates a handle for text, creates and initializes an edit record, and returns a handle to the new edit record. DestRect and viewRect are the destination and view rectangles, respectively. Both rectangles are specified in the current grafPort’s coordinates. The destination rectangle must always be at least as wide as the first character drawn (about 20 pixels is a good minimum width). The view rectangle must not be empty (for example, don’t make its right edge less than its left edge if you don’t want any text visible—specify a rectangle off the screen instead).
Call TENew once for every edit record you want allocated. The edit record incorporates the drawing environment of the grafPort, and is initialized for left-justified, single-spaced text with an insertion point at character position 0.
Note: The caret won’t appear until you call TEActivate.
FUNCTION TEStylNew (destRect,viewRect: Rect) : TEHandle; [Styled TextEdit]
The TEStylNew routine creates a new-style edit record with associated style information. It initializes the new record’s txSize, lineHeight, and fontAscent fields to –1; allocates a style record and stores a handle to it in the txFont and txFace fields.
PROCEDURE TEDispose (hTE: TEHandle);
TEDispose releases the memory allocated for the edit record and text specified by hTE. Call this procedure when you’re completely through with an edit record.
_______________________________________________________________________________
»Accessing the Text or Style Information of an Edit Record
PROCEDURE TESetText (text: Ptr; length: LONGINT; hTE: TEHandle);
TESetText incorporates a copy of the specified text into the edit record specified by hTE. The text parameter points to the text, and the length parameter indicates the number of characters in the text. The selection range is set to an insertion point at the end of the text. TESetText doesn’t affect the text drawn in the destination rectangle, so call InvalRect afterward if necessary. TESetText doesn’t dispose of any text currently in the edit record.
FUNCTION TEGetText (hTE: TEHandle) : CharsHandle;
TEGetText returns a handle to the text of the specified edit record. The result is the same as the handle in the hText field of the edit record, but has the CharsHandle data type, which is defined as:
TYPE CharsHandle = ^CharsPtr;
CharsPtr = ^Chars;
Chars = PACKED ARRAY[0..32000] OF CHAR;
You can get the length of the text from the teLength field of the edit record.
PROCEDURE TEGetStyle (offset: INTEGER; VAR theStyle: TextStyle;
VAR lineHeight,fontAscent: INTEGER; hTE: TEHandle);
[Styled TextEdit]
The TEGetStyle procedure returns the style information, including line height and font ascent, associated with a given character in an edit record’s text. For an old-style edit record, it returns the record’s global text characteristics.
PROCEDURE SetStylHandle (theHandle: TEStyleHandle; hTE: TEHandle);
[Styled TextEdit]
The SetStylHandle procedure sets an edit record’s style handle, stored in the txFont and txFace fields. SetStylHandle has no effect on an old-style edit record. Applications should always use SetStylHandle rather than manipulating the fields of the edit record directly.
FUNCTION GetStylHandle (hTE: TEHandle) : TEStyleHandle; [Styled TextEdit]
The GetStylHandle function gets an edit record’s style handle, stored in the txFont and txFace fields. GetStylHandle returns NIL when used with an old-style edit record. Applications should always use this function rather than manipulating the fields of the edit record directly.
Note: See Macintosh Technical Note #207 for information on TEContinuousStyle
and TENumStyles.
X-Ref: Technical Note #207
_______________________________________________________________________________
»Insertion Point and Selection Range
PROCEDURE TEIdle (hTE: TEHandle);
Call TEIdle repeatedly to make a blinking caret appear at the insertion point
(if any) in the text specified by hTE. (The caret appears only when the window containing that text is active, of course.) TextEdit observes a minimum blink interval: No matter how often you call TEIdle, the time between blinks will never be less than the minimum interval.
Note: The initial minimum blink interval setting is 32 ticks. The user
can adjust this setting with the Control Panel desk accessory.
To provide a constant frequency of blinking, you should call TEIdle as often as possible—at least once each time through your main event loop. Call it more than once if your application does an unusually large amount of processing each time through the loop.
Note: You actually need to call TEIdle only when the window containing
the text is active.
PROCEDURE TEClick (pt: Point; extend: BOOLEAN; hTE: TEHandle);
TEClick controls the placement and highlighting of the selection range as determined by mouse events. Call TEClick whenever a mouse-down event occurs in the view rectangle of the edit record specified by hTE, and the window associated with that edit record is active. TEClick keeps control until the mouse button is released. Pt is the mouse location (in local coordinates) at the time the button was pressed, obtainable from the event record.
Note: Use the QuickDraw procedure GlobalToLocal to convert the global
coordinates of the mouse location given in the event record to
the local coordinate system for pt.
Pass TRUE for the extend parameter if the Event Manager indicates that the Shift key was held down at the time of the click (to extend the selection).
TEClick unhighlights the old selection range unless the selection range is being extended. If the mouse moves, meaning that a drag is occurring, TEClick expands or shortens the selection range accordingly. In the case of a double-click, the word under the cursor becomes the selection range; dragging expands or shortens the selection a word at a time.
PROCEDURE TESetSelect (selStart,selEnd: LONGINT; hTE: TEHandle);
TESetSelect sets the selection range to the text between selStart and selEnd in the text specified by hTE. The old selection range is unhighlighted, and the new one is highlighted. If selStart equals selEnd, the selection range is an insertion point, and a caret is displayed.
SelEnd and selStart can range from 0 to 32767. If selEnd is anywhere beyond the last character of the text, the position just past the last character is used.
PROCEDURE TEActivate (hTE: TEHandle);
TEActivate highlights the selection range in the view rectangle of the edit record specified by hTE. If the selection range is an insertion point, it displays a caret there. This procedure should be called every time the Toolbox Event Manager function GetNextEvent reports that the window containing the edit record has become active.
PROCEDURE TEDeactivate (hTE: TEHandle);
TEDeactivate unhighlights the selection range in the view rectangle of the edit record specified by hTE. If the selection range is an insertion point, it removes the caret. This procedure should be called every time the Toolbox Event Manager function GetNextEvent reports that the window containing the edit record has become inactive.
_______________________________________________________________________________
»Editing
PROCEDURE TEKey (key: CHAR; hTE: TEHandle);
TEKey replaces the selection range in the text specified by hTE with the character given by the key parameter, and leaves an insertion point just past the inserted character. If the selection range is an insertion point, TEKey just inserts the character there. If the key parameter contains a Backspace character, the selection range or the character immediately to the left of the insertion point is deleted. TEKey redraws the text as necessary. Call TEKey every time the Toolbox Event Manager function GetNextEvent reports a keyboard event that your application decides should be handled by TextEdit.
Note: TEKey inserts every character passed in the key parameter, so it’s
up to your application to filter out all characters that aren’t
actual text (such as keys typed in conjunction with the Command key).
PROCEDURE TECut (hTE: TEHandle);
TECut removes the selection range from the text specified by hTE and places it in the TextEdit scrap. The text is redrawn as necessary. Anything previously in the scrap is deleted. (See Figure 6.) If the selection range is an insertion point, the scrap is emptied.
Figure 6–Cutting
PROCEDURE TECopy (hTE: TEHandle);
TECopy copies the selection range from the text specified by hTE into the TextEdit scrap. Anything previously in the scrap is deleted. The selection range is not deleted. If the selection range is an insertion point, the scrap is emptied.
PROCEDURE TEPaste (hTE: TEHandle);
TEPaste replaces the selection range in the text specified by hTE with the contents of the TextEdit scrap, and leaves an insertion point just past the inserted text. (See Figure 7.) The text is redrawn as necessary. If the scrap is empty, the selection range is deleted. If the selection range is an insertion point, TEPaste just inserts the scrap there.
Figure 7–Cutting and Pasting
PROCEDURE TEStylPaste (hTE: TEHandle); [Styled TextEdit]
The TEStylPaste procedure pastes text from the desk scrap into the edit
record’s text at the current insertion point or replaces the current selection. The text is styled according to the style information found in the desk scrap; if there is none, it is given the same style as the first character of the replaced selection (or that of the preceding character if the selection is an insertion point). In an old-style edit record, just the text is pasted without its accompanying style.
PROCEDURE TEDelete (hTE: TEHandle);
TEDelete removes the selection range from the text specified by hTE, and redraws the text as necessary. TEDelete is the same as TECut (above) except that it doesn’t transfer the selection range to the scrap. If the selection range is an insertion point, nothing happens.
PROCEDURE TEInsert (text: Ptr; length: LONGINT; hTE: TEHandle);
TEInsert takes the specified text and inserts it just before the selection range into the text indicated by hTE, redrawing the text as necessary. The text parameter points to the text to be inserted, and the length parameter indicates the number of characters to be inserted. TEInsert doesn’t affect either the current selection range or the scrap.
PROCEDURE TEStylInsert (text: Ptr; length: LONGINT; hST: stScrpHandle;
hTE: TEHandle); [Styled TextEdit]
The TEStylInsert procedure takes the specified text and inserts it just before the selection range into the text indicated by hTE, redrawing the text as necessary. If hST is not NIL and hTE is a TextEdit record created using TEStylNew, the style information indicated by hST will also be inserted to correspond with the inserted text. When hST is NIL and/or hTE has not been created using TEStylNew, there is no difference between this procedure and TEInsert. TEStylInsert does not affect either the current selection range or the scrap.
PROCEDURE TEReplaceStyle (mode: INTEGER; oldStyle,newStyle: TextStyle;
redraw: BOOLEAN; hTE: TEHandle); [Styled TextEdit]
The TEReplaceStyle procedure replaces the style specified by oldStyle with that given by newStyle within the current selection. (It has no effect on an old-style edit record.) The mode parameter takes the same values as TESetStyle
(above), except that addSize has no meaning here. All styles for which the combination of attributes designated by mode have the values given by oldStyle are changed to have the corresponding values from newStyle instead. Style changes are made directly to the style-table elements within the table itself. If mode = doAll, newStyle simply replaces oldStyle outright.
PROCEDURE TESetStyle (mode: INTEGER; newStyle: TextStyle; redraw: BOOLEAN;
hTE: TEHandle); [Styled TextEdit]
The TESetStyle procedure sets the style of the current selection to that specified by newStyle. ( It has no effect on an old-style edit record.) The mode parameter controls which style attributes to set; it may be any additive combination of the following constants:
CONST
doFont = 1; {set font (family) number}
doFace = 2; {set character style}
doSize = 4; {set type size}
doColor = 8; {set color}
doAll = 15; {set all attributes}
addSize = 16; {adjust type size}
In the last case (addSize), the value of newStyle.tsSize is added to all type sizes within the current selection instead of replacing them; this value may be either positive or negative. (If present, addSize overrides doSize.) If redraw = TRUE, the affected text will be redrawn in the new style.
_______________________________________________________________________________
»Text Display and Scrolling
PROCEDURE TESetJust (just: INTEGER, hTE: TEHandle);
TESetJust sets the justification of the text specified by hTE to just. TextEdit provides three predefined constants for setting justification:
CONST teJustLeft = 0;
teJustCenter = 1;
teJustRight = -1;
By default, text is left-justified. If you change the justification, call InvalRect after TESetJust, so the text will be redrawn with the new justification.
PROCEDURE TEUpdate (rUpdate: Rect; hTE: TEHandle);
TEUpdate draws the text specified by hTE within the rectangle specified by rUpdate (given in the coordinates of the current grafPort). Call TEUpdate every time the Toolbox Event Manager function GetNextEvent reports an update event for a text editing window—after you call the Window Manager procedure BeginUpdate, and before you call EndUpdate.
Normally you’ll do the following when an update event occurs:
BeginUpdate(myWindow);
EraseRect(myWindow^.portRect);
TEUpdate(myWindow^.portRect,hTE);
EndUpdate(myWindow)
If you don’t include the EraseRect call, the caret may sometimes remain visible when the window is deactivated.
PROCEDURE TextBox (text: Ptr; length: LONGINT; box: Rect; just: INTEGER);
TextBox draws the specified text in the rectangle indicated by the box parameter, with justification just. (See “Justification” under “Edit Records”.) The text parameter points to the text, and the length parameter indicates the number of characters to draw. The rectangle is specified in local coordinates, and must be at least as wide as the first character drawn (a good rule of thumb is to make it at least 20 pixels wide). TextBox creates its own edit record, which it deletes when it’s finished with it, so the text it draws cannot be edited.
For example:
str := 'String in a box';
SetRect(r,100,100,200,200);
TextBox(POINTER(ORD(@str)+1),LENGTH(str),r,teJustCenter);
FrameRect(r)
Because Pascal strings start with a length byte, you must advance the pointer one position past the beginning of the string to point to the start of the text.
PROCEDURE TEScroll (dh,dv: INTEGER; hTE: TEHandle);
TEScroll scrolls the text within the view rectangle of the specified edit record by the number of pixels specified in the dh and dv parameters. The edit record is specified by the hTE parameter. Positive dh and dv values move the text right and down, respectively, and negative values move the text left and up. For example,
TEScroll(0,-hTE^^.lineHeight,hTE)
scrolls the text up one line. Remember that you scroll text up when the user clicks in the scroll arrow pointing down. The destination rectangle is offset by the amount you scroll.
Note: To implement automatic scrolling, you store the address of a routine
in the clikLoop field of the edit record, as described above under
“The TERec Data Type”.
PROCEDURE TESelView (hTE: TEHandle); [Volume IV addition]
If automatic scrolling has been enabled (by a call to TEAutoView, described below), TESelView makes sure that the selection range is visible, scrolling it into the view rectangle if necessary. If automatic scrolling is disabled, TESelView does nothing.
Note: The top left of the insertion is scrolled into view; if text is
being displayed in a rectangle that’s not tall enough, automatic
scrolling could cause the text to jump up and down at times.
PROCEDURE TEPinScroll (dh,dv: INTEGER; hTE: TEHandle); [Volume IV addition]
TEPinScroll is similar to TEScroll except that it stops scrolling when the last line scrolls into the view rectangle.
PROCEDURE TEAutoView (auto: BOOLEAN; hTE: TEHandle); [Volume IV addition]
TEAutoView enables and disables automatic scrolling of text in the edit record specified by hTe. If the auto parameter is FALSE, automatic scrolling is disabled and calling TESelView has no effect.
_______________________________________________________________________________
»Scrap Handling
The TEFromScrap and TEToScrap functions return a result code of type OSErr
(defined as INTEGER in the Operating System Utilities) indicating whether an error occurred. If no error occurred, they return the result code
CONST noErr = 0; {no error}
Otherwise, they return an Operating System result code indicating an error.
(See Appendix A for a list of all result codes.)
FUNCTION TEFromScrap : OSErr; [Not in ROM]
TEFromScrap copies the desk scrap to the TextEdit scrap. If no error occurs, it returns the result code noErr; otherwise, it returns an appropriate Operating System result code.
Assembly-language note: From assembly language, you can store a handle to
the desk scrap in the global variable TEScrpHandle,
and the size of the desk scrap in the global
variable TEScrpLength; you can get these values
with the Scrap Manager function InfoScrap.
FUNCTION TEToScrap : OSErr; [Not in ROM]
TEToScrap copies the TextEdit scrap to the desk scrap. If no error occurs, it returns the result code noErr; otherwise, it returns an appropriate Operating System result code.
Warning: You must call the Scrap Manager function ZeroScrap to initialize
the desk scrap or clear its previous contents before calling
TEToScrap.
Assembly-language note: From assembly language, you can copy the TextEdit
scrap to the desk scrap by calling the Scrap Manager
function PutScrap; you can get the values you need
from the global variables TEScrpHandle and
TEScrpLength.
FUNCTION TEScrapHandle : Handle; [Not in ROM]
TEScrapHandle returns a handle to the TextEdit scrap.
Assembly-language note: The global variable TEScrpHandle contains a handle
to the TextEdit scrap.
FUNCTION TEGetScrapLen : LONGINT; [Not in ROM]
TEGetScrapLen returns the size of the TextEdit scrap in bytes.
Assembly-language note: The global variable TEScrpLength contains the size
of the TextEdit scrap in bytes.
PROCEDURE TESetScrapLen (length: LONGINT); [Not in ROM]
TESetScrapLen sets the size of the TextEdit scrap to the given number of bytes.
Assembly-language note: From assembly language, you can set the global
variable TEScrpLength.
FUNCTION GetStylScrap (hTE: TEHandle) : StScrpHandle; [Styled TextEdit]
The GetStylScrap routine allocates a block of type StScrpRec and copies the style information associated with the current selection into it. This is the same as TECopy, except that no action is performed on the text, and the handle to the 'styl' scrap is output in this case. Unlike TECopy, the StScrpRec is not copied to the desk scrap.
GetStylScrap will return a NIL value if called with an old style TEHandle, or if the selection is NIL (stylStart equals stylEnd).
Note: See Macintosh Technical Note #207 for information on SetStylScrap.
X-Ref: Technical Note #207
_______________________________________________________________________________
»Advanced Routines
PROCEDURE TECalText (hTE: TEHandle);
TECalText recalculates the beginnings of all lines of text in the edit record specified by hTE, updating elements of the lineStarts array. Call TECalText if you’ve changed the destination rectangle, the hText field, or any other field that affects the number of characters per line.
Note: There are two ways to specify text to be edited. The easiest method
is to use TESetText, which takes an existing edit record, creates a
copy of the specified text, and stores a handle to the copy in the
edit record. You can instead directly change the hText field of the
edit record, and then call TECalText to recalculate the lineStarts
array to match the new text. If you have a lot of text, you can use
the latter method to save space.
Assembly-language note: The global variable TERecal contains the address
of the routine called by TECalText to recalculate
the line starts and set the first and last characters
that need to be redrawn. The registers contain the
following:
On entry A3: pointer to the locked edit record
D7: change in the length of the
record (word)
On exit D2: line start of the line containing
the first character to be
redrawn (word)
D3: position of first character to be
redrawn (word)
D4: position of last character to be
redrawn (word)
Assembly-language note: The global variable TEDoText contains the address
of a multi-purpose text editing routine that advanced
programmers may find useful. It lets you display,
highlight, and hit-test characters, and position the
pen to draw the caret. “Hit-test” means decide where
to place the insertion point when the user clicks the
mouse button; the point selected with the mouse is in
the teSelPoint field. The registers contain the
following:
On entry A3: pointer to the locked edit record
D3: position of first character to be
redrawn (word)
D4: position of last character to be
redrawn (word)
D7: (word) 0 to hit-test a character
1 to highlight the selection
range
–1 to display the text
–2 to position the pen to
draw the caret
On exit A0: pointer to current grafPort
D0: if hit-testing, character position
or –1 for none (word)
FUNCTION TEGetOffset (pt: Point; hTE: TEHandle) : INTEGER; [Styled TextEdit]
The TEGetOffset routine finds the character offset in an edit record’s text corresponding to the given point. TEGetOffset works for both old-style and
new-style edit records.
FUNCTION TEGetPoint (offset: INTEGER; hTE: TEHandle) : POINT; [Styled TextEdit]
The TEGetPoint routine returns the point corresponding to the given offset into the text. The point returned is to the bottom (baseline) left of the character at the specified offset. TEGetPoint works for both old- and new-style edit records.
FUNCTION TEGetHeight (endLine, startLine: LONGINT; hTE: TEHandle) : INTEGER;
[Styled TextEdit]
The TEGetHeight routine returns the total height of all the lines in the text between and including startLine and endLine. TEGetHeight works for both old- and new-style edit records.
PROCEDURE SetWordBreak (wBrkProc: ProcPtr; hTE: TEHandle); [Not in ROM]
SetWordBreak installs in the wordBreak field of the specified edit record a special routine that calls the word break routine pointed to by wBrkProc. The specified word break routine will be called instead of TextEdit’s default routine, as described under “The WordBreak Field” in the “Edit Records” section.
Assembly-language note: From assembly language you don’t need this
procedure; just set the field of the edit record
to point to your word break routine.
PROCEDURE SetClikLoop (clikProc: ProcPtr; hTE: TEHandle); [Not in ROM]
SetClikLoop installs in the clikLoop field of the specified edit record a special routine that calls the click loop routine pointed to by clikProc. The specified click loop routine will be called repeatedly as long as the user holds down the mouse button within the text, as described above under “The ClikLoop Field” in the “Edit Records” section.
Assembly-language note: Like SetWordBreak, this procedure isn’t necessary
from assembly language; just set the field of the
edit record to point to your click loop routine.
Note: See Macintosh Technical Note #207 for information on TECustomHook,
which provides TEEOLHook, TEWidthHook, TEDrawHook, and TEHitTestHook.
X-Ref: Technical Note #207
_______________________________________________________________________________
»SUMMARY OF TEXTEDIT
_______________________________________________________________________________
Constants
CONST
teJustLeft = 0; { Text justification }
teJustCenter = 1;
teJustRight = -1;
{[Styled TextEdit]}
doFont = 1; {set font (family) number}
doFace = 2; {set character style}
doSize = 4; {set type size}
doColor = 8; {set color}
doAll = 15; {set all attributes}
addSize = 16; {adjust type size}
_______________________________________________________________________________
Data Types
TYPE
TEHandle = ^TEPtr;
TEPtr = ^TERec;
TERec = RECORD
destRect: Rect; {destination rectangle}
viewRect: Rect; {view rectangle}
selRect: Rect; {used from assembly language}
lineHeight: INTEGER; {for line spacing}
fontAscent: INTEGER; {caret/highlighting position}
selPoint: Point; {used from assembly language}
selStart: INTEGER; {start of selection range}
selEnd: INTEGER; {end of selection range}
active: INTEGER; {used internally}
wordBreak: ProcPtr; {for word break routine}
clikLoop: ProcPtr; {for click loop routine}
clickTime: LONGINT; {used internally}
clickLoc: INTEGER; {used internally}
caretTime: LONGINT; {used internally}
caretState: INTEGER; {used internally}
just: INTEGER; {justification of text}
teLength: INTEGER; {length of text}
hText: Handle; {text to be edited}
recalBack: INTEGER; {used internally}
recalLines: INTEGER; {used internally}
clikStuff: INTEGER; {used internally}
crOnly: INTEGER; {if <0, new line at Return only}
txFont: INTEGER; {text font}
txFace: Style; {character style}
txMode: INTEGER; {pen mode}
txSize: INTEGER; {font size}
inPort: GrafPtr; {grafPort}
highHook: ProcPtr; {used from assembly language}
caretHook: ProcPtr; {used from assembly language}
nLines: INTEGER; {number of lines}
lineStarts: ARRAY[0..16000] OF INTEGER
{positions of line starts}
END;
CharsHandle = ^CharsPtr;
CharsPtr = ^Chars;
Chars = PACKED ARRAY[0..32000] OF CHAR;
{[Styled TextEdit]}
TEStyleHandle = ^TEStylePtr;
TEStylePtr = ^TEStyleRec;
TEStyleRec = RECORD
nRuns: INTEGER; {number of style runs}
nStyles: INTEGER; {number of distinct styles }
{ stored in style table}
styleTab: STHandle; {handle to style table}
lhTab: LHHandle; {handle to line-height table}
teRefCon: LONGINT; {reserved for application use}
nullStyle: nullSTHandle; {handle to style set }
{ at null selection}
runs: ARRAY [0..0] OF StyleRun
END;
StyleRun = RECORD
startChar: INTEGER; {starting character position}
styleIndex: INTEGER {index in style table}
END;
STHandle = ^STPtr;
STPtr = ^TEStyleTable;
TEStyleTable = ARRAY [0..0] OF STElement;
STElement = RECORD
stCount: INTEGER; {number of runs in this style}
stHeight: INTEGER; {line height}
stAscent: INTEGER; {font ascent}
stFont: INTEGER; {font (family) number}
stFace: Style; {character style}
stSize: INTEGER; {size in points}
stColor: RGBColor {absolute (RGB) color}
END;
LHHandle = ^LHPtr;
LHPtr = ^LHTable;
LHTable = ARRAY [0..0] OF LHElement;
LHElement = RECORD
lhHeight: INTEGER; {maximum height in line}
lhAscent: INTEGER {maximum ascent in line}
END;
NullSTHandle = ^NullSTPtr;
NullSTPtr = ^NullSTRec;
NullSTRec = RECORD
TEReserved: LONGINT; {reserved for future }
{ expansion}
nullScrap: STScrpHandle {handle to scrap style }
{ table}
END;
TextStyle = RECORD
tsFont: INTEGER; {Font (family) number}
tsFace: Style; {Character style}
tsSize: INTEGER; {Size in points}
tsColor: RGBColor {Absolute (RGB) color}
END;
StScrpHandle = ^StScrpPtr;
StScrpPtr = ^StScrpRec;
StScrpRec = RECORD
scrpNStyles: INTEGER; {number of distinct }
{ styles in scrap}
scrpStyleTab: ScrpSTTable {table of styles for scrap}
END;
ScrpSTTable = array [0..0] of ScrpSTElement;
ScrpSTElement = RECORD
scrpStartChar: LONGINT; {offset to start of style}
scrpHeight: INTEGER; {line height}
scrpAscent: INTEGER; {font ascent}
scrpFont: INTEGER; {font (family) number}
scrpFace: Style; {character style}
scrpSize: INTEGER; {size in points}
scrpColor: RGBColor; {absolute (RGB) color}
END;
_______________________________________________________________________________
Routines
Initialization and Allocation
PROCEDURE TEInit;
FUNCTION TENew (destRect,viewRect: Rect) : TEHandle;
FUNCTION TEStylNew (destRect,viewRect: Rect) : TEHandle; [Styled TextEdit]
PROCEDURE TEDispose (hTE: TEHandle);
Accessing the Text or Style Information of an Edit Record
PROCEDURE TESetText (text: Ptr; length: LONGINT; hTE: TEHandle);
FUNCTION TEGetText (hTE: TEHandle) : CharsHandle;
[Styled TextEdit additions]
PROCEDURE TEGetStyle (offset: INTEGER; VAR theStyle: TextStyle;
VAR lineHeight,fontAscent: INTEGER; hTE: TEHandle);
PROCEDURE SetStylHandle (theHandle: TEStyleHandle; hTE: TEHandle);
FUNCTION GetStylHandle (hTE: TEHandle) : TEStyleHandle;
Insertion Point and Selection Range
PROCEDURE TEIdle (hTE: TEHandle);
PROCEDURE TEClick (pt: Point; extend: BOOLEAN; hTE: TEHandle);
PROCEDURE TESetSelect (selStart,selEnd: LONGINT; hTE: TEHandle);
PROCEDURE TEActivate (hTE: TEHandle);
PROCEDURE TEDeactivate (hTE: TEHandle);
Editing
PROCEDURE TEKey (key: CHAR; hTE: TEHandle);
PROCEDURE TECut (hTE: TEHandle);
PROCEDURE TECopy (hTE: TEHandle);
PROCEDURE TEPaste (hTE: TEHandle);
PROCEDURE TEStylPaste (hTE: TEHandle); [Styled TextEdit]
PROCEDURE TEDelete (hTE: TEHandle);
PROCEDURE TEInsert (text: Ptr; length: LONGINT; hTE: TEHandle);
PROCEDURE TEStylInsert (text: Ptr; length: LONGINT; hST: stScrpHandle;
hTE: TEHandle); [Styled TextEdit]
PROCEDURE TEReplaceStyle (mode: INTEGER; oldStyle,newStyle: TextStyle;
redraw: BOOLEAN; hTE: TEHandle); [Styled TextEdit]
PROCEDURE TESetStyle (mode: INTEGER; newStyle: TextStyle; redraw: BOOLEAN;
hTE: TEHandle); [Styled TextEdit]
Text Display and Scrolling
PROCEDURE TESetJust (just: INTEGER; hTE: TEHandle);
PROCEDURE TEUpdate (rUpdate: Rect; hTE: TEHandle);
PROCEDURE TextBox (text: Ptr; length: LONGINT; box: Rect;
just: INTEGER);
PROCEDURE TEScroll (dh,dv: INTEGER; hTE: TEHandle);
[Volume IV additions]
PROCEDURE TESelView (hTE: TEHandle);
PROCEDURE TEPinScroll (dh,dv: INTEGER; hTE: TEHandle);
PROCEDURE TEAutoView (auto: BOOLEAN; hTE: TEHandle);
Scrap Handling [Not in ROM]
FUNCTION TEFromScrap : OSErr;
FUNCTION TEToScrap : OSErr;
FUNCTION TEScrapHandle : Handle;
FUNCTION TEGetScrapLen : LONGINT;
PROCEDURE TESetScrapLen : (length: LONGINT);
FUNCTION GetStylScrap (hTE: TEHandle) : StScrpHandle; [Styled TextEdit]
Advanced Routines
PROCEDURE TECalText (hTE: TEHandle);
FUNCTION TEGetOffset (pt: Point; hTE: TEHandle) : INTEGER; [Styled TextEdit]
FUNCTION TEGetPoint (offset: INTEGER;
hTE: TEHandle) : POINT; [Styled TextEdit]
FUNCTION TEGetHeight (endLine, startLine: LONGINT;
hTE: TEHandle) : INTEGER; [Styled TextEdit]
PROCEDURE SetWordBreak (wBrkProc: ProcPtr; hTE: TEHandle); [Not in ROM]
PROCEDURE SetClikLoop (clikProc: ProcPtr; hTE: TEHandle); [Not in ROM]
_______________________________________________________________________________
Word Break Routine
FUNCTION MyWordBreak (text: Ptr; charPos: INTEGER) : BOOLEAN;
_______________________________________________________________________________
Click Loop Routine
FUNCTION MyClikLoop : BOOLEAN;
_______________________________________________________________________________
Assembly-Language Information
Constants
Text justification
teJustLeft .EQU 0
teJustCenter .EQU 1
teJustRight .EQU –1
[Styled TextEdit additions]
Set/Replace style modes
fontBit EQU 0 ;set font
faceBit EQU 1 ;set face
sizeBit EQU 2 ;set size
clrBit EQU 3 ;set color
addSizeBit EQU 4 ;add size mode
teStylesH EQU teFont ;replaces teFont/teFace
Offsets into TEStyleRec
nRuns EQU 0 ;[integer] # of entries in styleStarts array
nStyles EQU 2 ;[integer] # of distinct styles
styleTab EQU 4 ;[STHandle] handle to distinct styles
lhTab EQU 8 ;[LHHandle] handle to line heights
teRefCon EQU 12 ;[longint] reserved
nullStyle EQU 16 ;[nullSTHandle] Handle to style set at null selection
runs EQU 20 ;array of styles
Offsets into StyleRun array
startChar EQU 0 ;[INTEGER] offset into text to start of style
styleIndex EQU 2 ;[INTEGER] style index
stStartSize EQU 4 ;size of a styleStarts entry
Offsets into STElement
stCount EQU 0 ;[integer] # of times this style is used
stHeight EQU 2 ;[integer] line height
stAscent EQU 4 ;[integer] ascent
stFont EQU 6 ;[integer] font
stFace EQU 8 ;[style] face
stSize EQU 10 ;[integer] size
stColor EQU 12 ;[RGBColor] color
stRecSize EQU 18 ;size of a teStylesRec
Offsets into TextStyle
tsFont EQU 0 ;[integer] font
tsFace EQU 2 ;[style] face
tsSize EQU 4 ;[integer] size
tsColor EQU 6 ;[RGBColor] color
styleSize EQU 12 ;size of a StylRec
Offsets into StScrpRec
scrpNStyles EQU 0 ;[integer] # of styles in scrap
scrpStyleTab EQU 2 ;[ScrpSTTable] start of scrap styles array
Offsets into scrpSTElement
scrpStartChar EQU 0 ;[longint] char where this style starts
scrpHeight EQU 4 ;[integer] line height
scrpAscent EQU 6 ;[integer]ascent
scrpFont EQU 8 ;[integer]font
scrpFace EQU 10 ;[style] face
scrpSize EQU 12 ;[integer]size
scrpColor EQU 14 ;[RGBColor] color
scrpRecSize EQU 20 ;size of a scrap record
Edit Record Data Structure
teDestRect Destination rectangle (8 bytes)
teViewRect View rectangle (8 bytes)
teSelRect Selection rectangle (8 bytes)
teLineHite For line spacing (word)
teAscent Caret/highlighting position (word)
teSelPoint Point selected with mouse (long)
teSelStart Start of selection range (word)
teSelEnd End of selection range (word)
teWordBreak Address of word break routine (see below)
teClikProc Address of click loop routine (see below)
teJust Justification of text (word)
teLength Length of text (word)
teTextH Handle to text
teCROnly If <0, new line at Return only (byte)
teFont Text font (word)
teFace Character style (word)
teMode Pen mode (word)
teSize Font size (word)
teGrafPort Pointer to grafPort
teHiHook Address of text highlighting routine (see below)
teCarHook Address of routine to draw caret (see below)
teNLines Number of lines (word)
teLines Positions of line starts (teNLines*2 bytes)
teRecSize Size in bytes of edit record except teLines field
Word break routine
On entry A0: pointer to text
D0: character position (word)
On exit Z condition code: 0 to break at specified character
1 not to break there
Click loop routine
On exit D0: 1
D2: must be preserved
Text highlighting routine
On entry A3: pointer to locked edit record
Caret drawing routine
On entry A3: pointer to locked edit record
Variables
TEScrpHandle Handle to TextEdit scrap
TEScrpLength Size in bytes of TextEdit scrap (word)
TERecal Address of routine to recalculate line starts (see below)
TEDoText Address of multi-purpose routine (see below)
TERecal routine
On entry A3: pointer to locked edit record
D7: change in length of edit record (word)
On exit D2: line start of line containing first character to be
redrawn (word)
D3: position of first character to be redrawn (word)
D4: position of last character to be redrawn (word)
TEDoText routine
On entry A3: pointer to locked edit record
D3: position of first character to be redrawn (word)
D4: position of last character to be redrawn (word)
D7: (word) 0 to hit-test a character
1 to highlight selection range
–1 to display text
–2 to position pen to draw caret
On exit A0: pointer to current grafPort
D0: if hit-testing, character position or –1 for none (word)
Further Reference:
_______________________________________________________________________________
QuickDraw
Toolbox Event Manager
Window Manager
Font Manager
Script Manager
Technical Note #18, TextEdit Conversion Utility
Technical Note #22, TEScroll Bug
Technical Note #72, Optimizing for the LaserWriter — Techniques
Technical Note #82, TextEdit: Advice & Descent
Technical Note #127, TextEdit EOL Ambiguity
Technical Note #203, Don’t Abuse the Managers
Technical Note #207, Styled TextEdit Changes in System 6.0
Technical Note #237, TextEdit Record Size Limitations Revisited
The Apple Desktop Bus
_______________________________________________________________________________
THE APPLE DESKTOP BUS
_______________________________________________________________________________
About This Chapter
About the Apple Desktop Bus
Bus Commands
SendReset
Flush
Listen
Talk
Device Registers
Register 0
Register 3
Device Addressing
Standard ADB Device Drivers
ADB Manager Routines
Writing ADB Device Drivers
Installing an ADB Driver
Summary of the ADB Manager
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
This chapter tells you how to accomplish low-level communication with peripheral devices that are connected to the Apple Desktop Bus (ADB).
Reader’s guide: The standard mouse and keyboard drivers automatically take
care of all required ADB access functions. When the user
manipulates the mouse or keyboard, the system calls the
appropriate driver and the application never uses the ADB
Manager. Hence you need the information in this chapter only
if you are writing a special driver, such as a driver for a
new user-input device.
The ADB is a simple local-area network that connects low-speed input-only devices to the operating system. In the Macintosh II and Macintosh SE computers, the ADB is used to communicate with one or more keyboards, the mouse, and other user input devices.
Keys located on multiple keyboards are distinguished by the keyboard event message, as described in the Toolbox Event Manager chapter.
Note: An ADB, using the same operating protocols, is also part of the
Apple IIgs computer.
This chapter contains three principal sections:
• a description of the Apple Desktop Bus and how it works
• a description of the ADB Manager. This section of system ROM contains
the routines that a driver must use to access devices connected to the ADB.
• a discussion of the special requirements for drivers that support
devices connected to the ADB
You should already be familiar with
• the hardware interface to the Apple Desktop Bus, described in the
Macintosh Family Hardware Reference
• events generated by ADB keyboard devices (described in the Toolbox
Event Manager chapter) if your driver communicates with one or more
keyboards
_______________________________________________________________________________
»ABOUT THE APPLE DESKTOP BUS
_______________________________________________________________________________
The Apple Desktop Bus connects up to 16 low-speed input-only devices to the Macintosh II or Macintosh SE computer. Each device can maintain up to four variable-size registers, whose contents can be read from or written to by the ADB network. Each register may contain from two to eight bytes. Two of the device registers have an assigned meaning and a standardized format: register 0, used for interrupt information, and register 3, containing the device’s identification number. The other two device registers have no assigned meaning, and may have different meanings for read and write operations.
The system communicates with the Apple Desktop Bus through the system’s Versatile Interface Adapter chip (VIA). The VIA is described in the Macintosh Hardware chapter.
Warning: The ADB does not support connecting a device while the computer
is running. The result may be to reinitialize all devices on the
bus without informing the system.
The system always controls the bus. It issues commands to specific devices on the bus and they respond by accepting data, sending data, or changing their configuration. These commands are discussed below.
Note: Devices connected to the ADB contain their own single-chip
microprocessors, which handle both device routines and the
ADB interface. If the system sends commands to a device with
a duty cycle of more than 50%, the device’s microprocessor
may become overloaded.
_______________________________________________________________________________
»Bus Commands
Each bus command consists of a byte that the system sends to a device connected to the ADB. Applications may place bus commands on the network by calling the routine ADBOp, discussed under “ADB Manager Routines” later in this chapter. There are four bus commands; their bit layouts are shown in Figure 1. All other bit layouts are reserved.
Figure 1–ADB Command Formats
The individual commands are discussed below.
Warning: Values of the low bytes of the ADB command formats other than
those shown in Figure 1 are reserved, and should not be used.
»SendReset
The SendReset command forces a hardware reset of all devices connected to the ADB. Such a reset clears all pending device actions and places the devices in their startup state. All devices are able to accept new ADB commands and user inputs immediately thereafter. All devices ignore the high-order four bits of the SendReset command.
»Flush
The Flush command flushes data from the single device specified by the network address in its high-order four bits. Network addresses are discussed below, under “Device Addressing”. It purges any pending user inputs and make the device ready to accept new commands and input data.
»Listen
The Listen command is used to send instructions to devices connected to the ADB. It transfers data from a buffer in system RAM to a register in the device specified by the network address in its high-order four bits. The device register is specified by the low-order two bits of the Listen command.
»Talk
The Talk command is used to fetch user inputs from devices connected to the ADB. It is the complement of the Listen command. It transfers data from a register in the device specified by the network address in its high-order four bits to a buffer in system RAM. The device register is specified by the low-order two bits of the Talk command.
_______________________________________________________________________________
»Device Registers
Each device connected to the ADB contains four registers, each of which may store from two to eight bytes of data. Each register is identified by the value of the low-order two bits in a Listen or Talk command. Registers 0 and 3 have dedicated functions; registers 1 and 2 are used for purposes specific to each device, and need not be present in a device.
Note: ADB device registers are virtual registers; they need not be
implemented physically. The device firmware must only respond
to register commands as if a register were present.
»Register 0
Device register 0 is reserved for input data. If the device has user-input data to be fetched, it places the data in register 0 and requests service. It continues to request service until the system retrieves its data. The system responds to data-input requests with the following polling sequence:
• It generates a Talk command for register 0 in each device connected
to the ADB.
• If the device has data to send, it responds. The system does not
poll the next device until the data is exhausted.
• If the device has no data to send, or if its data is exhausted, the
VIA generates an interrupt. The system then polls the next device.
• This process continues until no devices request service.
»Register 3
Device register 3 is reserved for device identification data and operating flags. Application programs may set this data with Listen commands and read it with Talk commands. Register 3 stores 16 bits, divided into the fields shown in Figure 2.
Figure 2–Format of Device Register 3
Except for commands that contain certain reserved device handler ID values
(listed below), every command to register 3 changes the entire register contents. Hence to change part of the register, you should first fetch its current contents with a Talk command and then send it an updated value with Listen. You can change part of the contents of register 3 by using special device handler ID values, as described below.
The device handler ID field indicates the device’s type. With certain devices, an application can change the device’s mode of operation by sending it a new ID value. If the device supports the new mode, it stores the new value in this field.
Warning: You are assigned handler IDs by Apple Software Licensing, so
they do not conflict with the values of other devices that may
be connected to the ADB at the same time.
When certain reserved values are sent to the device handler ID field by a Listen command, they are not stored in the field; instead, they cause specific device actions. Hence these values cannot be used as device ID values. They are the following:
Value Action
$00 Change bits 8–13 of register 3 to match the rest of the command;
leave Device Handler ID value unchanged.
$FD Change Device Address to match bits 8–11 if the device activator
has been depressed; leave Device Handler ID value and flags unchanged.
$FE Change Device Address to match bits 8–11 if the result produces no
address duplication on the bus; leave Device Handler ID value and
flags unchanged.
$FF Initiate device self-test. If self-test succeeds, leave register 3
unchanged; if self-test fails, clear Device Handler ID field to $00.
Other Device Handler ID values may be stored in the field.
Note: Device Handler ID values below $20 are reserved by Apple.
The Device Address field indicates the device’s location within the 16 possible device locations of the ADB. An application may change its value with a Listen command. When this field is interrogated with a Talk command, it returns a random value. This helps you separate multiple devices that have the same ADB address; for further information, see “Device Addressing”, below.
The Service Request Enable bit is set by the device to request an interrupt poll.
_______________________________________________________________________________
»Device Addressing
There are 16 possible direct addresses, $00–$0F, for devices connected to the ADB. However, it is possible to connect more than one device to an address; this might happen, for example, in a system with two alternate keyboards.
When several devices share a single ADB address, but there are free addresses available in the net, the system will automatically reassign addresses until they are all different. It will do this every time the ADB Manager is initialized or reinitialized. To find out a device’s new address, use the calls GetIndADB or GetADBInfo, described later in this chapter.
_______________________________________________________________________________
»Standard ADB Device Drivers
The Macintosh II and Macintosh SE systems contain two standard ADB drivers:
• the mouse driver, which supports the ADB mouse. The Apple mouse
has an original ADB address of 3.
• the universal keyboard driver, which supports all Apple ADB keyboards.
The Apple keyboard has an original ADB address of 2, with a Device
Handler ID of 1 for the Macintosh II keyboard and 2 for the Apple
Extended Keyboard. These keyboards are described in the Toolbox
Event Manager chapter.
These drivers reside in the system ROM. In addition, ADB address 0 is reserved for the ADB chip itself. You can change the ADB addresses of the mouse or keyboard, as described above under “Device Registers,” but Apple does not recommend doing so.
Assembly-language note: The ADB address of the keyboard on which the
last-typed character was entered is now stored
in the global variable KbdLast. The type of the
keyboard on which the last-typed character was
entered is stored in the global variable KbdType.
The value of KbdType is the Device Handler ID
value in Register 3 of the device; values below
$20 are reserved by Apple.
The requirements for writing new ADB device drivers are discussed later in this chapter.
_______________________________________________________________________________
»ADB MANAGER ROUTINES
_______________________________________________________________________________
The ADB Manager consists of six routines located in the 256K ROM. You would use them only if you needed to access bus devices directly or communicate with a special device.
Some of these routines access and update information in the ADB device table, a structure placed in the system heap by ROM code during system startup. It lists for each device the device’s type, its original ADB address, its current ADB address, the address of the routine that services the device, and the address of the area in RAM used for temporary data storage by its driver. The ADB device table is accessible only through ADB Manager routines.
PROCEDURE ADBReInit;
Trap macro _ADBReInit
ADBReInit reinitializes the entire Apple Desktop Bus. It clears the ADB device table to zeros and places a SendReset command on the bus to reset all devices to their original addresses. ADBReInit has no parameters.
Because it does not deallocate ADB resources on the system heap, ADBReInit should not be used for routine bus initialization. Apple strongly recommends against adding devices while the system is running; therefore, you should never call ADBReInit.
ADBReInit also calls a routine pointed to by the low memory global JADBProc
at the beginning and end of its execution. You can insert your own
preprocessing/postprocessing routine by changing the value of JADBProc; ADBReInit conditions it by setting D0 to 0 for preprocessing and to 1 for postprocessing. Your procedure must restore the value of D0 and branch to the original value of JADBProc on exit. JADBProc should be used to de-allocate memory used by the driver (see MacDTS Sample Code “TbltDrvr” for an example), and then it should chain to the procedure originally found in JADBProc.
The complete ADBReInit sequence is therefore the following:
• JSR to JADBProc with D0 set to 0
• reinitialize the Apple Desktop Bus
• clear the ADB device table
• JSR to JADBProc with D0 set to 1
FUNCTION ADBOp (data: Ptr; compRout: ProcPtr; buffer: Ptr;
commandNum: INTEGER) : OSErr;
Trap macro _ADBOp
On entry: A0: pointer to parameter block
D0: commandNum (byte)
Parameter block
--> 0 buffer pointer
--> 4 compRout pointer
--> 8 data pointer
On exit: D0: result code (byte)
The completion routine pointed to by compRout will be passed the following parameters on entry:
D0: commandNum (byte)
A0: pointer to buffer, data stored as a Pascal string (maximum
8 bytes data preceded by one length byte)
A1: pointer to completion routine (compRout)
A2: pointer to optional data area (data)
ADBOp transmits over the bus the command byte whose value is given by commandNum. The structure of the command byte is given earlier in Figure 1. ADBOp executes only when the ADB is otherwise idle; otherwise it is held in a command queue. It returns an error if the command queue is full. The length of the data buffer pointed to by buffer is contained in its first byte, like a Pascal string. The optional data area pointed to by data is for local storage by the completion routine pointed to by compRout. ADBop should be used sparingly; it is not intended for polling a device. The host automatically polls devices with data to deliver.
Result codes noErr No error
–1 Unsuccessful completion
FUNCTION CountADBs: INTEGER;
Trap macro _CountADBs
On exit: D0: number of devices (byte)
CountADBs returns a value representing the number of devices connected to the ADB by counting the number of entries in the device table. It has no arguments and returns no error codes.
FUNCTION GetIndADB (VAR info: ADBDataBlock;
devTableIndex: INTEGER) : ADBAddress;
Trap macro _GetIndADB
On entry: A0: pointer to parameter block
D0: entry index number; range = 1..CountADBs (byte)
Parameter block
<-- 0 device type byte (handler ID)
<-- 1 original ADB address byte
<-- 2 service routine address pointer (compRout)
<-- 6 data area address pointer (data)
On exit: D0: positive value: current ADB address (byte)
negative value: error code (byte)
GetIndADB returns information from the ADB device table entry whose index number is given by devTableIndex. ADBDataBlock has this form:
TYPE ADBDataBlock =
PACKED RECORD
devType: SignedByte; {device type (handler ID)}
origADBAddr: SignedByte; {original ADB address}
dbServiceRtPtr: Ptr; {service routine address (compRout)}
dbDataAreaAddr: Ptr {data area address (data)}
END;
GetIndADB returns the current ADB address of the device. If it is unable to complete execution successfully, GetIndADB returns a negative value.
FUNCTION GetADBInfo (VAR info: ADBDataBlock; ADBAddr: ADBAddress) : OsErr;
Trap macro _GetADBInfo
On entry: A0: pointer to parameter block
D0: ADB address of the device (byte)
Parameter block
<-- 0 device handler ID byte
<-- 1 original ADB address byte
<-- 2 service routine address pointer (compRout)
<-- 6 data area address pointer (data)
On exit: D0: result code (byte)
GetADBInfo returns information from the ADB device table entry of the device whose ADB address is given by ABDAddr. The structure of ADBDataBlock is given above under “GetIndADB”.
Result codes noErr No error
FUNCTION SetADBInfo (VAR info: ADBSetInfoBlock; ADBAddr: ADBAddress) : OsErr;
Trap macro _SetADBInfo
On entry: A0: pointer to parameter block
D0: ADB address of the device (byte)
Parameter block
--> 0 service routine address pointer (compRout)
--> 4 data area address pointer (data)
On exit: D0: result code (byte)
SetADBInfo sets the service routine address and the data area address in the ADB device table entry for the device whose ADB address is given by ABDAddr. ADBSetInfoBlock has this form:
TYPE ADBSetInfoBlock =
RECORD
siServiceRtPtr: Ptr; {service routine address (compRout)}
siDataAreaAddr: Ptr {data area address (data)}
END;
Result codes noErr No error
Warning: You should send a Flush command to the device after calling it
with SetADBInfo, to prevent it sending old data to the new data
area address.
_______________________________________________________________________________
»WRITING ADB DEVICE DRIVERS
_______________________________________________________________________________
Drivers for devices connected to the ADB have the following special requirements:
• Each ADB device driver must reside in a resource of type 'ADBS'.
(An example 'ADBS' resource is available in MacDTS Sample Code
“TbltDrvr.”) This type has two sections: initialization and driver code.
• The initialization section of each ADB device driver must support the
installation procedure described below.
When the system calls an ADB device driver, it passes it the following values:
• Register A0 points to the data buffer, which is formatted as a
Pascal string (buffer).
• Register A1 points to the driver’s completion routine (compRout).
• Register A2 points to the optional data area (data).
• Register D0 contains the ADB command that resulted in the driver
being called (commandNum).
The ADB driver should handle the ADB command passed to it and store any resulting input data by an appropriate action, such as by posting an event or moving the cursor.
Note: Events posted from keyboards connected to the ADB now have an
expanded structure. For more information, see the Toolbox Event
Manager chapter.
_______________________________________________________________________________
»Installing an ADB Driver
The Start Manager (described in this volume) finds all the ADB devices connected to the system and places their device types and ADB addresses in the ADB device table. It then calls the initialization section of each ADB device driver by executing the initialization code in its 'ADBS' resource.
As a minimum, the initialization section of each ADB device driver must do the following:
• The driver must allocate all the memory required by the driver code
in one or more nonrelocatable blocks in the system heap area.
• The driver must install its own preprocessing/postprocessing routine
(if any) as described above under “ADBReInit”.
• Finally, the driver must initialize the service routine address and
data area address of its entry in the ADB device table, using SetADBInfo.
_______________________________________________________________________________
»SUMMARY OF THE ADB MANAGER
_______________________________________________________________________________
Data Types
TYPE
ADBDataBlock =
PACKED RECORD
devType: SignedByte; {Handler ID}
origADBAddr: SignedByte; {original ADB address}
dbServiceRtPtr: Ptr; {service routine address (compRout)}
dbDataAreaAddr: Ptr {data area address (area)}
END;
ADBSetInfoBlock =
RECORD
siServiceRtPtr: Ptr; {service routine address}
siDataAreaAddr: Ptr {data area address}
END;
_______________________________________________________________________________
Routines
Initializing the ADB Manager
PROCEDURE ADBReInit;
Communicating Through the ADB
FUNCTION ADBOp (data: Ptr; compRout: ProcPtr; buffer: Ptr;
commandNum: INTEGER) : OSErr;
Getting ADB Device Information
FUNCTION CountADBs: INTEGER;
FUNCTION GetIndADB (VAR info: ADBDataBlock;
devTableIndex: INTEGER) : ADBAddress;
FUNCTION GetADBInfo (VAR info: ADBDataBlock; ADBAddr: ADBAddress) : OsErr;
Setting ADB Device Information
FUNCTION SetADBInfo (VAR info: ADBSetInfoBlock; ADBAddr: ADBAddress) : OsErr;
_______________________________________________________________________________
Assembly-Language Information
Variables
JADBProc Pointer to ADBReInit preprocessing/postprocessing routine
KbdLast ADB address of the keyboard last used (byte)
KbdType Keyboard type of the keyboard last used (byte)
Routines
Trap macro On entry On Exit
_ADBReInit
_ADBOp A0: pointer to parameter block D0: result code (byte)
buffer (pointer)
compRout (pointer)
data (pointer)
D0: commandNum (byte)
_CountADBs D0: result code (byte)
_GetIndADB A0: pointer to parameter block D0: positive value:
device type (byte) current ADB
original ADB address (byte) address (byte)
service routine address (pointer) negative value:
data area address (pointer) error code (byte)
D0: entry index number;
range = 1..CountADBs (byte)
_GetADBInfo A0: pointer to parameter block D0: result code (byte)
device handler ID (byte)
original ADB address (byte)
service routine address (pointer)
data area address (pointer)
D0: current ADB address of the device (byte)
_SetADBInfo A0: pointer to parameter block D0: result code (byte)
service routine address (pointer)
data area address (pointer)
D0: current ADB address of the device (byte)
Further Reference:
_______________________________________________________________________________
Toolbox Event Manager
Technical Note #143, Don’t Call ADBReInit on the SE with System 4.1
Technical Note #160, Key Mapping
Technical Note #206, Space Aliens Ate My Mouse
“Macintosh Family Hardware Reference”
The AppleTalk Manager
_______________________________________________________________________________
THE APPLETALK MANAGER
_______________________________________________________________________________
About This Chapter
AppleTalk Protocols
AppleTalk Transaction Protocol
Transactions
Datagram Loss Recovery
About the AppleTalk Manager
Calling the AppleTalk Manager from Pascal
Opening and Closing AppleTalk
AppleTalk Link Access Protocol
Data Structures
Using ALAP
ALAP Routines
Example
Datagram Delivery Protocol
Data Structures
Using DDP
DDP Routines
Example
AppleTalk Transaction Protocol
Data Structures
Using ATP
ATP Routines
Example
Name-Binding Protocol
Data Structures
Using NBP
NBP Routines
Example
Miscellaneous Routines
New AppleTalk Manager Pascal Interface
Using Pascal
MPP Parameter Block
ATP Parameter Block
Building Data Structures
Picking a Node Address in the Server Range
Sending Packets to One’s Own Node
ATP Driver Changes
Sending an ATP Request Through a Specified Socket
Aborting ATP SendRequests
Aborting ATP GetRequests
Name Binding Protocol Changes
Multiple Concurrent NBP Requests
KillNBP function
Variable Resources
Calling the AppleTalk Manager from Assembly Language
Opening AppleTalk
Example
AppleTalk Link Access Protocol
Data Structures
Using ALAP
ALAP Routines
Datagram Delivery Protocol
Data Structures
Using DDP
DDP Routines
AppleTalk Transaction Protocol
Data Structures
Using ATP
ATP Routines
Name-Binding Protocol
Data Structures
Using NBP
NBP Routines
Extended Protocol Package Driver
Version
Error Reporting
.XPP Driver Functions Overview
Using AppleTalk Name Binding Protocol
Opening and Closing Sessions
Session Maintenance
Commands on an Open Session
Getting Server Status Information
Attention Mechanism
The Attention Routine
Calling the .XPP Driver
Using XPP
Allocating Memory
Opening the .XPP Driver
Example
Open Errors
Closing the .XPP Driver
Close Errors
Session Control Block
How to Access the .XPP Driver
General
.XPP Driver Parameter Block Record
AppleTalk Session Protocol Functions
Note on Result Codes
AFP Implementation
Mapping AFP Commands
AFPCall Function
General Command Format
Login Command Format
AFPWrite Command Format
AFPRead Command Format
CCB Sizes
.XPP Driver Result Codes
Protocol Handlers and Socket Listeners
Data Reception in the AppleTalk Manager
Writing Protocol Handlers
Timing Considerations
Writing Socket Listeners
Summary of the AppleTalk Manager
_______________________________________________________________________________
»ABOUT THIS CHAPTER
_______________________________________________________________________________
The AppleTalk Manager is an interface to a pair of RAM device drivers that allow Macintosh programs to send and receive information via an AppleTalk network. This chapter describes the AppleTalk Manager in detail.
The AppleTalk Manager has been enhanced through the implementation of new protocols and an increase in the functionality of the existing interface.
Reader’s guide: The AppleTalk Manager provides services that allow Macintosh
programs to interact with clients in devices connected to an
AppleTalk network. Hence you need the information in this
chapter only if your application uses AppleTalk.
The following is a brief summary of the changes that have been made to the AppleTalk Manager interface.
• New parameter block–style Pascal calls have been added for the entire
AppleTalk Manager. These new calls give the application programmer
better control of AppleTalk operation within an application.
• At open time, the .MPP driver can be told to pick a node number in
the server range. This is a more time consuming but more thorough
operation than is selecting a node number in the workstation range,
and it is required for devices acting as servers.
• Multiple concurrent NBP requests are now supported (just as multiple
concurrent ATP requests have been supported). The KillNBP command
has been implemented to abort an outstanding NBP request.
• ATP requests can now be sent through client-specified sockets, instead
of having ATP pick the socket itself.
• The ability to send packets to one’s own node is supported (although
this functionality is, in the default case, disabled).
• Two new ATP abort calls have been added: KillSendReq and KillGetReq.
KillSendReq is functionally equivalent to RelTCB, although its
arguments are different. KillGetReq is a new call for aborting
outstanding GetRequests.
• Additional machine-dependent resources have been added to support,
for example, more dynamic sockets and more concurrent ATP requests.
• A new protocol called the Echo Protocol (EP) is supported.
• A new driver, .XPP, has been added. The .XPP driver implements the
workstation side of the AppleTalk Session Protocol (ASP) and a small
portion of the AppleTalk Filing Protocol.
To determine if your application is running on a machine that supports these enhanced features, check the version number of the .MPP driver (at offset DCtlQueue+1 in the Device Control Entry). A version number of 48 (NCVersion) or greater indicates the presence of the new drivers.
You should already be familiar with:
• events, as discussed in the Toolbox Event Manager chapter
• interrupts and the use of devices and device drivers, as described in
the Device Manager chapter, if you want to write your own assembly-
language additions to the AppleTalk Manager
• the Inside AppleTalk manual, if you want to understand AppleTalk
protocols in detail
_______________________________________________________________________________
»APPLETALK PROTOCOLS
_______________________________________________________________________________
The AppleTalk Manager provides a variety of services that allow Macintosh programs to interact with programs in devices connected to an AppleTalk network. This interaction, achieved through the exchange of variable-length blocks of data (known as packets) over AppleTalk, follows well-defined sets of rules known as protocols.
Although most programmers using AppleTalk needn’t understand the details of these protocols, they should understand the information in this section—what the services provided by the different protocols are, and how the protocols are interrelated. Detailed information about AppleTalk protocols is available in Inside AppleTalk.
The AppleTalk system architecture consists of a number of protocols arranged in layers. Each protocol in a specific layer provides services to higher-level layers (known as the protocol’s clients) by building on the services provided by lower-level layers. A Macintosh program can use services provided by any of the layers in order to construct more sophisticated or more specialized services. Figure 1 shows the AppleTalk Protocols and their corresponding network layers.
The AppleTalk Manager contains the following protocols:
• AppleTalk Link Access Protocol
• Datagram Delivery Protocol
• Routing Table Maintenance Protocol
• Name-Binding Protocol
• AppleTalk Transaction Protocol
The following protocols have been added to the AppleTalk Manager:
• Echo Protocol
• AppleTalk Session Protocol (workstation side)
• AppleTalk Filing Protocol (small portion of the workstation side)
In Figure 1, the lines indicate the interaction between the protocols. Notice that like the Routing Table Maintenance Protocol, the Echo Protocol is not directly accessible to Macintosh programs.
The details of these protocols are provided in Inside AppleTalk.
Figure 1–AppleTalk Protocols and OSI Network Layers
Figure 2 illustrates the Macintosh AppleTalk Drivers and the layered structure of the protocols which are accessible through each driver. Note that the Routing Table Maintenance Protocol isn’t directly accessible to Macintosh Programs.
Figure 2–Macintosh AppleTalk Drivers
The AppleTalk Link Access Protocol (ALAP) provides the lowest-level services of the AppleTalk system. Its main function is to control access to the AppleTalk network among various competing devices. Each device connected to an AppleTalk network, known as a node, is assigned an eight-bit node ID number that identifies the node. ALAP ensures that each node on an AppleTalk network has a unique node ID, assigned dynamically when the node is started up.
ALAP provides its clients with node-to-node delivery of data frames on a single AppleTalk network. An ALAP frame is a variable-length packet of data preceded and followed by control information referred to as the ALAP frame header and frame trailer, respectively. The ALAP frame header includes the node IDs of the frame’s destination and source nodes. The AppleTalk hardware uses the destination node ID to deliver the frame. The frame’s source node ID allows a program in the receiving node to determine the identity of the source. A sending node can ask ALAP to send a frame to all nodes on the AppleTalk network; this broadcast service is obtained by specifying a destination node ID of 255.
ALAP can have multiple clients in a single node. When a frame arrives at a node, ALAP determines which client it should be delivered to by reading the frame’s ALAP protocol type. The ALAP protocol type is an eight-bit quantity, contained in the frame’s header, that identifies the ALAP client to whom the frame will be sent. ALAP calls the client’s protocol handler, which is a software process in the node that reads in and then services the frames. The protocol handlers for a node are listed in a protocol handler table.
An ALAP frame trailer contains a 16-bit frame check sequence generated by the AppleTalk hardware. The receiving node uses the frame check sequence to detect transmission errors, and discards frames with errors. In effect, a frame with an error is “lost” in the AppleTalk network, because ALAP doesn’t attempt to recover from errors by requesting the sending node to retransmit such frames. Thus ALAP is said to make a “best effort” to deliver frames, without any guarantee of delivery.
An ALAP frame can contain up to 600 bytes of client data. The first two bytes must be an integer equal to the length of the client data (including the length bytes themselves).
Datagram Delivery Protocol (DDP) provides the next-higher level protocol in the AppleTalk architecture, managing socket-to-socket delivery of datagrams over AppleTalk internets. DDP is an ALAP client, and uses the node-to-node delivery service provided by ALAP to send and receive datagrams. Datagrams are packets of data transmitted by DDP. A DDP datagram can contain up to 586 bytes of client data. Sockets are logical entities within the nodes of a network; each socket within a given node has a unique eight-bit socket number.
On a single AppleTalk network, a socket is uniquely identified by its AppleTalk address—its socket number together with its node ID. To identify a socket in the scope of an AppleTalk internet, the socket’s AppleTalk address and network number are needed. Internets are formed by interconnecting AppleTalk networks via intelligent nodes called bridges. A network number is a 16-bit number that uniquely identifies a network in an internet. A socket’s AppleTalk address together with its network number provide an internet-wide unique socket identifier called an internet address.
Sockets are owned by socket clients, which typically are software processes in the node. Socket clients include code called the socket listener, which receives and services datagrams addressed to that socket. Socket clients must open a socket before datagrams can be sent or received through it. Each node contains a socket table that lists the listener for each open socket.
A datagram is sent from its source socket through a series of AppleTalk networks, being passed on from bridge to bridge, until it reaches its destination network. The ALAP in the destination network then delivers the datagram to the node containing the destination socket. Within that node the datagram is received by ALAP calling the DDP protocol handler, and by the DDP protocol handler in turn calling the destination socket listener, which for most applications will be a higher-level protocol such as the AppleTalk Transaction Protocol.
Bridges on AppleTalk internets use the Routing Table Maintenance Protocol (RTMP) to maintain routing tables for routing datagrams through the internet. In addition, nonbridge nodes use RTMP to determine the number of the network to which they’re connected and the node ID of one bridge on their network. The RTMP code in nonbridge nodes contains only a subset of RTMP (the RTMP stub), and is a DDP client owning socket number 1 (the RTMP socket).
Socket clients are also known as network-visible entities, because they’re the primary accessible entities on an internet. Network-visible entities can choose to identify themselves by an entity name, an identifier of the form
object:type@zone
Each of the three fields of this name is an alphanumeric string of up to 32 characters. The object and type fields are arbitrary identifiers assigned by a socket client, to provide itself with a name and type descriptor (for example, abs:Mailbox). The zone field identifies the zone in which the socket client is located; a zone is an arbitrary subset of AppleTalk networks in an internet. A socket client can identify itself by as many different names as it chooses. These aliases are all treated as independent identifiers for the same socket client.
The Name-Binding Protocol (NBP) maintains a names table in each node that contains the name and internet address of each entity in that node. These name-address pairs are called NBP tuples. The collection of names tables in an internet is known as the names directory.
NBP allows its clients to add or delete their name-address tuples from the
node’s names table. It also allows its clients to obtain the internet addresses of entities from their names. This latter operation, known as name lookup (in the names directory), requires that NBP install itself as a DDP client and broadcast special name-lookup packets to the nodes in a specified zone. These datagrams are sent by NBP to the names information socket—socket number 2 in every node using NBP.
NBP clients can use special meta-characters in place of one or more of the three fields of the name of an entity it wishes to look up. The character “=” in the object or type field signifies “all possible values”. The zone field can be replaced by “*”, which signifies “this zone”—the zone in which the NBP client’s node is located. For example, an NBP client performing a lookup with the name
=:Mailbox@*
will obtain in return the entity names and internet addresses of all mailboxes in the client’s zone (excluding the client’s own names and addresses). The client can specify whether one or all of the matching names should be returned.
NBP clients specify how thorough a name lookup should be by providing NBP with the number of times (retry count) that NBP should broadcast the lookup packets and the time interval (retry interval) between these retries.
As noted above, ALAP and DDP provide “best effort” delivery services with no recovery mechanism when packets are lost or discarded because of errors. Although for many situations such a service suffices, the AppleTalk Transaction Protocol (ATP) provides a reliable loss-free transport service. ATP uses transactions, consisting of a transaction request and a transaction response, to deliver data reliably. Each transaction is assigned a 16-bit transaction ID number to distinguish it from other transactions. A transaction request is retransmitted by ATP until a complete response has been received, thus allowing for recovery from packet-loss situations. The retry interval and retry count are specified by the ATP client sending the request.
Although transaction requests must be contained in a single datagram, transaction responses can consist of as many as eight datagrams. Each datagram in a response is assigned a sequence number from 0 to 7, to indicate its ordering within the response.
ATP is a DDP client, and uses the services provided by DDP to transmit requests and responses. ATP supports both at-least-once and exactly-once transactions. Four of the bytes in an ATP header, called the user bytes, are provided for use by ATP’s clients—they’re ignored by ATP.
ATP’s transaction model and means of recovering from datagram loss are covered in detail below.
The Echo Protocol (EP) provides an echoing service through static socket number 4 known as the echoer socket. The echoer listens for packets received through this socket. Any correctly formed packet sent to the echoer socket on a node will be echoed back to its sender.
This simple protocol can be used for two important purposes:
• EP can be used by any Datagram Delivery Protocol (DDP) client to
determine if a particular node (known to have an echoer) is accessible
over an internet.
• EP is useful in determining the average time it takes for a packet to
travel to a remote node and back. This is very helpful in developing
client-dependent heuristics for estimating the timeouts to be specified
by clients of ATP, ASP, and other protocols.
Programs cannot access EP directly via the AppleTalk Manager. The EP implementation exists solely to respond to EP requests sent by other nodes. EP is a DDP client residing on statically-assigned socket 4, the echoing socket. Clients wishing to send EP requests (and receive EP responses) should use the Datagram Delivery Protocol (DDP) to send the appropriate packet. For more information about the EP packet format, see Inside AppleTalk.
The AppleTalk Session Protocol (ASP) provides for the setting up, maintaining and closing down of a session. A session is a logical relationship between two network entities, a workstation and a server. The workstation tells the server what to do, and the server responds with the appropriate actions. ASP makes sure that the session dialog is maintained in the correct sequence and that both ends of the conversation are properly participating.
ASP will generally be used between two communicating network entities where one is providing a service to the other (for example, a server is providing a service to a workstation) and the service provided is state-dependent. That is, the response to a particular request from an entity is dependent upon other previous requests from that entity. For example, a request to read bytes from a file is dependent upon a previous request to open that file in the first place. However, a request to return the time of day is independent of all such previous requests.
When the service provided is state-dependent, requests must be delivered to the server in the same order as generated by the workstation. ASP guarantees requests are delivered to the server in the order in which they are issued, and that duplicate requests are never delivered (another requirement of state-dependent service).
ASP is an asymmetric protocol, providing one set of services to the workstation and a different set of services to the server.
ASP workstation clients initiate (open) sessions, send requests (commands) on that session, and close sessions down. ASP server clients receive and respond
(through command replies) to these requests. ASP guarantees that these requests are delivered in the same order as they are made, and without duplication. ASP is also responsible for closing down the session if one end fails or becomes unreachable, and will inform its client (either server or workstation) of the action.
ASP also provides various additional services, such as allowing a workstation to obtain server status information without opening a session to a server, writing blocks of data from the workstation to the server end of the session, and providing the ability for a server to send an attention message to the workstation.
ASP assumes that the workstation client has a mechanism for looking up the network address of the server with which it wants to set up a session.
(Generally this is done using the AppleTalk Name Binding Protocol.)
Both ends of the session periodically check to see that the other end of the session is still responsive. If one end fails or becomes unreachable the other end closes the session.
ASP is a client of ATP and calls ATP for transport services.
ASP does not
• ensure that consecutive commands complete in the order in which they
were sent (and delivered) to the server
• understand or interpret the syntax or the semantics of the commands
sent to the server by the workstation
• allow the server to send commands to the workstation (The server
is allowed to alert the workstation through the server’s attention
mechanism only.)
Note: The .XPP driver does implement the workstation side of the
AppleTalk Filing Protocol login command.
The AppleTalk Filing Protocol (AFP) allows a workstation on an AppleTalk network to access files on an AFP file server. AFP specifies a remote filing system that provides user authentication and an access control mechanism that supports volume and folder-level access rights. For details of AFP, refer to Inside AppleTalk.
_______________________________________________________________________________
»APPLETALK TRANSACTION PROTOCOL
_______________________________________________________________________________
This section covers ATP in greater depth, providing more detail about three of its fundamental concepts: transactions, buffer allocation, and recovery of lost datagrams.
_______________________________________________________________________________
»Transactions
A transaction is a interaction between two ATP clients, known as the requester and the responder. The requester calls the .ATP driver in its node to send a transaction request (TReq) to the responder, and then awaits a response. The TReq is received by the .ATP driver in the responder’s node and is delivered to the responder. The responder then calls its .ATP driver to send back a transaction response (TResp), which is received by the requester’s .ATP driver and delivered to the requester. Figure 3 illustrates this process.
Figure 3–Transaction Process
Simple examples of transactions are:
• read a counter, reset it and send back the value read
• read six sectors of a disk and send back the data read
• write the data sent in the TReq to a printer
A basic assumption of the transaction model is that the amount of ATP data sent in the TReq specifying the operation to be performed is small enough to fit in a single datagram. A TResp, on the other hand, may span several datagrams, as in the second example. Thus, a TReq is a single datagram, while a TResp consists of up to eight datagrams, each of which is assigned a sequence number from 0 to 7 to indicate its position in the response.
The requester must, before calling for a TReq to be sent, set aside enough buffer space to receive the datagram(s) of the TResp. The number of buffers allocated (in other words, the maximum number of datagrams that the responder can send) is indicated in the TReq by an eight-bit bit map. The bits of this bit map are numbered 0 to 7 (the least significant bit being number 0); each bit corresponds to the response datagram with the respective sequence number.
_______________________________________________________________________________
»Datagram Loss Recovery
The way that ATP recovers from datagram loss situations is best explained by an example; see Figure 4. Assume that the requester wants to read six sectors of 512 bytes each from the responder’s disk. The requester puts aside six 512-byte buffers (which may or may not be contiguous) for the response datagrams, and calls ATP to send a TReq. In this TReq the bit map is set to binary 00111111 or decimal 63. The TReq carries a 16-bit transaction ID, generated by the requester’s .ATP driver before sending it. (This example assumes that the requester and responder have already agreed that each buffer can hold 512
bytes.) The TReq is delivered to the responder, which reads the six disk sectors and sends them back, through ATP, in TResp datagrams bearing sequence numbers 0 through 5. Each TResp datagram also carries exactly the same transaction ID as the TReq to which they’re responding.
There are several ways that datagrams may be lost in this case. The original TReq could be lost for one of many reasons. The responding node might be too busy to receive the TReq or might be out of buffers for receiving it, there could be an undetected collision on the network, a bit error in the transmission line, and so on. To recover from such errors, the requester’s .ATP driver maintains an ATP retry timer for each transaction sent. If this timer expires and the complete TResp has not been received, the TReq is retransmitted and the retry timer is restarted.
A second error situation occurs when one or more of the TResp datagrams isn’t received correctly by the requester’s .ATP driver (datagram 1 in Figure 4). Again, the retry timer will expire and the complete TResp will not have been received; this will result in a retransmission of the TReq. However, to avoid unnecessary retransmission of the TResp datagrams already properly received, the bit map of this retransmitted TReq is modified to reflect only those datagrams not yet received. Upon receiving this TReq, the responder retransmits only the missing response datagrams.
Another possible failure is that the responder’s .ATP driver goes down or the responder becomes unreachable through the underlying network system. In this case, retransmission of the TReq could continue indefinitely. To avoid this situation, the requester provides a maximum retry count; if this count is exceeded, the requester’s .ATP driver returns an appropriate error message to the requester.
Figure 4–Datagram Loss Recovery
Note: There may be situations where, due to an anticipated delay, you’ll
want a request to be retransmitted more than 255 times; specifying a
retry count of 255 indicates “infinite retries” to ATP and will cause
a message to be retransmitted until the request has either been
serviced, or been cancelled through a specific call.
Finally, in our example, what if the responder is able to provide only four disk sectors (having reached the end of the disk) instead of the six requested? To handle this situation, there’s an end-of-message (EOM) flag in each TResp datagram. In this case, the TResp datagram numbered 3 would come with this flag set. The reception of this datagram informs the requester’s .ATP driver that TResps numbered 4 and 5 will not be sent and should not be expected.
When the transaction completes successfully (all expected TResp datagrams are received or TResp datagrams numbered 0 to n are received with datagram n’s EOM flag set), the requester is informed and can then use the data received in the TResp.
ATP provides two classes of service: at-least-once (ALO) and exactly-once (XO). The TReq datagram contains an XO flag that’s set if XO service is required and cleared if ALO service is adequate. The main difference between the two is in the sequence of events that occurs when the TReq is received by the responder’s .ATP driver.
In the case of ALO service, each time a TReq is received (with the XO flag cleared), it’s delivered to the responder by its .ATP driver; this is true even for retransmitted TReqs of the same transaction. Each time the TReq is delivered, the responder performs the requested operation and sends the necessary TResp datagrams. Thus, the requested operation is performed at least once, and perhaps several times, until the transaction is completed at the requester’s end.
The at-least-once service is satisfactory in a variety of situations—for instance, if the requester wishes to read a clock or a counter being maintained at the responder’s end. However, in other circumstances, repeated execution of the requested operation is unacceptable. This is the case, for instance, if the requester is sending data to be printed at the responding end; exactly-once service is designed for such situations.
The responder’s .ATP driver maintains a transactions list of recently received XO TReqs. Whenever a TReq is received with its XO flag set, the driver goes through this list to see if this is a retransmitted TReq. If it’s the first TReq of a transaction, it’s entered into the list and delivered to the responder. The responder executes the requested operation and calls its driver to send a TResp. Before sending it out, the .ATP driver saves the TResp in the list.
When a retransmitted TReq for the same XO transaction is received, the responder’s .ATP driver will find a corresponding entry in the list. The retransmitted TReq is not delivered to the responder; instead, the driver automatically retransmits the response datagrams that were saved in the list. In this way, the responder never sees the retransmitted TReqs and the requested operation is performed only once.
ATP must include a mechanism for eventually removing XO entries from the responding end’s transaction list; two provisions are made for this. When the requester’s .ATP driver has received all the TResp datagrams of a particular transaction, it sends a datagram known as a transaction release (TRel); this tells the responder’s .ATP driver to remove the transaction from the list. However, the TRel could be lost in the network (or the responding end may die, and so on), leaving the entry in the list forever. To account for this situation, the responder’s .ATP driver maintains a release timer for each transaction. If this timer expires and no activity has occurred for the transaction, its entry is removed from the transactions list.
_______________________________________________________________________________
»ABOUT THE APPLETALK MANAGER
_______________________________________________________________________________
The AppleTalk Manager is divided into three parts (see Figure 5):
• A lower-level driver called “.MPP” that contains code to implement ALAP,
DDP, NBP, and the RTMP stub; this includes separate code resources loaded
in when an NBP name is registered or looked up.
• A higher-level driver called “.ATP” that implements ATP.
• A Pascal interface to these two drivers, which is a set of Pascal data
types and routines to aid Pascal programmers in calling the AppleTalk
Manager.
Figure 5–Calling the AppleTalk Manager
The two drivers and the interface to them are not in ROM; your application must link to the appropriate object files.
Pascal programmers make calls to the AppleTalk Manager’s Pascal interface, which in turn makes Device Manager Control calls to the two drivers. Assembly-language programmers make Device Manager Control calls directly to the drivers.
Note: Pascal programmers can, of course, make PBControl calls directly
if they wish.
The AppleTalk Manager provides ALAP routines that allow a program to:
• send a frame to another node
• receive a frame from another node
• add a protocol handler to the protocol handler table
• remove a protocol handler from the protocol handler table
Each node may have up to four protocol handlers in its protocol handler table, two of which are currently used by DDP.
By calling DDP, socket clients can:
• send a datagram via a socket
• receive a datagram via a socket
• open a socket and add a socket listener to the socket table
• close a socket and remove a socket listener from the socket table
Each node may have up to 12 open sockets in its socket table.
Programs cannot access RTMP directly via the AppleTalk Manager; RTMP exists solely for the purpose of providing DDP with routing information.
The NBP code allows a socket client to:
• register the name and socket number of an entity in the node’s names table
• determine the address (and confirm the existence) of an entity
• delete the name of an entity from the node’s names table
The AppleTalk Manager’s .ATP driver allows a socket client to do the following:
• open a responding socket to receive requests
• send a request to another socket and get back a response
• receive a request via a responding socket
• send a response via a responding socket
• close a responding socket
Note: Although the AppleTalk Manager provides four different protocols
for your use, you’re not bound to use all of them. In fact, most
programmers will use only the NBP and ATP protocols.
AppleTalk communicates via channel B of the Serial Communications Controller
(SCC). When the Macintosh is started up with a disk containing the AppleTalk code, the status of serial port B is checked. If port B isn’t being used by another device driver, and is available for use by AppleTalk, the .MPP driver is loaded into the system heap. On a Macintosh 128K, only the MPP code is loaded at system startup; the .ATP driver and NBP code are read into the application heap when the appropriate commands are issued. On a Macintosh 512K or XL, all AppleTalk code is loaded into the system heap at system startup.
After loading the AppleTalk code, the .MPP driver installs its own interrupt handlers, installs a task into the vertical retrace queue, and prepares the SCC for use. It then chooses a node ID for the Macintosh and confirms that the node ID isn’t already being used by another node on the network.
Warning: For this reason it’s imperative that the Macintosh be connected
to the AppleTalk network through serial port B (the printer port)
before being switched on.
The AppleTalk Manager also provides Pascal routines for opening and closing the .MPP and .ATP drivers. The open calls allow a program to load AppleTalk code at times other than system startup. The close calls allow a program to remove the AppleTalk code from the Macintosh; the use of close calls is highly discouraged, since other co-resident programs are then “disconnected” from AppleTalk. Both sets of calls are described in detail under “Calling the AppleTalk Manager from Pascal”.
Warning: If, at system startup, serial port B isn’t available for use by
AppleTalk, the .MPP driver won’t open. However, a driver doesn’t
return an error message when it fails to open. Pascal programmers
must ensure the proper opening of AppleTalk by calling one of the
two routines for opening the AppleTalk drivers (either MPPOpen or
ATPLoad). If AppleTalk was successfully loaded at system startup,
these calls will have no effect; otherwise they’ll check the
availability of port B, attempt to load the AppleTalk code, and
return an appropriate result code.
Assembly-language note: Assembly-language programmers can use the Pascal
routines for opening AppleTalk. They can also check
the availability of port B themselves and then decide
whether to open MPP or ATP. Detailed information on
how to do this is provided in the section “Calling
the AppleTalk Manager from Assembly Language”.
The two AppleTalk device drivers, named .MPP and .ATP, are included in the 128K ROM. The AppleTalk Manager, however (the interface to the drivers), is not in ROM; your application must link to the appropriate object files.
On the Macintosh Plus, you need only open the .MPP driver; this will also load the .ATP driver and NBP code automatically. Since, in the 128K ROM, device drivers return errors, it’s no longer necessary to check whether port B is free and configured for AppleTalk. If port B isn’t available, the .MPP driver won’t open and the result code portInUse or portNotCf will be returned.
Assembly-language note: When called from assembly language, the Datagram
Delivery Protocol (DDP) allows 14 (instead of 12)
open sockets.
The changes to the AppleTalk manager increase functionality and resources. Two interfaces for the AppleTalk Manager calls are discussed: the new or preferred interface and the alternate interface. Picking a node address in the server range, sending packets to one’s own node, multiple concurrent NBP requests, sending ATP requests through a specified socket and two new ATP calls are also discussed in this section. These calls can only be made with the preferred interface.
_______________________________________________________________________________
»CALLING THE APPLETALK MANAGER FROM PASCAL
_______________________________________________________________________________
This section discusses how to use the AppleTalk Manager from Pascal. Equivalent assembly-language information is given in the “Calling the AppleTalk Manager from Assembly Language” section.
You can execute many AppleTalk Manager routines either synchronously (meaning that the application can’t continue until the routine is completed) or asynchronously (meaning that the application is free to perform other tasks while the routine is being executed).
When an application calls an AppleTalk Manager routine asynchronously, an I/O request is placed in the appropriate driver’s I/O queue, and control returns to the calling program—possibly even before the actual I/O is completed. Requests are taken from the queue one at a time, and processed; meanwhile, the calling program is free to work on other things.
The routines that can be executed asynchronously contain a Boolean parameter called async. If async is TRUE, the call is executed asynchronously; otherwise the call is executed synchronously. Every time an asynchronous routine call is completed, the AppleTalk Manager posts a network event. The message field of the event record will contain a handle to the parameter block that was used to make that call.
Most AppleTalk Manager routines return an integer result code of type OSErr. Each routine description lists all of the applicable result codes generated by the AppleTalk Manager, along with a short description of what the result code means. Lengthier explanations of all the result codes can be found in the summary at the end of the chapter. Result codes from other parts of the Operating System may also be returned. (See Appendix A for a list of all result codes.)
Many Pascal calls to the AppleTalk Manager require information passed in a parameter block of type ABusRecord. The exact content of an ABusRecord depends on the protocol being called:
TYPE ABProtoType = (lapProto,ddpProto,nbpProto,atpProto);
ABusRecord = RECORD
abOpcode: ABCallType; {type of call}
abResult: INTEGER; {result code}
abUserReference: LONGINT; {for your use}
CASE ABProtoType OF
lapProto:
. . . {ALAP parameters}
ddpProto:
. . . {DDP parameters}
nbpProto:
. . . {NBP parameters}
atpProto:
. . . {ATP parameters}
END;
END;
ABRecPtr = ^ABusRecord;
ABRecHandle = ^ABRecPtr;
The value of the abOpcode field is inserted by the AppleTalk Manager when the call is made, and is always a member of the following set:
TYPE ABCallType = (tLAPRead,tLAPWrite,tDDPRead,tDDPWrite,tNBPLookup,
tNBPConfirm,tNBPRegister,tATPSndRequest,
tATPGetRequest,tATPSdRsp,tATPAddRsp,tATPRequest,
tATPRespond);
The abUserReference field is available for use by the calling program in any way it wants. This field isn’t used by the AppleTalk Manager routines or drivers.
The size of an ABusRecord data structure in bytes is given by one of the following constants:
CONST lapSize = 20;
ddpSize = 26;
nbpSize = 26;
atpSize = 56;
Variables of type ABusRecord must be allocated in the heap with Memory Manager NewHandle calls. For example:
myABRecord := ABRecHandle(NewHandle(ddpSize))
Warning: These Memory Manager calls can’t be made inside interrupts.
Routines that are executed asynchronously return control to the calling program with the result code noErr as soon as the call is placed in the driver’s I/O queue. This isn’t an indication of successful call completion; it simply indicates that the call was successfully queued to the appropriate driver. To determine when the call is actually completed, you can either check for a network event or poll the abResult field of the call’s ABusRecord. The abResult field, set to 1 when the call is made, receives the actual result code upon completion of the call.
Warning: A data structure of type ABusRecord is often used by the AppleTalk
Manager during an asynchronous call, and so is locked by the
AppleTalk Manager. Don’t attempt to unlock or use such a variable.
Each routine description includes a list of the ABusRecord fields affected by the routine. The arrow next to each field name indicates whether it’s an input, output, or input/output parameter:
Arrow Meaning
--> Parameter is passed to the routine
<-- Parameter is returned by the routine
<-> Parameter is passed to and returned by the routine
_______________________________________________________________________________
»Opening and Closing AppleTalk
X-Ref: Technical Note #224
FUNCTION MPPOpen : OSErr; [Not in ROM]
MPPOpen first checks whether the .MPP driver has already been loaded; if it has, MPPOpen does nothing and returns noErr. If MPP hasn’t been loaded, MPPOpen attempts to load it into the system heap. If it succeeds, it then initializes the driver’s variables and goes through the process of dynamically assigning a node ID to that Macintosh. On a Macintosh 512K or XL, it also loads the .ATP driver and NBP code into the system heap.
If serial port B isn’t configured for AppleTalk, or is already in use, the .MPP driver isn’t loaded and an appropriate result code is returned.
Result codes noErr No error
portInUse Port B is already in use
portNotCf Port B not configured for AppleTalk
FUNCTION MPPClose : OSErr; [Not in ROM]
MPPClose removes the .MPP driver, and any data structures associated with it, from memory. If the .ATP driver or NBP code were also installed, they’re removed as well. MPPClose also returns the use of port B to the Serial Driver.
Warning: Since other co-resident programs may be using AppleTalk, it’s
strongly recommended that you never use this call. MPPClose will
completely disable AppleTalk; the only way to restore AppleTalk
is to call MPPOpen again.
_______________________________________________________________________________
»AppleTalk Link Access Protocol
»Data Structures
ALAP calls use the following ABusRecord fields:
lapProto:
(lapAddress: LAPAdrBlock; {destination or source node ID}
lapReqCount: INTEGER; {length of frame data or buffer size in bytes}
lapActCount: INTEGER; {number of frame data bytes actually received}
lapDataPtr: Ptr); {pointer to frame data or pointer to buffer}
When an ALAP frame is sent, the lapAddress field indicates the ID of the destination node. When an ALAP frame is received, lapAddress returns the ID of the source node. The lapAddress field also indicates the ALAP protocol type of the frame:
TYPE LAPAdrBlock = PACKED RECORD
dstNodeID: Byte; {destination node ID}
srcNodeID: Byte; {source node ID}
lapProtType: ABByte {ALAP protocol type}
END;
When an ALAP frame is sent, lapReqCount indicates the size of the frame data in bytes and lapDataPtr points to a buffer containing the frame data to be sent. When an ALAP frame is received, lapDataPtr points to a buffer in which the incoming data can be stored and lapReqCount indicates the size of the buffer in bytes. The number of bytes actually sent or received is returned in the lapActCount field.
Each ALAP frame contains an eight-bit ALAP protocol type in the header. ALAP protocol types 128 through 255 are reserved for internal use by ALAP, hence the declaration:
TYPE ABByte = 1..127; {ALAP protocol type}
Warning: Don’t use ALAP protocol type values 1 and 2; they’re reserved
for use by DDP. Value 3 through 15 are reserved for internal
use by Apple and also shouldn’t be used.
»Using ALAP
Most programs will never need to call ALAP, because higher-level protocols will automatically call it as necessary. If you do want to send a frame directly via ALAP, call the LAPWrite function. If you want to read ALAP frames, you have two choices:
• Call LAPOpenProtocol with NIL for protoPtr (see below); this installs
the default protocol handler provided by the AppleTalk Manager. Then
call LAPRead to receive frames.
• Write your own protocol handler, and call LAPOpenProtocol to add it
to the node’s protocol handler table. The ALAP code will examine every
incoming frame and send all those with the correct ALAP protocol type
to your protocol handler. See the section “Protocol Handlers and Socket
Listeners” for information on how to write a protocol handler.
When your program no longer wants to receive frames with a particular ALAP protocol type value, it can call LAPCloseProtocol to remove the corresponding protocol handler from the protocol handler table.
»ALAP Routines
FUNCTION LAPOpenProtocol (theLAPType: ABByte;
protoPtr: Ptr) : OSErr; [Not in ROM]
LAPOpenProtocol adds the ALAP protocol type specified by theLAPType to the
node’s protocol table. If you provide a pointer to a protocol handler in protoPtr, ALAP will send each frame with an ALAP protocol type of theLAPType to that protocol handler.
If protoPtr is NIL, the default protocol handler will be used for receiving frames with an ALAP protocol type of theLAPType. In this case, to receive a frame you must call LAPRead to provide the default protocol handler with a buffer for placing the data. If, however, you’ve written your own protocol handler and protoPtr points to it, your protocol handler will have the responsibility for receiving the frame and it’s not necessary to call LAPRead.
Result codes noErr No error
lapProtErr Error attaching protocol type
FUNCTION LAPCloseProtocol (theLAPType: ABByte) : OSErr; [Not in ROM]
LAPCloseProtocol removes from the node’s protocol table the specified ALAP protocol type, as well as its protocol handler.
Warning: Don’t close ALAP protocol type values 1 or 2. If you close these
protocol types, DDP will be disabled; once disabled, the only way
to restore DDP is to restart the system, or to close and then
reopen AppleTalk.
Result codes noErr No error
lapProtErr Error detaching protocol type
FUNCTION LAPWrite (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tLAPWrite}
<-- abResult {result code}
--> abUserReference {for your use}
--> lapAddress.dstNodeID {destination node ID}
--> lapAddress.lapProtType {ALAP protocol type}
--> lapReqCount {length of frame data}
--> lapDataPtr {pointer to frame data}
LAPWrite sends a frame to another node. LAPReqCount and lapDataPtr specify the length and location of the data to send. The lapAddress.lapProtType field indicates the ALAP protocol type of the frame and the lapAddress.dstNodeID indicates the node ID of the node to which the frame should be sent.
Note: The first two bytes of an ALAP frame’s data must contain the length
in bytes of that data, including the length bytes themselves.
Result codes noErr No error
excessCollsns Unable to contact destination node;
packet not sent
ddpLenErr ALAP data length too big
lapProtErr Invalid ALAP protocol type
FUNCTION LAPRead (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tLAPRead}
<-- abResult {result code}
--> abUserReference {for your use}
<-- lapAddress.dstNodeID {destination node ID}
<-- lapAddress.srcNodeID {source node ID}
--> lapAddress.lapProtType {ALAP protocol type}
--> lapReqCount {buffer size in bytes}
<-- lapActCount {number of frame data bytes actually received}
--> lapDataPtr {pointer to buffer}
LAPRead receives a frame from another node. LAPReqCount and lapDataPtr specify the size and location of the buffer that will receive the frame data. If the buffer isn’t large enough to hold all of the incoming frame data, the extra bytes will be discarded and buf2SmallErr will be returned. The number of bytes actually received is returned in lapActCount. Only frames with ALAP protocol type equal to lapAddress.lapProtType will be received. The node IDs of the frame’s source and destination nodes are returned in lapAddress.srcNodeID and lapAddress.dstNodeID. You can determine whether the packet was broadcast to you by examining the value of lapAddress.dstNodeID—if the packet was broadcast it’s equal to 255, otherwise it’s equal to your node ID.
Note: You should issue LAPRead calls only for ALAP protocol types that were
opened (via LAPOpenProtocol) to use the default protocol handler.
Warning: If you close a protocol type for which there are still LAPRead
calls pending, the calls will be canceled but the memory occupied
by their ABusRecords will not be released. For this reason, before
closing a protocol type, call LAPRdCancel to cancel any pending
LAPRead calls associated with that protocol type.
Result codes noErr No error
buf2SmallErr Frame too large for buffer
readQErr Invalid protocol type or protocol type not
found in table
FUNCTION LAPRdCancel (abRecord: ABRecHandle) : OSErr; [Not in ROM]
Given the handle to the ABusRecord of a previously made LAPRead call, LAPRdCancel dequeues the LAPRead call, provided that a packet satisfying the LAPRead has not already arrived. LAPRdCancel returns noErr if the LAPRead call is successfully removed from the queue. If LAPRdCancel returns recNotFnd, check the abResult field to verify that the LAPRead has been completed and determine its outcome.
Result codes noErr No error
readQErr Invalid protocol type or protocol type not
found in table
recNotFnd ABRecord not found in queue
»Example
This example sends an ALAP packet synchronously and waits asynchronously for a response. Assume that both nodes are using a known protocol type (in this case, 73) to receive packets, and that the destination node has a node ID of 4.
VAR
myABRecord: ABRecHandle;
myBuffer: PACKED ARRAY [0..599] OF CHAR; {buffer for both send and receive}
myLAPType: Byte;
errCode, index, dataLen: INTEGER;
someText: Str255;
async: BOOLEAN;
BEGIN
errCode := MPPOpen;
IF errCode <> noErr THEN
WRITELN('Error in opening AppleTalk')
{Maybe serial port B isn't available for use by AppleTalk}
ELSE
BEGIN
{Call Memory Manager to allocate ABusRecord}
myABRecord := ABRecHandle(NewHandle(lapSize));
myLAPType := 73;
{Enter myLAPType into protocol handler table and install default handler to }
{ service frames of that ALAP type. No packets of that ALAP type will be }
{ received until we call LAPRead.}
errCode := LAPOpenProtocol(myLAPType, NIL);
IF errCode <> noErr THEN
WRITELN('Error while opening the protocol type')
{Have we opened too many protocol types? Remember that DDP uses two of }
{ them.}
ELSE
BEGIN
{Prepare data to be sent}
someText := 'This data will be in the ALAP data area';
{The .MPP implementation requires that the first two bytes of the ALAP }
{ data field contain the length of the data, including the length bytes }
{ themselves.}
dataLen := LENGTH(someText) + 2;
buffer[0] := CHR(dataLen DIV 256); {high byte of data length}
buffer[1] := CHR(dataLen MOD 256); {low byte of data length}
FOR index := 1 TO dataLen - 2 DO {stuff buffer with packet data}
buffer[index + 1] := someText[index];
async := FALSE;
WITH myABRecord^^ DO {fill parameters in the ABusRecord}
BEGIN
lapAddress.lapProtType := myLAPType;
lapAddress.dstNodeID := 4;
lapReqCount := dataLen;
lapDataPtr := @buffer;
END;
{Send the frame}
errCode := LAPWrite(myABRecord, async);
{In the case of a sync call, errCode and the abResult field of }
{ the myABRecord will contain the same result code. We can also }
{ reuse myABRecord, since we know whether the call has completed.}
IF errCode <> noErr THEN
WRITELN('Error while writing out the packet')
{Maybe the receiving node wasn't on-line}
ELSE
BEGIN
{We have sent out the packet and are now waiting for a response. We }
{ issue an async LAPRead call so that we don't “hang” waiting for a }
{ response that may not come.}
async := TRUE;
WITH myABRecord^^ DO
BEGIN
lapAddress.lapProtType := myLAPType;
{ALAP type we want to receive }
lapReqCount := 600; {our buffer is maximum size}
lapDataPtr := @buffer;
END;
errCode := LAPRead(myABRecord, async); {wait for a packet}
IF errCode <> noErr THEN
WRITELN('Error while trying to queue up a LAPRead')
{Was the protocol handler installed correctly?}
ELSE
BEGIN
{We can either sit here in a loop and poll the abResult }
{ field or just exit our code and use the event }
{ mechanism to flag us when the packet arrives.}
CheckForMyEvent; {your procedure for checking for a network event}
errCode := LAPCloseProtocol(myLAPType);
IF errCode <> noErr THEN
WRITELN('Error while closing the protocol type');
END;
END;
END;
END;
END.
_______________________________________________________________________________
»Datagram Delivery Protocol
»Data Structures
DDP calls use the following ABusRecord fields:
ddpProto:
(ddpType: Byte; {DDP protocol type}
ddpSocket: Byte; {source or listening socket number}
ddpAddress: AddrBlock; {destination or source socket address}
ddpReqCount: INTEGER; {length of datagram data or buffer size in bytes}
ddpActCount: INTEGER; {number of bytes actually received}
ddpDataPtr: Ptr; {pointer to buffer}
ddpNodeID: Byte); {original destination node ID}
When a DDP datagram is sent, ddpReqCount indicates the size of the datagram data in bytes and ddpDataPtr points to a buffer containing the datagram data. DDPSocket specifies the socket from which the datagram should be sent. DDPAddress is the internet address of the socket to which the datagram should be sent:
TYPE AddrBlock = PACKED RECORD
aNet: INTEGER; {network number}
aNode: Byte; {node ID}
aSocket: Byte {socket number}
END;
Note: The network number you specify in ddpAddress.aNet tells MPP whether
to create a long header (for an internet) or a short header (for a
local network only). A short DDP header will be sent if ddpAddress.aNet
is 0 or equal to the network number of the local network.
When a DDP datagram is received, ddpDataPtr points to a buffer in which the incoming data can be stored and ddpReqCount indicates the size of the buffer in bytes. The number of bytes actually sent or received is returned in the ddpActCount field. DDPAddress is the internet address of the socket from which the datagram was sent.
DDPType is the DDP protocol type of the datagram, and ddpSocket specifies the socket that will receive the datagram.
Warning: DDP protocol types 1 through 15 and DDP socket numbers 1 through 63
are reserved by Apple for internal use. Socket numbers 64 through 127
are available for experimental use. Use of these experimental sockets
isn’t recommended for commercial products, since there’s no mechanism
for eliminating conflicting usage by different developers.
»Using DDP
Before it can use a socket, the program must call DDPOpenSocket, which adds a socket and its socket listener to the socket table. When a program is finished using a socket, call DDPCloseSocket, which removes the socket’s entry from the socket table. To send a datagram via DDP, call DDPWrite. To receive datagrams, you have two choices:
• Call DDPOpenSocket with NIL for sktListener (see below); this installs
the default socket listener provided by the AppleTalk Manager. Then call
DDPRead to receive datagrams.
• Write your own socket listener and call DDPOpenSocket to install it. DDP
will call your socket listener for every incoming datagram for that
socket; in this case, you shouldn’t call DDPRead. For information on how
to write a socket listener, see the section “Protocol Handlers and Socket
Listeners”.
To cancel a previously issued DDPRead call (provided it’s still in the queue), call DDPRdCancel.
»DDP Routines
FUNCTION DDPOpenSocket (VAR theSocket: Byte;
sktListener: Ptr) : OSErr; [Not in ROM]
DDPOpenSocket adds a socket and its socket listener to the socket table. If theSocket is nonzero, it must be in the range 64 to 127, and it specifies the socket’s number; if theSocket is 0, DDPOpenSocket dynamically assigns a socket number in the range 128 to 254, and returns it in theSocket. SktListener contains a pointer to the socket listener; if it’s NIL, the default listener will be used.
If you’re using the default socket listener, you must then call DDPRead to receive a datagram (in order to specify buffer space for the default socket listener). If, however, you’ve written your own socket listener and sktListener points to it, your listener will provide buffers for receiving datagrams and you shouldn’t use DDPRead calls.
DDPOpenSocket will return ddpSktErr if you pass the number of an already opened socket, if you pass a socket number greater than 127, or if the socket table is full.
Note: The range of static socket numbers 1 through 63 is reserved by Apple
for internal use. Socket numbers 64 through 127 are available for
unrestricted experimental use.
Result codes noErr No error
ddpSktErr Socket error
FUNCTION DDPCloseSocket (theSocket: Byte) : OSErr; [Not in ROM]
DDPCloseSocket removes the entry of the specified socket from the socket table and cancels all pending DDPRead calls that have been made for that socket. If you pass a socket number of 0, or if you attempt to close a socket that isn’t open, DDPCloseSocket will return ddpSktErr.
Result codes noErr No error
ddpSktErr Socket error
FUNCTION DDPWrite (abRecord: ABRecHandle; doChecksum: BOOLEAN;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tDDPWrite}
<-- abResult {result code}
--> abUserReference {for your use}
--> ddpType {DDP protocol type}
--> ddpSocket {source socket number}
--> ddpAddress {destination socket address}
--> ddpReqCount {length of datagram data}
--> ddpDataPtr {pointer to buffer}
DDPWrite sends a datagram to another socket. DDPReqCount and ddpDataPtr specify the length and location of the data to send. The ddpType field indicates the DDP protocol type of the frame, and ddpAddress is the complete internet address of the socket to which the datagram should be sent. DDPSocket specifies the socket from which the datagram should be sent. Datagrams sent over the internet to a node on an AppleTalk network different from the sending node’s network have an optional software checksum to detect errors that might occur inside the intermediate bridges. If doChecksum is TRUE, DDPWrite will compute this checksum; if it’s FALSE, this software checksum feature is ignored.
Note: The destination socket can’t be in the same node as the program
making the DDPWrite call.
Result codes noErr No error
ddpLenErr Datagram length too big
ddpSktErr Source socket not open
noBridgeErr No bridge found
FUNCTION DDPRead (abRecord: ABRecHandle; retCksumErrs: BOOLEAN;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tDDPRead}
<-- abResult {result code}
--> abUserReference {for your use}
<-- ddpType {DDP protocol type}
--> ddpSocket {listening socket number}
<-- ddpAddress {source socket address}
--> ddpReqCount {buffer size in bytes}
<-- ddpActCount {number of bytes actually received}
--> ddpDataPtr {pointer to buffer}
<-- ddpNodeID {original destination node ID}
DDPRead receives a datagram from another socket. The size and location of the buffer that will receive the data are specified by ddpReqCount and ddpDataPtr. If the buffer isn’t large enough to hold all of the incoming frame data, the extra bytes will be discarded and buf2SmallErr will be returned. The number of bytes actually received is returned in ddpActCount. DDPSocket specifies the socket to receive the datagram (the “listening” socket). The node to which the packet was sent is returned in ddpNodeID; if the packet was broadcast ddpNodeID will contain 255. The address of the socket that sent the packet is returned in ddpAddress. If retCksumErrs is FALSE, DDPRead will discard any packets received with an invalid checksum and inform the caller of the error. If retCksumErrs is TRUE, DDPRead will deliver all packets, whether or not the checksum is valid; it will also notify the caller when there’s a checksum error.
Note: The sender of the datagram must be in a different node from the
receiver. You should issue DDPRead calls only for receiving datagrams
for sockets opened with the default socket listener; see the
description of DDPOpenSocket.
Note: If the buffer provided isn’t large enough to hold all of the incoming
frame data (buf2SmallErr), the checksum can’t be calculated; in this
case, DDPRead will deliver packets even if retCksumErrs is FALSE.
Result codes noErr No error
buf2SmallErr Datagram too large for buffer
cksumErr Checksum error
ddpLenErr Datagram length too big
ddpSktErr Socket error
readQErr Invalid socket or socket not found in table
FUNCTION DDPRdCancel (abRecord: ABRecHandle) : OSErr; [Not in ROM]
Given the handle to the ABusRecord of a previously made DDPRead call, DDPRdCancel dequeues the DDPRead call, provided that a packet satisfying the DDPRead hasn’t already arrived. DDPRdCancel returns noErr if the DDPRead call is successfully removed from the queue. If DDPRdCancel returns recNotFnd, check the abResult field of abRecord to verify that the DDPRead has been completed and determine its outcome.
Result codes noErr No error
readQErr Invalid socket or socket not found in table
recNotFnd ABRecord not found in queue
»Example
This example sends a DDP packet synchronously and waits asynchronously for a response. Assume that both nodes are using a known socket number (in this case, 30) to receive packets. Normally, you would want to use NBP to look up your destination’s socket address.
VAR
myABRecord: ABRecHandle;
myBuffer: PACKED ARRAY [0..599] OF CHAR; {buffer for both send and receive}
mySocket: Byte;
errCode, index, dataLen: INTEGER;
someText: Str255;
async, retCksumErrs, doChecksum: BOOLEAN;
BEGIN
errCode := MPPOpen;
IF errCode <> noErr THEN
WRITELN('Error in opening AppleTalk')
{Maybe serial port B isn't available for use by AppleTalk}
ELSE
BEGIN
{Call Memory Manager to allocate ABusRecord}
myABRecord := ABRecHandle(NewHandle(ddpSize));
mySocket := 30;
{Add mySocket to socket table and install default socket listener to service }
{ datagrams addressed to that socket. No packets addressed to mySocket will be }
{ received until we call DDPRead. }
errCode := DDPOpenSocket(mySocket, NIL);
IF errCode <> noErr THEN
WRITELN('Error while opening the socket')
{Have we opened too many socket listeners? Remember that DDP uses two of }
{ them.}
ELSE
BEGIN
{Prepare data to be sent}
someText := 'This is a sample datagram';
dataLen := LENGTH(someText);
FOR index := 0 TO dataLen - 1 DO {stuff buffer with packet data}
myBuffer[index] := someText[index + 1];
async := FALSE;
WITH myABRecord^^ DO {fill the parameters in the ABusRecord}
BEGIN
ddpType := 5;
ddpAddress.aNet := 0; {send on “our” network}
ddpAddress.aNode := 34;
ddpAddress.aSocket := mySocket;
ddpReqCount := dataLen;
ddpDataPtr := @myBuffer;
END;
doChecksum := FALSE;
{If packet contains a DDP long header, compute checksum and insert it into }
{ the header.}
errCode := DDPWrite(myABRecord, doChecksum, async); {send packet}
{In the case of a sync call, errCode and the abResult field of myABRecord }
{ will contain the same result code. We can also reuse myABRecord, since we }
{ know whether the call has completed.}
IF errCode <> noErr THEN
WRITELN('Error while writing out the packet')
{Maybe the receiving node wasn't on-line}
ELSE
BEGIN
{We have sent out the packet and are now waiting for a response. We }
{ issue an async DDPRead call so that we don't “hang” waiting for a }
{ response that may not come. To cancel the async read call, we must }
{ close the socket associated with the call or call DDPRdCancel.}
async := TRUE;
retCksumErrs := TRUE; {return packets even if }
{ they have a checksum error}
WITH myABRecord^^ DO
BEGIN
ddpSocket := mySocket;
ddpReqCount := 600; {our reception buffer is max size}
ddpDataPtr := @myBuffer;
END;
{Wait for a packet asynchronously}
errCode := DDPRead(myABRecord, retCksumErrs, async);
IF errCode <> noErr THEN
WRITELN('Error while trying to queue up a DDPRead')
{Was the socket listener installed correctly?}
ELSE
BEGIN
{We can either sit here in a loop and poll the }
{ abResult field or just exit our code and use the }
{ event mechanism to flag us when the packet arrives.}
CheckForMyEvent; {your procedure for checking for a }
{ network event}
{If there were no errors, a packet is inside the array }
{ mybuffer, the length is in ddpActCount, and the }
{ address of the sending socket is in ddpAddress. }
{ Process the packet received here and report any errors.}
errCode := DDPCloseSocket(mySocket); {we're done with it}
IF errCode <> noErr THEN
WRITELN('Error while closing the socket');
END;
END;
END;
END;
END.
_______________________________________________________________________________
»AppleTalk Transaction Protocol
»Data Structures
ATP calls use the following ABusRecord fields:
atpProto:
(atpSocket: Byte; {listening or responding socket number}
atpAddress: AddrBlock; {destination or source socket address}
atpReqCount: INTEGER; {request size or buffer size}
atpDataPtr: Ptr; {pointer to buffer}
atpRspBDSPtr: BDSPtr; {pointer to response BDS}
atpBitMap: BitMapType; {transaction bit map}
atpTransID: INTEGER; {transaction ID}
atpActCount: INTEGER; {number of bytes actually received}
atpUserData: LONGINT; {user bytes}
atpXO: BOOLEAN; {exactly-once flag}
atpEOM: BOOLEAN; {end-of-message flag}
atpTimeOut: Byte; {retry timeout interval in seconds}
atpRetries: Byte; {maximum number of retries}
atpNumBufs: Byte; {number of elements in response BDS or number }
{ of response packets sent}
atpNumRsp: Byte; {number of response packets received or }
{ sequence number}
atpBDSSize: Byte; {number of elements in response BDS}
atpRspUData: LONGINT; {user bytes sent or received in transaction }
{ response}
atpRspBuf: Ptr; {pointer to response message buffer}
atpRspSize: INTEGER); {size of response message buffer}
The socket receiving the request or sending the response is identified by atpSocket. ATPAddress is the address of either the destination or the source socket of a transaction, depending on whether the call is sending or receiving data, respectively. ATPDataPtr and atpReqCount specify the location and size
(in bytes) of a buffer that either contains a request or will receive a request. The number of bytes actually received in a request is returned in atpActCount. ATPTransID specifies the transaction ID. The transaction bit map is contained in atpBitMap, in the form:
TYPE BitMapType = PACKED ARRAY[0..7] OF BOOLEAN;
Each bit in the bit map corresponds to one of the eight possible packets in a response. For example, when a request is made for which five response packets are expected, the bit map sent is binary 00011111 or decimal 31. If the second packet in the response is lost, the requesting socket will retransmit the request with a bit map of binary 00000010 or decimal 2.
ATPUserData contains the user bytes of an ATP header. ATPXO is TRUE if the transaction is to be made with exactly-once service. ATPEOM is TRUE if the response packet is the last packet of a transaction. If the number of responses is less than the number that were requested, then ATPEOM must also be TRUE. ATPNumRsp contains either the number of responses received or the sequence number of a response.
The timeout interval in seconds and the maximum number of times that a request should be made are indicated by atpTimeOut and atpRetries, respectively.
Note: Setting atpRetries to 255 will cause the request to be retransmitted
indefinitely, until a full response is received or the call is canceled.
ATP provides a data structure, known as a response buffer data structure
(response BDS), for allocating buffer space to receive the datagram(s) of the response. A response BDS is an array of one to eight elements. Each BDS element defines the size and location of a buffer for receiving one response datagram; they’re numbered 0 to 7 to correspond to the sequence numbers of the response datagrams.
ATP needs a separate buffer for each response datagram expected, since packets may not arrive in the proper sequence. It does not, however, require you to set up and use the BDS data structure to describe the response buffers; if you
don’t, ATP will do it for you. Two sets of calls are provided for both requests and responses; one set requires you to allocate a response BDS and the other doesn’t.
Assembly-language note: The two calls that don’t require you to define a BDS
data structure (ATPRequest and ATPResponse) are
available in Pascal only.
The number of BDS elements allocated (in other words, the maximum number of datagrams that the responder can send) is indicated in the TReq by an eight-bit bit map. The bits of this bit map are numbered 0 to 7 (the least significant bit being number 0); each bit corresponds to the response datagram with the respective sequence number.
ATPRspBDSPtr and atpBDSSize indicate the location and number of elements in the response BDS, which has the following structure:
TYPE BDSElement =
RECORD
buffSize: INTEGER; {buffer size in bytes}
buffPtr: Ptr; {pointer to buffer}
dataSize: INTEGER; {number of bytes actually received}
userBytes: LONGINT {user bytes}
END;
BDSType = ARRAY[0..7] OF BDSElement; {response BDS}
BDSPtr = ^BDSType;
ATPNumBufs indicates the number of elements in the response BDS that contain information. In most cases, you can allocate space for your variables of BDSType statically with a VAR declaration. However, you can allocate only the minimum space required by your ATP calls by doing the following:
VAR myBDSPtr: BDSPtr;
. . .
numOfBDS := 3; {number of elements needed}
myBDSPtr := BDSPtr(NewPtr(SIZEOF(BDSElement) * numOfBDS));
Note: The userBytes field of the BDSElement and the atpUserData field
of the ABusRecord represent the same information in the datagram.
Depending on the ATP call made, one or both of these fields will be used.
»Using ATP
Before you can use ATP on a Macintosh 128K, the .ATP driver must be read from the system resource file via an ATPLoad call. The .ATP driver loads itself into the application heap and installs a task into the vertical retrace queue.
Warning: When another application starts up, the application heap is
reinitialized; on a Macintosh 128K, this means that the ATP
code is lost (and must be reloaded by the next application).
When you’re through using ATP on a Macintosh 128K, call ATPUnload—the system will be returned to the state it was in before the .ATP driver was opened.
On a Macintosh 512K or XL, the .ATP driver will have been loaded into the system heap either at system startup or upon execution of MPPOpen or ATPLoad. ATPUnload has no effect on a Macintosh 512K or XL.
To send a transaction request, call ATPSndRequest or ATPRequest. The .ATP driver will automatically select and open a socket through which the request datagram will be sent, and through which the response datagrams will be received. The requester must specify the full network address (network number, node ID, and socket number) of the socket to which the request is to be sent. This socket is known as the responding socket, and its address must be known in advance by the requester.
At the responder’s end, before a transaction request can be received, a responding socket must be opened, and the appropriate calls be made, to receive a request. To do this, the responder first makes an ATPOpenSocket call which allows the responder to specify the address (or part of it) of the requesters from whom it’s willing to accept transaction requests. Then it issues an ATPGetRequest call to provide ATP with a buffer for receiving a request; when a request is received, ATPGetRequest is completed. The responder can queue up several ATPGetRequest calls, each of which will be completed as requests are received.
Upon receiving a request, the responder performs the requested operation, and then prepares the information to be returned to the requester. It then calls ATPSndRsp (or ATPResponse) to send the response. Actually, the responder can issue the ATPSndRsp call with only part (or none) of the response specified. Additional portions of the response can be sent later by calling ATPAddRsp.
The ATPSndRsp and ATPAddRsp calls provide flexibility in the design (and range of types) of transaction responders. For instance, the responder may, for some reason, be forced to send the responses out of sequence. Also, there might be memory constraints that force sending the complete transaction response in parts. Even though eight response datagrams might need to be sent, the responder might have only enough memory to build one datagram at a time. In this case, it would build the first response datagram and call ATPSndRsp to send it. It would then build the second response datagram in the same buffer and call ATPAddRsp to send it; and so on, for the third through eighth response datagrams.
A responder can close a responding socket by calling ATPCloseSocket. This call cancels all pending ATP calls for that socket, such as ATPGetRequest, ATPSndRsp, and ATPResponse.
For exactly-once transactions, the ATPSndRsp and ATPAddRsp calls don’t terminate until the entire transaction has completed (that is, the responding end receives a release packet, or the release timer has expired).
To cancel a pending, asynchronous ATPSndRequest or ATPRequest call, call ATPReqCancel. To cancel a pending, asynchronous ATPSndRsp or ATPResponse call, call ATPRspCancel. Pending asynchronous ATPGetRequest calls can be canceled only by issuing the ATPCloseSocket call, but that will cancel all outstanding calls for that socket.
X-Ref: Technical Note #250
Warning: You cannot reuse a variable of type ABusRecord passed to an ATP
routine until the entire transaction has either been completed
or canceled.
»ATP Routines
FUNCTION ATPLoad : OSErr; [Not in ROM]
X-Ref: Technical Note #224
ATPLoad first verifies that the .MPP driver is loaded and running. If it isn’t, ATPLoad verifies that port B is configured for AppleTalk and isn’t in use, and then loads MPP into the system heap.
ATPLoad then loads the .ATP driver, unless it’s already in memory. On a Macintosh 128K, ATPLoad reads the .ATP driver from the system resource file into the application heap; on a Macintosh 512K or XL, ATP is read into the system heap.
Note: On a Macintosh 512K or XL, ATPLoad and MPPOpen perform essentially
the same function.
Result codes noErr No error
portInUse Port B is already in use
portNotCf Port B not configured for AppleTalk
FUNCTION ATPUnload : OSErr; [Not in ROM]
ATPUnload makes the .ATP driver purgeable; the space isn’t actually released by the Memory Manager until necessary.
Note: This call applies only to a Macintosh 128K; on a Macintosh 512K
or Macintosh XL, ATPUnload has no effect.
Result codes noErr No error
FUNCTION ATPOpenSocket (addrRcvd: AddrBlock;
VAR atpSocket: Byte) : OSErr; [Not in ROM]
ATPOpenSocket opens a socket for the purpose of receiving requests. ATPSocket contains the socket number of the socket to open; if it’s 0, a number is dynamically assigned and returned in atpSocket. AddrRcvd contains a filter of the sockets from which requests will be accepted. A 0 in the network number, node ID, or socket number field of the addrRcvd record acts as a “wild card”; for instance, a 0 in the socket number field means that requests will be accepted from all sockets in the node(s) specified by the network and node fields.
Result codes noErr No error
tooManySkts Socket table full
noDataArea Too many outstanding ATP calls
Note: If you’re only going to send requests and receive responses to
these requests, you don’t need to open an ATP socket. When you
make the ATPSndRequest or ATPRequest call, ATP automatically
opens a dynamically assigned socket for that purpose.
FUNCTION ATPCloseSocket (atpSocket: Byte) : OSErr; [Not in ROM]
ATPCloseSocket closes the responding socket whose number is specified by atpSocket. It releases the data structures associated with all pending, asynchronous calls involving that socket; these pending calls are completed immediately and return the result code sktClosed.
Result codes noErr No error
noDataArea Too many outstanding ATP calls
FUNCTION ATPSndRequest (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tATPSndRequest}
<-- abResult {result code}
--> abUserReference {for your use}
--> atpAddress {destination socket address}
--> atpReqCount {request size in bytes}
--> atpDataPtr {pointer to buffer}
--> atpRspBDSPtr {pointer to response BDS}
--> atpUserData {user bytes}
--> atpXO {exactly-once flag}
<-- atpEOM {end-of-message flag}
--> atpTimeOut {retry timeout interval in seconds}
--> atpRetries {maximum number of retries}
--> atpNumBufs {number of elements in response BDS}
<-- atpNumRsp {number of response packets actually received}
ATPSndRequest sends a request to another socket. ATPAddress is the internet address of the socket to which the request should be sent. ATPDataPtr and atpReqCount specify the location and size of a buffer that contains the request information to be sent. ATPUserData contains the user bytes for the ATP header.
ATPSndRequest requires you to allocate a response BDS. ATPRspBDSPtr is a pointer to the response BDS; atpNumBufs indicates the number of elements in the BDS (this is also the maximum number of response datagrams that will be accepted). The number of response datagrams actually received is returned in atpNumRsp; if a nonzero value is returned, you can examine the response BDS to determine which packets of the transaction were actually received. If the number returned is less than requested, one of the following is true:
• Some of the packets have been lost and the retry count has been exceeded.
• ATPEOM is TRUE; this means that the response consisted of fewer packets
than were expected, but that all packets sent were received (the last
packet came with the atpEOM flag set).
ATPTimeOut indicates the length of time that ATPSndRequest should wait for a response before retransmitting the request. ATPRetries indicates the maximum number of retries ATPSndRequest should attempt. ATPXO should be TRUE if you want the request to be part of an exactly-once transaction.
ATPSndRequest completes when either the transaction is completed or the retry count is exceeded.
Result codes noErr No error
reqFailed Retry count exceeded
tooManyReqs Too many concurrent requests
noDataArea Too many outstanding ATP calls
FUNCTION ATPRequest (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tATPRequest}
<-- abResult {result code}
--> abUserReference {for your use}
--> atpAddress {destination socket address}
--> atpReqCount {request size in bytes}
--> atpDataPtr {pointer to buffer}
<-- atpActCount {number of bytes actually received}
--> atpUserData {user bytes}
--> atpXO {exactly-once flag}
<-- atpEOM {end-of-message flag}
--> atpTimeOut {retry timeout interval in seconds}
--> atpRetries {maximum number of retries}
<-- atpRspUData {user bytes received in transaction response}
--> atpRspBuf {pointer to response message buffer}
--> atpRspSize {size of response message buffer}
ATPRequest is functionally analogous to ATPSndRequest. It sends a request to another socket, but doesn’t require the caller to set up and use the BDS data structure to describe the response buffers. ATPAddress indicates the socket to which the request should be sent. ATPDataPtr and atpReqCount specify the location and size of a buffer that contains the request information to be sent. ATPUserData contains the user bytes to be sent in the request’s ATP header. ATPTimeOut indicates the length of time that ATPRequest should wait for a response before retransmitting the request. ATPRetries indicates the maximum number of retries ATPRequest should attempt.
To use this call, you must have an area of contiguous buffer space that’s large enough to receive all expected datagrams. The various datagrams will be assembled in this buffer and returned to you as a complete message upon completion of the transaction. The location and size of this buffer are passed in atpRspBuf and atpRspSize. Upon completion of the call, the size of the received response message is returned in atpActCount. The user bytes received in the ATP header of the first response packet are returned in atpRspUData. ATPXO should be TRUE if you want the request to be part of an exactly-once transaction.
Although you don’t provide a BDS, ATPRequest in fact creates one and calls the
.ATP driver (as in an ATPSndRequest call). For this reason, the abRecord fields atpRspBDSPtr and atpNumBufs are used by ATPRequest; you should not expect these fields to remain unaltered during or after the function’s execution.
For ATPRequest to receive and correctly deliver the response as a single message, the responding end must, upon receiving the request (with an ATPGetRequest call), generate the complete response as a message in a single buffer and then call ATPResponse.
Note: The responding end could also use ATPSndRsp and ATPAddRsp provided
that each response packet (except the last one) contains exactly 578
ATP data bytes; the last packet in the response can contain less than
578 ATP data bytes. Also, if this method is used, only the ATP user
bytes of the first response packet will be delivered to the requester;
any information in the user bytes of the remaining response packets
will not be delivered.
ATPRequest completes when either the transaction is completed or the retry count is exceeded.
Result codes noErr No error
reqFailed Retry count exceeded
tooManyReqs Too many concurrent requests
sktClosed Socket closed by a cancel call
noDataArea Too many outstanding ATP calls
FUNCTION ATPReqCancel (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
Given the handle to the ABusRecord of a previously made ATPSndRequest or ATPRequest call, ATPReqCancel dequeues the ATPSndRequest or ATPRequest call, provided that the call hasn’t already completed. ATPReqCancel returns noErr if the ATPSndRequest or ATPRequest call is successfully removed from the queue. If it returns cbNotFound, check the abResult field of abRecord to verify that the ATPSndRequest or ATPRequest call has completed and determine its outcome.
Result codes noErr No error
cbNotFound ATP control block not found
FUNCTION ATPGetRequest (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tATPGetRequest}
<-- abResult {result code}
--> abUserReference {for your use}
--> atpSocket {listening socket number}
<-- atpAddress {source socket address}
--> atpReqCount {buffer size in bytes}
--> atpDataPtr {pointer to buffer}
<-- atpBitMap {transaction bit map}
<-- atpTransID {transaction ID}
<-- atpActCount {number of bytes actually received}
<-- atpUserData {user bytes}
<-- atpXO {exactly-once flag}
ATPGetRequest sets up the mechanism to receive a request sent by either an ATPSndRequest or an ATPRequest call. ATPSocket contains the socket number of the socket that should listen for a request; this socket must already have been opened by calling ATPOpenSocket. The address of the socket from which the request was sent is returned in atpAddress. ATPDataPtr specifies a buffer to store the incoming request; atpReqCount indicates the size of the buffer in bytes. The number of bytes actually received in the request is returned in atpActCount. ATPUserData contains the user bytes from the ATP header. The transaction bit map is returned in atpBitMap. The transaction ID is returned in atpTransID. ATPXO will be TRUE if the request is part of an exactly-once transaction.
ATPGetRequest completes when a request is received. To cancel an asynchronous ATPGetRequest call, you must call ATPCloseSocket, but this cancels all pending calls involving that socket.
Result codes noErr No error
badATPSkt Bad responding socket
sktClosed Socket closed by a cancel call
FUNCTION ATPSndRsp (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tATPSdRsp}
<-- abResult {result code}
--> abUserReference {for your use}
--> atpSocket {responding socket number}
--> atpAddress {destination socket address}
--> atpRspBDSPtr {pointer to response BDS}
--> atpTransID {transaction ID}
--> atpEOM {end-of-message flag}
--> atpNumBufs {number of response packets being sent}
--> atpBDSSize {number of elements in response BDS}
ATPSndRsp sends a response to another socket. ATPSocket contains the socket number from which the response should be sent and atpAddress contains the internet address of the socket to which the response should be sent. ATPTransID must contain the transaction ID. ATPEOM is TRUE if the response BDS contains the final packet in a transaction composed of a group of packets and the number of packets in the response is less than expected. ATPRspBDSPtr points to the buffer data structure containing the responses to be sent. ATPBDSSize indicates the number of elements in the response BDS, and must be in the range 1 to 8. ATPNumBufs indicates the number of response packets being sent with this call, and must be in the range 0 to 8.
Note: In some situations, you may want to send only part (or possibly none)
of your response message back immediately. For instance, you might be
requested to send back seven disk blocks, but have only enough internal
memory to store one block. In this case, set atpBDSSize to 7 (total
number of response packets), atpNumBufs to 0 (number of response
packets currently being sent), and call ATPSndRsp. Then as you read
in one block at a time, call ATPAddRsp until all seven response
datagrams have been sent.
During exactly-once transactions, ATPSndRsp won’t complete until the release packet is received or the release timer expires.
Result codes noErr No error
badATPSkt Bad responding socket
noRelErr No release received
sktClosed Socket closed by a cancel call
noDataArea Too many outstanding ATP calls
badBuffNum Bad sequence number
FUNCTION ATPAddRsp (abRecord: ABRecHandle) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tATPAddRsp}
<-- abResult {result code}
--> abUserReference {for your use}
--> atpSocket {responding socket number}
--> atpAddress {destination socket address}
--> atpReqCount {buffer size in bytes}
--> atpDataPtr {pointer to buffer}
--> atpTransID {transaction ID}
--> atpUserData {user bytes}
--> atpEOM {end-of-message flag}
--> atpNumRsp {sequence number}
ATPAddRsp sends one additional response packet to a socket that has already been sent the initial part of a response via ATPSndRsp. ATPSocket contains the socket number from which the response should be sent and atpAddress contains the internet address of the socket to which the response should be sent. ATPTransID must contain the transaction ID. ATPDataPtr and atpReqCount specify the location and size of a buffer that contains the information to send; atpNumRsp is the sequence number of the response. ATPEOM is TRUE if this response datagram is the final packet in a transaction composed of a group of packets. ATPUserData contains the user bytes to be sent in this response datagram’s ATP header.
Note: No BDS is needed with ATPAddRsp because all pertinent information
is passed within the record.
Result codes noErr No error
badATPSkt Bad responding socket
badBuffNum Bad sequence number
noSendResp ATPAddRsp issued before ATPSndRsp
noDataArea Too many outstanding ATP calls
FUNCTION ATPResponse (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tATPResponse}
<-- abResult {result code}
--> abUserReference {for your use}
--> atpSocket {responding socket number}
--> atpAddress {destination socket address}
--> atpTransID {transaction ID)
--> atpRspUData {user bytes sent in transaction response}
--> atpRspBuf {pointer to response message buffer}
--> atpRspSize {size of response message buffer}
ATPResponse is functionally analogous to ATPSndRsp. It sends a response to another socket, but doesn’t require the caller to provide a BDS. ATPAddress must contain the complete network address of the socket to which the response should be sent (taken from the data provided by an ATPGetRequest call). ATPTransID must contain the transaction ID. ATPSocket indicates the socket from which the response should be sent (the socket on which the corresponding ATPGetRequest was issued). ATPRspBuf points to the buffer containing the response message; the size of this buffer must be passed in atpRspSize. The four user bytes to be sent in the ATP header of the first response packet are passed in atpRspUData. The last packet of the transaction response is sent with the EOM flag set.
Although you don’t provide a BDS, ATPResponse in fact creates one and calls the .ATP driver (as in an ATPSndRsp call). For this reason, the abRecord fields atpRspBDSPtr and atpNumBufs are used by ATPResponse; you should not expect these fields to remain unaltered during or after the function’s execution.
During exactly-once transactions ATPResponse won’t complete until the release packet is received or the release timer expires.
Warning: The maximum permissible size of the response message is 4624 bytes.
Result codes noErr No error
badATPSkt Bad responding socket
noRelErr No release received
atpLenErr Response too big
sktClosed Socket closed by a cancel call
noDataArea Too many outstanding ATP calls
FUNCTION ATPRspCancel (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
Given the handle to the ABusRecord of a previously made ATPSndRsp or ATPResponse call, ATPRspCancel dequeues the ATPSndRsp or ATPResponse call, provided that the call hasn’t already completed. ATPRspCancel returns noErr if the ATPSndRsp or ATPResponse call is successfully removed from the queue. If it returns cbNotFound, check the abResult field of abRecord to verify that the ATPSndRsp or ATPResponse call has completed and determine its outcome.
Result codes noErr No error
cbNotFound ATP control block not found
»Example
This example shows the requesting side of an ATP transaction that asks for a 512-byte disk block from the responding end. The block number of the file is a byte and is contained in myBuffer[0].
VAR
myABRecord: ABRecHandle;
myBDSPtr: BDSPtr;
myBuffer: PACKED ARRAY [0..511] OF CHAR;
errCode: INTEGER;
async: BOOLEAN;
BEGIN
errCode := ATPLoad;
IF errCode <> noErr THEN
WRITELN('Error in opening AppleTalk')
{Maybe serial port B isn't available for use by AppleTalk}
ELSE
BEGIN
{Prepare the BDS; allocate space for a one-element BDS}
myBDSPtr := BDSPtr(NewPtr(SIZEOF(BDSElement)));
WITH myBDSPtr^[0] DO
BEGIN
buffSize := 512; {size of our buffer used in reception}
buffPtr := @myBuffer; {pointer to the buffer}
END;
{Prepare the ABusRecord}
myBuffer[0] := CHR(1); {requesting disk block number 1}
myABRecord := ABRecHandle(NewHandle(atpSize));
WITH myABRecord^^ DO
BEGIN
atpAddress.aNet := 0;
atpAddress.aNode := 30; {we probably got this from an NBP call}
atpAddress.aSocket := 15; {socket to send request to}
atpReqCount := 1; {size of request data field (disk block #)}
atpDataPtr := @myBuffer; {ptr to request to be sent}
atpRspBDSPtr := @myBDSPtr;
atpUserData := 0; {for your use}
atpXO := FALSE; {at-least-once service}
atpTimeOut := 5; {5-second timeout}
atpRetries := 3; {3 retries; request will be sent 4 times max}
atpNumBufs := 1; {we're only expecting 1 block to be returned}
END;
async := FALSE;
{Send the request and wait for the response}
errCode := ATPSndRequest(myABRecord, async);
IF errCode <> noErr THEN
WRITELN('An error occurred in the ATPSndRequest call')
ELSE
BEGIN
{The disk block requested is now in myBuffer. We can verify }
{ that atpNumRsp contains 1, meaning one response received.}
. . .
END;
END;
END.
_______________________________________________________________________________
»Name-Binding Protocol
»Data Structures
NBP calls use the following fields:
nbpProto:
(nbpEntityPtr: EntityPtr; {pointer to entity name}
nbpBufPtr: Ptr; {pointer to buffer}
nbpBufSize: INTEGER; {buffer size in bytes}
nbpDataField: INTEGER; {number of addresses or socket number}
nbpAddress: AddrBlock; {socket address}
nbpRetransmitInfo: RetransType); {retransmission information}
When data is sent via NBP, nbpBufSize indicates the size of the data in bytes and nbpBufPtr points to a buffer containing the data. When data is received via NBP, nbpBufPtr points to a buffer in which the incoming data can be stored and nbpBufSize indicates the size of the buffer in bytes. NBPAddress is used in some calls to give the internet address of a named entity. The AddrBlock data type is described above under “Datagram Delivery Protocol”.
NBPEntityPtr points to a variable of type EntityName, which has the following data structure:
TYPE EntityName = RECORD
objStr: Str32; {object}
typeStr: Str32; {type}
zoneStr: Str32 {zone}
END;
EntityPtr = ^EntityName;
Str32 = STRING[32];
NBPRetransmitInfo contains information about the number of times a packet should be transmitted and the interval between retransmissions:
TYPE RetransType = PACKED RECORD
retransInterval: Byte; {retransmit interval in }
{ 8-tick units}
retransCount: Byte {total number of attempts}
END;
RetransCount contains the total number of times a packet should be transmitted, including the first transmission. If retransCount is 0, the packet will be transmitted a total of 255 times.
»Using NBP
On a Macintosh 128K, the AppleTalk Manager’s NBP code is read into the application heap when any one of the NBP (Pascal) routines is called; you can call the NBPLoad function yourself if you want to load the NBP code explicitly. When you’re finished with the NBP code and want to reclaim the space it occupies, call NBPUnload. On a Macintosh 512K or XL, the NBP code is read in when the .MPP driver is loaded.
Note: When another application starts up, the application heap is
reinitialized; on a Macintosh 128K, this means that the NBP
code is lost (and must be reloaded by the next application).
When an entity wants to communicate via an AppleTalk network, it should call NBPRegister to place its name and internet address in the names table. When an entity no longer wants to communicate on the network, or is being shut down, it should call NBPRemove to remove its entry from the names table.
To determine the address of an entity you know only by name, call NBPLookup, which returns a list of all entities with the name you specify. Call NBPExtract to extract entity names from the list.
If you already know the address of an entity, and want only to confirm that it still exists, call NBPConfirm. NBPConfirm is more efficient than NBPLookup in terms of network traffic.
»NBP Routines
FUNCTION NBPRegister (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tNBPRegister}
<-- abResult {result code}
--> abUserReference {for your use}
--> nbpEntityPtr {pointer to entity name}
--> nbpBufPtr {pointer to buffer}
--> nbpBufSize {buffer size in bytes}
--> nbpAddress.aSocket {socket address}
--> nbpRetransmitInfo {retransmission information}
NBPRegister adds the name and address of an entity to the node’s names table. NBPEntityPtr points to a variable of type EntityName containing the entity’s name. If the name is already registered, NBPRegister returns the result code nbpDuplicate. NBPAddress indicates the socket for which the name should be registered. NBPBufPtr and nbpBufSize specify the location and size of a buffer for NBP to use internally.
While the variable of type EntityName is declared as three 32-byte strings, only the actual characters of the name are placed in the buffer pointed to by nbpBufPtr. For this reason, nbpBufSize needs only to be equal to the actual length of the name, plus an additional 12 bytes for use by NBP.
Warning: This buffer must not be altered or released until the name is
removed from the names table via an NBPRemove call. If you
allocate the buffer through a NewHandle call, you must lock
it as long as the name is registered.
Warning: The zone field of the entity name must be set to the
meta-character “*”.
Result codes noErr No error
nbpDuplicate Duplicate name already exists
FUNCTION NBPLookup (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tNBPLookup}
<-- abResult {result code}
--> abUserReference {for your use}
--> nbpEntityPtr {pointer to entity name}
--> nbpBufPtr {pointer to buffer}
--> nbpBufSize {buffer size in bytes}
<-> nbpDataField {number of addresses received}
--> nbpRetransmitInfo {retransmission information}
NBPLookup returns the addresses of all entities with a specified name. NBPEntityPtr points to a variable of type EntityName containing the name of the entity whose address should be returned. (Meta-characters are allowed in the entity name.) NBPBufPtr and nbpBufSize contain the location and size of an area of memory in which the entity names and their corresponding addresses should be returned. NBPDataField indicates the maximum number of matching names to find addresses for; the actual number of addresses found is returned in nbpDataField. NBPRetransmitInfo contains the retry interval and the retry count.
When specifying nbpBufSize, for each NBP tuple expected, allow space for the actual characters of the name, the address, and four bytes for use by NBP.
Result codes noErr No error
nbpBuffOvr Buffer overflow
FUNCTION NBPExtract (theBuffer: Ptr; numInBuf: INTEGER; whichOne: INTEGER;
VAR abEntity: EntityName;
VAR address: AddrBlock) : OSErr; [Not in ROM]
NBPExtract returns one address from the list of addresses returned by NBPLookup. TheBuffer and numInBuf indicate the location and number of tuples in the buffer. WhichOne specifies which one of the tuples in the buffer should be returned in the abEntity and address parameters.
Result codes noErr No error
extractErr Can’t find tuple in buffer
FUNCTION NBPConfirm (abRecord: ABRecHandle;
async: BOOLEAN) : OSErr; [Not in ROM]
ABusRecord
<-- abOpcode {always tNBPConfirm}
<-- abResult {result code}
--> abUserReference {for your use}
--> nbpEntityPtr {pointer to entity name}
<-- nbpDataField {socket number}
--> nbpAddress {socket address}
--> nbpRetransmitInfo {retransmission information}
NBPConfirm confirms that an entity known by name and address still exists (is still entered in the names directory). NBPEntityPtr points to a variable of type EntityName that contains the name to confirm, and nbpAddress specifies the address to be confirmed. (No meta-characters are allowed in the entity name.) NBPRetransmitInfo contains the retry interval and the retry count. The socket number of the entity is returned in nbpDataField. NBPConfirm is more efficient than NBPLookup in terms of network traffic.
Result codes noErr No error
nbpConfDiff Name confirmed for different socket
nbpNoConfirm Name not confirmed
FUNCTION NBPRemove (abEntity: EntityPtr) : OSErr; [Not in ROM]
NBPRemove removes an entity name from the names table of the given entity’s node.
Result codes noErr No error
nbpNotFound Name not found
FUNCTION NBPLoad : OSErr; [Not in ROM]
On a Macintosh 128K, NBPLoad reads the NBP code from the system resource file into the application heap. On a Macintosh 512K or XL, NBPLoad has no effect since the NBP code should have already been loaded when the .MPP driver was opened. Normally you’ll never need to call NBPLoad, because the AppleTalk Manager calls it when necessary.
Result codes noErr No error
FUNCTION NBPUnload : OSErr; [Not in ROM]
On a Macintosh 128K, NBPUnload makes the NBP code purgeable; the space isn’t actually released by the Memory Manager until necessary. On a Macintosh 512K or Macintosh XL, NBPUnload has no effect.
Result codes noErr No error
»Example
This example of NBP registers our node as a print spooler, searches for any print spoolers registered on the network, and then extracts the information for the first one found.
CONST
mySocket = 20;
VAR
myABRecord: ABRecHandle;
myEntity: EntityName;
entityAddr: AddrBlock;
nbpNamePtr: Ptr;
myBuffer: PACKED ARRAY [0..999] OF CHAR;
errCode: INTEGER;
async: BOOLEAN;
BEGIN
errCode := MPPOpen;
IF errCode <> noErr THEN
WRITELN('Error in opening AppleTalk')
{Maybe serial port B isn't available for use by AppleTalk}
ELSE
BEGIN
{Call Memory Manager to allocate ABusRecord}
myABRecord := ABRecHandle(NewHandle(nbpSize));
{Set up our entity name to register}
WITH myEntity DO
BEGIN
objStr := 'Gene Station'; {we are called 'Gene Station' }
typeStr := 'PrintSpooler'; { and are of type 'PrintSpooler'}
zoneStr := '*';
{Allocate data space for the entity name (used by NBP)}
nbpNamePtr := NewPtr(LENGTH(objStr) + LENGTH(typeStr) +
LENGTH(zoneStr) + 12);
END;
{Set up the ABusRecord for the NBPRegister call}
WITH myABRecord^^ DO
BEGIN
nbpEntityPtr := @myEntity;
nbpBufPtr := nbpNamePtr; {buffer used by NBP internally}
nbpBufSize := nbpNameBufSize;
nbpAddress.aSocket := mySocket; {socket to register us on}
nbpRetransmitInfo.retransInterval := 8; {retransmit every 64 }
nbpRetransmitInfo.retransCount := 3; { ticks and try 3 times}
END;
async := FALSE;
errCode := NBPRegister(myABRecord, async);
IF errCode <> noErr THEN
WRITELN('Error occurred in the NBPRegister call')
{Maybe the name is already registered somewhere else on the }
{ network.}
ELSE
BEGIN
{Now that we've registered our name, find others of type }
{ 'PrintSpooler'.}
WITH myEntity DO
BEGIN
objStr := '='; {any one of type }
typeStr := 'PrintSpooler'; { “PrintSpooler” }
zoneStr := '*'; { in our zone}
END;
WITH myABRecord^^ DO
BEGIN
nbpEntityPtr := @myEntity;
nbpBufPtr := @myBuffer; {buffer to place responses in}
nbpBufSize := SIZEOF(myBuffer);
{The field nbpDataField, before the NBPLookup call, represents an }
{ approximate number of responses. After the call, nbpDataField }
{ contains the actual number of responses received.}
nbpDataField := 100; {we want about 100 responses back}
END;
errCode := NBPLookup(myABRecord, async); {make sync call}
IF errCode <> noErr THEN
WRITELN('An error occurred in the NBPLookup')
{Did the buffer overflow?}
ELSE
BEGIN
{Get the first reply}
errCode := NBPExtract(@mybuffer, myABRecord^^.nbpDataField, 1,
myEntity, entityAddr);
{The socket address and name of the entity are returned here. If we }
{ want all of them, we'll have to loop for each one in the buffer.}
IF errCode <> noErr THEN WRITELN('Error in NBPExtract');
{Maybe the one we wanted wasn't in the buffer}
END;
END;
END;
END.
_______________________________________________________________________________
»Miscellaneous Routines
FUNCTION GetNodeAddress (VAR myNode,myNet: INTEGER) : OSErr; [Not in ROM]
GetNodeAddress returns the current node ID and network number of the caller. If the .MPP driver isn’t installed, it returns noMPPErr. If myNet contains 0, this means that a bridge hasn’t yet been found.
Result codes noErr No error
noMPPErr MPP driver not installed
FUNCTION IsMPPOpen : BOOLEAN; [Not in ROM]
IsMPPOpen returns TRUE if the .MPP driver is loaded and running.
FUNCTION IsATPOpen : BOOLEAN; [Not in ROM]
IsATPOpen returns TRUE if the .ATP driver is loaded and running.
_______________________________________________________________________________
»NEW APPLETALK MANAGER PASCAL INTERFACE
_______________________________________________________________________________
In addition to the interface documented in the previous section, a new parameter block–style interface to the AppleTalk Manager is now available for Pascal programmers. This new interface, referred to as the preferred interface, is available in addition to the Pascal interface described in the previous section, which is referred to as the alternate interface. All AppleTalk Manager calls, old and new, are supported by the preferred interface.
The alternate interface has not been extended to support the new AppleTalk Manager calls. However, the alternate interface provides the only implementation of LAPRead and DDPRead. These are higher-level calls not directly supported through the assembly-language interface. Developers will wish to use the alternate interface for these calls, and also for compatibility with previous applications. In all other cases, it is recommended that the new preferred interface be used.
_______________________________________________________________________________
»Using Pascal
All AppleTalk Manager calls in the preferred interface are essentially equivalent to the corresponding assembly-language calls. Their form is
FUNCTION MPPCall (pbPtr: Ptr; asyncFlag: BOOLEAN) : OSErr;
where pbPtr points to a device manager parameter block, and asyncFlag is TRUE if the call is to be executed asynchronously. Three parameter block types are provided by the preferred interface (MPP, ATP, and XPP). The MPP parameter block is shown below. The ATP parameter block is shown in the following section, and the XPP parameter block is shown in the “Calling the .XPP Driver” section of this document. The field names in these parameter blocks are the same as the parameter block offset names defined in the assembly-language section (except as documented below). The caller fills in the parameter block with the fields as specified in that section and issues the appropriate call. The interface issues the actual device manager control call.
On asynchronous calls, the caller may pass a completion routine pointer in the parameter block, at offset ioCompletion. This routine will be executed upon completion of the call. It is executed at interrupt level and must not make any memory manager calls. If it uses application globals, it must ensure that register A5 is set up correctly; for details see SetupA5 and RestoreA5 in the Operating System Utilities chapter. If no completion routine is desired, ioCompletion should be set to NIL.
Asynchronous calls return control to the caller with result code of noErr as soon as they are queued to the driver. This isn’t an indication of successful completion. To determine when the call is actually completed, if you don’t want to use a completion routine, you can poll the ioResult field; this field is set to 1 when the call is made, and receives the actual result code upon completion.
Refer to the appropriate sections of this chapter for the parameter blocks used by each MPP and ATP call. As different MPP and ATP calls take different arguments in their parameter block, two Pascal variant records have been defined to account for all the different cases. These parameter blocks are shown in the sections that follow. The first four fields (which are the same for all calls) are automatically filled in by the device manager. The csCode and ioRefnum fields are automatically filled in by the interface, depending on which call is being made, except in XPP where the caller must fill in the ioRefnum. The ioVRefnum field is unused.
There are two fields that at the assembly-language level have more than one name. These two fields have been given only one name in the preferred interface. These are entityPtr and ntqelPtr, which are both referred to as entityPtr, and atpSocket and currBitmap, which are both referred to as atpSocket. These are the only exceptions to the naming convention.
»MPP Parameter Block
MPPParamBlock = PACKED RECORD
qLink: QElemPtr; {next queue entry}
qType: INTEGER; {queue type}
ioTrap: INTEGER; {routine trap}
ioCmdAddr: Ptr; {routine address}
ioCompletion: ProcPtr; {completion routine}
ioResult: OSErr; {result code}
ioNamePtr: StringPtr; {command result (ATP user bytes) [long]}
ioVRefNum: INTEGER; {volume reference or drive number}
ioRefNum: INTEGER; {driver reference number}
csCode: INTEGER; {call command code AUTOMATICALLY SET}
CASE MPPParmType OF
LAPWriteParm:
(filler0:INTEGER;
wdsPointer:Ptr); {->Write Data Structure}
AttachPHParm,DetachPHParm:
(protType:Byte; {ALAP Protocol Type}
filler1:Byte;
handler:Ptr); {->protocol handler routine}
OpenSktParm,CloseSktParm,WriteDDPParm:
(socket:Byte; {socket number}
checksumFlag:Byte; {checksum flag}
listener:Ptr); {->socket listener routine}
RegisterNameParm,LookupNameParm,ConfirmNameParm,RemoveNameParm:
(interval:Byte; {retry interval}
count:Byte; {retry count}
entityPtr:Ptr; {->names table element or }
{ ->entity name}
CASE MPPParmType OF
RegisterNameParm:
(verifyFlag:Byte; {set if verify needed}
filler3:Byte);
LookupNameParm:
(retBuffPtr:Ptr; {->return buffer}
retBuffSize:INTEGER; {return buffer size}
maxToGet:INTEGER; {matches to get}
numGotten:INTEGER); {matched gotten}
ConfirmNameParm:
(confirmAddr:AddrBlock; {->entity}
newSocket:Byte; {socket number}
filler4:Byte));
SetSelfSendParm:
(newSelfFlag:Byte; {self-send toggle flag}
oldSelfFlag:Byte); {previous self-send state}
KillNBPParm:
(nKillQEl:Ptr); {ptr to Q element to cancel}
END;
»ATP Parameter Block
ATPParamBlock = PACKED RECORD
qLink: QElemPtr; {next queue entry}
qType: INTEGER; {queue type}
ioTrap: INTEGER; {routine trap}
ioCmdAddr: Ptr; {routine address}
ioCompletion: ProcPtr; {completion routine}
ioResult: OSErr; {result code}
userData: LONGINT; {ATP user bytes [long]}
reqTID: INTEGER; {request transaction ID}
ioRefNum: INTEGER; {driver reference number
csCode: INTEGER; {Call command code }
{ AUTOMATICALLY SET}
atpSocket: Byte; {currBitMap or socket number}
atpFlags: Byte; {control information}
addrBlock: AddrBlock; {source/dest. socket address}
reqLength: INTEGER; {request/response length}
reqPointer: Ptr; {-> request/response data}
bdsPointer: Ptr; {-> response BDS}
CASE MPPParmType OF
SendRequestParm,NSendRequestParm:
(numOfBuffs:Byte; {numOfBuffs}
timeOutVal:Byte; {timeout interval}
numOfResps:Byte; {number responses actually received}
retryCount:Byte; {number of retries}
intBuff:INTEGER); {used internally for NSendRequest}
SendResponseParm:
(filler0:Byte; {number of responses being sent}
bdsSize:Byte; {number of BDS elements}
transID:INTEGER); {transaction ID}
GetRequestParm:
(bitMap:Byte; {bit map}
filler1:Byte);
AddResponseParm:
(rspNum:Byte; {sequence number}
filler2:Byte);
KillSendReqParm,KillGetReqParm:
(aKillQEl:Ptr); {ptr to Q element to cancel}
END;
The following table is a complete list of all the parameter block calls provided by the preferred interface.
AppleTalk
Manager
Routine Preferred Interface Call
AttachPH Function PAttachPH (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
DetachPH Function PDetachPH (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
WriteLAP Function PWriteLAP (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
OpenSkt Function POpenSkt (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
CloseSkt Function PCloseSkt (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
WriteDDP Function PWriteDDP (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
RegisterName Function PRegisterName (thePBptr: MPPPBPtr;
async: BOOLEAN) : OSErr;
LookupName Function PLookupName (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
ConfirmName Function PConfirmName (thePBptr: MPPPBPtr;
async: BOOLEAN) : OSErr;
RemoveName Function PRemoveName (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
OpenATPSkt Function POpenATPSkt (thePBptr: ATPPBPtr; async: BOOLEAN) : OSErr;
CloseATPSkt Function PCloseATPSkt (thePBptr: ATPPBPtr;
async: BOOLEAN) : OSErr;
SendRequest Function PSendRequest (thePBptr: ATPPBPtr;
async: BOOLEAN) : OSErr;
GetRequest Function PGetRequest (thePBptr: ATPPBPtr; async: BOOLEAN) : OSErr;
SendResponse Function PSendResponse (thePBptr: ATPPBPtr;
async: BOOLEAN) : OSErr;
AddResponse Function PAddResponse(thePBptr: ATPPBPtr; async: BOOLEAN) : OSErr;
ReltCB Function PRelTCB (thePBptr: ATPPBPtr; async: BOOLEAN) : OSErr;
RelRspCB Function PRelRspCB (thePBptr: ATPPBPtr; async: BOOLEAN) : OSErr;
SetSelfSend Function PSetSelfSend (thePBptr: MPPPBPtr;
async: BOOLEAN) : OSErr;
NSendRequest Function PNSendRequest (thePBptr: ATPPBPtr;
async: BOOLEAN) : OSErr;
KillSendReq Function PKillSendReq (thePBptr: ATPPBPtr;
async: BOOLEAN) : OSErr;
KillGetReq Function PKillGetReq (thePBptr: ATPPBPtr; async: BOOLEAN) : OSErr;
KillNBP Function PKillNBP (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
_______________________________________________________________________________
»Building Data Structures
Because it is difficult for Pascal to deal with certain assembly-language structures, the preferred interface provides a number of routines for building these structures. These routines are summarized below.
PROCEDURE BuildLAPwds (wdsPtr,dataPtr: Ptr;
destHost,protoType,frameLen: INTEGER);
This routine builds a single-frame write data structure LAP WDS for use with the PWriteLAP call. Given a buffer of length frameLen pointed to by dataPtr, it fills in the WDS pointed to by wdsPtr and sets the destination node and protocol type as indicated by destHost and protoType, respectively. The WDS indicated must contain at least two elements.
PROCEDURE BuildDDPwds (wdsPtr,headerPtr,dataPtr: Ptr; destAddress: AddrBlock;
DDPType : INTEGER; dataLen: INTEGER);
This routine builds a single-frame write data structure DDP WDS, for use with the PWriteDDP call. Given a header buffer of at least 17 bytes pointed to by headerPtr and a data buffer of length dataLen pointed to by dataPtr, it fills in the WDS pointed to by wdsPtr, and sets the destination address and protocol type as indicated by destaddress and DDPtype, respectively. The WDS indicated must contain at least 3 elements.
PROCEDURE NBPSetEntity (buffer: Ptr; nbpObject,nbpType,nbpZone: Str32);
This routine builds an NBP entity structure, for use with the PLookupNBP and PConfirmName calls. Given a buffer of at least the size of the EntityName data structure (99 bytes) pointed to by buffer, this routine sets the indicated object, type, and zone in that buffer.
PROCEDURE NBPSetNTE (ntePtr: Ptr; nbpObject,nbpType,nbpZone: Str32;
Socket: INTEGER);
This routine builds an NBP names table entry, for use with the PRegisterName call. Given a names table entry of at least the size of the EntityName data structure plus nine bytes (108 bytes) pointed to by ntePtr, this routine sets the indicated object, type, zone, and socket in that names table entry.
FUNCTION NBPExtract (theBuffer: Ptr; numInBuf: INTEGER; whichOne: INTEGER; VAR abEntity: EntityName; VAR address: AddrBlock) : OSErr;
This routine is provided in the alternate interface, but can be used as provided for extracting NBP entity names from a look-up response buffer.
FUNCTION GetBridgeAddress: INTEGER;
This routine returns the current address of a bridge in the low byte, or zero if there is none.
FUNCTION BuildBDS (buffPtr,bdsPtr: Ptr; buffSize: INTEGER) : INTEGER;
This routine builds a BDS, for use with the ATP calls. Given a data buffer of length buffSize pointed to by buffPtr, it fills in the BDS pointed to by bdsPtr. The buffer will be broken up into pieces of maximum size (578 bytes). The user bytes in the BDS are not modified by this routine. This routine is provided only as a convenience; generally the caller will be able to build the BDS completely from Pascal without it.
_______________________________________________________________________________
»PICKING A NODE ADDRESS IN THE SERVER RANGE
_______________________________________________________________________________
Normally upon opening, the node number picked by the AppleTalk manager will be in the node number range ($01–$7F). It is possible to indicate that a node number in the server range ($80–$FE) is desired. Picking a number in the server range is a more time-consuming but more thorough process, and it’s required for server nodes because it greatly decreases the possibility of a node number conflict.
To open AppleTalk with a server node number, an extended open call is used. An extended open call is indicated by having the immediate bit set in the Open trap itself. In the extended open call, the high bit (bit 31) of the extension longword field (ioMix) indicates whether a server or workstation node number should be picked. Set this bit to 1 to request a server node number. The rest of this field should be zero, as should all other unused fields in the queue element. A server node number can only be requested on the first Open call to the .MPP driver.
_______________________________________________________________________________
»SENDING PACKETS TO ONE’S OWN NODE
_______________________________________________________________________________
Upon opening, the ability to send a packet to one’s own node (intranode delivery) is disabled. This feature of the AppleTalk Manager can be manipulated through the SetSelfSend function. Once enabled, it is possible, at all levels, to send packets to entities within one’s own node. An example of where this might be desirable is an application sending data to a print spooler that is actually running in the background on the same node.
Enabling (or disabling) this feature affects the entire node and should be performed with care. For instance, a desk accessory may not expect to receive names from within its own node as a response to an NBP look-up; enabling this feature from an application could break the desk accessory. All future programs should be written with this feature in mind.
FUNCTION PSetSelfSend (thePBptr: MPPPBPtr; async: BOOLEAN) : OSErr;
Parameter Block
--> 26 csCode word Always PSetSelfSend
--> 28 newSelfFlag byte New SelfSend flag
<-- 29 oldSelfFlag byte Old SelfSend flag
PSetSelfSend enables or disables the intranode delivery feature of the AppleTalk Manager. If newSelfFlag is nonzero, the feature will be enabled; otherwise it will be disabled. The previous value of the flag will be returned in oldSelfFlag.
Result Codes noErr No error
_______________________________________________________________________________
»ATP DRIVER CHANGES
_______________________________________________________________________________
Changes to the ATP driver include the ability to send an ATP request through a specific socket rather than having ATP open a new socket, a new call to abort outstanding SendRequest calls, and a new call to abort specific outstanding GetRequest calls.
_______________________________________________________________________________
»Sending an ATP Request Through a Specified Socket
ATP requests can now be sent through client-specified sockets. ATP previously would open a dynamic socket, send the request through it, and close the socket when the request was completed. The client can now choose to send a request through an already-opened socket; this also allows more than one request to be sent per socket. A new call, PNSendRequest, has been added for this purpose. The function of the old SendRequest call itself remains unchanged.
FUNCTION PNSendRequest (thePBptr: ATPBPtr; async: BOOLEAN) : OSErr;
Parameter block
--> 18 userData longword User bytes
<-- 22 reqTID word Transaction ID used in request
--> 26 csCode word Always sendRequest
<-> 28 atpSocket byte Socket to send request on
or current bitmap
<-> 29 atpFlags byte Control information
--> 30 addrBlock longword Destination socket address
--> 34 reqLength word Request size in bytes
--> 36 reqPointer pointer Pointer to request data
--> 40 bdsPointer pointer Pointer to response BDS
--> 44 numOfBuffs byte Number of responses expected
--> 45 timeOutVal byte Timeout interval
<-- 46 numOf Resps byte Number of responses received
<-> 47 retryCount byte Number of retries
<-- 48 intBuff word Used internally
The PNSendRequest call is functionally equivalent to the SendRequest call, however PNSendRequest allows you to specify, in the atpSocket field, the socket through which the request is to be sent. This socket must have been previously opened through an OpenATPSkt request (otherwise a badATPSkt error will be returned). Note that PNSendRequest requires two additional bytes of memory at the end of the parameter block, immediately following the retryCount. These bytes are for the internal use of the AppleTalk Manager and should not be modified while the PNSendRequest call is active.
There is a machine-dependent limit as to the number of concurrent PNSendRequests that can be active on a given socket. If this limit is exceeded, the error tooManyReqs is returned.
One additional difference between SendRequest and PNSendRequest is that a PNSendRequest can only be aborted by a PKillSendReq call (see below), whereas a SendRequest can be aborted by either a RelTCB or KillSendReq call.
Result Codes noErr No error
reqFailed Retry count exceeded
tooManyReqs Too many concurrent requests
noDataArea Too many outstanding ATP calls
reqAborted Request cancelled by user
_______________________________________________________________________________
»Aborting ATP SendRequests
The RelTCB call is still supported, but only for aborting SendRequests. To abort PNSendRequests, a new call, PKillSendReq, has been added. This call will abort both SendRequests and PNSendRequests. PKillSendReq’s only argument is the queue element pointer of the request to be aborted. The queue element pointer is passed at the offset of the PKillSendReq queue element specified by aKillQE1.
FUNCTION PKillSendReq (thePBptr: ATPPBPtr; async: BOOLEAN) : OSErr;
Parameter block
--> 26 csCode word Always PKillSendReq
--> 44 aKillQEl pointer Pointer to queue element
PKillSendReq is functionally equivalent to RelTCB, except that it takes different arguments and will abort both SendRequests and PNSendRequests. To abort one of these calls, place a pointer to the queue element of the call to abort in aKillQEl and issue the PKillSendReq call.
Result Codes noErr No error
cbNotFound aKillQEl does not point to a SendReq
or NSendReq queue element
_______________________________________________________________________________
»Aborting ATP GetRequests
ATP GetRequests can now be aborted through the PKillGetReq call. This call looks and works just like the PKillSendReq call, and is used to abort a specific GetRequest call. Previously it was necessary to close the socket to abort all GetRequest calls on the socket.
FUNCTION PKillGetReq (thePBptr: ATPPBPtr; async: BOOLEAN) : OSErr;
Parameter block
--> 26 csCode word Always PKillGetReq
--> 44 aKillQEl pointer Pointer to queue element
PKillGetReq will abort a specific outstanding GetRequest call (as opposed to closing the socket, which aborts all outstanding GetRequests on that socket). The call will be completed with a reqAborted error. To abort a GetRequest, place a pointer to the queue element of the call to abort in aKillQEl and issue the PKillGetReq call.
Result Codes noErr No error
cbNotFound aKillQEl does not point to a GetReq
queue element
_______________________________________________________________________________
»NAME BINDING PROTOCOL CHANGES
_______________________________________________________________________________
Changes to the Name Binding Protocol include supporting multiple concurrent requests and a means for aborting an active request.
»Multiple Concurrent NBP Requests
NBP now supports multiple concurrent active requests. Specifically, a number of LookupNames, RegisterNames and ConfirmNames can all be active concurrently. The maximum number of concurrent requests is machine dependent; if it is exceeded the error tooManyReqs will be returned. Active requests can be aborted by the PKillNBP call.
»KillNBP function
FUNCTION PKillNBP (thePBptr: ATPPBPtr; async: BOOLEAN) : OSErr;
X-Ref: Technical Note #199
Parameter block
--> 26 csCode word Always PKillNBP
--> 28 aKillQEl pointer Pointer to queue element
PKillNBP is used to abort an outstanding LookupName, RegisterName or ConfirmName request. To abort one of these calls, place a pointer to the queue element of the call to abort in a KillQEl and issue the PKillNBP call. The call will be completed with a ReqAborted error.
Result Codes noErr No error
cbNotFound aKillQEl does not point to a valid
NBP queue element
_______________________________________________________________________________
»VARIABLE RESOURCES
_______________________________________________________________________________
The table below lists machine-dependent resources for the different Macintosh system configurations. The RAM-based resources are available through the AppleShare Server.
Resource Macintosh Plus RAM-Based Macintosh SE Macintosh II
Protocol
Handlers 4 8 8 8
Statically
Assigned
Sockets 14* 12 12 14
Concurrent
ATP SendRequests 6 12 12 12
ATP Sockets 6 32 32 126
Concurrent
ATP Responses 8 16 16 32
Concurrent
NBP Requests 1 6 6 10
Concurrent
ASP Sessions N/A 5 10 20
Concurrent
ATP NSendRequests
Per Socket ** N/A 9 14 62
* Includes dynamic sockets
** Determined dynamically at runtime based on CPU speed.
N/A : Not Applicable
_______________________________________________________________________________
»CALLING THE APPLETALK MANAGER FROM ASSEMBLY LANGUAGE
_______________________________________________________________________________
This section discusses how to use the AppleTalk Manager from assembly language. Equivalent Pascal information is given in the preceding section.
All routines make Device Manager Control calls. The description of each routine includes a list of the fields needed. Some of these fields are part of the parameter block described in the Device Manager chapter; additional fields are provided for the AppleTalk Manager.
The number next to each field name indicates the byte offset of the field from the start of the parameter block pointed to by A0. An arrow next to each parameter name indicates whether it’s an input, output, or input/output parameter:
Arrow Meaning
--> Parameter is passed to the routine
<-- Parameter is returned by the routine
<-> Parameter is passed to and returned by the routine
All Device Manager Control calls return an integer result code of type OSErr in the ioResult field. Each routine description lists all of the applicable result codes generated by the AppleTalk Manager, along with a short description of what the result code means. Lengthier explanations of all the result codes can be found in the summary at the end of this chapter. Result codes from other parts of the Operating System may also be returned. (See Appendix A for a list of all result codes.)
_______________________________________________________________________________
»Opening AppleTalk
X-Ref: Technical Note #224
Two tests are made at system startup to determine whether the .MPP driver should be opened at that time. If port B is already in use, or isn’t configured for AppleTalk, .MPP isn’t opened until explicitly requested by an application; otherwise it’s opened at system startup.
It’s the application’s responsibility to test the availability of port B before opening AppleTalk. Assembly-language programmers can use the Pascal calls MPPOpen and ATPLoad to open the .MPP and .ATP drivers.
The global variable SPConfig is used for configuring the serial ports; it’s copied from a byte in parameter RAM (which is discussed in the Operating System Utilities chapter). The low-order four bits of this variable contain the current configuration of port B. The following use types are provided as global constants for testing or setting the configuration of port B:
useFree .EQU 0 ;unconfigured
useATalk .EQU 1 ;configured for AppleTalk
useAsync .EQU 2 ;configured for the Serial Driver
The application shouldn’t attempt to open AppleTalk unless SPConfig is equal to either useFree or useATalk.
A second test involves the global variable PortBUse; the low-order four bits of this byte are used to monitor the current use of port B. If PortBUse is negative, the program is free to open AppleTalk. If PortBUse is positive, the program should test to see whether port B is already being used by AppleTalk; if it is, the low-order four bits of PortBUse will be equal to the use type useATalk.
The .MPP driver sets PortBUse to the correct value (useATalk) when it’s opened and resets it to $FF when it’s closed. Bits 4-6 of this byte are used for driver-specific information; ATP uses bit 4 to indicate whether it’s currently opened:
atpLoadedBit .EQU 4 ;set if ATP is opened
»Example
The following code illustrates the use of the SPConfig and PortBUse variables.
MOVE #-<atpUnitNum+1>,atpRefNum(A0) ;save known ATP refNum in
; case ATP not opened
OpenAbus SUB #ioQElSize,SP ;allocate queue entry
MOVE.L SP,A0 ;A0 -> queue entry
CLR.B ioPermssn(A0) ;make sure permission's clear
MOVE.B PortBUse,D1 ;is port B in use?
BPL.S @10 ;if so, make sure by AppleTalk
MOVEQ #portNotCf,D0 ;assume port not configured for AppleTalk
MOVE.B SPConfig,D1 ;get configuration data
AND.B #$0F,D1 ;mask it to low 4 bits
SUBQ.B #useATalk,D1 ;unconfigured or configured for AppleTalk
BGT.S @30 ;if not, return error
LEA mppName,A1 ;A1 = address of driver name
MOVE.L A1,ioFileName(A0) ;set in queue entry
_Open ;open MPP
BNE.S @30 ;return error, if it can't load it
BRA.S @20 ;otherwise, go check ATP
@10 MOVEQ #portInUse,D0 ;assume port in use error
AND.B #$0F,D1 ;clear all but use bits
SUBQ.B #useATalk,D1 ;is AppleTalk using it?
BNE.S @30 ;if not, then error
@20 MOVEQ #0,D0 ;assume no error
BTST #atpLoadedBit,PortBUse ;ATP already open?
BNE.S @30 ;just return if so
LEA atpName,A1 ;A1 = address of driver name
MOVE.L A1,ioFileName(A0) ;set in queue entry
_Open ;open ATP
@30 ADD #ioQElSize,SP ;deallocate queue entry
RTS ;and return
mppName .BYTE 4 ;length of .MPP driver name
.ASCII '.MPP' ;name of .MPP driver
atpName .BYTE 4 ;length of .ATP driver name
.ASCII '.ATP' ;name of .ATP driver
_______________________________________________________________________________
»AppleTalk Link Access Protocol
»Data Structures
An ALAP frame is composed of a three-byte header, up to 600 bytes of data, and a two-byte frame check sequence (Figure 6). You can use the following global constants to access the contents of an ALAP header:
lapDstAdr .EQU 0 ;destination node ID
lapSrcAdr .EQU 1 ;source node ID
lapType .EQU 2 ;ALAP protocol type
lapHdSz .EQU 3 ;ALAP header size
Figure 6–ALAP Frame
Two of the protocol handlers in every node are used by DDP. These protocol handlers service frames with ALAP protocol types equal to the following global constants:
shortDDP .EQU 1 ;short DDP header
longDDP .EQU 2 ;long DDP header
When you call ALAP to send a frame, you pass it information about the frame in a write data structure, which has the format shown in Figure 7.
Figure 7–Write Data Structure for ALAP
If you specify a destination node ID of 255, the frame will be broadcast to all nodes. The byte that’s “used internally” is used by the AppleTalk Manager to store the address of the node sending the frame.
»Using ALAP
Most programs will never need to call ALAP, because higher-level protocols will automatically call ALAP as necessary. If you do want to send a frame directly via an ALAP, call the WriteLAP function. There’s no ReadLAP function in assembly language; if you want to read ALAP frames, you must call AttachPH to add your protocol handler to the node’s protocol handler table. The ALAP module will examine every incoming frame and call your protocol handler for each frame received with the correct ALAP protocol. When your program no longer wants to receive frames with a particular ALAP protocol type value, it can call DetachPH to remove the corresponding protocol handler from the protocol handler table.
See the “Protocol Handlers and Socket Listeners” section for information on how to write a protocol handler.
»ALAP Routines
WriteLAP function
Parameter block
--> 26 csCode word ;always writeLAP
--> 30 wdsPointer pointer ;write data structure
WriteLAP sends a frame to another node. The frame data and destination of the frame are described by the write data structure pointed to by wdsPointer. The first two data bytes of an ALAP frame sent to another computer using the AppleTalk Manager must indicate the length of the frame in bytes. The ALAP protocol type byte must be in the range 1 to 127.
Result codes noErr No error
excessCollsns No CTS received after 32 RTS’s
ddpLengthErr Packet length exceeds maximum
lapProtErr Invalid ALAP protocol type
AttachPH function
Parameter block
--> 26 csCode word ;always attachPH
--> 28 protType byte ;ALAP protocol type
--> 30 handler pointer ;protocol handler
AttachPH adds the protocol handler pointed to by handler to the node’s protocol table. ProtType specifies what kind of frame the protocol handler can service. After AttachPH is called, the protocol handler is called for each incoming frame whose ALAP protocol type equals protType.
Result codes noErr No error
lapProtErr Error attaching protocol type
DetachPH function
Parameter block
--> 26 csCode word ;always detachPH
--> 28 protType byte ;ALAP protocol type
DetachPH removes from the node’s protocol table the specified ALAP protocol type and corresponding protocol handler.
Result codes noErr No error
lapProtErr Error detaching protocol type
_______________________________________________________________________________
»Datagram Delivery Protocol
»Data Structures
A DDP datagram consists of a header followed by up to 586 bytes of actual data
(Figure 8). The headers can be of two different lengths; they’re identified by the following ALAP protocol types:
shortDDP .EQU 1 ;short DDP header
longDDP .EQU 2 ;long DDP header
Figure 8–DDP Datagram
Long DDP headers (13 bytes) are used for sending datagrams between two or more different AppleTalk networks. You can use the following global constants to access the contents of a long DDP header:
ddpHopCnt .EQU 0 ;count of bridges passed (4 bits)
ddpLength .EQU 0 ;datagram length (10 bits)
ddpChecksum .EQU 2 ;checksum
ddpDstNet .EQU 4 ;destination network number
ddpSrcNet .EQU 6 ;source network number
ddpDstNode .EQU 8 ;destination node ID
ddpSrcNode .EQU 9 ;source node ID
ddpDstSkt .EQU 10 ;destination socket number
ddpSrcSkt .EQU 11 ;source socket number
ddpType .EQU 12 ;DDP protocol type
The size of a DDP long header is given by the following constant:
ddpHSzLong .EQU ddpType+1
The short headers (five bytes) are used for datagrams sent to sockets within the same network as the source socket. You can use the following global constants to access the contents of a short DDP header:
ddpLength .EQU 0 ;datagram length
sDDPDstSkt .EQU ddpChecksum ;destination socket number
sDDPSrcSkt .EQU sDDPDstSkt+1 ;source socket number
sDDPType .EQU sDDPSrcSkt+1 ;DDP protocol type
The size of a DDP short header is given by the following constant:
ddpHSzShort .EQU sDDPType+1
The datagram length is a ten-bit field. You can use the following global constant as a mask for these bits:
ddpLenMask .EQU $03FF
The following constant indicates the maximum length of a DDP datagram:
ddpMaxData .EQU 586
When you call DDP to send a datagram, you pass it information about the datagram in a write data structure with the format shown in Figure 9.
Figure 9–Write Data Structure for DDP
The first seven bytes are used internally for the ALAP header and the DDP datagram length and checksum. The other bytes used internally store the network number, node ID, and socket number of the socket client sending the datagram.
Warning: The first entry in a DDP write data structure must begin at
an odd address.
If you specify a node ID of 255, the datagram will be broadcast to all nodes within the destination network. A network number of 0 means the local network to which the node is connected.
Warning: DDP always destroys the high-order byte of the destination
network number when it sends a datagram with a short header.
Therefore, if you want to reuse the first entry of a DDP write
data structure entry, you must restore the destination network number.
»Using DDP
Before it can use a socket, the program must call OpenSkt, which adds a socket and its socket listener to the socket table. When a client is finished using a socket, call CloseSkt, which removes the socket’s entry from the socket table. To send a datagram via DDP, call WriteDDP. If you want to read DDP datagrams, you must write your own socket listener. DDP will send every incoming datagram for that socket to your socket listener.
See the “Protocol Handlers and Socket Listeners” section for information on how to write a socket listener.
»DDP Routines
OpenSkt function
Parameter block
--> 26 csCode word ;always openSkt
<-> 28 socket byte ;socket number
--> 30 listener pointer ;socket listener
OpenSkt adds a socket and its socket listener to the socket table. If the socket parameter is nonzero, it must be in the range 64 to 127, and it specifies the socket’s number; if socket is 0, OpenSkt opens a socket with a socket number in the range 128 to 254, and returns it in the socket parameter. Listener contains a pointer to the socket listener.
OpenSkt will return ddpSktErr if you pass the number of an already opened socket, if you pass a socket number greater than 127, or if the socket table is full (the socket table can hold a maximum of 12 sockets).
Result codes noErr No error
ddpSktErr Socket error
CloseSkt function
Parameter block
--> 26 csCode word ;always closeSkt
--> 28 socket byte ;socket number
CloseSkt removes the entry of the specified socket from the socket table. If you pass a socket number of 0, or if you attempt to close a socket that isn’t open, CloseSkt will return ddpSktErr.
Result codes noErr No error
ddpSktErr Socket error
WriteDDP function
Parameter block
--> 26 csCode word ;always writeDDP
--> 28 socket byte ;socket number
--> 29 checksumFlag byte ;checksum flag
--> 30 wdsPointer pointer ;write data structure
WriteDDP sends a datagram to another socket. WDSPointer points to a write data structure containing the datagram and the address of the destination socket. If checksumFlag is TRUE, WriteDDP will compute the checksum for all datagrams requiring long headers.
Result codes noErr No error
ddpLenErr Datagram length too big
ddpSktErr Socket error
noBridgeErr No bridge found
_______________________________________________________________________________
»AppleTalk Transaction Protocol
»Data Structures
An ATP packet consists of an ALAP header, DDP header, and ATP header, followed by actual data (Figure 10). You can use the following global constants to access the contents of an ATP header:
atpControl .EQU 0 ;control information
atpBitMap .EQU 1 ;bit map
atpRespNo .EQU 1 ;sequence number
atpTransID .EQU 2 ;transaction ID
atpUserData .EQU 4 ;user bytes
The size of an ATP header is given by the following constant:
atpHdSz .EQU 8
Figure 10–ATP Packet
ATP packets are identified by the following DDP protocol type:
atp .EQU 3
The control information contains a function code and various control bits. The function code identifies either a TReq, TResp, or TRel packet with one of the following global constants:
atpReqCode .EQU $40 ;TReq packet
atpRspCode .EQU $80 ;TResp packet
atpRelCode .EQU $C0 ;TRel packet
The send-transmission-status, end-of-message, and exactly-once bits in the control information are accessed via the following global constants:
atpSTSBit .EQU 3 ;send-transmission-status bit
atpEOMBit .EQU 4 ;end-of-message bit
atpXOBit .EQU 5 ;exactly-once bit
Many ATP calls require a field called atpFlags (Figure 11), which contains the above three bits plus the following two bits:
sendChk .EQU 0 ;send-checksum bit
tidValid .EQU 1 ;transaction ID validity bit
Figure 11–ATPFlags Field
The maximum number of response packets in an ATP transaction is given by the following global constant:
atpMaxNum .EQU 8
When you call ATP to send responses, you pass the responses in a response BDS, which is a list of up to eight elements, each of which contains the following:
bdsBuffSz .EQU 0 ;size of data to send
bdsBuffAddr .EQU 2 ;pointer to data
bdsUserData .EQU 8 ;user bytes
When you call ATP to receive responses, you pass it a response BDS with up to eight elements, each in the following format:
bdsBuffSz .EQU 0 ;buffer size in bytes
bdsBuffAddr .EQU 2 ;pointer to buffer
bdsDataSz .EQU 6 ;number of bytes actually received
bdsUserData .EQU 8 ;user bytes
The size of a BDS element is given by the following constant:
bdsEntrySz .EQU 12
ATP clients are identified by internet addresses in the form shown in Figure 12.
Figure 12–Internet Address
»Using ATP
Before you can use ATP on a Macintosh 128K, the .ATP driver must be read from the system resource file via a Device Manager Open call. The name of the .ATP driver is '.ATP' and its reference number is –11. When the .ATP driver is opened, it reads its ATP code into the application heap and installs a task into the vertical retrace queue.
Warning: When another application starts up, the application heap is
reinitialized; on a Macintosh 128K, this means that the ATP
code is lost (and must be reloaded by the next application).
When you’re through using ATP on a Macintosh 128K, call the Device Manager Close routine—the system will be returned to the state it was in before the
.ATP driver was opened.
On a Macintosh 512K or XL, the .ATP driver will have been loaded into the system heap either at system startup or upon execution of a Device Manager Open call loading MPP. You shouldn’t close the .ATP driver on a Macintosh 512K or XL; AppleTalk expects it to remain open on these systems.
To send a request to another socket and get a response, call SendRequest. The call terminates when either an entire response is received or a specified retry timeout interval elapses. To open a socket for the purpose of responding to requests, call OpenATPSkt. Then call GetRequest to receive a request; when a request is received, the call is completed. After receiving and servicing a request, call SendResponse to return response information. If you cannot or do not want to send the entire response all at once, make a SendResponse call to send some of the response, and then call AddResponse later to send the remainder of the response. To close a socket opened for the purpose of sending responses, call CloseATPSkt.
During exactly-once transactions, SendResponse doesn’t terminate until the transaction is completed via a TRel packet, or the retry count is exceeded.
Warning: Don’t modify the parameter block passed to an ATP call until
the call is completed.
»ATP Routines
OpenATPSkt function
Parameter block
--> 26 csCode word ;always openATPSkt
<-> 28 atpSocket byte ;socket number
--> 30 addrBlock long word ;socket request specification
OpenATPSkt opens a socket for the purpose of receiving requests. ATPSocket contains the socket number of the socket to open. If it’s 0, a number is dynamically assigned and returned in atpSocket. AddrBlock contains a specification of the socket addresses from which requests will be accepted. A 0 in the network number, node ID, or socket number field of addrBlock means that requests will be accepted from every network, node, or socket, respectively.
Result codes noErr No error
tooManySkts Too many responding sockets
noDataArea Too many outstanding ATP calls
CloseATPSkt function
Parameter block
--> 26 csCode word ;always closeATPSkt
--> 28 atpSocket byte ;socket number
CloseATPSkt closes the socket whose number is specified by atpSocket, for the purpose of receiving requests.
Result codes noErr No error
noDataArea Too many outstanding ATP calls
SendRequest function
Parameter block
--> 18 userData long word ;user bytes
<-- 22 reqTID word ;transaction ID used in request
--> 26 csCode word ;always sendRequest
<-- 28 currBitMap byte ;bit map
<-> 29 atpFlags byte ;control information
--> 30 addrBlock long word ;destination socket address
--> 34 reqLength word ;request size in bytes
--> 36 reqPointer pointer ;pointer to request data
--> 40 bdsPointer pointer ;pointer to response BDS
--> 44 numOfBuffs byte ;number of responses expected
--> 45 timeOutVal byte ;timeout interval
<-- 46 numOfResps byte ;number of responses received
<-> 47 retryCount byte ;number of retries
SendRequest sends a request to another socket and waits for a response. UserData contains the four user bytes. AddrBlock indicates the socket to which the request should be sent. ReqLength and reqPointer contain the size and location of the request to send. BDSPointer points to a response BDS where the responses are to be returned; numOfBuffs indicates the number of responses requested. The number of responses received is returned in numOfResps. If a nonzero value is returned in numOfResps, you can examine currBitMap to determine which packets of the transaction were actually received and to detect pieces for higher-level recovery, if desired.
TimeOutVal indicates the number of seconds that SendRequest should wait for a response before resending the request. RetryCount indicates the maximum number of retries SendRequest should attempt. The end-of-message flag of atpFlags will be set if the EOM bit is set in the last packet received in a valid response sequence. The exactly-once flag should be set if you want the request to be part of an exactly-once transaction.
To cancel a SendRequest call, you need the transaction ID; it’s returned in reqTID. You can examine reqTID before the completion of the call, but its contents are valid only after the tidValid bit of atpFlags has been set.
SendRequest completes when either an entire response is received or the retry count is exceeded.
Note: The value provided in retryCount will be modified during SendRequest
if any retries are made. This field is used to monitor the number of
retries; for each retry, it’s decremented by 1.
Result codes noErr No error
reqFailed Retry count exceeded
tooManyReqs Too many concurrent requests
noDataArea Too many outstanding ATP calls
reqAborted Request canceled by user
GetRequest function
Parameter block
<-- 18 userData long word ;user bytes
--> 26 csCode word ;always getRequest
--> 28 atpSocket byte ;socket number
<-- 29 atpFlags byte ;control information
<-- 30 addrBlock long word ;source of request
<-> 34 reqLength word ;request buffer size
--> 36 reqPointer pointer ;pointer to request buffer
<-- 44 bitMap byte ;bit map
<-- 46 transID word ;transaction ID
GetRequest sets up the mechanism to receive a request sent by a SendRequest call. UserData returns the four user bytes from the request. ATPSocket contains the socket number of the socket that should listen for a request. The internet address of the socket from which the request was sent is returned in addrBlock. ReqLength and reqPointer indicate the size (in bytes) and location of a buffer to store the incoming request. The actual size of the request is returned in reqLength. The transaction bit map and transaction ID will be returned in bitMap and transID. The exactly-once flag in atpFlags will be set if the request is part of an exactly-once transaction.
GetRequest completes when a request is received.
Result codes noErr No error
badATPSkt Bad responding socket
SendResponse function
Parameter block
<-- 18 userData long word ;user bytes from TRel
<-- 22 reqTID word ;transaction ID used in request
--> 26 csCode word ;always sendResponse
--> 28 atpSocket byte ;socket number
--> 29 atpFlags byte ;control information
--> 30 addrBlock long word ;response destination
--> 40 bdsPointer pointer ;pointer to response BDS
--> 44 numOfBuffs byte ;number of response packets being sent
--> 45 bdsSize byte ;BDS size in elements
--> 46 transID word ;transaction ID
SendResponse sends a response to a socket. If the response was part of an exactly-once transaction, userData will contain the user bytes from the TRel packet. ATPSocket contains the socket number from which the response should be sent. The end-of-message flag in atpFlags should be set if the response contains the final packet in a transaction composed of a group of packets and the number of responses is less than requested. AddrBlock indicates the address of the socket to which the response should be sent. BDSPointer points to a response BDS containing room for the maximum number of responses to be sent; bdsSize contains this maximum number. NumOfBuffs contains the number of response packets to be sent in this call; you may wish to make AddResponse calls to complete the response. TransID indicates the transaction ID of the associated request.
During exactly-once transactions, SendResponse doesn’t complete until either a TRel packet is received from the socket that made the request, or the retry count is exceeded.
Result codes noErr No error
badATPSkt Bad responding socket
noRelErr No release received
noDataArea Too many outstanding ATP calls
badBuffNum Sequence number out of range
AddResponse function
Parameter block
--> 18 userData long word ;user bytes
--> 26 csCode word ;always addResponse
--> 28 atpSocket byte ;socket number
--> 29 atpFlags byte ;control information
--> 30 addrBlock long word ;response destination
--> 34 reqLength word ;response size
--> 36 reqPointer pointer ;pointer to response
--> 44 rspNum byte ;sequence number
--> 46 transID word ;transaction ID
AddResponse sends an additional response packet to a socket that has already been sent the initial part of a response via SendResponse. UserData contains the four user bytes. ATPSocket contains the socket number from which the response should be sent. The end-of-message flag in atpFlags should be set if this response packet is the final packet in a transaction composed of a group of packets and the number of responses is less than requested. AddrBlock indicates the socket to which the response should be sent. ReqLength and reqPointer contain the size (in bytes) and location of the response to send; rspNum indicates the sequence number of the response (in the range 0 to 7). TransID must contain the transaction ID.
Warning: If the transaction is part of an exactly-once transaction, the
buffer used in the AddResponse call must not be altered or
released until the corresponding SendResponse call has completed.
Result codes noErr No error
badATPSkt Bad responding socket
noSendResp AddResponse issued before SendResponse
badBuffNum Sequence number out of range
noDataArea Too many outstanding ATP calls
RelTCB function
Parameter block
--> 26 csCode word ;always relTCB
--> 30 addrBlock long word ;destination of request
--> 46 transID word ;transaction ID of request
RelTCB dequeues the specified SendRequest call and returns the result code reqAborted for the aborted call. The transaction ID can be obtained from the reqTID field of the SendRequest queue entry; see the description of SendRequest for details.
Result codes noErr No error
cbNotFound ATP control block not found
noDataArea Too many outstanding ATP calls
RelRspCB function
Parameter block
--> 26 csCode word ;always relRspCB
--> 28 atpSocket byte ;socket number that request was received on
--> 30 addrBlock long word ;source of request
--> 46 transID word ;transaction ID of request
In an exactly-once transaction, RelRspCB cancels the specified SendResponse, without waiting for the release timer to expire or a TRel packet to be received. No error is returned for the SendResponse call. Whan called to cancel a transaction that isn’t using exactly-once service, RelRspCB returns cbNotFound. The transaction ID can be obtained from the reqTID field of the SendResponse queue entry; see the description of SendResponse for details.
Result codes noErr No error
cbNotFound ATP control block not found
_______________________________________________________________________________
»Name-Binding Protocol
»Data Structures
The first two bytes in the NBP header (Figure 13) indicate the type of the packet, the number of tuples in the packet, and an NBP packet identifier. You can use the following global constants to access these bytes:
nbpControl .EQU 0 ;packet type
nbpTCount .EQU 0 ;tuple count
nbpID .EQU 1 ;packet identifier
nbpTuple .EQU 2 ;start of first tuple
Figure 13–NBP Packet
NBP packets are identified by the following DDP protocol type:
nbp .EQU 2
NBP uses the following global constants in the nbpControl field to identify NBP packets:
brRq .EQU 1 ;broadcast request
lkUp .EQU 2 ;lookup request
lkUpReply .EQU 3 ;lookup reply
NBP entities are identified by internet address in the form shown in Figure 14 below. Entities are also identified by tuples, which include both an internet address and an entity name. You can use the following global constants to access information in tuples:
tupleNet .EQU 0 ;network number
tupleNode .EQU 2 ;node ID
tupleSkt .EQU 3 ;socket number
tupleEnum .EQU 4 ;used internally
tupleName .EQU 5 ;entity name
The meta-characters in an entity name can be identified with the following global constants:
equals .EQU '=' ;“wild-card” meta-character
star .EQU '*' ;“this zone” meta-character
Figure 14–Names Table Entry
The maximum number of tuples in an NBP packet is given by the following global constant:
tupleMax .EQU 15
Entity names are mapped to sockets via the names table. Each entry in the names table has the structure shown in Figure 14.
You can use the following global constants to access some of the elements of a names table entry:
ntLink .EQU 0 ;pointer to next entry
ntTuple .EQU 4 ;tuple
ntSocket .EQU 7 ;socket number
ntEntity .EQU 9 ;entity name
The socket number of the names information socket is given by the following global constant:
nis .EQU 2
»Using NBP
On a Macintosh 128K, before calling any other NBP routines, call the LoadNBP function, which reads the NBP code from the system resource file into the application heap. (The NBP code is part of the .MPP driver, which has a driver reference number of –10.) When you’re finished with NBP and want to reclaim the space its code occupies, call UnloadNBP. On a Macintosh 512K or XL, the NBP code is read in when the .MPP driver is loaded.
Warning: When an application starts up, the application heap is
reinitialized; on a Macintosh 128K, this means that the
NBP code is lost (and must be reloaded by the next application).
When an entity wants to communicate via an AppleTalk network, it should call RegisterName to place its name and internet address in the names table. When an entity no longer wants to communicate on the network, or is being shut down, it should call RemoveName to remove its entry from the names table.
To determine the address of an entity you know only by name, call LookupName, which returns a list of all entities with the name you specify. If you already know the address of an entity, and want only to confirm that it still exists, call ConfirmName. ConfirmName is more efficient than LookupName in terms of network traffic.
»NBP Routines
RegisterName function
Parameter block
--> 26 csCode word ;always registerName
--> 28 interval byte ;retry interval
<-> 29 count byte ;retry count
--> 30 ntQElPtr pointer ;names table element pointer
--> 34 verifyFlag byte ;set if verify needed
RegisterName adds the name and address of an entity to the node’s names table. NTQElPtr points to a names table entry containing the entity’s name and internet address (in the form shown in Figure 14 above). Meta-characters aren’t allowed in the object and type fields of the entity name; the zone field, however, must contain the meta-character “*”. If verifyFlag is TRUE, RegisterName checks on the network to see if the name is already in use, and returns a result code of nbpDuplicate if so. Interval and count contain the retry interval in eight-tick units and the retry count. When a retry is made, the count field is modified.
X-Ref: Technical Note #225
Warning: The names table entry passed to RegisterName remains the
property of NBP until removed from the names table. Don’t
attempt to remove or modify it. If you’ve allocated memory
using a NewHandle call, you must lock it as long as the name
is registered.
Warning: VerifyFlag should normally be set before calling RegisterName.
Result codes noErr No error
nbpDuplicate Duplicate name already exists
nbpNISErr Error opening names information socket
LookupName function
Parameter block
--> 26 csCode word ;always lookupName
--> 28 interval byte ;retry interval
<-> 29 count byte ;retry count
--> 30 entityPtr pointer ;pointer to entity name
--> 34 retBuffPtr pointer ;pointer to buffer
--> 38 retBuffSize word ;buffer size in bytes
--> 40 maxToGet word ;matches to get
<-- 42 numGotten word ;matches found
LookupName returns the addresses of all entities with a specified name. EntityPtr points to the entity’s name (in the form shown in Figure 14 above). Meta-characters are allowed in the entity name. RetBuffPtr and retBuffSize contain the location and size of an area of memory in which the tuples describing the entity names and their corresponding addresses should be returned. MaxToGet indicates the maximum number of matching names to find addresses for; the actual number of addresses found is returned in numGotten. Interval and count contain the retry interval and the retry count. LookupName completes when either the number of matches is equal to or greater than maxToGet, or the retry count has been exceeded. The count field is decremented for each retransmission.
Note: NumGotten is first set to 0 and then incremented with each match
found. You can test the value in this field, and can start examining
the received addresses in the buffer while the lookup continues.
Result codes noErr No error
nbpBuffOvr Buffer overflow
ConfirmName function
Parameter block
--> 26 csCode word ;always confirmName
--> 28 interval byte ;retry interval
<-> 29 count byte ;retry count
--> 30 entityPtr pointer ;pointer to entity name
--> 34 confirmAddr pointer ;entity address
<-- 38 newSocket byte ;socket number
ConfirmName confirms that an entity known by name and address still exists (is still entered in the names directory). EntityPtr points to the entity’s name
(in the form shown in Figure 14 above). ConfirmAddr specifies the address to confirmed. No meta-characters are allowed in the entity name. Interval and count contain the retry interval and the retry count. The socket number of the entity is returned in newSocket. ConfirmName is more efficient than LookupName in terms of network traffic.
Result codes noErr No error
nbpConfDiff Name confirmed for different socket
nbpNoConfirm Name not confirmed
RemoveName function
Parameter block
--> 26 csCode word ;always removeName
--> 30 entityPtr pointer ;pointer to entity name
RemoveName removes an entity name from the names table of the given entity’s node.
Result codes noErr No error
nbpNotFound Name not found
LoadNBP function
Parameter block
--> 26 csCode word ;always loadNBP
On a Macintosh 128K, LoadNBP reads the NBP code from the system resource file into the application heap; on a Macintosh 512K or XL it has no effect.
Result codes noErr No error
UnloadNBP function
Parameter block
--> 26 csCode word ;always unloadNBP
On a Macintosh 128K, UnloadNBP makes the NBP code purgeable; the space isn’t actually released by the Memory Manager until necessary. On a Macintosh 512K or XL, UnloadNBP has no effect.
Result codes noErr No error
_______________________________________________________________________________
»EXTENDED PROTOCOL PACKAGE DRIVER
_______________________________________________________________________________
The Extended Protocol Package (XPP) driver is intended to implement several AppleTalk communication protocols in the same package for ease of use. The
.XPP driver currently consists of two modules that operate on two levels: the low-level module implements the workstation side of AppleTalk Session Protocol, and the high-level module implements a small portion of the workstation side of the AppleTalk Filing Protocol.
This driver adds functionality to the AppleTalk manager by providing services additional to those provided in the .MPP and .ATP drivers. Figure 2 shows the Macintosh AppleTalk drivers and the protocols accessible through each driver.
The .XPP driver maps an AFP call from the client workstation into one or more ASP calls. .XPP provides one client-level call for AFP.
The implementation of AFP in the .XPP driver is very limited. Most calls are a very simple one-to-one mapping from an AFP call to an ASP command without any interpretation of the syntax of the AFP command by the .XPP driver. Refer to the “Mapping AFP Commands” section of this chapter for further information.
_______________________________________________________________________________
»Version
The .XPP driver supports ASP Version (hex) $100, as described in Inside AppleTalk.
_______________________________________________________________________________
»Error Reporting
Errors are returned by the .XPP driver in the ioResult field of the Device Manager Control calls.
The error conditions reported by the .XPP driver may represent the unsuccessful completion of a routine in more than just one process involved in the interaction of the session. System-level, .XPP driver, AppleTalk, and server errors can all turn up in the ioResult field.
AFP calls return codes indicating the unsuccessful completion of AFP commands in the Command Result field of the parameter block (described below).
An application using the .XPP driver should respond appropriately to error conditions reported from the different parts of the interaction. As shown in Figure 3, the following errors can be returned in the ioResult field:
1. System-level errors
System errors returned by the .XPP driver indicate such conditions
as the driver not being open or a specific system call not being
supported. For a complete list of result codes returned by the
Macintosh system software, refer to Appendix A.
2. XPP errors (for example, “Session not opened”)
The .XPP driver can also return errors resulting from its own
activity (for example, the referenced session isn’t open). The
possible .XPP driver errors returned are listed in the .XPP driver
results codes section with each function that can return the code.
3. AppleTalk Errors (returned from lower-level protocols)
.XPP may also return errors from lower-level protocols (for example,
“Socket not open”). Possible error conditions and codes are described
elsewhere in this chapter.
4. An ASP-specific error could be returned from an ASP server in
response to a failed OpenSession call. Errors of this type, returned
by the server to the workstation, are documented both in Inside
AppleTalk, section 11, “AppleTalk Session Protocol”, and in the .XPP
driver results code section of this chapter.
5. The AppleTalk Filing Protocol defines errors that are returned from
the server to the workstation client. These errors are returned in
the cmdResult field of the parameter block (error type 5 in Figure 15).
This field is valid if no system-level error is returned by the call.
Note that at the ASP level, the cmdResult field is client-defined data
and may not be an error code.
Figure 15–Error Reporting
_______________________________________________________________________________
».XPP Driver Functions Overview
The paragraphs below describe the implementation of ASP in the .XPP driver. For more detailed information about ASP, refer to Inside AppleTalk, Section 11, “AppleTalk Session Protocol (ASP)”.
»Using AppleTalk Name Binding Protocol
A server wishing to advertise its service on the AppleTalk network calls ATP to open an ATP responding socket known as the session listening socket (SLS). The server then calls the Name Binding Protocol (NBP) to register a name on this socket. At this point, the server calls the server side of ASP to pass it the address of the SLS. Then, the server starts listening on the SLS for session opening requests coming over the network.
»Opening and Closing Sessions
When a workstation wishes to access a server, the workstation must call NBP to discover the SLS for that server. Then the workstation calls ASP to open a session with that server.
After determining the SLS (address) of the server, the workstation client issues an OpenSession (or AFPLogin) call to open a session with that server. As a result of this call, ASP sends a special OpenSession packet (an ATP request) to the SLS; this packet carries the address of a workstation socket for use in the session. This socket is referred to as the workstation session socket (WSS). If the server is unable to set up the session, it returns an error. If the request is successful, the server returns no error, and the session is opened. The open session packet also contains a version number so that both ends can verify that they are speaking the same version of ASP.
The AbortOS function can be used to abort an outstanding OpenSession request before it has completed.
The workstation client closes the session by issuing a CloseSession (or AFPLogout). The CloseSession call aborts any calls that are active on the session and closes the session. The session can also be closed by the server or by ASP itself, such as when one end of the session fails. The CloseAll call (which should be used with care) aborts every session that the driver has active.
»Session Maintenance
A session will remain open until it is explicitly terminated by the ASP client at either end or until one of the sessions ends, fails, or becomes unreachable.
»Commands on an Open Session
Once a session has been opened, the workstation client can send a sequence of commands over the session to the server end. The commands are delivered in the same order as they are issued at the workstation end, and replies to the commands are returned to the workstation end.
Three types of commands can be made on an open session. These commands are UserCommand, UserWrite, and AFPCall functions described in the following paragraphs.
UserCommand calls are similar to ATP requests. The workstation client sends a command (included in a variable size command block) to the server client requesting it to perform a particular function and send back a variable size command reply. Examples of such commands vary from a request to open a particular file on a file server, to reading a certain range of bytes from a device. In the first case, a small amount of reply data is returned; in the second case a multiple-packet reply might be generated.
The .XPP driver does not interpret the command block or in any way participate in executing the command’s function. It simply conveys the command block, included in a higher-level format, to the server end of the session, and returns the command reply to the workstation-end client. The command reply consists of a four-byte command result and a variable size command reply block.
UserWrite allows the workstation to convey blocks of data to the server. UserWrite is used to transfer a variable size block of data to the server end of the session and to receive a reply.
The AFPCall function provides a mechanism for passing an AFP command to the server end of an open session and receiving a reply. The first byte of the AFPCall command buffer contains the code for the AFP command that is to be passed to the server for execution. Most AFP calls are implemented through a very simple one-to-one mapping that takes the call and makes an ASP command out of it.
The AFPCall function can have one of four different, but very similar, formats.
»Getting Server Status Information
ASP provides a service to allow its workstation clients to obtain a block of service status information from a server without the need for opening a session. The GetStatus function returns a status block from the server identified by the indicated address. ASP does not impose any structure on the status block. This structure is defined by the protocol above ASP.
»Attention Mechanism
Attentions are defined in ASP as a way for the server to alert the workstation of some event or critical piece of information. The ASP OpenSession and AFPLogin calls include a pointer to an attention routine in their parameter blocks. This attention routine is called by the .XPP driver when it receives an attention from the server and also when the session is closing as described below.
In addition, upon receiving an OpenSession call or AFPLogin call, the .XPP driver sets the first two bytes of the session control block (SCB) to zero. When the .XPP driver receives an attention, the first two bytes of the SCB are set to the attention bytes from the packet (which are always nonzero).
Note: A higher-level language such as Pascal may not wish to have a low-level
attention routine called. A Pascal program can poll the attention bytes,
and if they are ever nonzero, the program will know that an attention
has come in. (It would then set the attention bytes back to zero.)
Of course, two or more attentions could be received between successive
polls, and only the last one would be recorded.
The .XPP driver also calls the attention routine when the session is closed by either the server, workstation, or ASP itself (if the ASP session times out). In these cases, the attention bytes in the SCB are unchanged.
»The Attention Routine
The attention routine is called at interrupt level and must observe interrupt conventions. Specifically, the interrupt routine can change registers A0 through A3 and D0 through D3 and it must not make any Memory Manager calls.
It will be called with
• D0 (word) equal to the SessRefnum for that session (see OpenSession
Function)
• D1 (word) equal to the attention bytes passed by the server (or zero
if the session is closing)
Return with an RTS (return from subroutine) to resume normal execution.
The next section describes the calls that can be made to the .XPP driver.
_______________________________________________________________________________
»CALLING THE .XPP DRIVER
_______________________________________________________________________________
This section describes how to use the .XPP driver and how to call the .XPP driver routines from assembly language and Pascal.
_______________________________________________________________________________
»Using XPP
The .XPP driver implements the workstation side of ASP and provides a mechanism for the workstation to send AppleTalk Filing Protocol (AFP) commands to the server.
»Allocating Memory
Every call to the .XPP driver requires the caller to pass in whatever memory is needed by the driver for the call, generally at the end of the queue element. When a session is opened, the memory required for maintenance of that session
(that is, the Session Control Block) is also passed in.
For standard Device Manager calls, a queue element of a specific size equal to IOQElSize is allocated. When issuing many calls to XPP, it is the caller’s responsibility to allocate a queue element that is large enough to accommodate the .XPP driver’s requirements for executing that call, as defined below. Once allocated, that memory can’t be modified until the call completes.
»Opening the .XPP Driver
To open the .XPP driver, issue a Device Manager Open call. (Refer to the Device Manager chapter.) The name of the .XPP driver is '.XPP'. The original Macintosh ROMs require that .XPP be opened only once. With new ROMs, the .XPP unit number can always be obtained through an Open call. With old ROMs only, the .XPP unit number must be hard coded to XPPUnitNum (40) since only one Open call can be issued to the driver.
The .XPP driver cannot be opened unless AppleTalk is open. The application must ensure that the .MPP and .ATP drivers are opened, as described earlier in this chapter.
The xppLoaded bit (bit 5) in the PortBUse byte in low memory indicates whether or not the .XPP driver is open.
»Example
The following is an example of the procedure an application might use to open the .XPP driver.
; Routine: OpenXPP
;
; Open the .XPP driver and return the driver refNum for it.
;
; Exit: D0 = error code (ccr's set)
; D1 = XPP driver refNum (if no errors)
;
; All other registers preserved
;
xppUnitNum EQU 40 ;default XPP driver number
xppTfRNum EQU -(xppUnitNum+1) ;default XPP driver refNum
OpenXPP
MOVE.L A0-A1/D2,-(SP) ;save registers
MOVE ROM85,D0 ;check ROM type byte
BPL.S @10 ;branch if >=128K ROMs
BTST #xppLoadedBit,PortBUse ;is the XPP driver open already?
BEQ.S @10 ;if not open, then branch to Open code
MOVE #xppTfRNum,D1 ;else use this as driver refnum
MOVEQ #0,D0 ;set noErr
BRA.S @90 ;and exit
;
; XPP driver not open. Make an _Open call to it. If using a 128K
; ROM machine and the driver is already open, we will make another
; Open call to it just so we get the correct driver refNum.
;
@10 SUB #ioQElSize,SP ;allocate temporary param block
MOVE.L SP,A0 ;A0 -> param block
LEA XPPName, A1 ;A1 -> XPP (ASP/AFP) driver name
MOVE.L A1,ioFileName(A0) ;driver name into param block
CLR.B ioPermssn(A0) ;clear permissions byte
_Open
MOVE ioRefNum(A0),D1 ;D1=driver refNum (invalid if error)
ADD #ioQElSize,SP ;deallocate temp param block
@90 MOVE.L (SP)+,A0-A1/D2 ;restore registers
TST D0 ;error? (set ccr's)
RTS
XPPName DC.B 4 ;length of string
DC.B '.XPP' ;driver name
From Pascal, XPP can be opened through the OpenXPP call, which returns the driver’s reference number:
FUNCTION OpenXPP (VAR xppRefnum: INTEGER) : OSErr;
»Open Errors
Errors returned when calling the Device Manager Open routine if the function does not execute properly include the following:
• errors returned by System
• portInUse is returned if the AppleTalk port is in use by a driver
other than AppleTalk or if AppleTalk is not open.
»Closing the .XPP Driver
To close the .XPP driver, call the Device Manager Close routine.
Warning: There is generally no reason to close the driver. Use this
call sparingly, if at all. This call should generally be used
only by system-level applications.
»Close Errors
Errors returned when calling the Device Manager Close routine if the function does not execute properly include the following:
• errors returned by System
• closeErr (new ROMs only) is returned if you try to close the driver
and there are sessions active through that driver. When sessions are
active, closeErr is returned and the driver remains open.
• on old ROMs the driver is closed whether or not sessions are active
and no error is returned. Results are unpredictable if sessions are
still active.
»Session Control Block
The session control block (SCB) is a nonrelocatable block of data passed by the caller to XPP upon session opening. XPP reserves this block for use in maintaining an open session. The SCB size is defined by the constant scbMemSize. The SCB is a locked block, and as long as the session is open, the SCB cannot be modified in any way by the application. There is one SCB for each open session. This block can be reused once a CloseSess call is issued and completed for that session or when the session is indicated as closed.
_______________________________________________________________________________
»How to Access the .XPP Driver
This section contains information for programmers using Pascal and assembly-language routines.
All .XPP driver routines can be executed either synchronously (meaning that the application can’t continue until the routine is completed) or asynchronously
(meaning that the application is free to perform other tasks while the routine is executing).
XPP calls are made from Pascal in the same manner as MPP and ATP calls, with the exception that when making XPP calls the caller must set the XPP driver’s refnum. This refnum is returned in the XPPOpen call’s parameter block.
A Pascal variant record has been defined for all XPP calls. This parameter block is detailed in the “.XPP Driver Parameter Block Record” section below. The first four fields (which are the same for all calls) are automatically filled in by the device manager. The csCode field is automatically filled in by Pascal, depending on which call is being made. The caller must, however, set the ioRefnum field to XPP’s reference number, as returned in the OpenXPP call. The ioVRefnum field is unused.
Note that the parameter block is defined so as to be the maximum size used by any call. Different calls take different size parameter blocks, each call requiring a certain minimum size. Callers are free to abbreviate the parameter block where appropriate.
»General
With each routine, a list of the parameter block fields used by the call is also given. All routines are invoked by Device Manager Control calls with the csCode field equal to the code corresponding to the function being called. The number next to each field name indicates the byte offset of the field from the start of the parameter block pointed to by A0; only assembly-language programmers need to be concerned with it. An arrow next to each parameter name indicates whether it’s an input, output, or input/output parameter:
Arrow Meaning
<-- Parameter is passed
<-- Parameter is returned
<-> Parameter is passed and returned
All Device Manager Control calls return an integer result code in the ioResult field. Each routine description lists all the applicable result codes, along with a short description of what the result code means. Refer to the section
“XPP Driver Result Codes” for an alphabetical list of result codes returned by the .XPP driver.
Each routine description includes a Pascal form of the call. Pascal calls to the .XPP Driver are of the form:
FUNCTION XPPCall (paramBlock: XPPParmBlkPtr,async: BOOLEAN) : OSErr;
XPPCall is the name of the routine.
The parameter paramBlock points to the actual I/O queue element used in the
_Control call, filled in by the caller with the parameters of the routine.
The parameter async indicates whether or not the call should be made asynchronously. If async is TRUE, the call is executed asynchronously; otherwise the call is executed synchronously.
The routine returns a result code of type OSErr.
».XPP Driver Parameter Block Record
XPPParamBlock = PACKED RECORD
qLink: QElemPtr; {next queue entry}
qType: INTEGER; {queue type}
ioTrap: INTEGER; {routine trap}
ioCmdAddr: Ptr; {routine address}
ioCompletion: ProcPtr; {completion routine}
ioResult: OSErr; {result code}
cmdResult: LONGINT; {command result (ATP user bytes) [long]}
ioVRefNum: INTEGER; {volume reference or drive number)
ioRefNum: INTEGER; {driver reference number)
csCode: INTEGER; {Call command code}
CASE XPPPrmBlkType OF
ASPAbortPrm:
(abortSCBPtr: Ptr); {SCB pointer for AbortOS [long]}
ASPSizeBlk:
(aspMaxCmdSize: INTEGER; {for SPGetParms [word]
aspQuantumSize: INTEGER; {for SPGetParms [word]}
numSesss: INTEGER); {for SPGetParms [word]}
XPPPrmBlk:
(sessRefnum: INTEGER; {offset to session refnum [word]}
aspTimeout: Byte; {timeout for ATP [byte]}
aspRetry: Byte; {retry count for ATP [byte]}
CASE XPPSubPrmType OF
ASPOpenPrm:
(serverAddr: AddrBlock; {server address block [longword]}
scbPointer: Ptr; {SCB pointer [longword]}
attnRoutine: Ptr); {attention routine pointer [long]}
ASPSubPrm:
(cbSize: INTEGER; {command block size [word]}
cbPtr: Ptr; {command block pointer [long]}
rbSize: INTEGER; {reply buffer size [word]}
rbPtr: Ptr; {reply buffer pointer [long]}
CASE XPPEndPrmType OF
AFPLoginPrm:
(afpAddrBlock: AddrBlock; {address block in}
{ AFPlogin [long]}
afpSCBPtr: Ptr; {SCB pointer in }
{ AFPlogin [long]}
afpAttnRoutine: Ptr); {attn routine pointer }
{ in AFPlogin}
ASPEndPrm:
(wdSize: INTEGER; {write data size [word]}
wdPtr: Ptr; {write data pointer [long]}
ccbStart: ARRAY[0..295] OF Byte))); {CCB memory }
{ for driver}
{Write max size(CCB) = 296; all other calls = 150}
END;
_______________________________________________________________________________
»AppleTalk Session Protocol Functions
This section contains descriptions of the .XPP driver functions that you can call. Each function description shows the required parameter block fields, their offsets within the parameter block and a brief definition of the field. Possible result codes are also described.
»Note on Result Codes
An important distinction exists between the aspParamErr and aspSessClose result codes that may be returned by the .XPP driver.
When the driver returns aspParamErr to a call that takes as an input a session reference number, the session reference number does not relate to a valid open session. There could be several reasons for this, such as the workstation or server end closed the session or the server end of the session died.
The aspSessClosed result code indicates that even though the session reference number relates to a valid session, that particular session is in the process of closing down (although the session is not yet closed).
FUNCTION ASPOpenSession (xParamBlock: XPPParmBlkPtr; async: BOOLEAN) : OSErr;
Parameter block
--> 26 csCode word Always ASPOpenSess
-->