diff --git a/src/gui/widgets/fanchart.py b/src/gui/widgets/fanchart.py index 56bde37a0..76afe5c1d 100644 --- a/src/gui/widgets/fanchart.py +++ b/src/gui/widgets/fanchart.py @@ -77,7 +77,7 @@ class FanChartWidget(Gtk.DrawingArea): """ Interactive Fan Chart Widget. """ - BORDER_WIDTH = 10 + BORDER_EDGE_WIDTH = 10 GENCOLOR = ((229,191,252), (191,191,252), (191,222,252), @@ -131,9 +131,7 @@ class FanChartWidget(Gtk.DrawingArea): self.set_generations(self.generations) self.center = 50 # pixel radius of center self.gen_color = True - self.layout = self.create_pango_layout('cairo') - self.layout.set_font_description(Pango.FontDescription("sans 8")) - self.set_size_request(120,120) + self.set_size_request(120, 120) def reset_generations(self): """ @@ -165,9 +163,8 @@ class FanChartWidget(Gtk.DrawingArea): """ Overridden method to handle size request events. """ - width, height = self.layout.get_size() - requisition.width = (width // Pango.SCALE + self.BORDER_WIDTH*4)* 1.45 - requisition.height = (3 * height // Pango.SCALE + self.BORDER_WIDTH*4) * 1.2 + requisition.width = 2 * self.halfdist() + requisition.height = requisition.width def do_get_preferred_width(self): """ GTK3 uses width for height sizing model. This method will @@ -185,11 +182,8 @@ class FanChartWidget(Gtk.DrawingArea): self.do_size_request(req) return req.height, req.height - def on_draw(self, widget, cr): - """ - The main method to do the drawing. - """ - # first do size request of what we will need + def nrgen(self): + #compute the number of generations present nrgen = None for generation in range(self.generations - 1, 0, -1): for p in range(len(self.data[generation])): @@ -201,13 +195,35 @@ class FanChartWidget(Gtk.DrawingArea): break if nrgen is None: nrgen = 1 + return nrgen + + def halfdist(self): + """ + Compute the half radius of the circle + """ + nrgen = self.nrgen() + return self.pixels_per_generation * nrgen + self.center \ + + self.BORDER_EDGE_WIDTH + + def on_draw(self, widget, cr, scale=1.): + """ + The main method to do the drawing. + If widget is given, we assume we draw in GTK3 and use the allocation. + To draw raw on the cairo context cr, set widget=None. + """ + # first do size request of what we will need + nrgen = self.nrgen() halfdist = self.pixels_per_generation * nrgen + self.center self.set_size_request(2 * halfdist, 2 * halfdist) #obtain the allocation alloc = self.get_allocation() x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height - cr.translate(w/2. - self.center_xy[0], h/2. - self.center_xy[1]) + cr.scale(scale, scale) + if widget: + cr.translate(w/2. - self.center_xy[0], h/2. - self.center_xy[1]) + else: + cr.translate(halfdist - self.center_xy[0], halfdist - self.center_xy[1]) cr.save() cr.rotate(self.rotate_value * math.pi/180) for generation in range(self.generations - 1, 0, -1): @@ -241,10 +257,6 @@ class FanChartWidget(Gtk.DrawingArea): cr.arc(0, 0, self.TRANSLATE_PX, 0, 2 * math.pi) cr.move_to(0,0) cr.fill() - fontw, fonth = self.layout.get_pixel_size() - cr.move_to((w - fontw - 4), (h - fonth )) - self.layout.context_changed() - PangoCairo.show_layout(cr, self.layout) def draw_person(self, cr, gender, name, start, stop, generation, state, parents, child, person): @@ -276,11 +288,11 @@ class FanChartWidget(Gtk.DrawingArea): # draw an indicator cr.move_to(0, 0) cr.set_source_rgb(255, 255, 255) # white - cr.arc(0, 0, radius + 10, start_rad, stop_rad) + cr.arc(0, 0, radius + self.BORDER_EDGE_WIDTH, start_rad, stop_rad) cr.fill() cr.move_to(0, 0) cr.set_source_rgb(0, 0, 0) # black - cr.arc(0, 0, radius + 10, start_rad, stop_rad) + cr.arc(0, 0, radius + self.BORDER_EDGE_WIDTH, start_rad, stop_rad) cr.line_to(0, 0) cr.stroke() cr.set_source_rgb(r/255., g/255., b/255.) diff --git a/src/plugins/lib/libcairodoc.py b/src/plugins/lib/libcairodoc.py index dabbd77b7..e42ad0ea1 100644 --- a/src/plugins/lib/libcairodoc.py +++ b/src/plugins/lib/libcairodoc.py @@ -1699,5 +1699,3 @@ links (like ODF) and write PDF from that format. cr.stroke() self._pages[page_nr].draw(cr, layout, width, dpi_x, dpi_y) - - diff --git a/src/plugins/view/fanchartview.py b/src/plugins/view/fanchartview.py index 6817141be..27d821f6e 100644 --- a/src/plugins/view/fanchartview.py +++ b/src/plugins/view/fanchartview.py @@ -34,6 +34,7 @@ #------------------------------------------------------------------------- from gi.repository import Gdk from gi.repository import Gtk +import cairo from cgi import escape from gen.ggettext import gettext as _ @@ -52,6 +53,10 @@ from gen.errors import WindowActiveError from gui.views.bookmarks import PersonBookmarks from gui.editors import EditPerson + +# the print settings to remember between print sessions +PRINT_SETTINGS = None + class FanChartView(NavigationView): """ The Gramplet code that realizes the FanChartWidget. @@ -110,6 +115,11 @@ class FanChartView(NavigationView): + + + + + @@ -117,10 +127,24 @@ class FanChartView(NavigationView): + + + ''' + def define_actions(self): + """ + Required define_actions function for PageView. Builds the action + group information required. + """ + NavigationView.define_actions(self) + + self._add_action('PrintView', Gtk.STOCK_PRINT, _("_Print/Save View..."), + accel="P", + tip=_("Print or save the Fan Chart View"), + callback=self.printview) def build_tree(self): pass # will build when active_changes @@ -473,9 +497,9 @@ class FanChartView(NavigationView): per_item.set_image(go_image) label.set_use_markup(True) label.show() - label.set_alignment(0,0) + label.set_alignment(0, 0) per_item.add(label) - per_item.connect("activate",self.on_childmenu_changed,p_id) + per_item.connect("activate", self.on_childmenu_changed, p_id) per_item.show() per_menu.append(per_item) @@ -485,3 +509,146 @@ class FanChartView(NavigationView): menu.append(item) menu.popup(None, None, None, None, event.button, event.time) return 1 + + def printview(self, obj): + """ + Print or save the view that is currently shown + """ + widthpx = 2*(self.fan.pixels_per_generation * self.fan.nrgen() + + self.fan.center) + prt = CairoPrintSave(widthpx, self.fan.on_draw, self.uistate.window) + prt.run() + +#------------------------------------------------------------------------ +# +# CairoPrintSave class +# +#------------------------------------------------------------------------ +class CairoPrintSave(): + """Act as an abstract document that can render onto a cairo context. + + It can render the model onto cairo context pages, according to the received + page style. + + """ + + def __init__(self, widthpx, drawfunc, parent): + """ + This class provides the things needed so as to dump a cairo drawing on + a context to output + """ + self.widthpx = widthpx + self.drawfunc = drawfunc + self.parent = parent + + def run(self): + """Create the physical output from the meta document. + + """ + global PRINT_SETTINGS + + # set up a print operation + operation = Gtk.PrintOperation() + operation.connect("draw_page", self.on_draw_page) + operation.connect("preview", self.on_preview) + operation.connect("paginate", self.on_paginate) + operation.set_n_pages(1) + #paper_size = Gtk.PaperSize.new(name="iso_a4") + ## WHY no Gtk.Unit.PIXEL ?? Is there a better way to convert + ## Pixels to MM ?? + paper_size = Gtk.PaperSize.new_custom("custom", + "Custom Size", + round(self.widthpx * 0.2646), + round(self.widthpx * 0.2646), + Gtk.Unit.MM) + page_setup = Gtk.PageSetup() + page_setup.set_paper_size(paper_size) + #page_setup.set_orientation(Gtk.PageOrientation.PORTRAIT) + operation.set_default_page_setup(page_setup) + #operation.set_use_full_page(True) + + if PRINT_SETTINGS is not None: + operation.set_print_settings(PRINT_SETTINGS) + + # run print dialog + while True: + self.preview = None + res = operation.run(Gtk.PrintOperationAction.PRINT_DIALOG, self.parent) + 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("draw_page", self.on_draw_page) + operation.connect("preview", self.on_preview) + operation.connect("paginate", self.on_paginate) + # 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.PrintOperationResult.APPLY: + PRINT_SETTINGS = operation.get_print_settings() + + def on_draw_page(self, operation, context, page_nr): + """Draw a page on a Cairo context. + """ + cr = context.get_cairo_context() + pxwidth = round(context.get_width()) + pxheight = round(context.get_height()) + dpi_x = context.get_dpi_x() + dpi_y = context.get_dpi_y() + self.drawfunc(None, cr, scale=pxwidth/self.widthpx) + + def on_paginate(self, operation, context): + """Paginate the whole document in chunks. + We don't need this as there is only one page, however, + we provide a dummy holder here, because on_preview crashes if no + default application is set with gir 3.3.2 (typically evince not installed)! + It will provide the start of the preview dialog, which cannot be + started in on_preview + """ + finished = True + # update page number + operation.set_n_pages(1) + + # start preview if needed + if self.preview: + self.preview.run() + + return finished + + def on_preview(self, operation, preview, context, parent): + """Implement custom print preview functionality. + We provide a dummy holder here, because on_preview crashes if no + default application is set with gir 3.3.2 (typically evince not installed)! + """ + dlg = Gtk.MessageDialog(parent, + flags=Gtk.DialogFlags.MODAL, + type=Gtk.MessageType.WARNING, + buttons=Gtk.ButtonsType.CLOSE, + message_format=_('No preview available')) + self.preview = dlg + self.previewopr = operation + #dlg.format_secondary_markup(msg2) + dlg.set_title("Fan Chart Preview - Gramps") + dlg.connect('response', self.previewdestroy) + + # give a dummy cairo context to Gtk.PrintContext, + 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, 72.0, 72.0) + + return True + + def previewdestroy(self, dlg, res): + self.preview.destroy() + self.previewopr.end_preview()