diff --git a/gramps2/ChangeLog b/gramps2/ChangeLog index 0a621dcb2..5bb8e30ac 100644 --- a/gramps2/ChangeLog +++ b/gramps2/ChangeLog @@ -1,3 +1,32 @@ +2006-02-28 Eero Tamminen + * src/plugins/GraphViz.py: major changes: + Re-factoring: + - split large functions to smaller ones to make code more readable + - renamed few methods and variables to what they actually do + - collect the report text to a buffer first so that it can be + converted to latin-1 at one go + - Move common style stuff to GraphViz header and remove from .dot + comments stuff that's obvious from first .dot file settings + Fixes: + - latin-1 conversion option is explicit, not depending on font + - set the GraphViz charset attribute according to latin1 + - quote node and edge identifiers + - rankdir options correspond now to TB and LR (earlier LR and RL) + New features: + - Use christening/burial dates if birth/death dates are missing + - User can add a (e.g. copyright) note to the graph and set its + location (newlines and double quotes are automatically escaped) + - User can give the paging direction + - User can set the GraphViz aspect ratio setting + - The weight option (either for children or parents) + - Women have rounded boxes, except in filled mode + (current GraphViz doesn't support boxes that are + both rounded and filled) + New default settings: + - set the ratio to "compress" instead of "fill" + - "mclimit=2.0", prevents crossed lines + - minimum node separation is 0.25 + 2006-02-27 Alex Roitman * configure.in: Bump up release number. * Release: Version 2.0.10 "Holy Hand Grenade of Antioch" released. diff --git a/gramps2/src/plugins/GraphViz.py b/gramps2/src/plugins/GraphViz.py index 061143cc7..f37cad8e2 100644 --- a/gramps2/src/plugins/GraphViz.py +++ b/gramps2/src/plugins/GraphViz.py @@ -18,7 +18,6 @@ # 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" @@ -68,19 +67,42 @@ class _options: ("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), + ("", "Default", _("Default")), + ("Helvetica", "Postscript / Helvetica", _("Postscript / Helvetica")), + ("FreeSans", "Truetype / FreeSans", _("Truetype / FreeSans")), ) colors = ( ("outline", "B&W Outline", _("B&W outline")), ("colored", "Colored outline", _("Colored outline")), ("filled", "Color fill", _("Color fill")), ) + ratio = ( + ("compress", "Minimal size", _("Minimal size")), + ("fill", "Fill the given area", _("Fill the given area")), + ("expand", "Automatically use optimal number of pages", _("Automatically use optimal number of pages")) + ) + attract = ( + ("parents", "Parents", _("Parents")), + ("children", "Children", _("Children")), + ("none", "Neither", _("Neither")) + ) rankdir = ( + ("TB", "Vertical", _("Vertical")), ("LR", "Horizontal", _("Horizontal")), - ("RL", "Vertical", _("Vertical")), + ) + pagedir = ( + ("BL", "Bottom, left", _("Bottom, left")), + ("BR", "Bottom, right", _("Bottom, right")), + ("TL", "Top, left", _("Top, left")), + ("TR", "Top, right", _("Top, Right")), + ("RB", "Right, bottom", _("Right, bottom")), + ("RT", "Right, top", _("Right, top")), + ("LB", "Left, bottom", _("Left, bottom")), + ("LT", "Left, top", _("Left, top")), + ) + noteloc = ( + ("t", "Top", _("Top")), + ("b", "Bottom", _("Bottom")), ) arrowstyles = ( ('d', "Descendants <- Ancestors", _("Descendants <- Ancestors")), @@ -115,7 +137,7 @@ class GraphViz: 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 + latin - Set if text needs to be converted to latin-1 arrow - Arrow styles for heads and tails. showfamily - Whether to show family nodes. incid - Whether to include IDs. @@ -123,12 +145,16 @@ class GraphViz: justyears - Use years only. placecause - Whether to replace missing dates with place or cause url - Whether to include URLs. - rankdir - Graph direction + rankdir - Graph direction, LR or RL + ratio - Output aspect ration, fill/compress/auto 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. + pagedir - Paging direction + note - Note to add to the graph + noteloc - Note location t/b """ colored = { 'male': 'dodgerblue4', @@ -151,11 +177,14 @@ class GraphViz: self.height = self.paper.get_height_inches() options = options_class.handler.options_dict + self.pagedir = options['pagedir'] 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: + # GraphViz has rounding errors so have to make the real + # margins slightly smaller than (page - content size) self.margin_small = round((margin_cm-0.1)/2.54,2) else: self.margin_small = 0 @@ -167,8 +196,9 @@ class GraphViz: self.just_years = options['justyears'] self.placecause = options['placecause'] self.rankdir = options['rankdir'] + self.attract = options['attract'] + self.ratio = options['ratio'] self.fontname = options['font'] - self.latin = options['latin'] self.colorize = options['color'] if self.colorize == 'colored': self.colors = colored @@ -184,185 +214,300 @@ class GraphViz: else: self.arrowtailstyle = 'none' + self.latin = options['latin'] + self.noteloc = options['noteloc'] + self.note = options['note'] + 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] + buffer = self.get_report() self.f = open(options_class.get_output(),'w') - self.write_report() + if self.latin: + self.f.write(buffer.encode('iso-8859-1')) + else: + self.f.write(buffer) self.f.close() - def write_report(self): - - self.ind_list = self.filter.apply(self.database, + + def get_report(self): + "return string of the .dot file contents" + self.person_handles = 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") + # graph size 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 - )) + rotate = 90 + sizew = (self.height - self.margin*2) * self.hpages + sizeh = (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)) + rotate = 0 + sizew = (self.width - self.margin*2) * self.hpages + sizeh = (self.height - self.margin*2) * self.vpages + + + buffer = self.get_comment_header() + buffer += """ +digraph GRAMPS_relationship_graph { +/* whole graph attributes */ +bgcolor=white; +center=1; +ratio=%s; +rankdir=%s; +mclimit=2.0; +margin="%3.2f"; +pagedir="%s"; +page="%3.2f,%3.2f"; +size="%3.2f,%3.2f"; +rotate=%d; +/* default node and edge attributes */ +nodesep=0.25; +edge [syle=solid, arrowhead=%s arrowtail=%s]; +""" % ( + self.ratio, + self.rankdir, + self.margin_small, + self.pagedir, + self.width, self.height, + sizew, sizeh, + rotate, + self.arrowheadstyle, + self.arrowtailstyle + ) - if self.orient == PAPER_LANDSCAPE: - self.f.write("rotate=90;\n") + if self.fontname: + font = 'fontname="%s"' % self.fontname + else: + font = '' + if self.colorize == 'filled': + buffer += 'node [style=filled %s];\n' % font + else: + buffer += 'node [%s];\n' % font + if self.latin: + # GraphViz default is UTF-8 + buffer += 'charset="iso-8859-1";\n' + + if len(self.person_handles) > 1: + buffer += "/* persons and their families */\n" + buffer += self.get_persons_and_families() + buffer += "/* link children to families */\n" + buffer += self.get_child_links_to_families() - if len(self.ind_list) > 1: - self.dump_index() - self.dump_person() + if self.note: + buffer += 'labelloc="%s";\n' % self.noteloc + buffer += 'label="%s";\n' % self.note.replace('\n', '\\n').replace('"', '\\\"') + buffer += 'fontsize="20";\n' # in points - self.f.write("}\n") + return buffer + "}\n" - def dump_person(self): - # Hash people in a dictionary for faster inclusion checking. + + def get_comment_header(self): + "return comment of Gramps options which are not Graphviz options" + return """/* +GRAMPS - Relationship graph + +Generated on %s. + +Report content options: + include URLs : %s + IDs : %s + dates : %s + just year : %s + place or cause : %s + colorize : %s + dotted adoptions : %s + show family nodes : %s + pages horizontal : %s + vertical : %s + +For other options, see graph settings below. + +If you need to switch between iso-8859-1 and utf-8 text encodings, +e.g. because you're using different font or -T output format, +just use iconv: + iconv -f iso-8859-1 -t utf-8 old.dot > new.dot + iconv -t utf-8 -f iso-8859-1 old.dot > new.dot +*/ +""" % ( + asctime(), + bool(self.includeurl), + bool(self.includeid), + bool(self.includedates), + bool(self.just_years), + bool(self.placecause), + bool(self.colorize), + bool(self.adoptionsdashed), + bool(self.show_families), + self.hpages, self.vpages + ) + + + def get_child_links_to_families(self): + "returns string of GraphViz edges linking parents to families or children" person_dict = {} - for p_id in self.ind_list: - person_dict[p_id] = 1 - - for person_handle in self.ind_list: + # Hash people in a dictionary for faster inclusion checking + for person_handle in self.person_handles: + person_dict[person_handle] = 1 + buffer = "" + for person_handle in self.person_handles: 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) + p_id = person.get_gramps_id() + for fam_handle,mrel,frel in person.get_parent_family_handle_list(): + family = self.database.get_family_from_handle(fam_handle) father_handle = family.get_father_handle() mother_handle = family.get_mother_handle() - fadopted = frel != RelLib.Person.CHILD_REL_BIRTH - madopted = mrel != RelLib.Person.CHILD_REL_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') + ((father_handle and father_handle in person_dict) or + (mother_handle and mother_handle in person_dict))): + # Link to the family node if either parent is in graph + buffer += self.get_family_link(p_id, family, frel, mrel) 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_handle = person.get_birth_handle() - if birth_handle: - birth_event = self.database.get_event_from_handle(birth_handle) - birth = self.dump_event(birth_event) - else: - birth = '' - death_handle = person.get_death_handle() - if death_handle: - death_event = self.database.get_event_from_handle(death_handle) - 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)) + # Link to the parents' nodes directly, if they are in graph + if father_handle and father_handle in person_dict: + buffer += self.get_parent_link(p_id, father_handle, frel) + if mother_handle and mother_handle in person_dict: + buffer += self.get_parent_link(p_id, mother_handle, mrel) + return buffer - # Output families's nodes. + + def get_family_link(self, p_id, family, frel, mrel): + "returns string of GraphViz edge linking child to family" + weight = style = '' + adopted = ((frel != RelLib.Person.CHILD_REL_BIRTH) or + (mrel != RelLib.Person.CHILD_REL_BIRTH)) + if adopted and self.adoptionsdashed: + style = 'style=dotted' + if self.attract == "children": + weight = 'weight="5"' + return '"p%s" -> "f%s" [%s %s];\n' % (p_id, + family.get_gramps_id(), style, weight) + + def get_parent_link(self, p_id, parent_handle, rel): + "returns string of GraphViz edge linking child to parent" + style = '' + if (rel != RelLib.Person.CHILD_REL_BIRTH) and self.adoptionsdashed: + style = 'style=dotted' + parent = self.database.get_person_from_handle(parent_handle) + return '"p%s" -> "p%s" [%s];\n' % (p_id, parent.get_gramps_id(), style) + + + def get_persons_and_families(self): + "returns string of GraphViz nodes for persons and their families" + # The list of families for which we have output the node, + # so we don't do it twice + buffer = "" + families_done = {} + for person_handle in self.person_handles: + person = self.database.get_person_from_handle(person_handle) + p_id = person.get_gramps_id() + # Output the person's node + label = self.get_person_label(person) + style = self.get_gender_style(person) + url = "" + if self.includeurl: + h = person_handle + url = ', URL="ppl/%s/%s/%s.html", ' % (h[0],h[1],h) + buffer += '"p%s" [label="%s", %s%s];\n' % (p_id, label, style, url) + + # Output families where person is a parent 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('-','_') + fam_id = fam.get_gramps_id() 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 = "" + families_done[fam_handle] = 1 + label = "" for event_handle in fam.get_event_list(): if event_handle: event = self.database.get_event_from_handle(event_handle) if event.get_name() == "Marriage": - m = event + label = self.get_event_string(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)) + label = "%s (%s)" % (label, fam_id) + color = "" + if self.colorize == 'colored': + color = ', color="%s"' % self.colors['family'] + elif self.colorize == 'filled': + color = ', fillcolor="%s"' % self.colors['family'] + buffer += '"f%s" [shape=ellipse, label="%s"%s];\n' % (fam_id, label, color) # 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') + if self.attract == "parents": + weight = 'weight="5"' + else: + weight = '' + buffer += '"f%s" -> "p%s" [%s];\n' % (fam_id, p_id, weight) + return buffer + + + def get_gender_style(self, person): + "return gender specific person style" + gender = person.get_gender() + if gender == person.MALE: + shape = 'shape="box"' + elif gender == person.FEMALE: + shape = 'shape="box", style="rounded"' + else: + shape = 'shape="hexagon"' + if self.colorize == 'outline': + return shape + else: + if gender == person.MALE: + color = self.colors['male'] + elif gender == person.FEMALE: + color = self.colors['female'] + else: + color = self.colors['unknown'] + if self.colorize == 'filled': + # In current GraphViz boxes cannot be both rounded and filled + return 'shape="box", fillcolor="%s"' % color + else: + return '%s, color="%s"' % (shape, color) + + def get_person_label(self, person): + "return person label string" + label = person.get_primary_name().get_name() + p_id = person.get_gramps_id() + if self.includeid: + label = label + " (%s)" % p_id + if self.includedates: + birth, death = self.get_date_strings(person) + label = label + '\\n(%s - %s)' % (birth, death) + return label.replace('"', '\\\"') - def dump_event(self,event): + def get_date_strings(self, person): + "returns tuple of birth/christening and death/burying date strings" + birth_handle = person.get_birth_handle() + if birth_handle: + birth_event = self.database.get_event_from_handle(birth_handle) + birth = self.get_event_string(birth_event) + else: + birth = '' + death_handle = person.get_death_handle() + if death_handle: + death_event = self.database.get_event_from_handle(death_handle) + death = self.get_event_string(death_event) + else: + death = '' + if birth and death: + return (birth, death) + # missing info, use (first) christening/burial instead + for event_handle in person.get_event_list(): + event = self.database.get_event_from_handle(event_handle) + if event.get_name() == "Christening": + if not birth: + birth = self.get_event_string(event) + elif event.get_name() == "Burial": + if not death: + death = self.get_event_string(event) + return (birth, death) + + def get_event_string(self, event): """ - Compile an event label. + return string for for an event label. Based on the data availability and preferences, we select one of the following for a given event: @@ -386,33 +531,6 @@ class GraphViz: 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") #------------------------------------------------------------------------ # @@ -440,12 +558,17 @@ class GraphVizOptions(ReportOptions.ReportOptions): 'justyears' : 0, 'placecause' : 1, 'url' : 1, + 'ratio' : "compress", + 'attract' : "none", 'rankdir' : "LR", 'color' : "filled", 'dashedl' : 1, 'margin' : 1.0, + 'pagedir' : 'BL', 'pagesh' : 1, 'pagesv' : 1, + 'note' : '', + 'noteloc' : 'b', 'gvof' : 'ps', } @@ -477,7 +600,13 @@ class GraphVizOptions(ReportOptions.ReportOptions): 'url' : ("=0/1","Whether to include URLs.", ["Do not include URLs","Include URLs"], True), - 'rankdir' : ("=str","Graph direction.", + 'ratio' : ("=str","Graph aspect ratio.", + [ "%s\t%s" % (item[0],item[1]) for item in _options.ratio ], + False), + 'attract' : ("=str","What to group closer.", + [ "%s\t%s" % (item[0],item[1]) for item in _options.attract ], + False), + '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.", @@ -488,10 +617,18 @@ class GraphVizOptions(ReportOptions.ReportOptions): True), 'margin' : ("=num","Margin size.", "Floating point value, in cm"), + 'pagedir' : ("=str","Paging direction.", + [ "%s\t%s" % (item[0],item[1]) for item in _options.pagedir ], + False), 'pagesh' : ("=num","Number of pages in horizontal direction.", "Integer values"), 'pagesv' : ("=num","Number of pages in vertical direction.", "Integer values"), + 'note' : ("=str","Note to add to the graph.", + "Text"), + 'noteloc' : ("=str","Note location.", + [ "%s\t%s" % (item[0],item[1]) for item in _options.noteloc ], + False), 'gvof' : ("=str","Output format to convert dot file into.", [ "%s\t%s" % (item[0],item[1]) for item in _options.formats ], False), @@ -533,6 +670,22 @@ class GraphVizOptions(ReportOptions.ReportOptions): def make_doc_menu(self,dialog,active=None): pass + def add_list(self, options, default): + "returns compobox of given options and default value" + box = gtk.ComboBox() + store = gtk.ListStore(str) + box.set_model(store) + cell = gtk.CellRendererText() + box.pack_start(cell,True) + box.add_attribute(cell,'text',0) + index = 0 + for item in options: + store.append(row=[item[2]]) + if item[0] == default: + box.set_active(index) + index = index + 1 + return box + def add_user_options(self,dialog): if self.handler.module_name == "rel_graph2": dialog.make_doc_menu = self.make_doc_menu @@ -586,36 +739,16 @@ class GraphVizOptions(ReportOptions.ReportOptions): _("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 + self.rank_box = self.add_list(_options.rankdir, + self.options_dict['rankdir']) 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 + self.color_box = self.add_list(_options.colors, + self.options_dict['color']) dialog.add_frame_option(_("GraphViz Options"), _("Graph coloring"), self.color_box, @@ -623,39 +756,15 @@ class GraphVizOptions(ReportOptions.ReportOptions): "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 + self.arrowstyle_box = self.add_list(_options.arrowstyles, + self.options_dict['arrow']) 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 + self.font_box = self.add_list(_options.fonts, + self.options_dict['font']) dialog.add_frame_option(_("GraphViz Options"), _("Font family"), self.font_box, @@ -664,6 +773,13 @@ class GraphVizOptions(ReportOptions.ReportOptions): "FreeSans is available from: " "http://www.nongnu.org/freefont/")) + self.latin_cb = gtk.CheckButton(_("Output format/font requires text as latin-1")) + self.latin_cb.set_active(self.options_dict['latin']) + dialog.add_frame_option(_("GraphViz Options"), '', + self.latin_cb, + _("If text doesn't show correctly in report, use this. " + "Required e.g. for default font with PS output.")) + 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"), '', @@ -678,13 +794,31 @@ class GraphVizOptions(ReportOptions.ReportOptions): _("Families will show up as ellipses, linked " "to parents and children.")) - # Page options tab + # Page/layout options tab + self.attract_box = self.add_list(_options.attract, + self.options_dict['attract']) + dialog.add_frame_option(_("Layout Options"), + _("What to group closer together"), + self.attract_box, + _("Whether to group parents or children " + "together or whether both have same " + "weights.")) + + self.ratio_box = self.add_list(_options.ratio, + self.options_dict['ratio']) + dialog.add_frame_option(_("Layout Options"), + _("Aspect ratio"), + self.ratio_box, + _("Affects greatly how the graph is layed out " + "on the page. Multiple pages overrides the " + "pages settings below.")) + 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"), + dialog.add_frame_option(_("Layout Options"), _("Margin size"), self.margin_sb) @@ -696,14 +830,14 @@ class GraphVizOptions(ReportOptions.ReportOptions): 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"), + dialog.add_frame_option(_("Layout 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"), + dialog.add_frame_option(_("Layout Options"), _("Number of Vertical Pages"), self.vpages_sb, _("GraphViz can create very large graphs " @@ -711,6 +845,32 @@ class GraphVizOptions(ReportOptions.ReportOptions): "rectangular array of pages. This " "controls the number pages in the array " "vertically.")) + self.pagedir_box = self.add_list(_options.pagedir, + self.options_dict['pagedir']) + dialog.add_frame_option(_("Layout Options"), + _("Paging direction"), + self.pagedir_box, + _("The order in which the graph pages are output.")) + + # Notes tab + self.textbox = gtk.TextView() + self.textbox.get_buffer().set_text(self.options_dict['note']) + self.textbox.set_editable(1) + swin = gtk.ScrolledWindow() + swin.set_shadow_type(gtk.SHADOW_IN) + swin.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) + swin.add(self.textbox) + dialog.add_frame_option(_("Notes"), + _("Note to add to the graph"), + swin, + _("This text will be added to the graph.")) + self.noteloc_box = self.add_list(_options.noteloc, + self.options_dict['noteloc']) + dialog.add_frame_option(_("Notes"), + _("Note location"), + self.noteloc_box, + _("Whether note will on top or bottom of the page.")) + def toggle_date(self, obj): self.just_years_cb.set_sensitive(self.includedates_cb.get_active()) @@ -727,6 +887,10 @@ class GraphVizOptions(ReportOptions.ReportOptions): 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['attract'] = \ + _options.attract[self.attract_box.get_active()][0] + self.options_dict['ratio'] = \ + _options.ratio[self.ratio_box.get_active()][0] self.options_dict['rankdir'] = \ _options.rankdir[self.rank_box.get_active()][0] self.options_dict['color'] = \ @@ -735,8 +899,14 @@ class GraphVizOptions(ReportOptions.ReportOptions): _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] + self.options_dict['pagedir'] = \ + _options.pagedir[self.pagedir_box.get_active()][0] + self.options_dict['noteloc'] = \ + _options.noteloc[self.noteloc_box.get_active()][0] + b = self.textbox.get_buffer() + self.options_dict['note'] = \ + b.get_text(b.get_start_iter(), b.get_end_iter(), False) + if self.handler.module_name == "rel_graph2": self.options_dict['gvof'] = dialog.format_menu.get_format_str()