# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham # Contributions by Lorenzo Cappelletti # # 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$ "Generate files/Relationship graph" #------------------------------------------------------------------------ # # python modules # #------------------------------------------------------------------------ import os from gettext import gettext as _ from time import asctime #------------------------------------------------------------------------ # # Set up logging # #------------------------------------------------------------------------ import logging log = logging.getLogger(".GraphViz") #------------------------------------------------------------------------ # # GNOME/gtk # #------------------------------------------------------------------------ import gtk #------------------------------------------------------------------------ # # GRAMPS modules # #------------------------------------------------------------------------ from PluginUtils import Report, ReportOptions, register_report import GenericFilter import RelLib import DateHandler import const from BaseDoc import PAPER_LANDSCAPE from QuestionDialog import ErrorDialog #------------------------------------------------------------------------ # # Constant options items # #------------------------------------------------------------------------ class _options: # internal ID, english option name (for cli), localized option name (for gui) formats = ( ("ps", "Postscript", _("Postscript")), ("svg", "Structured Vector Graphics (SVG)", _("Structured Vector Graphics (SVG)")), ("svgz", "Compressed Structured Vector Graphics (SVG)", _("Compressed Structured Vector Graphics (SVG)")), ("png", "PNG image", _("PNG image")), ("jpg", "JPEG image", _("JPEG image")), ("gif", "GIF image", _("GIF image")), ) fonts = ( # Last items tells whether strings need to be converted to Latin1 ("", "Default", _("Default"), 1), ("Helvetica", "Postscript / Helvetica", _("Postscript / Helvetica"), 1), ("FreeSans", "Truetype / FreeSans", _("Truetype / FreeSans"), 0), ) colors = ( ("outline", "B&W Outline", _("B&W outline")), ("colored", "Colored outline", _("Colored outline")), ("filled", "Color fill", _("Color fill")), ) rankdir = ( ("LR", "Horizontal", _("Horizontal")), ("RL", "Vertical", _("Vertical")), ) arrowstyles = ( ('d', "Descendants <- Ancestors", _("Descendants <- Ancestors")), ('a', "Descendants -> Ancestors", _("Descendants -> Ancestors")), ('da',"Descendants <-> Ancestors", _("Descendants <-> Ancestors")), ('', "Descendants - Ancestors", _("Descendants - Ancestors")), ) dot_found = os.system("dot -V 2>/dev/null") == 0 #------------------------------------------------------------------------ # # Report class # #------------------------------------------------------------------------ class GraphViz: def __init__(self,database,person,options_class): """ Creates ComprehensiveAncestorsReport object that produces the report. The arguments are: database - the GRAMPS database instance person - currently selected person options_class - instance of the Options class for this report This report needs the following parameters (class variables) that come in the options class. filter - Filter to be applied to the people of the database. The option class carries its number, and the function returning the list of filters. font - Font to use. latin - Set if font supports only Latin1 arrow - Arrow styles for heads and tails. showfamily - Whether to show family nodes. incid - Whether to include IDs. incdate - Whether to include dates. justyears - Use years only. placecause - Whether to replace missing dates with place or cause url - Whether to include URLs. rankdir - Graph direction color - Whether to use outline, colored outline or filled color in graph dashedl - Whether to use dashed lines for non-birth relationships. margin - Margins, in cm. pagesh - Number of pages in horizontal direction. pagesv - Number of pages in vertical direction. """ colored = { 'male': 'dodgerblue4', 'female': 'deeppink', 'unknown': 'black', 'family': 'darkgreen' } filled = { 'male': 'lightblue', 'female': 'lightpink', 'unknown': 'lightgray', 'family': 'lightyellow' } self.database = database self.start_person = person self.paper = options_class.handler.get_paper() self.orient = options_class.handler.get_orientation() self.width = self.paper.get_width_inches() self.height = self.paper.get_height_inches() options = options_class.handler.options_dict self.hpages = options['pagesh'] self.vpages = options['pagesv'] margin_cm = options['margin'] self.margin = round(margin_cm/2.54,2) if margin_cm > 0.1: self.margin_small = round((margin_cm-0.1)/2.54,2) else: self.margin_small = 0 self.includeid = options['incid'] self.includedates = options['incdate'] self.includeurl = options['url'] self.adoptionsdashed = options['dashedl'] self.show_families = options['showfamily'] self.just_years = options['justyears'] self.placecause = options['placecause'] self.rankdir = options['rankdir'] self.fontname = options['font'] self.latin = options['latin'] self.colorize = options['color'] if self.colorize == 'colored': self.colors = colored elif self.colorize == 'filled': self.colors = filled arrow_str = options['arrow'] if arrow_str.find('a') + 1: self.arrowheadstyle = 'normal' else: self.arrowheadstyle = 'none' if arrow_str.find('d') + 1: self.arrowtailstyle = 'normal' else: self.arrowtailstyle = 'none' filter_num = options_class.get_filter_number() filters = options_class.get_report_filters(person) filters.extend(GenericFilter.CustomFilters.get_filters()) self.filter = filters[filter_num] self.f = open(options_class.get_output(),'w') self.write_report() self.f.close() def write_report(self): self.ind_list = self.filter.apply(self.database, self.database.get_person_handles(sort_handles=False)) self.write_header() self.f.write("digraph GRAMPS_relationship_graph {\n") self.f.write("bgcolor=white;\n") self.f.write("rankdir=%s;\n" % self.rankdir) self.f.write("center=1;\n") self.f.write("margin=%3.2f;\n" % self.margin_small) self.f.write("ratio=fill;\n") if self.orient == PAPER_LANDSCAPE: self.f.write("size=\"%3.2f,%3.2f\";\n" % ( (self.height-self.margin*2)*self.hpages, (self.width-self.margin*2)*self.vpages )) else: self.f.write("size=\"%3.2f,%3.2f\";\n" % ( (self.width-self.margin*2)*self.hpages, (self.height-self.margin*2)*self.vpages )) self.f.write("page=\"%3.2f,%3.2f\";\n" % (self.width,self.height)) if self.orient == PAPER_LANDSCAPE: self.f.write("rotate=90;\n") if len(self.ind_list) > 1: self.dump_index() self.dump_person() self.f.write("}\n") def dump_person(self): # Hash people in a dictionary for faster inclusion checking. person_dict = {} for p_id in self.ind_list: person_dict[p_id] = 1 for person_handle in self.ind_list: person = self.database.get_person_from_handle(person_handle) pid = person.get_gramps_id().replace('-','_') for family_handle, mrel, frel in person.get_parent_family_handle_list(): family = self.database.get_family_from_handle(family_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() fadopted = frel != RelLib.Person.CHILD_BIRTH madopted = mrel != RelLib.Person.CHILD_BIRTH famid = family.get_gramps_id().replace('-','_') if (self.show_families and (father_handle and person_dict.has_key(father_handle) or mother_handle and person_dict.has_key(mother_handle))): # Link to the family node. self.f.write('p%s -> f%s [' % (pid, famid)) self.f.write('arrowhead=%s, arrowtail=%s, ' % (self.arrowheadstyle, self.arrowtailstyle)) if self.adoptionsdashed and (fadopted or madopted): self.f.write('style=dotted') else: self.f.write('style=solid') self.f.write('];\n') else: # Link to the parents' nodes directly. if father_handle and person_dict.has_key(father_handle): father = self.database.get_person_from_handle(father_handle) fid = father.get_gramps_id().replace('-','_') self.f.write('p%s -> p%s [' % (pid, fid)) self.f.write('arrowhead=%s, arrowtail=%s, ' % (self.arrowheadstyle, self.arrowtailstyle)) if self.adoptionsdashed and fadopted: self.f.write('style=dotted') else: self.f.write('style=solid') self.f.write('];\n') if mother_handle and person_dict.has_key(mother_handle): mother = self.database.get_person_from_handle(mother_handle) mid = mother.get_gramps_id().replace('-','_') self.f.write('p%s -> p%s [' % (pid, mid)) self.f.write('arrowhead=%s, arrowtail=%s, ' % (self.arrowheadstyle, self.arrowtailstyle)) if self.adoptionsdashed and madopted: self.f.write('style=dotted') else: self.f.write('style=solid') self.f.write('];\n') def dump_index(self): # The list of families for which we have output the node, so we # don't do it twice. families_done = [] for person_handle in self.ind_list: person = self.database.get_person_from_handle(person_handle) # Output the person's node. label = person.get_primary_name().get_name() the_id = person.get_gramps_id().replace('-','_') if self.includeid: label = label + " (%s)" % the_id if self.includedates: birth_ref = person.get_birth_ref() if birth_ref: birth_event = self.database.get_event_from_handle(birth_ref.ref) birth = self.dump_event(birth_event) else: birth = '' death_ref = person.get_death_ref() if death_ref: death_event = self.database.get_event_from_handle(death_ref.ref) death = self.dump_event(death_event) else: death = '' label = label + '\\n(%s - %s)' % (birth, death) self.f.write('p%s [shape=box, ' % the_id) if self.includeurl: h = person.get_handle() self.f.write('URL="ppl/%s/%s/%s.html", ' % (h[0],h[1],h)) if self.colorize != 'outline': if self.colorize == 'filled': style = 'style=filled, fillcolor' else: style = 'color' gender = person.get_gender() if gender == person.MALE: self.f.write('%s=%s, ' % (style, self.colors['male'])) elif gender == person.FEMALE: self.f.write('%s=%s, ' % (style, self.colors['female'])) else: self.f.write('%s=%s, ' % (style, self.colors['unknown'])) if self.latin: label = label.encode('iso-8859-1') self.f.write('fontname="%s", label="%s"];\n' % (self.fontname,label)) # Output families's nodes. if self.show_families: family_list = person.get_family_handle_list() for fam_handle in family_list: fam = self.database.get_family_from_handle(fam_handle) fid = fam.get_gramps_id().replace('-','_') if fam_handle not in families_done: families_done.append(fam_handle) self.f.write('f%s [shape=ellipse, ' % fid) if self.colorize == 'colored': self.f.write('color=%s, ' % self.colors['family']) elif self.colorize == 'filled': self.f.write('style=filled fillcolor=%s, ' % self.colors['family']) marriage = "" for event_ref in fam.get_event_ref_list(): if event_ref: event = self.database.get_event_from_handle(event_ref.ref) if event.get_type() == RelLib.Event.MARRIAGE: m = event break else: m = None if m: marriage = self.dump_event(m) if self.includeid: marriage = marriage + " (%s)" % fid self.f.write('fontname="%s", label="%s"];\n' % (self.fontname,marriage)) # Link this person to all his/her families. self.f.write('f%s -> p%s [' % (fid, the_id)) self.f.write('arrowhead=%s, arrowtail=%s, ' % (self.arrowheadstyle, self.arrowtailstyle)) self.f.write('style=solid];\n') def dump_event(self,event): """ Compile an event label. Based on the data availability and preferences, we select one of the following for a given event: year only complete date place name cause empty string """ if event.get_date_object().get_year_valid(): if self.just_years: return '%i' % event.get_date_object().get_year() else: return DateHandler.get_date(event) elif self.placecause: place_handle = event.get_place_handle() place = self.database.get_place_from_handle(place_handle) if place and place.get_title(): return place.get_title() else: return event.get_cause() return '' def write_header(self): """ Write header listing the options used. """ self.f.write("/* GRAMPS - Relationship graph\n") self.f.write(" *\n") self.f.write(" * Report options:\n") self.f.write(" * font style : %s\n" % self.fontname) self.f.write(" * style arrow head : %s\n" % self.arrowheadstyle) self.f.write(" * tail : %s\n" % self.arrowtailstyle) self.f.write(" * graph direction : %s\n" % self.rankdir) self.f.write(" * include URLs : %s\n" % bool(self.includeurl)) self.f.write(" * IDs : %s\n" % bool(self.includeid)) self.f.write(" * dates : %s\n" % bool(self.includedates)) self.f.write(" * just year : %s\n" % bool(self.just_years)) self.f.write(" * place or cause : %s\n" % bool(self.placecause)) self.f.write(" * colorize : %s\n" % bool(self.colorize)) self.f.write(" * dotted adoptions : %s\n" % bool(self.adoptionsdashed)) self.f.write(" * show family nodes : %s\n" % bool(self.show_families)) self.f.write(" * margin : %3.2fin\n" % self.margin_small) self.f.write(" * pages horizontal : %s\n" % self.hpages) self.f.write(" * vertical : %s\n" % self.vpages) self.f.write(" * page width : %3.2fin\n" % self.width) self.f.write(" * height : %3.2fin\n" % self.height) self.f.write(" *\n") self.f.write(" * Generated on %s by GRAMPS\n" % asctime()) self.f.write(" */\n\n") #------------------------------------------------------------------------ # # Options class # #------------------------------------------------------------------------ class GraphVizOptions(ReportOptions.ReportOptions): """ Defines options and provides handling interface. """ def __init__(self,name,person_id=None): ReportOptions.ReportOptions.__init__(self,name,person_id) def set_new_options(self): # Options specific for this report self.options_dict = { 'font' : "", 'latin' : 1, 'arrow' : 'd', 'showfamily' : 1, 'incdate' : 1, 'incid' : 0, 'justyears' : 0, 'placecause' : 1, 'url' : 1, 'rankdir' : "LR", 'color' : "filled", 'dashedl' : 1, 'margin' : 1.0, 'pagesh' : 1, 'pagesv' : 1, 'gvof' : 'ps', } self.options_help = { 'font' : ("=str","Font to use in the report.", [ "%s\t%s" % (item[0],item[1]) for item in _options.fonts ], False), 'latin' : ("=0/1","Needs to be set if font doesn't support unicode.", ["Supports unicode","Supports only Latin1"], True), 'arrow' : ("=str","Arrow styles for heads and tails.", [ "%s\t%s" % (item[0],item[1]) for item in _options.arrowstyles ], False), 'showfamily': ("=0/1","Whether to show family nodes.", ["Do not show family nodes","Show family nodes"], True), 'incdate' : ("=0/1","Whether to include dates.", ["Do not include dates","Include dates"], True), 'incid' : ("=0/1","Whether to include IDs.", ["Do not include IDs","Include IDs"], True), 'justyears' : ("=0/1","Whether to use years only.", ["Do not use years only","Use years only"], True), 'placecause': ("=0/1","Whether to replace missing dates with place/cause.", ["Do not replace blank dates","Replace blank dates"], True), 'url' : ("=0/1","Whether to include URLs.", ["Do not include URLs","Include URLs"], True), 'rankdir' : ("=str","Graph direction.", [ "%s\t%s" % (item[0],item[1]) for item in _options.rankdir ], False), 'color' : ("=str","Whether and how to colorize graph.", [ "%s\t%s" % (item[0],item[1]) for item in _options.colors ], False), 'dashedl' : ("=0/1","Whether to use dotted lines for non-birth relationships.", ["Do not use dotted lines","Use dotted lines"], True), 'margin' : ("=num","Margin size.", "Floating point value, in cm"), 'pagesh' : ("=num","Number of pages in horizontal direction.", "Integer values"), 'pagesv' : ("=num","Number of pages in vertical direction.", "Integer values"), 'gvof' : ("=str","Output format to convert dot file into.", [ "%s\t%s" % (item[0],item[1]) for item in _options.formats ], False), } def enable_options(self): # Semi-common options that should be enabled for this report self.enable_dict = { 'filter' : 0, } def get_report_filters(self,person): """Set up the list of possible content filters.""" if person: name = person.get_primary_name().get_name() gramps_id = person.get_gramps_id() else: name = 'PERSON' gramps_id = '' all = GenericFilter.GenericFilter() all.set_name(_("Entire Database")) all.add_rule(GenericFilter.Everyone([])) des = GenericFilter.GenericFilter() des.set_name(_("Descendants of %s") % name) des.add_rule(GenericFilter.IsDescendantOf([gramps_id,1])) ans = GenericFilter.GenericFilter() ans.set_name(_("Ancestors of %s") % name) ans.add_rule(GenericFilter.IsAncestorOf([gramps_id,1])) com = GenericFilter.GenericFilter() com.set_name(_("People with common ancestor with %s") % name) com.add_rule(GenericFilter.HasCommonAncestorWith([gramps_id])) return [all,des,ans,com] def make_doc_menu(self,dialog,active=None): pass def add_user_options(self,dialog): if self.handler.module_name == "rel_graph2": dialog.make_doc_menu = self.make_doc_menu dialog.format_menu = GraphicsFormatComboBox() dialog.format_menu.set(self.options_dict['gvof']) # Content options tab msg = _("Include Birth, Marriage and Death dates") self.includedates_cb = gtk.CheckButton(msg) self.includedates_cb.set_active(self.options_dict['incdate']) dialog.add_option(None, self.includedates_cb, _("Include the dates that the individual " "was born, got married and/or died " "in the graph labels.")) self.just_years_cb = gtk.CheckButton(_("Limit dates to years only")) self.just_years_cb.set_active(self.options_dict['justyears']) dialog.add_option(None, self.just_years_cb, _("Prints just dates' year, neither " "month or day nor date approximation " "or interval are shown.")) self.place_cause_cb = gtk.CheckButton(_("Place/cause when no date")) self.place_cause_cb.set_active(self.options_dict['placecause']) dialog.add_option(None, self.place_cause_cb, _("When no birth, marriage, or death date " "is available, the correspondent place field " "(or cause field when blank place) will be used.")) # disable other date options if no dates self.includedates_cb.connect('toggled',self.toggle_date) self.toggle_date(self.includedates_cb) self.includeurl_cb = gtk.CheckButton(_("Include URLs")) self.includeurl_cb.set_active(self.options_dict['url']) dialog.add_option(None, self.includeurl_cb, _("Include a URL in each graph node so " "that PDF and imagemap files can be " "generated that contain active links " "to the files generated by the 'Generate " "Web Site' report.")) self.includeid_cb = gtk.CheckButton(_("Include IDs")) self.includeid_cb.set_active(self.options_dict['incid']) dialog.add_option(None, self.includeid_cb, _("Include individual and family IDs.")) # GraphViz output options tab self.rank_box = gtk.ComboBox() store = gtk.ListStore(str) self.rank_box.set_model(store) cell = gtk.CellRendererText() self.rank_box.pack_start(cell,True) self.rank_box.add_attribute(cell,'text',0) index = 0 for item in _options.rankdir: store.append(row=[item[2]]) if item[0] == self.options_dict['rankdir']: self.rank_box.set_active(index) index = index + 1 dialog.add_frame_option(_("GraphViz Options"), _("Graph direction"), self.rank_box, _("Whether generations go from top to bottom " "or left to right.")) self.color_box = gtk.ComboBox() store = gtk.ListStore(str) self.color_box.set_model(store) cell = gtk.CellRendererText() self.color_box.pack_start(cell,True) self.color_box.add_attribute(cell,'text',0) index = 0 for item in _options.colors: store.append(row=[item[2]]) if item[0] == self.options_dict['color']: self.color_box.set_active(index) index = index + 1 dialog.add_frame_option(_("GraphViz Options"), _("Graph coloring"), self.color_box, _("Males will be shown with blue, females " "with red. If the sex of an individual " "is unknown it will be shown with gray.")) self.arrowstyle_box = gtk.ComboBox() store = gtk.ListStore(str) self.arrowstyle_box.set_model(store) cell = gtk.CellRendererText() self.arrowstyle_box.pack_start(cell,True) self.arrowstyle_box.add_attribute(cell,'text',0) index = 0 for item in _options.arrowstyles: store.append(row=[item[2]]) if item[0] == self.options_dict['arrow']: self.arrowstyle_box.set_active(index) index = index + 1 dialog.add_frame_option(_("GraphViz Options"), _("Arrowhead direction"), self.arrowstyle_box, _("Choose the direction that the arrows point.")) self.font_box = gtk.ComboBox() store = gtk.ListStore(str) self.font_box.set_model(store) cell = gtk.CellRendererText() self.font_box.pack_start(cell,True) self.font_box.add_attribute(cell,'text',0) index = 0 for item in _options.fonts: if item[3]: name = "%s (iso-latin1 font)" % item[2] else: name = item[2] store.append(row=[name]) if item[0] == self.options_dict['font']: self.font_box.set_active(index) index = index + 1 dialog.add_frame_option(_("GraphViz Options"), _("Font family"), self.font_box, _("Choose the font family. If international " "characters don't show, use FreeSans font. " "FreeSans is available from: " "http://www.nongnu.org/freefont/")) self.adoptionsdashed_cb = gtk.CheckButton(_("Indicate non-birth relationships with dotted lines")) self.adoptionsdashed_cb.set_active(self.options_dict['dashedl']) dialog.add_frame_option(_("GraphViz Options"), '', self.adoptionsdashed_cb, _("Non-birth relationships will show up " "as dotted lines in the graph.")) self.show_families_cb = gtk.CheckButton(_("Show family nodes")) self.show_families_cb.set_active(self.options_dict['showfamily']) dialog.add_frame_option(_("GraphViz Options"), '', self.show_families_cb, _("Families will show up as ellipses, linked " "to parents and children.")) # Page options tab margin_adj = gtk.Adjustment(value=self.options_dict['margin'], lower=0.0, upper=10.0, step_incr=1.0) self.margin_sb = gtk.SpinButton(adjustment=margin_adj, digits=1) dialog.add_frame_option(_("Page Options"), _("Margin size"), self.margin_sb) hpages_adj = gtk.Adjustment(value=self.options_dict['pagesh'], lower=1, upper=25, step_incr=1) vpages_adj = gtk.Adjustment(value=self.options_dict['pagesv'], lower=1, upper=25, step_incr=1) self.hpages_sb = gtk.SpinButton(adjustment=hpages_adj, digits=0) self.vpages_sb = gtk.SpinButton(adjustment=vpages_adj, digits=0) dialog.add_frame_option(_("Page Options"), _("Number of Horizontal Pages"), self.hpages_sb, _("GraphViz can create very large graphs by " "spreading the graph across a rectangular " "array of pages. This controls the number " "pages in the array horizontally.")) dialog.add_frame_option(_("Page Options"), _("Number of Vertical Pages"), self.vpages_sb, _("GraphViz can create very large graphs " "by spreading the graph across a " "rectangular array of pages. This " "controls the number pages in the array " "vertically.")) def toggle_date(self, obj): self.just_years_cb.set_sensitive(self.includedates_cb.get_active()) self.place_cause_cb.set_sensitive(self.includedates_cb.get_active()) def parse_user_options(self,dialog): self.options_dict['incdate'] = int(self.includedates_cb.get_active()) self.options_dict['url'] = int(self.includeurl_cb.get_active()) self.options_dict['margin'] = self.margin_sb.get_value() self.options_dict['dashedl'] = int(self.adoptionsdashed_cb.get_active()) self.options_dict['pagesh'] = self.hpages_sb.get_value_as_int() self.options_dict['pagesv'] = self.vpages_sb.get_value_as_int() self.options_dict['showfamily'] = int(self.show_families_cb.get_active()) self.options_dict['incid'] = int(self.includeid_cb.get_active()) self.options_dict['justyears'] = int(self.just_years_cb.get_active()) self.options_dict['placecause'] = int(self.place_cause_cb.get_active()) self.options_dict['rankdir'] = \ _options.rankdir[self.rank_box.get_active()][0] self.options_dict['color'] = \ _options.colors[self.color_box.get_active()][0] self.options_dict['arrow'] = \ _options.arrowstyles[self.arrowstyle_box.get_active()][0] self.options_dict['font'] = \ _options.fonts[self.font_box.get_active()][0] self.options_dict['latin'] = \ _options.fonts[self.font_box.get_active()][3] if self.handler.module_name == "rel_graph2": self.options_dict['gvof'] = dialog.format_menu.get_format_str() #------------------------------------------------------------------------ # # Dialog class # #------------------------------------------------------------------------ class GraphVizDialog(Report.ReportDialog): def __init__(self,database,person): self.database = database self.person = person name = "rel_graph" translated_name = _("Relationship Graph") self.options_class = GraphVizOptions(name) self.category = Report.CATEGORY_CODE Report.ReportDialog.__init__(self,database,person,self.options_class, name,translated_name) response = self.window.run() if response == gtk.RESPONSE_OK: try: self.make_report() except (IOError,OSError),msg: ErrorDialog(str(msg)) self.window.destroy() def make_doc_menu(self,active=None): """Build a one item menu of document types that are appropriate for this report.""" self.format_menu = FormatComboBox() self.format_menu.set() def make_document(self): """Do Nothing. This document will be created in the make_report routine.""" pass def setup_style_frame(self): """The style frame is not used in this dialog.""" pass def parse_style_frame(self): """The style frame is not used in this dialog.""" pass def make_report(self): """Create the object that will produce the GraphViz file.""" GraphViz(self.database,self.person,self.options_class) #------------------------------------------------------------------------ # # Combo Box classes # #------------------------------------------------------------------------ class FormatComboBox(gtk.ComboBox): """ Format combo box class. Trivial class supporting only one format. """ def set(self,tables=0,callback=None,obj=None,active=None): self.store = gtk.ListStore(str) self.set_model(self.store) cell = gtk.CellRendererText() self.pack_start(cell,True) self.add_attribute(cell,'text',0) self.store.append(row=["Graphviz (dot)"]) self.set_active(0) def get_label(self): return "Graphviz (dot)" def get_reference(self): return None def get_paper(self): return 1 def get_styles(self): return 0 def get_ext(self): return '.dot' def get_printable(self): return None def get_clname(self): return 'dot' class GraphicsFormatComboBox(gtk.ComboBox): """ Format combo box class for graphical (not codegen) report. """ def set(self,active=None): self.store = gtk.ListStore(str) self.set_model(self.store) cell = gtk.CellRendererText() self.pack_start(cell,True) self.add_attribute(cell,'text',0) active_index = 0 index = 0 for item in _options.formats: self.store.append(row=[item[2]]) if active == item[0]: active_index = index index = index + 1 self.set_active(active_index) def get_label(self): return _options.formats[self.get_active()][2] def get_reference(self): return EmptyDoc def get_paper(self): return 1 def get_styles(self): return 0 def get_ext(self): return '.%s' % _options.formats[self.get_active()][0] def get_format_str(self): return _options.formats[self.get_active()][0] def get_printable(self): return None def get_clname(self): return 'print' #------------------------------------------------------------------------ # # Empty class to keep the BaseDoc-targeted format happy # #------------------------------------------------------------------------ class EmptyDoc: def __init__(self,styles,type,template,orientation,source=None): pass def init(self): pass #------------------------------------------------------------------------ # # # #------------------------------------------------------------------------ def cl_report(database,name,category,options_str_dict): clr = Report.CommandLineReport(database,name,category,GraphVizOptions, options_str_dict) # Exit here if show option was given if clr.show: return GraphViz(database,clr.person,clr.option_class) #------------------------------------------------------------------------ # # # #------------------------------------------------------------------------ class GraphVizGraphics(Report.Report): def __init__(self,database,person,options_class): self.database = database self.start_person = person self.options_class = options_class self.doc = options_class.get_document() self.user_output = options_class.get_output() self.junk_output = os.path.join(const.home_dir,"junk") self.the_format = self.options_class.handler.options_dict['gvof'] self.the_font = self.options_class.handler.options_dict['font'] def begin_report(self): self.options_class.set_output(self.junk_output) def write_report(self): GraphViz(self.database,self.start_person,self.options_class) def end_report(self): os.system('dot -T%s -o%s %s ; rm %s' % (self.the_format,self.user_output, self.junk_output,self.junk_output)) #------------------------------------------------------------------------ # # # #------------------------------------------------------------------------ def get_description(): return _("Generates relationship graphs, currently only in GraphViz " "format. GraphViz (dot) can transform the graph into " "postscript, jpeg, png, vrml, svg, and many other formats. " "For more information or to get a copy of GraphViz, " "goto http://www.graphviz.org") def get_description_graphics(): return _("Generates relationship graphs using GraphViz (dot) program. " "This report generates dot file behind the scene and then " "uses dot to convert it into a graph. If you want the dot" "file itself, please use the Code Generators category.") #------------------------------------------------------------------------ # # # #------------------------------------------------------------------------ register_report( name = 'rel_graph', category = Report.CATEGORY_CODE, report_class = GraphVizDialog, options_class = cl_report, modes = Report.MODE_GUI | Report.MODE_CLI, translated_name = _("Relationship Graph"), status = _("Stable"), description= get_description(), author_name="Donald N. Allingham", author_email="don@gramps-project.org" ) if dot_found: register_report( name = 'rel_graph2', category = Report.CATEGORY_DRAW, report_class = GraphVizGraphics, options_class = GraphVizOptions, modes = Report.MODE_GUI | Report.MODE_CLI, translated_name = _("Relationship Graph"), status = _("Stable"), description= get_description_graphics(), author_name="Donald N. Allingham", author_email="don@gramps-project.org" )