September 94 - Adding QuickDraw GX Printing to QuickDraw Applications
Adding QuickDraw GX Printing to QuickDraw Applications
DAVE HERSEY
Now that QuickDraw GX has been released, you may be wondering what to do with
your older QuickDraw applications. The good news is that by adding a relatively
small percentage of code to your existing applications, you can support all the new
features of the QuickDraw GX printing architecture and still retain full
compatibility with non-QuickDraw GX systems.
Compatibility with the existing application base was a primary concern during thedevelopment of
QuickDraw GX; only the most hardened "print criminals" shouldhave compatibility problems. Since
a pre-QuickDraw GX application should work in both QuickDraw GX and non-QuickDraw GX
environments, you may think that leaving your code as it stands is good enough. Becoming
compatible probably doesn't require any revisions to your existing software, and the fact that your
application will perform with or without QuickDraw GX sounds like a pretty good deal. Where's the
catch?
Here it is: A non-QuickDraw GX application doesn't have access to several key features of
QuickDraw GX, so its users can't take advantage of many of the new printing features. For example,
with QuickDraw GX a user can:
- Redirect print jobs using the new Print dialog.
- Choose page-by-page formatting, so that page 1 prints on US Letter, page 2 prints
on #10 envelope, page 3 prints on landscape-oriented US Legal, and so on, instead
of printing the entire document in a single format.
- Work with the new QuickDraw GX printing dialogs (shown in Figure 1) instead
of the dialogs provided for compatibility with non-QuickDraw GX applications.
- Use features provided by printing extensions. Printing extensions can add items to
the QuickDraw GX printing dialogs, but not to the compatibility dialogs. (Added
items show up as icons in the column on the left side of each dialog.)
- Print a single copy of a document with the new Print One Copy command.
- Print documents by dragging them to desktop printers.
Page Setup dialog
Custom Page Setup dialog
Print dialog
Figure 1. The new QuickDraw GX printing dialogs
Users are shut out from all these features if they're using a non-QuickDraw GX application. In this
article, you'll see how to increase an application's level of QuickDraw GX support. I'll take a
QuickDraw application and convert it step by step into an application that fully supports both the
QuickDraw and QuickDraw GX printing architectures. First, we'll consider some definitions and
background information.
DIFFERENT LEVELS OF QUICKDRAW GX SUPPORT
To begin, let's define the different levels of QuickDraw GX support (from lowest to highest) that an
application can have:
- QuickDraw GX unaware: The application doesn't implement any QuickDraw GX
code, and QuickDraw GX translates all QuickDraw printing and drawing calls
into QuickDraw GX equivalents. In other words, this is a non-QuickDraw GX
application.
- QuickDraw GX aware: The application implements a core set of QuickDraw GX
printing features but retains compatibility with documents created with earlier
versions.
- QuickDraw GX savvy: The application implements full support for QuickDraw GX
graphics, typography, and printing features. It also retains compatibility with
documents created with earlier versions.
- QuickDraw GX dependent: The application requires the presence of QuickDraw
GX graphics, typography, and printing routines to function.
Applications don't need to implement any code to be QuickDraw GX unaware. Becoming
QuickDraw GX aware requires some coding, but typically not very much. Making your application
QuickDraw GX aware is theminimal level of QuickDraw GX support that you should set your sights
on -- just follow the guidelines in this article.
A QuickDraw GX-aware application is really a non-QuickDraw GX application at heart, with
support for QuickDraw GX printing added. This application still uses its old QuickDraw commands
to represent shapes and text; however, in its print loop, the application uses the QuickDraw GX
graphics translator to translate QuickDraw commands into QuickDraw GX shapes for printing, if
QuickDraw GX is available.
If you're just starting out on a new project, or you're in the process of rewriting an existing product,
you should consider the next level of compatibility: QuickDraw GX savvy. A QuickDraw GX-savvy
application meets the requirements for being QuickDraw GX aware but also takes advantage of the
extensive QuickDraw GX graphics and typography tools. You may feel that becoming QuickDraw
GX savvy, although it brings with it a wealth of increased capabilities, is too big a leap to take in the
short term. In that case, consider making your existing applications QuickDraw GX aware and, once
they're released, going back and working on QuickDraw GX-savvy versions.
A WORD ABOUT COMPATIBILITY
It's Apple's hope and goal that no QuickDraw GX-unaware applications will be incompatible with
QuickDraw GX. An existing application can be incompatible only if it relies on unsupported methods
or on unpublished information that breaks under QuickDraw GX. For printing, this would include
things such as trying to access the Printer Access Protocol (PAP) code from the 'PDEF' 10 resource
of the LaserWriter driver. The QuickDraw GX LaserWriter driver doesn't contain a 'PDEF' 10
resource, so applications that rely on this approach must be modified to work with QuickDraw GX.
Developers were warned about the disappearance of the 'PDEF' 10 resource in "Print Hints: Looking
Ahead to QuickDraw GX" in develop Issue 13.*
If your application has compatibility problems with QuickDraw GX, you probably already suspect it.
You're a candidate for such problems if you're perpetrating any of the "printing crimes" or shaky
methods that Apple has warned about in the past. Sweaty palms and pangs of guilt whenever your
application is tested under new system software are likely indications that you should get out your
development tools and reform your code now. (If you need the old code to run on non-QuickDraw
GX systems, simply provide alternative code for running with QuickDraw GX.) Delve into the
QuickDraw GX API -- you're sure to find a better way of doing things.
SOME FUNDAMENTALS
Before we go into the specifics of how to add QuickDraw GX printing to your existing applications,
we need to cover some fundamental ideas. I'll start with a discussion of the Collection Manager,
which makes its debut with QuickDraw GX, and then briefly cover overriding messages and the
Message Manager.
For a review of QuickDraw GX printing, see "Getting Started With QuickDraw GX" and "Developing
QuickDraw GX Printing Extensions," both in develop Issue 15. See also Inside Macintosh: QuickDraw GX
Printing. *
CREATING A FOUNDATION TO BUILD ON: THE COLLECTION MANAGER
The Collection Manager is somewhat like a memory-based version of the Resource Manager. It
provides API calls that allow you to create and accesscollections, which are amorphous structures that
can contain data of different types with different sizes. Collections are similar to resource files under
the Resource Manager except that collections must be in memory, while resource files are by
definition disk based. Routines exist for "flattening" collections into a series of bytes that can be
written to disk and for "unflattening" them for later use.
Just as a resource file may contain Apple-defined data structures such as 'WIND' and 'MENU'
resources, a collection may contain predefined collection items such as 'copy' and 'rang', which
specify the number of copies of a print job and the page range of a print job, respectively. Like
resources, collection items have attributes that are predefined (for example, the collectionLockMask
attribute), but unlike resources, they also have attributes that can be programmer defined. And, as
with resources, you aren't simply stuck with the core set that's been defined by Apple. Figure 2
illustrates the similarities, including a programmer-defined resource of type 'USER' and a
programmer-defined collection item, also of type 'USER'.
Figure 2. Similarities between resources and collection items
Items in a collection are uniquely identified in three different ways. Every item has a 4-character
label, ortag , which identifies the type of collection item. In Figure 2, there are three collection items
having the tags 'USER', 'copy', and 'rang', respectively. In addition to the tag, every collection item
has a longword ID. Together, the tag and ID are one way to uniquely identify a collection item.
Each collection item can be also be referenced by its collectionindex or by its tag andtag list position.
The index is the relative position of an item in a collection, and the tag list position is the relative
position of a collection item within the item's tag type -- for example, the first, second, or third
'USER' item in a collection. This gives us three ways to refer to a specific collection item:
- by its tag and ID
- by its collection index
- by its tag and tag list position
It's important to note that although a collection item's index and tag list position may change as
items are added or removed from a collection, an item's tag and ID will never change. Therefore, the
most common way to refer to collection items is by tag and ID.
Collection tag and resource type naming
conventions are the same. Apple reserves tags listed in the
printing interface files, as well as those consisting entirely of lowercase letters. *
You can either create a collection with the NewCollection routine or use one of the three types of
collections that QuickDraw GX automatically creates for you during printing. These types are job
collections, format collections, and paper-type collections. Every time a new gxJob, gxFormat, or
gxPaperType object is created, a collection is also created for that object. These collections contain
items specifying the following types of information:
- job collection: number of copies, page range, destination file information (if
printing to a file)
- format collection: scaling factor, page orientation, page flipping information
- paper-type collection: paper-type creator, paper-type comment
These predefined collections contain many more collection items than are shown here. See the
QuickDraw GX interface file PrintingManager.h for the complete listing. By simply changing the
contents of the collection items, any application, printing extension, or printer driver can easily affect
how a document will be printed.
Note that all predefined collection items have the ID gxPrintingTagID = -28672. *
Your application can create and use collections for its own purposes. By no means are you restricted
to using collections only within the QuickDraw GX printing architecture, although that's probably
where you'll use them the most.
OVERRIDING MESSAGES FROM AN APPLICATION: THE MESSAGE MANAGER
Sam Weiss's article indevelop Issue 15, "Developing QuickDraw GX Printing Extensions,"
introduced the Message Manager. The QuickDraw GX printing architecture uses the Message
Manager to invoke printing operations, and your application, printing extension, or printer driver can
override messages to change printing behavior. This process is explained in Sam's article, which is a
good reference for anyone unfamiliar with the Message Manager and how QuickDraw GX uses it.
This article assumes that you already have at least a passing familiarity with the basic concepts.
When you override a message from an application, you're more restricted in what you can do than if
you override a message from a printing extension or printer driver. To understand why, consider a
case where you want to override the QuickDraw GX printing message GXDespoolPage to add a
serial number to the page before it's printed. You can add this override to a printing extension in a
straightforward way that works regardless of which application prints the document. If, on the other
hand, the override is attempted by an application, the situation becomes more problematic. The
override won't be invoked because it's a despooling-phase message; only application-phase and
spooling-phase messages can be overridden by applications.
To understand a second limitation of application overrides, let's look at how they're installed in the
message handler chain. This is done by passing the routine GXInstallApplicationOverride a pointer
to the override procedure and a reference
to the document's job, like so:
GXInstallApplicationOverride(docJob, gxJobPrintDialog,
MyJobPrintDialogOverride);
In this example, the application is overriding the GXJobPrintDialog message with an override
function called MyJobPrintDialogOverride. docJob is the gxJob object for a document. Because your
application has access only to its own gxJob objects, an application override can be invoked only for
the application that installed it. On the other hand, a message override from a printing extension can
affect every document that's printed, regardless of which application printed it.
In summary:
- Application overrides are reliable only during the application and spooling phases
of printing, which end once the document has been spooled to a print file. You
should attempt to override only application-phase and spooling-phase messages
from an application.
- Application overrides are invoked only for the application installing the override,
and only for the gxJob objects in which the overrides are installed.
Application overrides are best suited for adding features to the printing dialogs and modifying the
print file as a document is being spooled. If you feel limited by either of the conditions mentioned
above, you should move your override out of your application and into a printing extension.
BECOMING AWARE
Now let's look at what an application developer needs to do to support QuickDraw GX printing.
We'll take a QuickDraw application called Simple Sample and convert it into its QuickDraw
GX-aware counterpart, Simple Sample GX. The code for both samples is on this issue's CD.
The Simple Sample application draws using various QuickDraw commands, including those for
simple objects, bitmaps, and PicComments. It can handle multiple documents with multiple pages,
and although it knows nothing about QuickDraw GX, it is System 7 dependent (I made it that way to
save on code and confusion).
Here's a summary of what most QuickDraw GX-aware applications need to do:
- Determine whether QuickDraw GX is present, and if so, initialize the QuickDraw
GX managers (and close them down when the application quits).
- Create a gxJob object for each document created and dispose of the gxJob when
the document is closed.
- Override the GXPrintingEvent message (in order to support the QuickDraw GX
movable modal printing dialogs) while printing dialogs are displayed.
- Update gxJob objects on resume events (in case the characteristics of the desktop
printer you're printing to have changed).
- Save and load a document's gxJob object to and from disk and convert print
records to gxJob objects when loading documents created from pre-QuickDraw
GX versions of your application.
- Make the Custom Page Setup and Print One Copy menu items available in the
File menu.
- Invoke the QuickDraw GX printing dialogs and support custom formatting by
page (each page can have a unique page format, or can share another page's
format).
- Store page-to-format correspondences to disk with a document (and load them
again when the document is opened).
- Have a print loop that uses QuickDraw GX printing commands and translates
QuickDraw commands to QuickDraw GX shapes for printing.
- Implement the Print One Copy feature.
- Support the new attribute of the 'pdoc' Apple event to properly support drag-and-
drop printing of documents from the Finder.
Each of these is discussed in order in the rest of this article.
PREPARATION
Before you can do anything with QuickDraw GX, you need to determine whether it's available. You
should do this in your initialize routine, after you've determined that the other managers yourapplication requires are available. The MyInitGXIfPresent routine in Listing 1 shows one way of
doing this.
Listing 1. QuickDraw GX preparation and cleanup
void MyInitGXIfPresent()
{
long gxVersion, gxPrintVersion;
gGXIsPresent = false;
/* Check to see whether QuickDraw GX is available. */
if (Gestalt(gestaltGXVersion, &gxVersion) == noErr)
if (Gestalt(gestaltGXPrintingMgrVersion, &gxPrintVersion)
== noErr)
gGXIsPresent = true;
/* If so, initialize QuickDraw GX. */
if (gGXIsPresent) {
gClient = GXNewGraphicsClient(nil, kGraphicsHeapSize,
(gxClientAttribute) 0);
GXEnterGraphics();
GXInitPrinting();
}
}
void MyCleanUpGXIfPresent()
{
if (gGXIsPresent) {
GXExitPrinting();
GXDisposeGraphicsClient(gClient);
GXExitGraphics();
}
}
In MyInitGXIfPresent, we use Gestalt to determine whether the QuickDraw GX graphics and
printing routines are present. If so, we call GXNewGraphicsClient
to set aside memory for QuickDraw GX graphics operations, and we call GXEnterGraphics and
GXInitPrinting to enable the QuickDraw GX functions we'll use. Note that we also set a global
variable, gGXIsPresent, which indicates whether the QuickDraw GX managers we require are
present. We'll check this value whenever we need to make a decision about whether to use
QuickDraw or QuickDraw GX methods.
Important: You cannot intermix Printing Manager and QuickDraw GX printing calls. Do not call
_PrGlue[A8FD] if you have called _InitPrinting.
When the user quits the application, we need to release any memory we allocated and close down
QuickDraw GX printing and graphics. We do this as shown in the MyCleanUpGXIfPresent routine
in Listing 1.
CREATING AND DISPOSING OF GXJOB OBJECTS
With the MyInitGXIfPresent and MyCleanUpGXIfPresent routines added to our code, we're now
ready to make our application's documents fit into the QuickDraw GX print model. We do this by
creating a gxJob object whenever we create a document. Our non-QuickDraw GX application,
Simple Sample, contains a routine named MyCreateDocument that's called whenever the user
creates a document. As shown in the Simple Sample GX application, this routine is modified so that
when QuickDraw GX is present (as indicated by the gGXIsPresent global variable), the application
creates a gxJob for each document, using the GXNewJob routine. If QuickDraw GX is not present,
the application creates a print record handle (THPrint) instead. It's common for an application to store descriptive information about a document in a private data
structure, and our sample is no exception. The application uses a private structure called
MyDocumentRec that contains information about the number of pages in a document, the current
page being viewed, and so forth. We modify this structure also, so that we can store a document's job
reference and page formatting information with the document. As seen in Listing 2, we've added the
documentJob and pageFormat fields to the structure. The rest of the fields in this structure were
already being managed by the QuickDraw GX-unaware application, and we'll continue to use them.
Listing 2. MyDocumentRec, modified for QuickDraw GX
#define kMaxPages 20 /* Max pages the sample handles. */
typedef struct MyDocumentRec {
THPrint documentPrintHdl; /* Print record for document. */
gxJob documentJob; /* Job for document. */
gxFormat pageFormat[kMaxPages]; /* Format for each page. */
/* If nil, we use the job format. */
long numPages; /* Number of pages in document. */
long curPage; /* The current page displayed. */
FSSpec documentFSSpec; /* Document's file spec. */
Str31 documentTitle; /* The title of this document. */
WindowPtr documentWindow; /* The window for this document.*/
} MyDocumentRec, *MyDocumentPtr;
We also need to modify the application's MyDisposeDocument routine, which is called whenever a
document is closed. In the modified routine, we dispose of the document's gxJob.
The changes that we've made so far are indicative of the approach we'll continue to use as we make
our sample QuickDraw GX aware: adding code that executes only if QuickDraw GX is present. If the
user isn't running QuickDraw GX, our converted sample will appear and function as the original did.
But if QuickDraw GX is present, all the new functionality will kick in.
OVERRIDING GXPRINTINGEVENT
When we added support for gxJobs in the MyCreateDocument routine, we also added the following
line of code:
GXInstallApplicationOverride((*createdDocument)->documentJob,
gxPrintingEvent, MyPrintingEventOverride);
Every application that aspires to be QuickDraw GX aware should include such a
line. Remember, QuickDraw GX has movable modal printing dialogs; the GXPrintingEvent message
is sent whenever a dialog is moved. Unless you override GXPrintingEvent, you won't have a chance
to update your application's windows when the dialogs are moved.
The code to support window updates when the printing dialogs are moved is actually quite simple.
You just need to add a routine that overrides the GXPrintingEvent message and calls your event-
handling routine (Listing 3), and to make sure that you've disabled the appropriate menu items
before displaying the new printing dialogs. Note that your override should not forward the
GXPrintingEvent message, but instead should perform a total override of it.
Listing 3. MyPrintingEventOverride
OSErr MyPrintingEventOverride(EventRecord *anEvent,
Boolean filterEvent)
{
/* Handle events in whatever way is appropriate. MyDoEvent is
our generic event handler. We don't pass it events that it
shouldn't handle while printing dialogs are displayed. */
if (!filterEvent)
switch (anEvent->what) {
case mouseDown:
case keyDown:
case autoKey:
break;
default:
MyDoEvent(anEvent);
}
return noErr;
}
UPDATING GXJOB OBJECTS ON RESUME EVENTS
There's another piece of event-handling code that every QuickDraw GX-aware application should
include. To support the reentrant nature of QuickDraw GX, you must call GXUpdateJob on each
gxJob that your application is using whenever you receive a resume event. (See the code for Simple
Sample GX on the CD.) This enables QuickDraw GX to update the information in the gxJob in case
it changed while your application was in the background. As an example, suppose that a user suspends
your application to change the printing extension setup for the destination desktop printer. Upon
switching back to your application, this user will expect anyopen documents to use these new settings
at print time. Unless you call GXUpdateJobon your gxJob objects, the new settings won't be there.
SAVING AND LOADING A DOCUMENT'S GXJOB
Now that we can create gxJob objects for new documents, let's take a look at how we can save these
to disk and later retrieve them when the documents are opened. We want to save them for the same
reason that we want to store print records with documents under non-QuickDraw GX systems: if a
user has gone to the trouble of configuring print settings for a document, the user-friendly thing to
do is to use those settings the next time the document is opened.
Because the data we want to save is stored in an abstract data structure (gxJob), we need a way to
convert it to a more tangible form. We accomplish this with the GXFlattenJob or
GXFlattenJobToHdl routine. GXFlattenJob passes the converted data as a stream of bytes, whereas
GXFlattenJobToHdl places the flattened data in a handle. You would typically use GXFlattenJob to
store the flattened gxJob in a data fork, but GXFlattenJobToHdl to save it as a resource.
Since our application stores its print records in resources, we'll store its flattened gxJob objects in
resources as well. To do this, we modify our application's MySavePrintInfo routine, which is called
to save print records to disk. If QuickDraw GX is present, the routine will instead save our flattened
gxJob.
In the following code, we flatten a gxJob into a handle called thePrintData, which can then be
written to the disk as a resource.
thePrintData = NewHandle(0);
GXFlattenJobToHdl(whichDocument->documentJob, thePrintData);
Next, we modify the application's MyLoadPrintInfo routine, which is used to retrieve print records
that were previously saved with MySavePrintInfo. This routine must do several things, based on
whether QuickDraw GX is present and whether a gxJob or print record has been previously stored
with a document. The flow of control is shown in Listing 4.
As it turns out, some of the steps in Listing 4 can be combined. For example, regardless of whether
QuickDraw GX is present, we may need to retrieve a previously saved print record. What?!
Accessing old-style print records when QuickDraw GX is present? Sounds strange, doesn't it? We
need to do this if QuickDraw GX is available and the user opens a document that contains a print
record but not a gxJob (because it was created with an older version of the application). We convert
the print record to a gxJob with the GXConvertPrintRecord routine, so that the gxJob has as many
of the old print record's settings as possible.
Listing 4. MyLoadPrintInfo flow of control
if (gGXIsPresent) {
if there’s a previously saved gxJob
unflatten it and use it
else
if there's a previously saved print record
convert that to a gxJob and use it
else
use the default gxJob we created in MyCreateDocument
}
else {
if there's a previously saved print record
use it
else
use the default print record we created in MyCreateDocument
}
CONFIGURING AND HANDLING MENUS
We need to alter our menu routines so that the Custom Page Setup and Print One Copy commands
are available to QuickDraw GX users. We might decide to have two different File menus, the
installation of which would depend on whether QuickDraw GX is present. Or we might have only
one File menu and add or subtract items from it when it's installed in the menu bar. The approach
that's best for a particular application depends on how the application is structured. (The same choice
of approaches applies to other menus that might need to be modified based on whether QuickDraw
GX is available.)
In the sample code, we modify the File menu as follows: the application contains a 'MENU' resource
for the QuickDraw GX version of the File menu; if gGXIsPresent is false, we remove the Print One
Copy and Custom Page Setup menu items from this menu.
fileMenu = GetMHandle(mFile);
DelMenuItem(fileMenu, iPrintOneCopy);
DelMenuItem(fileMenu, iCustomPageSetup);
The order of the deletions is very important! If we deleted in the reverse order, the Custom Page
Setup menu item would be deleted, but when we tried to delete Print One Copy, we would actually
delete whatever cameafter Print One Copy. Why? Because the menu would be one item shorter, and
the index into it would no longer be valid. Just delete menu items from bottom to top and you'll be
fine.
Monkeying around with the placement (and inclusion) of menu items like this will throw our menu-
enabling and menu-selection routines out of whack. We need to make sure that regardless of whether
the Print command is item number 8 or 9 in the menu, we still treat it as a Print command. Again,
the approach to use will depend on the application.
It's conceivable that you would use macros and would make the same changes to both the menu-
enabling and menu-selection routines, but in the sample I chose to tackle menu enabling and
disabling differently than I did menu handling. For the simple enabling and disabling of menus in the
MyAdjustMenus routine, I check to see whether QuickDraw GX is present and adjust the item
numbers based on that. This is the easiest approach because it requires only minor changes to the
routine.
The menu selection situation is a little different. Typically, applications contain a routine that uses a
switch statement based on the ID of the menu and menu items chosen. This means that we'd need
either two such routines (one for QuickDraw
and one for QuickDraw GX) or a different approach from what we used in MyAdjustMenus. I opted
for a different approach. The sample application's MyDoMenuCommand routine uses a switch statement (as most
applications do) based on the menu ID and menu item extracted from the result of the MenuSelect
and MenuKey routines. When QuickDraw GX is not present, the Quit menu item will be item 10 in
the File menu; otherwise, it will be at item 12 (see Figure 3). The switch statement in the
MyDoMenuCommand function compares the menu item selected to the items in the QuickDraw
GX version of our File menu. Therefore, it will expect that item 12 will be Quit. Item 10 will be
interpreted as Print One Copy! This would be disastrous -- the application would not quit, and our
QuickDraw GX-specific code would be executed when QuickDraw GX wasn't around! That's not
likely to be a pleasant user experience.
Figure 3. The File menu without QuickDraw GX and with QuickDraw GX
A new routine, MyConvertMenuItem, solves this problem (Listing 5). This routine is called just
before we enter the switch statement in MyDoMenuCommand. If QuickDraw GX is present,
MyConvertMenuItem does nothing; otherwise, it checks to see if the menu item selected was
affected by our deletion of the QuickDraw GX menu items, and if so adjusts it. How's that for an
easy solution?
Listing 5. MyConvertMenuItem
void MyConvertMenuItem(short *menuID, short *menuItem)
{
if (!gGXIsPresent) {
if (*menuItem == iCustomPageSetup)
*menuItem = iPrint; /* Print was selected. */
else
if (*menuItem == iPrintOneCopy)
*menuItem = iQuit; /* Quit was selected. */
}
}
INVOKING THE PRINTING DIALOGS AND SUPPORTING CUSTOM FORMATTING
Earlier, we added code to our application to support window updates when the printing dialogs are
displayed. Now, let's discuss the code required to actually display the dialogs.
There are now three printing dialogs instead of two (as shown earlier in Figure 1). The Page Setup
dialog now allows users to modify a document's default page format. The new dialog, Custom Page
Setup, provides a way to change page formatting on a page-by-page basis. If your application creates
documents that can have only a single page, implementing the Custom Page Setup dialog isn'tnecessary; the Page Setup dialog can be used to configure the document's only page format. The
Print dialog is similar to its non-QuickDraw GX counterpart, although much enhanced.
We use GXJobDefaultFormatDialog, GXFormatDialog, and GXJobPrintDialog to display the Page
Setup, Custom Page Setup, and Print dialogs, respectively.
We modify our MyDoPageSetup routine to call GXJobDefaultFormatDialog instead of PrStlDialog
when QuickDraw GX is present. In our MyDoCustomPageSetup routine, which is called only when
QuickDraw GX is available, we simply call GXFormatDialog, passing the current page's gxFormat.
In both of these page setup routines, and in the code for MyPrintDocument that follows, we call a
function named MyAdjustMenusForPrintDialogs, which disables and enables entire menus (Listing
6). We disable menus before displaying a printing dialog, and enable them once the dialog goes away.
If we didn't disable the menus, users would be able to select menu items. And, because the
GXPrintingEvent override calls our MyDoEvent routine, any queued-up menu selections would be
processed when the GXPrintingEvent message was sent. The user could be in the Print dialog, then
select Quit from the File menu, and the next time the window was updated, the application would
quit! The only menus a user should have access to are the Edit menu and the system menus. (Users
can open desk accessories from the Apple menu, for example, while a printing dialog is displayed --
but they should not be able to open the About item for the application.)
Listing 6. MyAdjustMenusForPrintDialogs
void MyAdjustMenusForPrintDialogs(Boolean dialogGoingUp)
{
MenuHandle appleMenu, fileMenu, editMenu, documentMenu;
appleMenu = GetMHandle(mApple);
fileMenu = GetMHandle(mFile);
editMenu = GetMHandle(mEdit);
documentMenu = GetMHandle(mDocument);
if (dialogGoingUp) {
DisableItem(appleMenu, iAbout);
DisableItem(fileMenu, 0);
DisableItem(documentMenu, 0);
HiliteMenu(0);
}
else {
EnableItem(appleMenu, iAbout);
EnableItem(fileMenu, 0);
DisableItem(editMenu, 0);
EnableItem(documentMenu, 0);
}
DrawMenuBar();
gInPrintDialog = dialogGoingUp;
}
The system enables some menus, namely the Help menu, the Application menu, and the Keyboard menu,
even when dialogs are displayed. Your code doesn't need to deal with them. *
The MyPrintDocument routine (Listing 7) displays the appropriate Print dialog and then branches
to our QuickDraw or QuickDraw GX printing routine. Note that we've been careful to call PrOpen
and PrClose only when QuickDraw GX is not present. As mentioned earlier, you cannot intermix
Printing Manager and QuickDraw GX printing calls.
Listing 7. MyPrintDocument
OSErr MyPrintDocument(MyDocumentPtr whichDocument)
{
OSErr err = noErr;
gxEditMenuRecord editMenuRec;
gxDialogResult result;
if (gGXIsPresent) {
/* If GX is present, fill in the location of the
applications Edit menu items, enable/disable the
appropriate menu items, and display the Print dialog.
If the user clicks OK, print. */
editMenuRec.editMenuID = mEdit;
editMenuRec.cutItem = iCut;
editMenuRec.copyItem = iCopy;
editMenuRec.pasteItem = iPaste;
editMenuRec.clearItem = iClear;
editMenuRec.undoItem = iUndo;
MyAdjustMenusForPrintDialogs(true);
result = GXJobPrintDialog(whichDocument->documentJob,
&editMenuRec);
MyAdjustMenusForPrintDialogs(false);
if (result == gxOKSelected)
err = MyGXPrintLoop(whichDocument);
}
else {
/* If GX is NOT present, open the printer driver and print
using the Printing Manager. */
PrOpen();
if (PrJobDialog(whichDocument->documentPrintHdl))
err = MyQDPrintLoop(whichDocument);
PrClose();
}
return err;
}
We've made a few other changes behind the scenes; see the complete code on the
CD for details. The code for repaginating a document has been changed slightly to use the
dimensions of QuickDraw GX page formats, if available, rather than those
in the rPage field of the old print record. We also made minor changes to our MyInsertPage and
MyDisposePage routines in order to manage gxFormats on a page-by-page basis. In the Simple
Sample GX application, we store nil in our MyDocumentRec.pageFormat array whenever a new
page is created. Because nil is an invalid gxFormat reference, we can use it to tell the application to
use our gxJob object's default format for a given page. If the value stored is non-nil, we can safely
assume that it's a valid reference to a custom page format. Finally, in our MyDisposePage routine, we
now call GXDisposeFormat if the page being removed uses a custom page format.
Since we store gxFormat references on a page-by-page basis, shouldn't we also dispose of page
formats in the MyDisposeDocument routine? Well, we certainly can do that, but there's really no
need. When we dispose of the document's gxJob, QuickDraw GX automatically disposes of all of the
job's page formats. The flip side of this is that you must not attempt to use any gxFormat references
once the format's corresponding job is disposed of.
Continuing with this line of thought, what happens to a document's page formats when the
document is saved to disk and later reopened? As it turns out, flattening a gxJob causes all of the job's
page formats to be flattened also. This means that the code we wrote earlier to flatten and store a
document's gxJob in a resource will also store page formats. When we later unflatten the job, the
page formats will be unflattened as well.
STORING THE PAGE-TO-FORMAT CORRESPONDENCES
It's easy to fall into the trap of thinking that you don't need to do anything more to support saving
and loading of by-page formats -- but don't. There are still two more issues to consider.
The first issue is straightforward enough: we haven't stored our page-to-format correspondences, so
the next time we open the document we'll have no idea which format goes with which page. The
second issue requires a bit more explaining.
When QuickDraw GX creates a page format, it returns a format reference that's based partially on
the reference of the job with which the format is associated. Since reference IDs for gxJob objects
differ depending on conditions when the job is created, a job reference that's valid when a document
is saved is unlikely to be correct when the document is later reopened. Similarly, the format
references we have when a document is saved are unlikely to be correct when the document's job is
later unflattened.
It should now be clear that we can't simply store our page format reference IDs with a document. To
provide the page-to-format information we'll need when we open the document, we have to find
another method. Fortunately, there's a very easy way to do this via the Collection Manager.
As mentioned earlier, every gxFormat has a collection associated with it. QuickDraw GX creates
these collections automatically when a gxFormat is created. When a format is flattened, its collection
is also flattened. Specifically, any collection items that have the collectionPersistenceBit attribute set
are included in the flattened data stream. This attribute is set by default when a new collection item
is added, so you need to change it only if youdon't want a collection item to be included in the
flattened data.
To store page-to-format mapping, we'll create a custom collection item. We'll store this collection
item in the default format's format collection. The collection item consists of an array of long words
-- one for each page in the document. In this
array, we store the index of the format to use for each page, in order. Since formatindices are
preserved during flattening (unlike formatreferences ), we'll be able to reconstruct the page-to-format
relationships when we reopen the document. The MySaveFormatRefs routine (Listing 8) saves the
format indices. This routine is called from our MySavePrintInfo routine, just before we flatten the
document's gxJob (and in turn its page formats and format collections).
Listing 8. MySaveFormatRefs
#define kMyFormatInfoType 'FLST'
#define kMyFormatInfoTagID 1000
OSErr MySaveFormatRefs(MyDocumentPtr whichDocument)
{
OSErr err = noErr;
Handle theFormatIdxList;
Collection fmtCollection;
gxFormat defaultFmt;
if (whichDocument->numPages > 0) {
/* Get the job's default format's collection. */
defaultFmt = GXGetJobFormat(whichDocument->documentJob, 1);
fmtCollection = GXGetFormatCollection(defaultFmt);
/* Create a list of page-to-format correspondences for the
current document. If there are no errors, add the item
to the job's default format's collection for later
retrieval. */
err = MyCreateFormatIndexList(whichDocument,
&theFormatIdxList);
if (err == noErr) {
HLock(theFormatIdxList);
err = AddCollectionItem(fmtCollection, kMyFormatInfoType,
kMyFormatInfoTagID, GetHandleSize(theFormatIdxList),
*theFormatIdxList);
DisposeHandle(theFormatIdxList);
}
}
return err;
}
Our saved documents now contain information for associating pages with page formats. The
MyAdjustFormats routine (Listing 9) extracts this information from the default format's format
collection when we load the saved document. In effect, the code finds new format reference IDs for
each format we flattened and stores those IDs with the pages that use them. In this way, we
completely avoid relying on the old (and invalid) format references.
Listing 9. MyAdjustFormats
OSErr MyAdjustFormats(MyDocumentPtr whichDocument)
{
OSErr err = noErr;
Handle theFormatIdxList = nil;
gxFormat theFormat, defaultFmt;
long pg, numPages, fmtIdx, *idxList, idx, listSize,
attribs;
Collection fmtCollection;
defaultFmt = GXGetJobFormat(whichDocument->documentJob, 1);
fmtCollection = GXGetFormatCollection(defaultFmt);
err = GetCollectionItemInfo(fmtCollection, kMyFormatInfoType,
kMyFormatInfoTagID, &idx, &listSize, &attribs);
if (err == noErr)
theFormatIdxList = NewHandle(listSize);
if (theFormatIdxList != nil) {
HLock(theFormatIdxList);
err = GetCollectionItem(fmtCollection, kMyFormatInfoType,
kMyFormatInfoTagID, dontWantSize, *theFormatIdxList);
numPages = listSize / sizeof(long);
idxList = (long *) *theFormatIdxList;
for (pg = 0; (err == noErr) && (pg < numPages); pg++) {
fmtIdx = idxList[pg];
if (fmtIdx != (long) nil) {
theFormat = GXGetJobFormat
(whichDocument->documentJob, fmtIdx);
err = GXGetJobError(whichDocument->documentJob);
}
else
theFormat = nil;
if (err == noErr)
whichDocument->pageFormat[pg] = theFormat;
}
DisposeHandle(theFormatIdxList);
}
return err;
}
TRANSLATING AND PRINTING QUICKDRAW COMMANDS
At long last, we're ready to look at the code that translates our QuickDraw commands to QuickDraw
GX shapes and prints them. To do the translation, we'll use the QuickDraw GX translator routines. We specify how we would
like the translation to be performed by passing one of the gxTranslationOptions to the translator
routines. (The routines and translation options are listed inInside Macintosh: QuickDraw GX
Environment and Utilities. ) Normally, the default translation options are all you need, and those are
what we use in Simple Sample GX.
The translation routines come in two varieties -- those that take a single QuickDraw PicHandle and
convert it to a QuickDraw GX picture shape, and those that let you execute QuickDraw commands
and create equivalent QuickDraw GX shapes as you do so. Converting a PicHandle to a gxPicture
shape is straightforward; therefore, we're going to take the second approach. Most applications don't
print by simply making a QuickDraw DrawPicture call, so understanding how to convert individual
QuickDraw commands to QuickDraw GX shapes "on the fly" is probably more useful. If your needs
are different, you can use the GXConvertPICTToShape routine to convert a PicHandle into a
gxPicture shape.
The routines we'll use are GXInstallQDTranslator and GXRemoveQDTranslator.
GXInstallQDTranslator tells QuickDraw GX to begin translating QuickDraw commands into
QuickDraw GX shapes, and GXRemoveQDTranslator tells QuickDraw GX that we've completed
drawing. These routines are used in conjunction with a third routine, called a gxSpoolProc, which
you create. Your gxSpoolProc routine will have the following format and will be called whenever
QuickDraw GX completes a new shape during the translation:
OSErr MyShapeSpoolProc(gxShape currentShape, long refCon);
The currentShape parameter contains the QuickDraw GX shape that the translator just created, and
the refCon parameter is a programmer-defined value that you pass to GXInstallQDTranslator. You
needn't use the refCon parameter (you can pass nil), but as we'll see in a moment, the refCon can be
very handy.
The code in Listing 10 shows the basic QuickDraw GX print loop, with support added to translate
QuickDraw commands into QuickDraw GX shapes.
The code in Listing 10 contains nrequire, require, nrequire_action, and require_action macros, which are
discussed in the article "Living in an Exceptional World" in develop Issue 11. These macros, which don't
require QuickDraw GX themselves, are now included in the QuickDraw GX interface file
GXExceptions.h.*
Listing 10. MyGXPrintLoop
OSErr MyGXPrintLoop(MyDocumentPtr whichDocument)
{
OSErr err;
long firstPage, lastPage, numPages, pg;
short oldPage;
gxViewPort printViewPort;
Point patStretch = {1,1};
gxFormat pageFormat;
Rect everywhereRect;
gxShape pageShape;
MySpoolDataRec spoolData;
oldPage = whichDocument->curPage;
/* Determine which pages the user selected to print, and print
only those pages that are actually in the document. */
GXGetJobPageRange(whichDocument->documentJob, &firstPage,
&lastPage);
if (lastPage > whichDocument->numPages)
lastPage = whichDocument->numPages;
/* Calculate the number of pages to print and begin printing. */
numPages = lastPage - firstPage + 1;
err = GXGetJobError(whichDocument->documentJob);
nrequire(err, PageRangeError);
GXStartJob(whichDocument->documentJob,
whichDocument->documentTitle, numPages);
err = GXGetJobError(whichDocument->documentJob);
nrequire(err, StartJobFailed);
/* Create a new view port for printing and set our translator
rects to "wide open" so that they include all data
we're drawing. For each page we print, call GXStartPage,
draw, and call GXFinishPage. */
SetRect(&everywhereRect, 0, 0, 32767, 32767);
printViewPort = GXNewViewPort(gxScreenViewDevices);
for (pg = firstPage; (err == noErr) && (pg <= lastPage); pg++)
{
/* Get the page's format and start printing the page. */
pageFormat = whichDocument->pageFormat[pg - 1];
if (pageFormat == nil)
pageFormat = GXGetJobFormat
(whichDocument->documentJob, 1);
GXStartPage(whichDocument->documentJob, pg, pageFormat, 1,
&printViewPort);
err = GXGetJobError(whichDocument->documentJob);
/* If there were no errors, set up the translator, draw
the QuickDraw data for current page, and remove the
translator. */
nrequire(err, StartPageFailed);
spoolData.printViewPort = printViewPort;
GXGetFormatDimensions(pageFormat, &spoolData.pageArea, nil);
GXInstallQDTranslator(whichDocument->documentWindow,
gxDefaultOptionsTranslation, &everywhereRect,
&everywhereRect, patStretch, MyPrintAShape,
&spoolData);
whichDocument->curPage = pg;
SetPort(whichDocument->documentWindow);
MyDrawContents(whichDocument->documentWindow);
GXRemoveQDTranslator(whichDocument->documentWindow, nil);
GXFinishPage(whichDocument->documentJob);
}
StartPageFailed:
GXFinishJob(whichDocument->documentJob);
err = GXGetJobError(whichDocument->documentJob);
GXDisposeViewPort(printViewPort);
whichDocument->curPage = oldPage;
StartJobFailed:
PageRangeError:
return err;
}
Several important things are going on in the code in Listing 10. You may recognizethe basic
QuickDraw GX print loop, which consists of everything in MyGXPrintLoopexcept the view port and
translator routines. The first thing we do in the print loop is create a gxViewPort object, because
GXStartPage needs to know which view ports we'll be drawing to. Only shapes drawn in the
specified view ports will be printed. For our purposes, one view port will suffice, so that's all we
create.
There are two basic print loop methods for QuickDraw GX: the first uses GXPrintPageto print a
single gxPicture shape; the second method uses the GXStartPage and GXFinishPage routines. In the
second method, the application specifies a list of view ports, and any QuickDraw GX drawing that
occurs in any of these view ports is instead redirected to a print file. Since our application draws
several shapes on a page, it makes sense to use the GXStartPage and GXFinishPage approach. If we
had only one shape to print (for instance, if we had used GXConvertPICTToShape), or if our
gxSpoolProc collected all the converted shapes into one gxPicture shape, using GXPrintPage would
make more sense.
Notice that the custom page formats that we've added to our application are supported, and they
require only a couple of lines of code. Recall that in this application we've decided to use nil to
represent the default job format. If the current page's format reference ID is not nil, we pass
GXStartPage the reference; otherwise, we pass the gxJob object's default format. This default format
is always positioned as the first format in a gxJob, so we can obtain it as follows:
pageFormat = GXGetJobFormat(whichDocument->documentJob, 1);
Before we can issue our QuickDraw drawing commands, we must call GXInstallQDTranslator.
Because all QuickDraw drawing calls are ignored by the QuickDraw GX printing routines, we need
to translate all QuickDraw commands
to QuickDraw GX shapes for printing. In the GXInstallQDTranslator call, we specify that we want
to use the default translation options, that we don't want to stretch patterns, and that our shape-
handling routine is called MyPrintAShape. Finally, remember the refCon parameter we discussed
earlier? Well, here's where it comes into play. In the refCon, we pass a pointer to a
MySpoolDataRec, which is defined as
typedef struct MySpoolDataRec {
gxRectangle pageArea; /* Page rectangle. */
gxViewPort printViewPort; /* View port we're printing in. */
} MySpoolDataRec, *MySpoolDataPtr;
The MyPrintAShape routine is passed each QuickDraw GX shape that is created as the result of the
QuickDraw translation. We can print each shape because a pointer to our MySpoolDataRec is passed
in the refCon parameter of MyPrintAShape. We print a shape by attaching the
MySpoolDataRec.printViewPort to the current shape, and then drawing the shape. We use the page
rectangle in the MySpoolDataRec to determine whether a translated shape will appear on the printed
page. If the shape isn't on the page, it doesn't make sense to waste time and disk space spooling it.
Listing 11 shows how cleanly this all fits together.
Listing 11. MyPrintAShape
OSErr MyPrintAShape(gxShape currentShape, long refCon)
{
MySpoolDataPtr spoolData;
gxShapeType theShapeType;
spoolData = (MySpoolDataPtr) refCon;
theShapeType = GXGetShapeType(currentShape);
/* Don't waste time spooling the shape if it's being drawn off
the page. */
if ((theShapeType == gxEmptyType) ||
(theShapeType == gxFullType) ||
(theShapeType == gxPictureType) ||
GXTouchesBoundsShape(&spoolData->pageArea, currentShape)) {
GXSetShapeViewPorts(currentShape, 1,
&spoolData->printViewPort);
GXDrawShape(currentShape);
}
return (OSErr) GXGetGraphicsError(nil);
}
Back in our print loop, we simply draw our page's QuickDraw representation between the
GXInstallQDTranslator and GXRemoveQDTranslator calls. QuickDraw commands are translated
to QuickDraw GX shapes and printed in one fell swoop.
PRINT ONE COPY
A soon-to-be-familiar sign of the QuickDraw GX application will be the Print One Copy command
in the application's File menu. The option to print one copy of a document without any dialogs is a
big convenience to the user, and it requires only a few minutes of coding to support.
In addition to changing the necessary menu setup and handling routines, we need to add a routine to
support Print One Copy (Listing 12). In this routine, we temporarily reset three of the printing
options that the user may have previously changed. First, we set up the print job so that it prints only
one copy. The number of copies last printed is stored in the gxJob object, and we want to make sure
that if the user previously printed multiple copies of a document, only one copy comes out of the
printer when Print One Copy is chosen. Second, we indicate that we want to print all pages of the
document, rather than the last page range used. Finally, the output shouldcome out of the printer . If
the job was last printed to a file, we'll need to change the job object's "Print to disk" setting. Once
again, we call upon the Collection Manager, although this time we access the job collection.
Listing 12. MyPrintOneCopy
OSErr MyPrintOneCopy(MyDocumentPtr whichDocument)
{
OSErr err;
Collection jobCollection;
gxCopiesInfo copiesInfo;
gxFileDestinationInfo destInfo;
gxPageRangeInfo pageRangeInfo;
Ptr oldCopiesInfo = nil,
oldPageRangeInfo = nil,
oldDestInfo = nil;
long oldCopiesSize, oldPageRangeInfoSize,
oldDestInfoSize;
/* Get the job collection and set it up to print one copy. */
jobCollection = GXGetJobCollection(whichDocument->documentJob);
/* Set number of copies to 1. */
copiesInfo.copies = 1;
err = MyReplaceCollectionItem(&copiesInfo, sizeof(gxCopiesInfo),
gxCopiesTag, gxPrintingTagID, jobCollection,
&oldCopiesInfo, &oldCopiesSize);
nrequire(err, ReplaceCopies_error);
/* Set page range to "all". */
pageRangeInfo.simpleRange.optionChosen = gxDefaultPageRange;
pageRangeInfo.minFromPage = 1;
pageRangeInfo.simpleRange.fromPage = 1;
pageRangeInfo.maxToPage = whichDocument->numPages;
pageRangeInfo.simpleRange.toPage = whichDocument->numPages;
pageRangeInfo.simpleRange.printAll = true;
err = MyReplaceCollectionItem(&pageRangeInfo,
sizeof(gxPageRangeInfo), gxPageRangeTag,
gxPrintingTagID, jobCollection, &oldPageRangeInfo,
&oldPageRangeInfoSize);
nrequire(err, ReplacePageRange_error);
/* Set destination to "printer". */
destInfo.toFile = false;
err = MyReplaceCollectionItem(&destInfo,
sizeof(gxFileDestinationInfo), gxFileDestinationTag,
gxPrintingTagID, jobCollection, &oldDestInfo,
&oldDestInfoSize);
nrequire(err, ReplaceDestination_error);
/* Print one copy of our document. */
err = MyPrintDocument(whichDocument);
/* Restore original number of copies, page range, and output
destination in case anybody uses that info. */
ReplaceDestination_error:
MyReplaceCollectionItem(oldDestInfo, oldDestInfoSize,
gxFileDestinationTag, gxPrintingTagID, jobCollection, nil,
nil);
ReplacePageRange_error:
MyReplaceCollectionItem(oldPageRangeInfo, oldPageRangeInfoSize,
gxPageRangeTag, gxPrintingTagID, jobCollection, nil, nil);
ReplaceCopies_error:
MyReplaceCollectionItem(oldCopiesInfo, oldCopiesSize,
gxCopiesTag, gxPrintingTagID, jobCollection, nil, nil);
/* Dispose of pointers that MyReplaceCollectionItem created. */
if (oldCopiesInfo)
DisposePtr(oldCopiesInfo);
if (oldPageRangeInfo)
DisposePtr(oldPageRangeInfo);
if (oldDestInfo)
DisposePtr(oldDestInfo);
return err;
}
The MyReplaceCollectionItem routine, which I created for the Simple Sample GX application, has
the format
OSErr MyReplaceCollectionItem(void *newData, long collectionItemSize,
OSType collectionType, long collectionID, Collection
whichCollection, Ptr *oldData, long *oldDataSize);
This routine replaces a collection item and returns a copy of its old data. It takes a pointer to the
collection data we want to store, and the size, type, ID, and collection to store it in. In the last two
parameters, you pass a reference to a pointer in which to store the existing data and a reference to a
long word in which to store its size. If the oldData pointer is nil, the existing data is not returned;
otherwise, a new pointer is created in the oldData parameter, and the data is returned there.
MyReplaceCollectionItem allows you to replace a collection item, execute some code, and then
restore the collection item. That's exactly what we do in the MyPrintOneCopy routine.
DRAG-AND-DROP PRINTING
The final thing that a QuickDraw GX-aware application should support is the new attribute of the
'pdoc' Apple event, enabling users to print documents by dragging their icons to desktop printers.
You need to make only a few changes to your current 'pdoc' Apple event handler, as you can see
from Listing 13. With these changes to the Apple event handler, our conversion of the QuickDraw
sample application to one that's QuickDraw GX aware is complete.
Listing 13. 'pdoc' Apple event handler, modified for QuickDraw GX
pascal OSErr MyHandlePDOC(AppleEvent *theAppleEvent,
AppleEvent *reply, long myRefCon)
{
OSErr err;
AEDescList docList, dtpList;
FSSpec myFSS, dtpFSS;
long itemsInList, i;
AEKeyword theKeyword;
DescType typeCode;
Boolean draggedToDTP = false;
Size actualSize;
MyDocumentPtr newDocument;
/* See if the document was dragged to a desktop printer. */
err = AEGetAttributeDesc(theAppleEvent, keyOptionalKeywordAttr,
typeAEList, &dtpList);
if (err == noErr) draggedToDTP = true;
/* If we dragged to a desktop printer, get the name of that
printer and then throw away the description list for it. */
if (draggedToDTP) {
err = AEGetNthPtr(&dtpList, 1, typeFSS, &theKeyword,
&typeCode, (Ptr) &dtpFSS, sizeof(FSSpec),
&actualSize);
AEDisposeDesc(&dtpList);
}
/* Get our document list. */
err = AEGetParamDesc(theAppleEvent, keyDirectObject, typeAEList,
&docList);
nrequire(err, AEError);
/* Make sure we've accounted for all of the parameters passed,
and count the number of documents passed in. */
err = MyCheckAEParams(theAppleEvent);
nrequire(err, AEError);
err = AECountItems(&docList, &itemsInList);
nrequire(err, AEError);
/* For each entry in the doc list, load it, print it, and */
/* close it. */
for (i = 1; i <= itemsInList; i++) {
err = AEGetNthPtr(&docList, i, typeFSS, &theKeyword,
&typeCode, (Ptr) &myFSS, sizeof(FSSpec),
&actualSize);
nrequire(err, AEEntryError);
/* Load the document. */
err = MyCreateDocument(kDefaultTitle, &newDocument);
nrequire(err, CreateDocFailed);
err = MyFSLoadDocument(newDocument, &myFSS);
nrequire(err, LoadDocFailed);
/* If we dragged to a desktop printer, select that as the
output printer for this job, and print one copy. */
if (draggedToDTP) {
GXSelectJobOutputPrinter(newDocument->documentJob,
dtpFSS.name);
err = MyPrintOneCopy(newDocument);
}
else
/* "Print" chosen from Finder. Show dialog and print. */
err = MyPrintDocument(newDocument);
/* Close the document once it's printed. */
LoadDocFailed:
MyDisposeDocument(newDocument);
}
/* When we're all done, throw away the document list and exit. */
CreateDocFailed:
AEEntryError:
AEDisposeDesc(&docList);
AEError:
if (gQuitAfterPrinting)
gQuitting = true;
return err;
}
For information on the 'pdoc' Apple event, see Inside Macintosh: Interapplication Communication, Chapter
4.*
WHERE TO TAKE IT FROM HERE
Now that you know how to add QuickDraw GX printing to a QuickDraw application, go do it!
Think of all the features you'll instantly support by being QuickDraw GX aware. The time hit to
gain this level of compatibility is minimal for most applications, and well worth it.
Still not convinced? Take the sample applications and print with both versions under QuickDraw
GX. Even this simple program shows that if your applications aren't QuickDraw GX aware, you (and
your users) are really missing out.
RELATED READING
- Inside Macintosh: QuickDraw GX Printing and Inside Macintosh: QuickDraw GX Environment and Utilities (Addison-Wesley, 1994).
- "Getting Started With QuickDraw GX" by Pete ("Luke") Alexander, develop Issue 15.
- "Developing QuickDraw GX Printing Extensions" by Sam Weiss, develop Issue 15.
DAVE HERSEY likes the annoying buzzing noise that ImageWriters and ImageWriter LQs make. He also enjoys listening to
Guns N Roses, but only while he's answering developer e-mail at Apple's Developer Support Center. Dave recently wrote
a QuickDraw GX printer driver for the Apple Color Plotter (which is from an era before the first Macintosh). When asked
why he bothered, he said "Well, it makes this cool wacka-wacka sound." Clearly, Dave is an audiophile for the '90s. *
Thanks to our technical reviewers Pete ("Luke") Alexander, Hugo Ayala, Tom Dowdy, and Ken Hittleman. *