Saturday, February 11, 2006

I pulled down the sources from Mozilla and grabbed the code I wrote for a Gtk+ dialog back in 1999 while I was at Netscape. Here is a screen shot of what the dialog looks like:



Looking more closely at the Firefox page setup and print dialogs, they have much of what I did (perhaps my very early work inspired some of this, who knows). I think the thing to do is not get too caught up in designing yet another layout, and clone what Firefox is doing (but make it a full-on Gtk+ widget, one that perhaps could be absorbed into Gtk+).

I guess the next week or two will be spent working on the printing architecture, coding up a Gtk+ widget, and getting my story straight on Windows .NET too. Following that, I'll implement File Selection, which should be a whole lot less challenging.

So, what exactly should be the architecture in the layout engine to support printing (as well as page setup)? On MacOS X, I already have something working. I simply handle the menuitem click for Print internally, and then tell the NSView for the window to print itself. Like this:

case MenuItemClassPrint:
{
NSView *nsView;

// get the view of the containing window

WidgetImpl *top = GetRootWidget();

if (top) {
CocoaWindowImpl *winImpl = dynamic_cast<CocoaWindowImpl *>(top);
if (winImpl) {
nsView = winImpl->GetView();
if (nsView)
[[NSPrintOperation printOperationWithView:nsView] runOperation];
}
}
}
break;

At least I am hoping it is that easy :-) Print setup on MacOS X is even easier -- displaying the print setup dialog is all that seems to be needed; the results are remembered by MacOS X and apply to subsequent print operations. In Windows .NET, it appears things are a bit more complicated. There is a PrintDialog object that will post a UI on behalf of the client, and will, should the user go thru with the print operations, callback on a class that inherits from System.Drawing.Printing.PrintController. The class inheriting from the PrintController interface implements methods allowing it to monitor printing process, and to draw the content being printed to a graphics device that represents the printer. I'm not really experienced with this yet -- I'll need to prototype to become more clear. I am not yet sure what methods support print setup, or if the result of the print setup needs to be maintained by the application or if it is system wide like on MacOS X.

On Gtk+, it's all bare bones. When the user clicks print setup, I will need to display a dialog of my own creation. I will also need to store the results of that dialog in the application. Similarly, when print is clicked, I will need to refer back to these settings, and post my own print dialog. When the user clicks ok in the print dialog, I will need to send the Window a message asking it to print itself.

This implies the following. First, there is no intrinsic need for the user to assign a JavaScript callback to either the page setup or print dialogs (I'll make an exception to this below, but the page setup and print functions can operate on their own). Second, it implies that I need to design some interface class called Printable that allows the toolkit to call methods on an object to, at a minimum, print itself.

I mentioned that the client need not be called on JavaScript listeners when the page setup or print dialogs are invoked. In reality, it would be nice to callback a JavaScript function when printing is started, as each page is printed, when printing has completed, and should an error occur, some indication of the problem (an error code at least, a string to go along with it at most), so that the application can monitor the complete printing process and communicate it back to the user.

A nice discovery today was that CUPS comes with a C-based library that allows me to do a wide variety of things, such a querying for the list of configured printers, as well as the default, communicate print settings, and initiate a print job. I'm sure that the backend to the Gtk+ widget I will need to create will use this library; had it existed back when I worked on the Netscape/Mozilla printing system, I am sure it would have been a part of the solution there too.

Before I can claim victory with menus, there remains some non-trivial work related to supporting standard dialogs. By this, I mean the dialogs that are needed to support print, page setup, and file selection ("save as" and "open") functionality.

Let's begin by discussing the layout of these dialogs. On the Macintosh, these dialogs are all provided by Cocoa in AppKit. I imagine that in .Net on Windows, a similar level of support holds true. Gtk+ provides a very simple file selection dialog (GtkFileSelection), but, unfortunately, print and page setup are missing, requiring the end user to come up with his or her own solution. This is a horrible state of affairs, and somewhat of a disappointment -- how could the Gtk+ community pursue projects like mono (bringing .NET APIs to Gtk+), and still not take care of this core issue?

Focusing in on the print/page setup dialog problem, a google search indicates that various people are working on a solution. I even worked on the problem way back in 1999 for Netscape/Mozilla (see http://lxr.mozilla.org/seamonkey/source/widget/src/gtk2/nsPrintdGTK.c). In the end, the project (e.g., Mozilla, Open Office, GIMP) is almost always forced to come up with its own solution (see the following Figure) to the problem, or steal something from someone else (I'm using "steal" in the Open Source sense of the word, of course).



Even support for Page Setup and Print is far from consistent among applications. Look at the GIMP print dialog, for example:



Here, GIMP doesn't implement a Page Setup dialog, but, instead, it rolls that functionality into the print dialog. While powerful, it is inconsistent in the sense that learning this dialog does nothing to help a user, say, learn how to perform a page setup/print sequence in Open Office.

On top of the above problems, there is also the issue of how to communicate with the printer. The strategy that I employed back in the day at Netscape made use of Postscript and lpr(1), which, for decades, was the method for printing. Nowadays, there are architectures like CUPS (which is compatible with lpr, according to the man pages). I'm sure that over the years, there has been more than one attempt to take a stab in the open source community of coming up with a generic printing architecture that serves the needs of everyone. Perhaps CUPS is it for configuration (i.e., page setup), and lpr is the way you perform the actual print.

Interestingly, CUPS is the engine that sits behind printer configuration on MacOS X, making the decision to base a cross-platform architecture on CUPS/lpr a near slam dunk (well, except on Windows of course). This is even more true with the realization that CUPS is also available on Windows (but "available" doesn't mean "present", so sadly, one cannot rely upon it).

So, this is what we have to work with:

MacOS X/Cocoa Print Setup UI: Native
MacOS X/Cocoa Print Setup Engine: Native with CUPS backend
MacOS X/Cocoa Print UI: Native
MacOS X/Cocoa Print Engine: CUPS/lpr

Windows/.NET Print Setup UI: Native
Windows/.NET Print Setup Engine: Native
Windows/.NET Print UI: Native (perhaps CUPS if installed by the user)
Windows/.NET Print Engine: Native

Linux/Gtk+ Print Setup UI: Undefined/Varies by application
Linux/Gtk+ Print Setup Engine: CUPS
Linux/Gtk+ Print UI: Undefined/Varies by application
Linux/Gtk+ Print Engine: CUPS/lpr

After getting CUPS configured here at home (to support a crufty old HP LaserJet 4MP that I have connected to the parallel port of a Fedora Core 4 server, which is acting as a print server) and configuring a separate Linux laptop and MacOS X to use the printer to convince myself it works, I decided that, yes, CUPS needs to be a part of whatever solution I put together on Linux under Gtk+.

Just for posterity, here is the cupsd.conf file on the print server:

LogLevel info
Port 631
<Location />
Order Deny, Allow
Deny From All
Allow From 127.0.0.1
Allow From 192.168.1.*
</Location>
BrowseAddress 192.168.1.255

<Location /printers/HPLaser>
Order Deny,Allow
Deny From All
Allow From 127.0.0.1
Allow From 192.168.1.*
AuthType None
</Location>

And printers.conf:

# Printer configuration file for CUPS v1.1.23
# Written by cupsd on Sat 11 Feb 2006 04:13:22 PM PST
<DefaultPrinter HPLaser>
Info
Location
DeviceURI parallel:/dev/lp0
State Idle
Accepting Yes
JobSheets none none
QuotaPeriod 0
PageLimit 0
KLimit 0
</Printer>