Mac OS • 1:15:06
This session covers the advanced features of the Mac OS X print architecture and how applications can create custom print interfaces and take control of the printing process.
Speaker: Paul Danbold
Unlisted on Apple Developer site
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Ladies and gentlemen, please welcome manager for the printing group, Paul Danbold. Thank you. That's me. How many people came to the printing session this morning? I reckon that's most. So you saw a lot of the things that we can do with printing, specifically on 10 this morning, and the focus for this session is going to be on how you do that. So we're going to be looking at some code. We'll start off and take you through some of the session APIs.
If you've got CarbonLib, let's see, the early 1.1 builds, you'll have probably seen them in the header files. Certainly they're in DP3 and DP4, so we would like you to start using them, and we'll take you through those. The next thing we'll do is we'll show you how we were able to display the print dialogs as sheets, and that's something you saw this morning.
Then I'm going to talk a little bit about the issues to do with the Print dialogs and issues for people who want to do non-printing type applications but still using the printing system or printing without a UI. We'll talk a little bit about Post-Crit printing. Then, I guess the main topic for today is how to write a plug-in, how to add a panel to the print dialog. If we have time, we'll have a Q&A session. Although I should point out now we've got a feedback forum later on this afternoon.
Quite a lot of the things we've got to show you today don't look too good at 36 point on a PowerPoint slide, so we'll be using our favorite application, Project Builder, and taking you through some code. So, back by popular demand, to talk about the session APIs, we've got Alan, Alan Beck from the printing team. He's going to take that first topic.
Thank you, Paul. So basically, what I'm, first I want to go through is basically the major objects that are, an application would deal with when you're printing through the Carbon Printing Manager. The first two are basically the PM page, let's see, the PM page format and the PM print settings. These basically replace the old 128 byte print record, which we are so glad to get rid of. The PM page format is basically, Relevant to all the options that are dealing with the page setup dialog and basically is how it relates to imaging of the document's pages.
That is basically what should be saved in the, what is being saved in the page format object. This object should be flattened and saved with the document, kind of like the old print record was. And another point to be made about this is you can use the accessor functions to get the data out of the object without or outside of the PMBGN and PMEND calls.
And one last thing about this is it is extensible with the application data. Then we have the print settings again which basically is normally not saved with the document. And here again it is extensible with application data. Thirdly is the PM Print Context. Basically this is an opaque graph port that is used to image pages within the print loop.
Then we come over to the PM Print session. This is the major change that we have added to the PM application, .hpm application since last WWDC. It enables the application to have multiple concurrent printing sessions. is also needed to enable the document modal printing which I demoed this morning. For multi-threaded apps, each thread can have one or more of these sessions attached to it, but multi-threaded apps can only, a session cannot be shared between these multiple threads.
PM page format and PM print settings objects can be shared between multiple print sessions. And lastly, I'd like to point out that A session can only display one printing dialog at a time. In other words, you can't have a, in the document modal case, you can't have one document showing a print dialog, another document showing another print dialog, sharing the same session. That's not allowed.
Then we go to the PM dialog, which is basically our object for the print dialogs. And then lastly we have the PM printer, which basically represents the target printer. You use the accessor functions of this object to get specific features about the printer, such as driver version, resolution, PPD file that's associated with it.
Now I'd like to go talk about the differences between the mapping between the session and non-session APIs. Basically, as you can tell from this slide, there's basically a one-to-one mapping. PMBegin, This is maps to PM create session, PMN, maps to PM release. And basically the rest of them are the same API just with the session added to the name and the session, the PM session as basically the first parameter to any of these calls. With that, I would like to go over to demo machine and show you A side by side comparison of a PM session and PM non session and PM session apps.
Here we are. Okay. Basically, here we have... This is the print loop. This is our sample print loop in the non-session case, and here we should have... The session with using sessions. So basically, they're basically the same. Let's scroll down a bit. There's this. Okay, here's the main. So everything starts. Let's go over here and scroll down here. There we go.
Basically here we have the PM Begin on this side and the PM Create session over here. And then we basically go down on through and then we basically convert the old print record and then do the page setup dialog. Over here it's the same thing except we have PM session convert old print record which you can see passes in the print session as the first parameter.
And do the same thing down here with page setup. And print dialog. Now we'll just go on down to the, basically the same thing. We have, basically in the page setup dialog we're trying to create a page format object. Then basically, and this one here is not a session based API.
And let's go over here, actually this, Basically, the non-session way of doing it is doing a PM new page format and with sessions it is a PM create page format. And then basically we go down and validate the page format and then here in the session case we just pass the print session. And then the same thing with the print dialog. Basically, we try to create a new PM setting. Here we do a new print settings.
And here we do it to create with the session API and the new print settings is the non-session API. And do a validate. One thing that we would like to encourage our developers, application developers to do is before putting up the print dialog to basically set the page range in the page format. This allows us when we put up the dialog to give some clue to the user as in the length of the document.
So if he ever wants to do a page range, he actually, we can actually tell the user, you know, what the last page of the document is. And they will get some clue as to what they might want to input into those two edit boxes. Then here we just do the print dialog and here we do the.
PM Session Print Dialog. And then another thing I want to talk to you is about the new, basically how we're recommending the new Print Loop.
[Transcript missing]
And then basically get the first page. Another thing we want to make sure that you remember to do is to only print the pages that the user has specified. Don't expect the printing system to skip pages that are not in the page range that the user specified. So we will print every page that is spooled to us or that is imaged to us.
Then basically, here's the actual print loop. Here we have the begin document, PM session begin document, PM session end document. Basically then we loop through the pages, start the PM begin page, and over here is the PM session begin page. Another thing that you have to make sure you do is make sure you set the port before drawing each of the pages. You cannot guarantee that the port is the correct port during your draw page. So you have to remember to make sure you set the port. Basically here we do the draw page and then we start backing it out which is the PM session end page.
End document and then basically at the end report any errors. One thing I do want to remind you of is, another thing I want to remind you of, is the objects are ref counted. So basically you can see here when we're done doing the print loop we do a release of the page format, a release of the print settings, and finally a release of the print session that you've created. Ref counting is happening, so we want to make sure, remember to release the objects that you've created. And that's basically all I would like to talk to you about the print loop. If you go back to the slides.
So basically some summary points, hints and tips for Carbon printing. As I said before, remember to set the printing port with each call to PMBegin page, after the call to PMBegin page. Remember to spool only the pages selected by the user. We will print every page that is spooled to us.
The third point is on Mac OS X, let us display the schooling progress dialog. We will be putting up the progress dialog. And that will be the way that the user will be able to cancel the spooling of the job. And then call PM session set error to report any errors that occur during page drawing.
We cannot detect every error that you may encounter when you're drawing your pages, such as QD errors or something of the sort. So therefore, make sure that you set the-- if you call PM session set error, then the print loop-- we will cancel out of the print loop and you can deal with the error.
That's basically all on the basic Carbon printing. Now I'd like to talk about the printing dialog. Getting the printing dialog to show up as sheets. Basically, we have two new APIs that we're asking developers to use. The first one is PM Session Use Sheets. Basically, three basic parameters. The first one is the session, which I talked about.
The second one is the document window. That is the document window that you want the sheet to appear attached to. Basically, it's your document window. The third parameter is basically a Proc Pointer, or UPP, and this is what gets called when the User dismisses the dialog. One thing to remember about when you're using Sheets, your call to PM session print dialog is that it will return immediately to you.
As in before, if you don't use this call, basically the call to PM print dialog will not return until the user has dismissed the dialog. But if you're using Sheets, it will return immediately to you and this sheet done proc will get called when the user dismisses the dialog.
And this is a sample of a sheet done callback proc. What we give back to you when the user misses the dialog is basically the print session again. The document window that the dialog is attached to again so you can get any data you need to. And the last parameter is accepted which basically did the user cancel or print out of the dialog.
Now what I would like to do again is show you how easy it is, if you go back to the demo machine, how easy it is to convert your app To using sheets, if you're already using the session APIs and you have your application split up correctly, or factored, not correctly, but factored in a way that makes this easy. Basically, this is basically the same print loop that I showed before.
The only...where is it? Beginning, of course. The difference is when we make the window, we are going to save some stuff off into a document info record. Basically we're saving off the page format and the... Other things that we may need and setting it to the rough count in the window.
That way during our callback we will be able to extract this information Out of the window. So basically let me just run it first to show you that when you're not using Sheets. Just go ahead and run it. Just basically, you know, hello world sample app. Go to print and it's the modal, application modal.
It is, it's a You know, it is truly the only application model, unlike 8 and 9. Where did it go? There it is. So we'll just cancel all this and quit. So to make this use Sheets, what I will do And here it is. Before I call PM session print dialog, I am going to basically create a UPP, or create my dialog done, proc UPP.
Make the call to PM session use sheets, which basically I'm giving it this print session, the parent window that I wanted to display against, and our UPP. And then I want to comment out these two lines where the print loop was actually done because this will be called, I will show you, this instead will be called in our callback proc.
Which is right above this. Basically here's our print dialog done proc. Basically if it's accepted the user said print, I extract our document info pointer out of the refcon and if I got the information I just do the print loop, passing in the page format and print settings. So with that done I can just compile it.
"It's linking and it's done. Okay, now we can run it. And if I do print, magically, we are now printing it as a sheet. Everything you get from sheets are there.
[Transcript missing]
If your print loop is factored out of showing your print dialog, and you're already using sessions, there is not that much more work needed to get the print dialog to show as sheets. So if we go back to the slides, I'd like to summarize.
Go back to the slides. I'd like to summarize basically some of the key points of using dialog sheets. First point is PM session page, setup dialog, and PM session print log return immediately to the caller. That means the values that the PM print settings and PM page format objects that are passed into it are not really valid until your callback proc is called.
That's the next point is basically they're not valid until your sheet done proc is called. And last point about dialog sheets, printing dialogs that are customized by the append-dibble method will not be shown as sheets. Even if the call is made to use sheets, there's a different API which is dialog main, dialog init, dialog main. Those calls will go through the normal Well, we'll execute normally and basically the dialogs will appear as the modal dialogs. With that, I'd like to hand back to Paul. Paul, where are you? Oh, there. And he will take you about some of these interface issues.
Okay, so I'm going to try not to confuse you. I'll probably confuse myself. I'm going to try to go through some user interface issues with Carbon printing. There's a couple of things that have come up in discussions with developers in the past year or so. I think it's worth going through some of these. There are some developers who want to print without user interface at all. There are others who decide that the page setup dialogue doesn't make sense for them because they have their own document format dialogue or equivalent.
And there are others that want to do things like, for example, configure a printer or download fonts to a printer. They want to use the printing system, but they have no need for any of our user interface elements. So I'll start off with just a reminder of a point I made this morning that, like many other things in Carbon, we've decided, or rather it's been decided, that we should split our headers and our frameworks into two. So we have PM application, which used to contain everything, now just contains the function prototypes for all the page setup and print dialog calls. Pretty much everything else has been put into PM core.
So there's all the accessor functions, all the functions to create and deal with page format objects and print settings objects. And last of all are constants and typedefs, etc., in PM definitions. So if you have got need to print without a UI, there's no need for you to compile against, link, or load the UI-related framework.
Now, this is where I'm going to get tripped up, but I'm going to attempt to avoid stating the obvious, but to explain why we have a page setup dialogue, why we have a print dialogue, why the two are different, and why it's not a good idea to try to link the two together, but what we're trying to do to address some of the needs that you have.
So, simply put, the page setup dialogue is all about defining the parameters of the logical page into which your application is going to image its document. I think most people understand that, but obviously for a lot of users, they associate page setup with what they're going to do subsequently at print time.
So, it makes sense for them to pick a paper size as their document size. And for that reason, we have a pop-up in the page setup dialogue which lists printers so that you can format a document for a particular printer, and we have a paper size pop-up, scaling orientation, etc. So, that's well known. And I think well understood and related to by the average user.
And in theory, those settings are quite independent of what you'll do at print time. And of course, the print dialogue is for things that really have very little to do with how the contents of the document are imaged. You pick the number of copies in the page range, etc.
The two are distinct, but of course, the distinction is often probably lost on users of some applications. And because... users quite often, I guess, decide that they're going to print to a different printer at print time. Some applications have allowed you to play switcheroo and get back to the page setup dialog from the print dialog.
We're going to try to address this issue by providing an API and providing a facility for you to load a paper selection PDE, in other words, an extra panel in the print dialog, which can be used, as I said, to pick a paper size, to pick an orientation, possibly scaling, et cetera, and to avoid the need to ever switch back to page setup.
So, not in DP4, but in a subsequent release, we will add another API to the growing list of APIs we have for printing. It will allow you to tell the printing system to load this panel, and I think it will take care of the need that some of you have felt in the past to provide a button to take you back to page setup from the print dialog.
So, there's a couple of repercussions of doing this. If you can pick a paper type and orientation at print time, maybe you think you don't need a page setup dialog. And there's some applications, which I mentioned, have their own document setup or document formatting dialogs, which often overlap the functionality of the page setup dialog. So, we are quite happy if you choose to live without a page setup dialog if it doesn't make sense for your users.
If you do that, though, you've got to make two choices. You can either call the PM application APIs to emulate what happens at page setup time to set a page size, or rather a paper size, as scaling and orientation. Or you can bypass the thing altogether and call this API, which we've got listed here, PMSetAdjustedPageRect, which just goes straight to the heart of things and defines a logical page.
And then you can also call the PMPageFormat object, which is a very simple application, into which your application can image its document contents. But the thing is that you do have to call either the APIs that emulate what happens at the page setup time or this other API, because you can't go into the print dialog. And you certainly can't go to print if you haven't created a valid PMPageFormat object.
If you do this sort of thing, of course, you're incurring page to paper mapping code at print time because, of course, you're now allowed to create a document which doesn't map cleanly to the pages you've decided to print it on. You can decide, of course, whether scaling is appropriate to fit the document to the paper or whether you want to clip or tile. There are various solutions.
What we're going to do in the Print Center application is provide a UI so that the user generically can tell the printing system how you should, by default, deal with these page to paper mapping issues. Maybe I'm printing to an A4 printer and I'm always getting documents that were formatted for US letter. Well, I might just say that I want the printing system to always scale the pages for me.
I would do that via Print Center. Of course, applications might want to get into control of this, so we'll also be adding an API, again, post-DP4, but it would allow you to tell the printing system how you want us to do the page to paper mapping. So hopefully that addresses some of the issues that have been floating around between the developers and Apple over the last year.
Last set of issues to do with printing, as I mentioned, is What if you want to write a utility that downloads fonts to a printer? What happens if you want to write a--? A utility to configure a printer or to calibrate a printer. In the past, people have written standalone applications to do that. And in the past, a lot of applications, sorry, a lot of printers have had a nice user-friendly AppleTalk connection.
And that has been fairly straightforward. But now we're dealing with printers. They have USB connections. They have, I think in the future, we'll see a lot of FireWire printers. You've got different network protocols. So if you're writing one of these utilities, you've got to face the fact that you might have to communicate with a printer. You might have to communicate with a printer over various channels. And then you look at Tioga and you say, well, we've got all these nice IO modules.
Why can't we just use those? And our answer is that, well, maybe you could and we could publicize the APIs that host IO modules and printer browser modules, et cetera. But our preferred solution is to Let you write plug-ins for Print Center so that if you need to write a font downloading utility or printer calibration utility, you do that as a plug-in instead of a standalone application.
So we'd encourage you if you're in that sort of business, come and talk to us or email us and let us know what you want to do. And of course, I think long term we may have to open up some of these APIs, but certainly short term our strategy is to use Print Center as a host for those sort of operations.
The last thing we've got here on the UI front is It's really a word of warning to anybody who wants to print without a user interface. There are APIs that we've provided. They're in PM Core that allow you to do things, the obvious things, like set the number of copies you want to print, set the page range. But of course, you'll notice, we haven't provided APIs so that you can set layout options for doing nup or printing with a cover page, et cetera. And we could add those, of course.
But it's not in our plans. If you think you want to print without the print dialog, and you need access to that sort of functionality, then you should talk to us. Of course, there is a workaround, which is use a UI, create those settings, save them off as flattened print settings objects, and then use them in your UILess printing utility. But I think you realize that for basic UILess printing, you shouldn't have the full set of features that you get if you display a print dialog.
Okay, my second topic is for people who I either like to generate their own postscript or occasionally find themselves printing documents containing postscript like EPS graphics. And it might be interesting just to get a show of hands, how many people write applications that generate their own postscript? Fair few. Maybe 20, 30 people in the audience.
And how many of you have applications which can take an EPS graphic into a document and then you have to deal with printing that? Okay, about the same number. Okay, so as you probably know, When you print through, or I think as I pointed out, when you print through Tioga on X, you're effectively printing to a Quick Draw printer because by default, we turn Quick Draw into PDF and then we turn PDF into Post Script or Bits.
So if you want to generate your own postscript or if you want to hand EPS into a spool file, you're going to have to use some of the APIs that I've listed here. And I'll take you through them fairly quickly. First call is something we put in the original version of the Carbon Printing header file. PM is PostScript driver.
On Mac OS 8 and 9, if you call that, all the Carbon Printing manager does is just check the WDEV field of the print record, the current driver, and if it's 3, you know you're printing for the LaserWriter driver or maybe Adobe's driver. And we'll return true as a return for that call. If you call that routine on 10, though, it'll always return false because, as I said, by default, we're a Quick Draw--we behave as a Quick Draw printer and we only turn Quick Draw calls at the bottleneck level into PDF.
So what do you do about it? Well-- You should start looking at what we call the document format APIs and there are four of them. First one on the list is get document format generation and what that will do is that will return to you the list of spool file formats that the print job creator and the converter are able to generate for you.
Get document format generation actually returns to you the current flavor of spool file format that will be generated. And likely as not, the result of that call will be PDF. Actually, we return a MIME type, but the result will tell you that it's PDF by default. Set document format generation is a way to tell the printing system, I want a particular spool file format. So you can specify PDF. You can also specify picked with PostScript.
[Transcript missing]
The last batch of API's pretty straightforward. They make it a little bit more easy to use the old PostScript pick comment mechanisms. Using these in your page drawing code, you can insert PostScript into the spool file.
And in a moment I'll show you a little bit of code to demonstrate how those document format APIs work. I'm going to talk about one more issue here first. So the previous APIs were all about putting PostScript in the spool file and they're really talking about doing things with PostScript at the page level that are difficult or impossible to do with Quick Draw.
And that only goes so far. And there are people who want to have finer control over the PostScript they put into the spool file and the PostScript that gets sent to the printer. The common case is you want to add your own proc set in the prolog of the job or you want to get in there and change or add PostScript to the document or page setup parts of the PostScript file because your UI for printing allows you to do things like invoke some trapping in the rip, for example. So we need a way beyond the APIs I just showed you to tell the printing system to put PostScript in different ways. sections of the final output.
The prototype shown here is a work in progress. So we may change the name, but I think the arguments are pretty solid. Basically, it's a way for you, in your printing code, to add a bunch of PostScript snippets to the job and send it downstream so that those PostScript snippets can be added at appropriate places in the final output that's sent to the PostScript printer.
And That CFArray is an array of CFDictionary's and each CFDictionary contains a tag or a keyword and a snippet of postscript. And if you look at the tech note that's mentioned here, that's all about writing PostScript output filters for LaserWriter 8.7, you will see that, certainly at the back of the tech note, there's a big long set of tables which list all these tags.
And they map pretty closely to Adobe's document structuring conventions. They identify specific points in the PostScript job. And basically all you would need to do is create a list of PostScript snippets that you want inserted at these points in the PostScript file and hand that to the printing system. Basically attach it to the print settings object when you're printing. And what we will do at the server side of Tioga when we get this dictionary is we will dutifully go and insert your PostScript snippets in the PostScript stream that we send to the printer.
If you're interested in using the PostScript script, you can go to the post.script.com/postscript. And if you're interested in using this API, you should certainly talk to us because it's a work in progress. It obviously has use for high-end printing applications. I think I know who you guys are, but just in case I miss anybody, make it known that you're interested in this API and we'll work with you and get it right for the first time it's introduced into probably the next version of Mac OS X that goes out to developers. Lastly, if we can just switch over to the demo machine. Which demo machine? Is it this one? Okay. I'm just going to show you that some of those document format calls to show you how easy it is to, if I can find it.
Bear with me a moment. Sample code? Ah, Paul prints sample. Okay. So, hopefully that point says okay. Find is my friend. Well, we don't have a pop-up to get to functions. Okay. All I'm going to show you here is the sort of code that you would use if you wanted to tell the printing system to generate a PIC with PostScript spool file instead of a PDF spool file.
The first point I want to make clear is that if you're going to use code like this, you don't want to be calling this inside your, certainly not inside your page drawing part of the print loop. You need to call this before you even invoke the print dialog because you're telling us to generate a PIC with PostScript spool file. When we invoke the print dialog, we need to know whether we can enable the preview button.
And we of course know how to preview PDF, but we don't have anything right now which will preview PIC with PostScript. So if you've called the API to tell us to generate a non-PDF spool file, we'll be disabling the preview button. And then it's an exercise for the reader to write your own pic with PostScript Previewer.
So, the first interesting call is to PM session get format, get document format supported. Basically, here we're just providing, we're just getting back a list. It's an array of CFStrings that we get back as a result of calling this. And what's happening under the hood is that we're going to the printer module and the converter that belongs to the current printer, which is associated with the current print session, finding out what it can do and returning that list of supported spool file formats to the caller.
Next, we're just going to go through this list. We see how many are supported. Typically, if you've got a PostScript printer as your current printer, num supported formats will probably be three. And three is because the first result will be the default spool file format, and the second one will be PDF, and the third one will probably be picked with PostScript. But you want to make sure. So we go through this array, and we check each entry in the array to see if... It matches the string defined by KPM document format picked PS, which is defined in PM definitions.
And that's defined as a MIME type, so it's application/pick_ps. If we find a match, let me call this routine, pmset document format generation. And let me just scroll across a little bit here. What we're doing is we're saying, for this print session, I want you to generate a pick with PostScript spool file, so I want you to go into LaserWrite rate compatibility mode.
And I didn't expand upon it, but this third argument is there to provide you with a list of graphic contexts that you're going to be using to draw your pages. By default, and null means the default, I'm just going to be using a quick draw context to draw my pages.
But I can provide it with, I can tell the printing system, I'm going to draw my pages with core graphics, so I'll just tell it that I want it to core graphics context. Or I can say I want to use a combo of the two, so I can use both quick draw and core graphics to image my document during the print loop.
And I think if you went to earlier sessions, you'll know that a lot of the core graphics APIs are being opened up for you to use even within Carbon applications. That's pretty much it. The rest of this routine just cleans up and higher up in the code, but I don't think I need to show you in the page drawing portion of this sample code, we emit PostScript and that goes into the spool file. I think we can go back to the slides for a moment.
Just to summarize, for typical applications printing on X, Tioga appears as a quick draw printer so you can't send postscript via pic comments. PDF is the mainstream spool file format and we'd rather everybody use PDF of course. It's got lots of advantages like you can preview it and it's page independent, etc. and resolution independent. If you want to generate your own postscript, and certainly we understand a lot of applications today do that and will probably do that for a while, we are allowing you to do that and that's where those document format APIs come in.
And lastly, if you want to do more advanced PostScript printing, the prototype API that I showed on a previous slide, we will be introducing that. If you think you're going to use it, then let us know because we'll work with you and make sure the API meets your needs.
And now, We're going to go on to something very exciting. We're going to talk about how to write a plug-in to extend the print dialog or the page setup dialog. And for that, I'm going to bring Mike Conley on stage. He's going to take you through all the nifty code.
So first I'll thank Paul for leaving me with plenty of time to provide you in eye-glazing detail some of the code and information about how to write printing dialog extensions for Tioga printing dialogs. So you may have remembered this slide from the beginning of the session, or the first session this morning.
And I won't go into too much detail about it, except as I need to pad the rest of the session. The first method which is no longer supported, as you know, or not no longer supported, but is no longer encouraged, is the append-dittle method of extending a dialog.
And we won't talk about that much because everybody knows how to do that and you know what the downsides on that are, which are basically you don't get sheets and you don't get status in the status panel and all that good stuff. The print dialog extensions are the preferred way of extending dialogs in Tioga and the new Mac OS X printing manager. There's only support on a Mac OS X, so you don't get them on OS 9.
They are compatible with document modal printing, so you get sheets. And you can have multiple print dialogs open at the same time, same with page setup. You can organize your controls in your user face into multiple panels with different titles on each panel. So the user has a, probably a better user experience rather than having everything crammed into one dialog. If you're an application or a printing module, you can override the controls in the printing dialogs. Applications can override controls in page setup and the printing modules can override controls in both. In printing only.
Applications can also override controls in the print dialog. And eventually, hopefully fairly soon, we will have implemented code to handle interdependencies between the extensions. So if you have user options that depend on other user options in the dialog, we will notify you when you need to update your settings because something else has changed.
So what does a printing dialog extension look like? All the printing dialog extensions are based on CFPlugin. CFPlugin is the preferred mechanism for creating plugin code modules on OS X. And it is to some extent based on, actually to a large extent, based on the Microsoft COM plugin model. So this diagram here gives you sort of a real basic overview of what that might look like. The primary entry point for any CFPlugin is the factory function. The purpose of the factory function is to return an instance of an interface.
And once you get that, you can then go ahead and get a reference into the function pointer table for your plugin. Or rather, the host can get that. So you publish, you basically export a single entry point, which is your factory function. And then when we load your CFPlugin, we do all the work to get a hold of your function table and jump into your routines.
Inside your plugin you'll have the IUnknown interface. This is a little piece of code that sits up at the top of your CFPlugin and provides the support for the CFPlugin mechanism itself. It handles ref counting, increment and decrement, and it also handles the query interface function, which gets the interface.
The rest of your interface goes in below that. There are some pieces that are specific to the printing manager. So any printing manager plugin will use these particular functions. And then following that are the functions specific to your particular plugin. So for printing dialog extensions you'd have the PDE interface functions there. If you were doing a different kind of plugin like a printer browser module plugin, you would have the printer browser module API. And then of course the rest of it is the implementation of all that good stuff.
Just a quick note on terminology. We tend to use the words PDE or the terms PDE and user option sort of interchangeably. A user option is really a collection of controls that appears in single panel in the printing dialogue. Whereas a PDE technically is the plug-in that implements those controls and in fact one plug-in can implement more than one set of user options. So even though it tends to be a one-to-one mapping, it doesn't necessarily have to be. I just thought I'd point that out in case anybody's confused about why we sometimes call them user options and we sometimes call them PDEs. There's a distinction.
So how do you load a PDE? Well, applications register their PDEs with CFPlugin before you open up the dialog. And then the printing manager will go ahead and collect all the information about them that it needs to. It will actually load them and put them into the dialog as the dialog comes up.
If you're a printer module, we will load your plug-in for you. So in CF parlance, application PDEs tend to be dynamically loaded and printer module PDEs are static. That is, their loading and their registration occurs, is all set up on the disk. Whereas an application can go ahead and register the PDE dynamically in runtime. And finally, if you're an application also, when you load your user options, they will, in the hierarchy of things, override any other user options that are of the same type. Printer modules come second and then finally the system user options come last.
So here's the CFPlugin API as I mentioned earlier. The first thing is a factory function, which as I said, returns essentially a pointer into a table that contains these three other functions, which is query interface, add ref, and release. So what you have to do as a plugin writer is implement these.
What we have to do on our end in the printing dialogs and for any other of our host software that loads plugins is call your factory function to get back this pointer block. So we're going to talk about that in a little bit. Paul Danbold So the first thing is a query interface.
The query interface is a method that we use to get a hold of the actual instance for the API that we're looking for. In this case, printing dialog extensions. Add ref and release are there for ref counting and the other thing is that we're looking for a method that It's bookkeeping.
Printing Manager Plugin API. OK, all Printing Manager plugins have to have these three functions implemented at the top of their list of functions. The first one is Get API Version, which, as you might guess, returns to us the version of the API that you were using when you created your plugin.
And you get that from the header file. And you just return that constant. That way we know what we're calling into. Retain and Release, again, are ref counting. And those are implemented separately in the event that you use two separate objects to store the function tables for your IUnknown versus your PDE interface. So we can do ref counting on the two for you. If it turns out that's not the case, then internally you just tie them together and make them count up the same thing. So you have different ways you can implement that.
Printing Dialog Extension API. Okay, so this is the actually important part. Prolog is the first function that you get called, or first function of yours that will get called by the printing dialogs when they come up and load your plugin. Prolog allows us to acquire information about your plugin from you. We will get things from you like the creator code for your for your printer module or for your printer module or application.
A type code that identifies what kind of user option you're implementing so we know whether or not you're going to override somebody. Apple will provide a list of types for our universal and standard user options. So universal things like page range of copies or orientation, things like that.
So that if you want to override those, you would provide a user option with the same type and then we would know to replace ours with that. In addition, you'll pass us the dimensions of your user interface so we know what the maximum range that you want to display in are. And one or two other things that I can't remember offhand.
Anyway, so if you ask for more space than we can provide you, we won't load you right now. We'll call you terminate function and tell you why and then you can go ahead and redesign your user interface to fit in a little smaller space. To the extent possible, we will always try and accommodate you.
Initialize then gets called. If we get your stuff and everything's cool, we'll call your initialize function. And one of the things we'll pass to you is a reference, which you need to store and pass back to us any time you call into any printing manager functions that your plug-in may have to call. And we can talk about that in a second.
Paul Danbold Oh, and one of the other things you'll get passed, which you supplied to us in your prolog function, is a context value. So you create a context value. It's basically a refcon. You can do anything you want, 32-bit value. You pass it to us in a prolog call, and we will pass it to you in every subsequent call we make to you. So you can store global data or whatever you want to in that.
So at initialized time then that's your time to go ahead and load your controls. We also supply you a user pane reference, a reference to a control manager user pane into which you have to embed all of your controls. So all of your controls for your PDE will be embedded controls.
They will use the control manager, sorry, the Carbon event model to receive events. The application does not have to support the Carbon event model, but your PDEs do. So you embed those in that pane. That pane determines the pane that will be displayed when the user selects the panel from the pop-up menu in the dialog box.
Just before your panel becomes visible, you'll get an open call so you can do anything you might want to do. Just before your panel goes invisible, you'll get a close call just in case there's anything you want to do. And let's see, sync is an interesting one. Sync is the call you get when we want you to either read from or write to the session objects, either the page formatting object or the print settings object.
And so this is how you'll get one of these shortly after the initialize call and you will be able then to initialize your controls. Likewise, when your controls change, you can request a sync call if you detect that something has changed that you want to notify us about.
And when you request that sync call, that's one of those callbacks I was just telling you about, you pass your reference, you will then get a call into your sync function so that you can then update the page format or print settings object depending on which dialogue you're in.
This function becomes even more interesting later on when we provide dependency handling, which I'll talk about a little bit later.
[Transcript missing]
So, this is basically a very simple printing dialog extension. It has a single control in it for, just basically for demonstration purposes. Up at the top of the function we're going to, well we have all our declarations and all that good stuff.
Oh yeah. Let's see, do we actually define this part? We defined this part earlier, I think, someplace. No, it's down farther. In the custom Info.plist file for your plugin, if you're doing a printer module, printing dialog extension, you have to define these various This is the ID for your factory function and you specify the name of the actual factory function here.
Then down below, you tell it which types of plug-in that factory function can return API instances for. And here's the same ID for the factory function here and here. And this is the type for the printing dialog extension plug-in. So all of your printing dialog extensions will have this value on the left, and then you'll have an ID for your plug-in factory on the right.
We make some notice in here about how you may want to During your prolog call, open up your resource fork to get data for your controls and so forth and so on. And you can do that with some sample code here. We show you how to define a bundle identifier uh... which has to be specified in the C... uh... plist file and uh... then from inside the code you can actually do CFBundle get bundle with identifier.
You pass at that bundle identifier and you get a bundle reference and then you can do CFBundle open bundle resource map to actually get the resource file and you get back a resource manager reference number just like the good old days and uh... then you can do cure res file and all that good stuff And you use CFBundle to close the resource map again.
So up at the top of the file we have the ad ref function for the I unknown interface. And you'll notice that it basically takes this object here and increments the ref count field in it. So, add ref just increments the ref count and release will decrement the ref count and at zero it will unregister the factory.
Unregister the instance for the factory function. So that's pretty much standard boilerplate. Any CF plug-in that you do is going to have to do the same thing. So I encourage you to go read the CF plug-in documentation for how to do this. Eventually it was hoped that the Project Builder System will be able to generate CFPlugins directly and a lot of this sort of crufty stuff at the top will be taken care of for you by the compiler and linker.
Then following that we have the Printing Manager's, Printing Manager plugin APIs for retain and release which do very much the same thing. Get API version here basically takes a PM plugin API version object and populates it with the constants from the header. So you just load this thing up and then you return it.
These values are very similar to the verse resource values that we've been using all along in Mac OS. Create plugin interface. I don't want to spend a whole lot of time looking at CFPlugin stuff, but basically you create what's called a Vtable. That terminology is vector table. It's pretty common for C++ sort of stuff.
But basically you create this function pointer table and you populate it with pointers to your various functions and you return a pointer to that as part of the query interface call. When we call into your query interface function asking for a specific API, we'll get back a pointer into that function table and that way we'll have access then to all your printing dialog extension API functions.
So here's the factory function. And it creates the IUnknown Vtable that we initially get back through which we call query interface. One of the interesting things about query interface, I guess I'll just point out real quick, to the extent that anything about query interface is interesting, is that it goes ahead and it compares, it looks at the type of API that you're asking for and returns the appropriate pointer based on which type you asked for.
So a given factory function can return different kinds of APIs depending on your plug-in. In this case, you'll likely only have the one, but CF plug-in allows you to have more than one in there. Again, this is a way you would implement perhaps more than one user option in a given PDE, right? You'd have a factory function that would return more interfaces or you'd have more than, yeah, you'd be able to return more than one.
So the first of the printing dialog extension functions that we're interested in here is the prologue. And as you can see, okay, so we have the creator, the context code that we expect back from you, the creator, the user option kind, the title, that's what I forgot, I knew there was something, and the max and min extents.
The title is the title that you're going to see in the pop-up menu. For universal and standard user options, we provide the title, and we'll simply ignore anything you pass back. So the title really is meaningful only if you're implementing a custom print dialog extension. The intent is to provide a uniform, constant location for the user to be able to find certain types of features, like print quality or paper source.
So we will provide the title, we'll localize it and all that good stuff, and it saves you the trouble if that's all you're doing is overriding an existing one or providing a standard one. If you're doing a custom, you're on your own. You provide us a title, we'll display it. Okay.
So we initialize our context, we fill in our max and mins, we get our title with CFString up here. And we put in a creator and if we had an error, we return an error. So, next thing we get is an initialized call. Uh, and we pass in that context code that you supplied us with.
Uh, also some flags which tell you certain things about, or we get back from you certain flags which will tell us what you'd like to be able to do and various other things. Those flags are defined in the header file. We pass you your reference number, so that's your special reference number. Don't lose it. And we give you a user pane into which you can draw and the print session. So you can get your initialization from the print session objects there.
So, you check, first thing you do is you get your context, you check to make sure your context is cool. If you need to, you can save off a copy of the pointer to the window by doing get control owner from the user pane. You can get your boundaries for your user pane here and save those off. And right here we're keeping a copy of them around. This function here is our internal function. It gets our little--it just gets a rectangle that defines the bounds of our control.
And then we create a new control right here. And we save our... Reference off inside our context. The next thing we do is we embed our control into the user pane. And we make our control visible because the user pane will be invisible. Your control won't show up until we decide to show it.
So you go ahead and set yours to be visible as soon as you've embedded it. And that way when we bring up the user pane it will actually show up. If you make your control invisible, then you could make it visible when you get your open call, but it makes life easier for you if you just do it right away.
And we don't have any particular flags to set here, so we just do that. And we make an internal call to our own sync function inside this PDE here to initialize our values so you can reuse your code that way. So that works. I get summary text. Again, you get your context. Whoops, a little too far. And we expect back from you a couple of CFArrayRefs. So you're going to create two arrays, one array of titles, one array of summary strings.
And the title corresponds to the particular control, and the summary string will tell us what the value of that control or what the setting on that control is. And we will display all that text in the summary pane. So you'll get a call to get summary text when the user actually brings up the summary pane. If the user never does it, you'll never see this call.
Again, you use CF to create the array, get your control value, push your strings in here, Here's your title for the control and stick them in the pointers provided and return. Sync gives you a context and a print session. So from the print session, you can get all the relevant objects that you need to reference in order to get your control settings. and a Boolean to re-initialize the plugin. Okay, so the Boolean tells you which way you're going, whether you're reading or writing. Okay, if it's true, you're reading. If it's false, you're writing. So.
And so here we go. There are going to be probably somewhat slicker ways to get the print objects out of the print session in the final APIs, but right now, we just grab them out of there. And then we do a PM ticket get Boolean to find out what our setting is.
And then we set the control value to one of the two values-- true or false, essentially. This else statement is the else that says-- OK, so that's if we were reading. If we're writing, we go to the control, we get the control value, and then we set the print settings ticket either with a true or false value.
Here's the open call. And as you can see, we decided that we really don't need to do anything when the open call comes, but there it is anyway. You still have to implement it. I mean, if you don't implement it and the pointer is null, then we won't obviously be calling you. But, you know, it'd be nice to--it's there if you need it. It's basically just a hook.
And close, same thing, we decided we didn't need to do anything there. And terminate, as you can see, we get a status and the context. The status will tell you why you're being terminated. And if you've been a good boy or girl, you'll get a no error there and that'll be fine. And if you do get an error there, there's not a whole lot you can do about it. It's purely for your information. It's mainly a debugging tool so that you know that you've done something wrong.
Assuming of course that everything survived long enough for us actually to call you at that point. Let's see. Then you dispose of your controls, dispose of your context, and return. And here we have some of our internal functions, how we initialize our context, we just do a new pointer, and so forth. So I think that's enough of that. So we can go back to the slides, please.
So, in summary, with Printing Dialog extensions, use standard control manager controls calls, You'll be using, you'll be embedding your controls into a user pane so that we can show it and hide it easily and we don't have to know anything about what your hierarchy looks like. Use the Carbon event model to handle events. For the most part, you probably won't even need to handle events unless you're doing something special with the control where you actually need to intercept events and so forth. If you do, you need to write an event handler.
Apps do not need to support the Carbon event model in order to use these, but the PDE itself does. So even if you're not, you know, even if you're an application that's using the old style events, you'll still work fine if you have your own PDEs and you want to load them and get events and all that. That'll all work.
You synchronize with the PM session objects, the page format and print settings objects in your sync call, either reading or writing data, and provide useful summary text for the user. Useful, localized summary text. So that when the user brings up a summary pane, he'll be able to tell at a glance what your settings are. Keep them short and sweet, but informative. Don't write tomes and tomes of stuff.
More don'ts. Don't draw outside your user pane. Please don't draw in our dialogue. Don't try to talk to other PDEs. Don't talk to strangers, right? We are going to provide you with a mechanism to register dependencies on other types of user options and we will automatically call your sync function when those dependencies are triggered. So don't go trying to read other people's data to try and figure out what's going on. We'll let you know.
Don't write another PDE's data. Don't go messing around with somebody else's settings behind their back because then we don't know that that's happened and they don't know that that's happened and everybody gets really confused and things get messy. If you have a user pane--user panel controls in there, try not to scroll controls. Scrolling controls is frowned upon these days. Just try to fit everything into your panel. If you need another panel, make another panel. You can have as many as you like.
And don't bring up lots of extra dialogues to try and get more stuff on the screen if you can. An alert is one thing, that's fine, but don't have lots and lots of dialogues. If you need more dialogues, add another panel. You can have as many as you like. And with that, I'm going to hand it back over to Paul, who is right here someplace.
So I've just got two things to add to what Mike had to say. First is that We don't have it posted yet, but I believe in a week or two you'll find on the Carbon documentation site there will be a very comprehensive and complete write-up on how to develop PDEs. And we'll get that sample code out to you, and I think you'll find it rather easy to write.
And I would say, just showing how easy it is to write, that a few months ago I was, I guess, brave enough to try to write one when the code wasn't nearly as organized as it is now. And I even got my panel up on the screen. Of course, since then I think we've deleted it, but... Just proves that it's easy to do. Now we would have had a Q&A session but the clock in front of me is saying that we've got, let's see, 38 seconds to go.
There's a feedback forum and I wish I could tell you where it was. Maybe the next slide will tell us. In A2. So if you have more questions about printing and certainly if you have questions about graphics or anything relating to the two, I'd ask you to join us at five o'clock in Hall A2. Thank you very much.