gramps/src/plugins/docgen/GtkPrint.py
Craig J. Anderson 1a3a101420 Fixed issue 4670.
'one_page' option in the Ancestor/Descendant graphical repororts did not 
change the page type to 'custom'.
also landscape orientation did not switch the page.height and page.width
properties which made items print outside the page and give infinate loop
errors.



svn: r16708
2011-02-24 17:11:35 +00:00

609 lines
21 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2007 Zsolt Foldvari
# Copyright (C) 2008-2009 Brian G. Matherly
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"""Printing interface based on gtk.Print* classes.
"""
#------------------------------------------------------------------------
#
# Python modules
#
#------------------------------------------------------------------------
from gen.ggettext import gettext as _
from math import radians
##try:
##from cStringIO import StringIO
##except:
##from StringIO import StringIO
#------------------------------------------------------------------------
#
# Gramps modules
#
#------------------------------------------------------------------------
from gen.plug.docgen import PAPER_PORTRAIT
import libcairodoc
import Errors
from glade import Glade
#import constfunc
#------------------------------------------------------------------------
#
# Set up logging
#
#------------------------------------------------------------------------
import logging
log = logging.getLogger(".GtkPrint")
#-------------------------------------------------------------------------
#
# GTK modules
#
#-------------------------------------------------------------------------
import gtk
import cairo
if gtk.pygtk_version < (2, 10, 0):
raise Errors.UnavailableError(_("PyGtk 2.10 or later is required"))
#------------------------------------------------------------------------
#
# Constants
#
#------------------------------------------------------------------------
# printer settings (might be needed to align for different platforms)
PRINTER_DPI = 72.0
PRINTER_SCALE = 1.0
# the print settings to remember between print sessions
PRINT_SETTINGS = None
# minimum spacing around a page in print preview
MARGIN = 6
# zoom modes in print preview
(ZOOM_BEST_FIT,
ZOOM_FIT_WIDTH,
ZOOM_FREE,) = range(3)
#------------------------------------------------------------------------
#
# Converter functions
#
#------------------------------------------------------------------------
def paperstyle_to_pagesetup(paper_style):
"""Convert a PaperStyle instance into a gtk.PageSetup instance.
@param paper_style: Gramps paper style object to convert
@param type: PaperStyle
@return: page_setup
@rtype: gtk.PageSetup
"""
# paper size names according to 'PWG Candidate Standard 5101.1-2002'
# ftp://ftp.pwg.org/pub/pwg/candidates/cs-pwgmsn10-20020226-5101.1.pdf
gramps_to_gtk = {
"Letter": "na_letter",
"Legal": "na_legal",
"A0": "iso_a0",
"A1": "iso_a1",
"A2": "iso_a2",
"A3": "iso_a3",
"A4": "iso_a4",
"A5": "iso_a5",
"B0": "iso_b0",
"B1": "iso_b1",
"B2": "iso_b2",
"B3": "iso_b3",
"B4": "iso_b4",
"B5": "iso_b5",
"B6": "iso_b6",
"B": "na_ledger",
"C": "na_c",
"D": "na_d",
"E": "na_e",
}
# First set the paper size
gramps_paper_size = paper_style.get_size()
gramps_paper_name = gramps_paper_size.get_name()
# All sizes not included in the translation table (even if a standard size)
# are handled as custom format, because we are not intelligent enough.
if gramps_paper_name in gramps_to_gtk:
paper_size = gtk.PaperSize(gramps_to_gtk[gramps_paper_name])
log.debug("Selected paper size: %s" % gramps_to_gtk[gramps_paper_name])
else:
if paper_style.get_orientation() == PAPER_PORTRAIT:
paper_width = gramps_paper_size.get_width() * 10
paper_height = gramps_paper_size.get_height() * 10
else:
paper_width = gramps_paper_size.get_height() * 10
paper_height = gramps_paper_size.get_width() * 10
paper_size = gtk.paper_size_new_custom("custom",
"Custom Size",
paper_width,
paper_height,
gtk.UNIT_MM)
log.debug("Selected paper size: (%f,%f)" % (paper_width, paper_height))
page_setup = gtk.PageSetup()
page_setup.set_paper_size(paper_size)
# Set paper orientation
if paper_style.get_orientation() == PAPER_PORTRAIT:
page_setup.set_orientation(gtk.PAGE_ORIENTATION_PORTRAIT)
else:
page_setup.set_orientation(gtk.PAGE_ORIENTATION_LANDSCAPE)
# Set paper margins
page_setup.set_top_margin(paper_style.get_top_margin() * 10,
gtk.UNIT_MM)
page_setup.set_bottom_margin(paper_style.get_bottom_margin() * 10,
gtk.UNIT_MM)
page_setup.set_left_margin(paper_style.get_left_margin() * 10,
gtk.UNIT_MM)
page_setup.set_right_margin(paper_style.get_right_margin() * 10,
gtk.UNIT_MM)
return page_setup
#------------------------------------------------------------------------
#
# PrintPreview class
#
#------------------------------------------------------------------------
class PrintPreview(object):
"""Implement a dialog to show print preview.
"""
zoom_factors = {
0.50: '50%',
0.75: '75%',
1.00: '100%',
1.25: '125%',
1.50: '150%',
1.75: '175%',
2.00: '200%',
3.00: '300%',
4.00: '400%',
}
def __init__(self, operation, preview, context, parent):
self._operation = operation
self._preview = preview
self._context = context
self.__build_window()
self._current_page = None
# Private
def __build_window(self):
"""Build the window from Glade.
"""
glade_xml = Glade()
self._window = glade_xml.toplevel
#self._window.set_transient_for(parent)
# remember active widgets for future use
self._swin = glade_xml.get_object('swin')
self._drawing_area = glade_xml.get_object('drawingarea')
self._first_button = glade_xml.get_object('first')
self._prev_button = glade_xml.get_object('prev')
self._next_button = glade_xml.get_object('next')
self._last_button = glade_xml.get_object('last')
self._pages_entry = glade_xml.get_object('entry')
self._pages_label = glade_xml.get_object('label')
self._zoom_fit_width_button = glade_xml.get_object('zoom_fit_width')
self._zoom_fit_width_button.set_stock_id('gramps-zoom-fit-width')
self._zoom_best_fit_button = glade_xml.get_object('zoom_best_fit')
self._zoom_best_fit_button.set_stock_id('gramps-zoom-best-fit')
self._zoom_in_button = glade_xml.get_object('zoom_in')
self._zoom_in_button.set_stock_id('gramps-zoom-in')
self._zoom_out_button = glade_xml.get_object('zoom_out')
self._zoom_out_button.set_stock_id('gramps-zoom-out')
# connect the signals
glade_xml.connect_signals(self)
##def create_surface(self):
##return cairo.PDFSurface(StringIO(),
##self._context.get_width(),
##self._context.get_height())
##def get_page(self, page_no):
##"""Get the cairo surface of the given page.
##Surfaces are also cached for instant access.
##"""
##if page_no >= len(self._page_numbers):
##log.debug("Page number %d doesn't exist." % page_no)
##page_no = 0
##if page_no not in self._page_surfaces:
##surface = self.create_surface()
##cr = cairo.Context(surface)
##if PRINTER_SCALE != 1.0:
##cr.scale(PRINTER_SCALE, PRINTER_SCALE)
##self._context.set_cairo_context(cr, PRINTER_DPI, PRINTER_DPI)
##self._preview.render_page(self._page_numbers[page_no])
##self._page_surfaces[page_no] = surface
##return self._page_surfaces[page_no]
def __set_page(self, page_no):
if page_no < 0 or page_no >= self._page_no:
return
if self._current_page != page_no:
self._drawing_area.queue_draw()
self._current_page = page_no
self._first_button.set_sensitive(self._current_page)
self._prev_button.set_sensitive(self._current_page)
self._next_button.set_sensitive(self._current_page < self._page_no - 1)
self._last_button.set_sensitive(self._current_page < self._page_no - 1)
self._pages_entry.set_text('%d' % (self._current_page + 1))
def __set_zoom(self, zoom):
self._zoom = zoom
screen_width = int(self._paper_width * self._zoom + 2 * MARGIN)
screen_height = int(self._paper_height * self._zoom + 2 * MARGIN)
self._drawing_area.set_size_request(screen_width, screen_height)
self._drawing_area.queue_draw()
self._zoom_in_button.set_sensitive(self._zoom !=
max(self.zoom_factors))
self._zoom_out_button.set_sensitive(self._zoom !=
min(self.zoom_factors))
def __zoom_in(self):
zoom = [z for z in self.zoom_factors if z > self._zoom]
if zoom:
return min(zoom)
else:
return self._zoom
def __zoom_out(self):
zoom = [z for z in self.zoom_factors if z < self._zoom]
if zoom:
return max(zoom)
else:
return self._zoom
def __zoom_fit_width(self):
width, height, vsb_w, hsb_h = self.__get_view_size()
zoom = width / self._paper_width
if self._paper_height * zoom > height:
zoom = (width - vsb_w) / self._paper_width
return zoom
def __zoom_best_fit(self):
width, height, vsb_w, hsb_h = self.__get_view_size()
zoom = min(width / self._paper_width, height / self._paper_height)
return zoom
def __get_view_size(self):
"""Get the dimensions of the scrolled window.
"""
width = self._swin.allocation.width - 2 * MARGIN
height = self._swin.allocation.height - 2 * MARGIN
if self._swin.get_shadow_type() != gtk.SHADOW_NONE:
width -= 2 * self._swin.style.xthickness
height -= 2 * self._swin.style.ythickness
spacing = self._swin.style_get_property('scrollbar-spacing')
vsb_w, vsb_h = self._swin.get_vscrollbar().size_request()
vsb_w += spacing
hsb_w, hsb_h = self._swin.get_hscrollbar().size_request()
hsb_h += spacing
return width, height, vsb_w, hsb_h
def __end_preview(self):
self._operation.end_preview()
# Signal handlers
def on_drawingarea_expose_event(self, drawing_area, event):
cr = drawing_area.window.cairo_create()
cr.rectangle(event.area)
cr.clip()
# get the extents of the page and the screen
paper_w = int(self._paper_width * self._zoom)
paper_h = int(self._paper_height * self._zoom)
width, height, vsb_w, hsb_h = self.__get_view_size()
if paper_h > height:
width -= vsb_w
if paper_w > width:
height -= hsb_h
# put the paper on the middle of the window
xtranslate = MARGIN
if paper_w < width:
xtranslate += (width - paper_w) / 2
ytranslate = MARGIN
if paper_h < height:
ytranslate += (height - paper_h) / 2
cr.translate(xtranslate, ytranslate)
# draw an empty white page
cr.set_source_rgb(1.0, 1.0, 1.0)
cr.rectangle(0, 0, paper_w, paper_h)
cr.fill_preserve()
cr.set_source_rgb(0, 0, 0)
cr.set_line_width(1)
cr.stroke()
if self._orientation == gtk.PAGE_ORIENTATION_LANDSCAPE:
cr.rotate(radians(-90))
cr.translate(-paper_h, 0)
##page_setup = self._context.get_page_setup()
##cr.translate(page_setup.get_left_margin(gtk.UNIT_POINTS),
##page_setup.get_top_margin(gtk.UNIT_POINTS))
##cr.set_source_surface(self.get_page(0))
##cr.paint()
# draw the content of the currently selected page
# Here we use dpi scaling instead of scaling the cairo context,
# because it gives better result. In the latter case the distance
# of glyphs was changing.
dpi = PRINTER_DPI * self._zoom
self._context.set_cairo_context(cr, dpi, dpi)
self._preview.render_page(self._current_page)
def on_swin_size_allocate(self, scrolledwindow, allocation):
if self._zoom_mode == ZOOM_FIT_WIDTH:
self.__set_zoom(self.__zoom_fit_width())
if self._zoom_mode == ZOOM_BEST_FIT:
self.__set_zoom(self.__zoom_best_fit())
def on_print_clicked(self, toolbutton):
pass
def on_first_clicked(self, toolbutton):
self.__set_page(0)
def on_prev_clicked(self, toolbutton):
self.__set_page(self._current_page - 1)
def on_next_clicked(self, toolbutton):
self.__set_page(self._current_page + 1)
def on_last_clicked(self, toolbutton):
self.__set_page(self._page_no - 1)
def on_entry_activate(self, entry):
try:
new_page = int(entry.get_text()) - 1
except ValueError:
new_page = self._current_page
if new_page < 0 or new_page >= self._page_no:
new_page = self._current_page
self.__set_page(new_page)
def on_zoom_fit_width_toggled(self, toggletoolbutton):
if toggletoolbutton.get_active():
self._zoom_best_fit_button.set_active(False)
self._zoom_mode = ZOOM_FIT_WIDTH
self.__set_zoom(self.__zoom_fit_width())
else:
self._zoom_mode = ZOOM_FREE
def on_zoom_best_fit_toggled(self, toggletoolbutton):
if toggletoolbutton.get_active():
self._zoom_fit_width_button.set_active(False)
self._zoom_mode = ZOOM_BEST_FIT
self.__set_zoom(self.__zoom_best_fit())
else:
self._zoom_mode = ZOOM_FREE
def on_zoom_in_clicked(self, toolbutton):
self._zoom_fit_width_button.set_active(False)
self._zoom_best_fit_button.set_active(False)
self._zoom_mode = ZOOM_FREE
self.__set_zoom(self.__zoom_in())
def on_zoom_out_clicked(self, toolbutton):
self._zoom_fit_width_button.set_active(False)
self._zoom_best_fit_button.set_active(False)
self._zoom_mode = ZOOM_FREE
self.__set_zoom(self.__zoom_out())
def on_window_delete_event(self, widget, event):
self.__end_preview()
return False
def on_quit_clicked(self, toolbutton):
self.__end_preview()
self._window.destroy()
# Public
def start(self):
# get paper/page dimensions
page_setup = self._context.get_page_setup()
self._paper_width = page_setup.get_paper_width(gtk.UNIT_POINTS)
self._paper_height = page_setup.get_paper_height(gtk.UNIT_POINTS)
self._page_width = page_setup.get_page_width(gtk.UNIT_POINTS)
self._page_height = page_setup.get_page_height(gtk.UNIT_POINTS)
self._orientation = page_setup.get_orientation()
# get the total number of pages
##self._page_numbers = [0,]
##self._page_surfaces = {}
self._page_no = self._operation.get_property('n_pages')
self._pages_label.set_text(_('of %d') % self._page_no)
# set zoom level and initial page number
self._zoom_mode = ZOOM_FREE
self.__set_zoom(1.0)
self.__set_page(0)
# let's the show begin...
self._window.show()
#------------------------------------------------------------------------
#
# GtkPrint class
#
#------------------------------------------------------------------------
class GtkPrint(libcairodoc.CairoDoc):
"""Print document via GtkPrint* interface.
Requires Gtk+ 2.10.
"""
def run(self):
"""Run the Gtk Print operation.
"""
global PRINT_SETTINGS
# get a page setup from the paper style we have
page_setup = paperstyle_to_pagesetup(self.paper)
# set up a print operation
operation = gtk.PrintOperation()
operation.set_default_page_setup(page_setup)
operation.connect("begin_print", self.on_begin_print)
operation.connect("draw_page", self.on_draw_page)
operation.connect("paginate", self.on_paginate)
operation.connect("preview", self.on_preview)
# set print settings if it was stored previously
if PRINT_SETTINGS is not None:
operation.set_print_settings(PRINT_SETTINGS)
# run print dialog
while True:
self.preview = None
res = operation.run(gtk.PRINT_OPERATION_ACTION_PRINT_DIALOG)
if self.preview is None: # cancel or print
break
# set up printing again; can't reuse PrintOperation?
operation = gtk.PrintOperation()
operation.set_default_page_setup(page_setup)
operation.connect("begin_print", self.on_begin_print)
operation.connect("draw_page", self.on_draw_page)
operation.connect("paginate", self.on_paginate)
operation.connect("preview", self.on_preview)
# set print settings if it was stored previously
if PRINT_SETTINGS is not None:
operation.set_print_settings(PRINT_SETTINGS)
# store print settings if printing was successful
if res == gtk.PRINT_OPERATION_RESULT_APPLY:
PRINT_SETTINGS = operation.get_print_settings()
def on_begin_print(self, operation, context):
"""Setup environment for printing.
"""
# get data from context here only once to save time on pagination
self.page_width = round(context.get_width())
self.page_height = round(context.get_height())
self.dpi_x = context.get_dpi_x()
self.dpi_y = context.get_dpi_y()
def on_paginate(self, operation, context):
"""Paginate the whole document in chunks.
"""
layout = context.create_pango_layout()
finished = self.paginate(layout,
self.page_width,
self.page_height,
self.dpi_x,
self.dpi_y)
# update page number
operation.set_n_pages(len(self._pages))
# start preview if needed
if finished and self.preview:
self.preview.start()
return finished
def on_draw_page(self, operation, context, page_nr):
"""Draw the requested page.
"""
cr = context.get_cairo_context()
layout = context.create_pango_layout()
width = round(context.get_width())
height = round(context.get_height())
dpi_x = context.get_dpi_x()
dpi_y = context.get_dpi_y()
self.draw_page(page_nr, cr, layout, width, height, dpi_x, dpi_y)
def on_preview(self, operation, preview, context, parent):
"""Implement custom print preview functionality.
"""
##if constfunc.win()':
##return False
self.preview = PrintPreview(operation, preview, context, parent)
# give a dummy cairo context to gtk.PrintContext,
# PrintPreview will update it with the real one
try:
width = int(round(context.get_width()))
except ValueError:
width = 0
try:
height = int(round(context.get_height()))
except ValueError:
height = 0
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
cr = cairo.Context(surface)
context.set_cairo_context(cr, PRINTER_DPI, PRINTER_DPI)
return True