Printing
Most
of the time you won't have to do anything to get basic printing support
in your PyGUI application. There are default implementations of the Page Setup command in the Application class, and the Print
command in the View and ScrollableView classes. The generic printing
system uses the same code for printing a view as is used for drawing to
the screen, so once you've written a draw() method for your view, you can also print it.
While
the default printing system works well enough, it is fairly rudimentary
and won't always do exactly what you want. However, you can
customise and build upon it in various ways to provide more advanced
features. This section explains how the printing support in PyGUI works
and how you can extend it.
Page Setup
Page setup information is represented by an instance of the PageSetup
class. This class holds all of the information typically specified by a
"Page Setup" dialog. For PyGUI's purposes, the most important of these
are the paper size (the physical size of the sheet of paper) and the margins (the distances from the edge of the paper to the region that will be printed on). Together these determine the page size (the size of the printed area). The following diagram illustrates the relationships between these attributes.
The Application object has a page_setup attribute that holds a default PageSetup instance, and an implementation of the Page Setup command that presents a dialog for editing it. The Document class also has a page_setup attribute and a corresponding Page Setup
command handler. Thus, in a document-oriented application, each
document has its own set of page setup information. If a view is
associated with a document, it will use that document's PageSetup when
printed; otherwise, it will use the application-wide one.
None
of these PageSetup objects are automatically saved anywhere. If you
want them to persist, you will need to save them along with your
document data, or if you're not using documents, write the
application-wide one to a preferences file. To facilitate this,
PageSetup objects are designed to be pickled. They also have to_string() and from_string() methods, in case you don't want to use pickle.
You can customise the way page setup information is edited by overriding the page_setup_cmd() method of a view, a document or the application. You may want to make use of the utility function present_page_setup_dialog(), which displays the platform's standard page setup dialog for a given PageSetup instance.
Printing Views
The Print command is handled by the print_cmd() method of the View and ScrollableView classes. First, the view attempts to find a PageSetup instance. If the view's model attribute refers to a Document, and the document's page_setup
attribute is not None, then it is used. Otherwise, the application-wide
PageSetup is used. If you want the PageSetup to be located some other
way, you can override the get_page_setup() method of the view.
Next, the view's print_view()
method is called, with the PageSetup object as a parameter. This method
does most of the hard work. First it determines the total size of the
area to be printed. For a View, this is the same as the size of the
view on the screen; for a ScrollableView, it is the view's extent.
Then the printed area is divided into pages. with the size of each page equal to the page_size attribute of the PageSetup. The view's draw()
method is called once for each page, with a special canvas object that
draws to the printer instead of the screen. In place of the update_rect
parameter, a rectangle is passed representing the bounds of the page
currently being drawn.
The following diagram illustrates a view
with a large extent being divided into pages for printing. Note that
the origin of the coordinate system as seen by the draw() method is
always at the top left corner of the extent, regardless of which page
is being printed. So the view doesn't need to know whether it's drawing
to a screen or a printer (although it can find out if it wants to, as
we will see below).
Customising Printing
Often
you won't want to print a view exactly the same way as it appears on
the screen. For example, things like selection highlighting and page
boundaries should only be shown on the screen and not on the printed
page. The Canvas object passed to the draw() method has a printing
attribute that is true when printing and false when drawing to the
screen. You can use this to determine which elements of the view should
be drawn.
This technique is sufficient to accommodate minor
differences between screen drawing and printing. Sometimes, however,
you may want to lay out the document quite differently when printing.
An example would be a word processor where you want to display the text
in a continuous "galley" view on the screen, without any page breaks.
When printed, however, you want to add headers and footers to each
page. This presents a problem, because the extent of the view has to be
increased when printing in order to accommodate the headers and footers.
The
solution to this kind of problem is to use a different view subclass
for printing. When you come to print, instead of printing the view that
you use on the screen, create an off-screen instance of the printing
view and call its print_view()
method. The printing view can then calculate its extent appropriately
and generally do things in as different a way as needed from the
on-screen one.
An example of the use of this technique can be
seen in PyGUI's TextEditor class. When printed, it wraps to the width
of the page instead of the width of the view on the screen. It also
figures out how many lines will fit on a page and avoids splitting a
line between two pages. To accomplish this, it uses a separate View
subclass behind the scenes (TextEditorPrintingView). It's implemented
in pure Python, so you can examine it if you want to see how it works.
There
are a couple of ways you can intervene in order to introduce your
custom printing view into the printing process. One way is to override
the print_view() method of the on-screen view to instantiate a printing view and then call its print_view() method instead. (This is the technique used by TextEditor.)
The
other way, applicable in a document-oriented application, is to handle
printing at the document level instead of the view level. This may make
more sense if you have a number of different kinds of on-screen view of
the document, but only one way of printing it. Whichever view is active
when the user gives the Print command, you want the same printing code to be invoked.
To do it this way, you will first need to disable handling of the Print command in the view, otherwise it will never get as far as the document. You can do this by setting the printable property of the view to false. The view will then ignore the Print command and pass it on to the next handler.
Then you can give your Document subclass a print_cmd() method that creates an instance of your printing view and calls its print_view() method, passing it the document's page_setup. Remember to enable the Print command in the document's setup_menus() method.
---