From 95496e6449b4cdcf7d5c95e7a368105397859fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Charette?= Date: Wed, 2 Jan 2008 08:43:19 +0000 Subject: [PATCH] add_comment() and work in progress for the new GraphViz framework svn: r9679 --- ChangeLog | 10 ++ src/BaseDoc.py | 17 +- src/PluginUtils/_MenuOptions.py | 221 +++++++++++++++++++++++- src/PluginUtils/__init__.py | 2 +- src/ReportBase/_GraphvizReportDialog.py | 17 ++ src/plugins/GVFamilyLines.py | 38 ++-- 6 files changed, 282 insertions(+), 23 deletions(-) diff --git a/ChangeLog b/ChangeLog index b0f41190a..7a4ec8bb5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2008-01-02 Stéphane Charette + * src/BaseDoc.py: definition of add_comment() + * src/ReportBase/_GraphvizReportDialog.py: implement add_comment() + * src/plugins/GVFamilyLines.py: + * src/PluginUtils/__init__.py: + * src/PluginUtils/_MenuOptions.py: work in progress to migrate the + FamilyLines plugin to the new GraphViz framework; new menu option + widget called "SurnameColourOption" (which is actually quite specific + to the FamilyLines plugin) + 2008-01-01 Peter Landgren * src/plugins/rel_sv.py: Added some comments diff --git a/src/BaseDoc.py b/src/BaseDoc.py index a050107dd..f402b0012 100644 --- a/src/BaseDoc.py +++ b/src/BaseDoc.py @@ -1587,7 +1587,7 @@ class GVDoc: @return: nothing """ raise NotImplementedError - + def add_link(self, id1, id2, style="", head="", tail="", comment=""): """ Add a link between two nodes. @@ -1604,7 +1604,18 @@ class GVDoc: @return: nothing """ raise NotImplementedError - + + def add_comment(self, comment): + """ + Add a comment to the source file. + + @param comment: A text string to add as a comment. + Example: "Next comes the individuals." + @type comment: string + @return: nothing + """ + raise NotImplementedError + def start_subgraph(self,id): """ Start a subgraph in this graph. @@ -1615,7 +1626,7 @@ class GVDoc: @return: nothing """ raise NotImplementedError - + def end_subgraph(self): """ End a subgraph that was previously started in this graph. diff --git a/src/PluginUtils/_MenuOptions.py b/src/PluginUtils/_MenuOptions.py index 323d70145..bfa6eb4da 100644 --- a/src/PluginUtils/_MenuOptions.py +++ b/src/PluginUtils/_MenuOptions.py @@ -27,12 +27,103 @@ Abstracted option handling. # gramps modules # #------------------------------------------------------------------------- +import gtk import gobject +import Utils import _Tool as Tool import GrampsWidgets +import ManagedWindow from Selectors import selector_factory from BasicUtils import name_displayer as _nd +#------------------------------------------------------------------------ +# +# Dialog window used to select a surname +# +#------------------------------------------------------------------------ +class LastNameDialog(ManagedWindow.ManagedWindow): + + def __init__(self, database, uistate, track, surnames, skipList=set()): + + self.title = _('Select surname') + ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) + self.dlg = gtk.Dialog( + None, + uistate.window, + gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR, + (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) + self.dlg.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.set_window(self.dlg, None, self.title) + self.window.set_default_size(400,400) + + # build up a container to display all of the people of interest + self.model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT) + self.treeView = gtk.TreeView(self.model) + col1 = gtk.TreeViewColumn(_('Surname'), gtk.CellRendererText(), text=0) + col2 = gtk.TreeViewColumn(_('Count'), gtk.CellRendererText(), text=1) + col1.set_resizable(True) + col2.set_resizable(True) + col1.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) + col2.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) + col1.set_sort_column_id(0) + col2.set_sort_column_id(1) + self.treeView.append_column(col1) + self.treeView.append_column(col2) + self.scrolledWindow = gtk.ScrolledWindow() + self.scrolledWindow.add(self.treeView) + self.scrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.scrolledWindow.set_shadow_type(gtk.SHADOW_OUT) + self.dlg.vbox.pack_start(self.scrolledWindow, expand=True, fill=True) + self.scrolledWindow.show_all() + + if len(surnames) == 0: + # we could use database.get_surname_list(), but if we do that + # all we get is a list of names without a count...therefore + # we'll traverse the entire database ourself and build up a + # list that we can use +# for name in database.get_surname_list(): +# self.model.append([name, 0]) + + # build up the list of surnames, keeping track of the count for each name + # (this can be a lengthy process, so by passing in the dictionary we can + # be certain we only do this once) + progress = Utils.ProgressMeter(_('Finding surnames')) + progress.set_pass(_('Finding surnames'), database.get_number_of_people()) + for personHandle in database.get_person_handles(False): + progress.step() + person = database.get_person_from_handle(personHandle) + key = person.get_primary_name().get_surname() + count = 0 + if key in surnames: + count = surnames[key] + surnames[key] = count + 1 + progress.close() + + # insert the names and count into the model + for key in surnames: + if key.encode('iso-8859-1','xmlcharrefreplace') not in skipList: + self.model.append([key, surnames[key]]) + + # keep the list sorted starting with the most popular last name + self.model.set_sort_column_id(1, gtk.SORT_DESCENDING) + + # the "OK" button should be enabled/disabled based on the selection of a row + self.treeSelection = self.treeView.get_selection() + self.treeSelection.set_mode(gtk.SELECTION_MULTIPLE) + self.treeSelection.select_path(0) + + def run(self): + response = self.dlg.run() + surnameSet = set() + if response == gtk.RESPONSE_ACCEPT: + (mode, paths) = self.treeSelection.get_selected_rows() + for path in paths: + iter = self.model.get_iter(path) + surname = self.model.get_value(iter, 0) + surnameSet.add(surname) + self.dlg.destroy() + return surnameSet + #------------------------------------------------------------------------- # # Option class @@ -690,7 +781,7 @@ class PersonListOption(Option): @type label: string @param value: A set of GIDs as initial values for this option. Example: "111 222 333 444" - @type value: set() + @type value: string @return: nothing """ self.db = dbstate.get_database() @@ -775,6 +866,9 @@ class PersonListOption(Option): # if this person has a spouse, ask if we should include the spouse # in the list of "people of interest" + # + # NOTE: we may want to make this an optional thing, determined + # by the use of a parameter at the time this class is instatiated familyList = person.get_family_handle_list() if familyList: for familyHandle in familyList: @@ -807,6 +901,131 @@ class PersonListOption(Option): iter = self.model.get_iter(path) self.model.remove(iter) +#------------------------------------------------------------------------- +# +# SurnameColourOption class +# +#------------------------------------------------------------------------- +class SurnameColourOption(Option): + """ + This class describes a widget that allows multiple surnames to be + selected from the database, and to assign a colour (not necessarily + unique) to each one. + """ + def __init__(self, label, value, dbstate): + """ + @param label: A friendly label to be applied to this option. + Example: "Family lines" + @type label: string + @param value: A set of surnames and colours. + Example: "surname1 colour1 surname2 colour2" + @type value: string + @return: nothing + """ + self.db = dbstate.get_database() + self.dbstate = dbstate + Option.__init__(self,label,value) + + def make_gui_obj(self, gtk, dialog): + """ + Add a "surname-colour" widget to the dialog. + """ + self.dialog = dialog + self.surnames = {} # list of surnames and count + + self.model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) + self.treeView = gtk.TreeView(self.model) + self.treeView.set_size_request(150, 150) + self.treeView.connect('row-activated', self.clicked) + col1 = gtk.TreeViewColumn(_('Surname'), gtk.CellRendererText(), text=0) + col2 = gtk.TreeViewColumn(_('Colour'), gtk.CellRendererText(), text=1) + col1.set_resizable(True) + col2.set_resizable(True) + col1.set_sort_column_id(0) + col1.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) + col2.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE) + self.treeView.append_column(col1) + self.treeView.append_column(col2) + self.scrolledWindow = gtk.ScrolledWindow() + self.scrolledWindow.add(self.treeView) + self.scrolledWindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + self.scrolledWindow.set_shadow_type(gtk.SHADOW_OUT) + self.hbox = gtk.HBox() + self.hbox.pack_start(self.scrolledWindow, expand=True, fill=True) + + self.addSurname = GrampsWidgets.SimpleButton(gtk.STOCK_ADD, self.addSurnameClicked) + self.delSurname = GrampsWidgets.SimpleButton(gtk.STOCK_REMOVE, self.delSurnameClicked) + self.vbbox = gtk.VButtonBox() + self.vbbox.add(self.addSurname) + self.vbbox.add(self.delSurname) + self.vbbox.set_layout(gtk.BUTTONBOX_SPREAD) + self.hbox.pack_end(self.vbbox, expand=False) + + # populate the surname/colour treeview + tmp = self.get_value().split() + while len(tmp) > 1: + surname = tmp.pop(0) + colour = tmp.pop(0) + self.model.append([surname, colour]) + + # parent expects the widget as "self.gobj" + self.gobj = self.hbox + + def parse(self): + """ + Parse the object and return. + """ + surnameColours = '' + iter = self.model.get_iter_first() + while (iter): + surname = self.model.get_value(iter, 0) # .encode('iso-8859-1','xmlcharrefreplace') + colour = self.model.get_value(iter, 1) + # tried to use a dictionary, and tried to save it as a tuple, + # but coulnd't get this to work right -- this is lame, but now + # the surnames and colours are saved as a plain text string + surnameColours += surname + ' ' + colour + ' ' + iter = self.model.iter_next(iter) + return surnameColours + + def clicked(self, treeview, path, column): + # get the surname and colour value for this family + iter = self.model.get_iter(path) + surname = self.model.get_value(iter, 0) + colour = gtk.gdk.color_parse(self.model.get_value(iter, 1)) + + colourDialog = gtk.ColorSelectionDialog('Select colour for %s' % surname) + colourDialog.colorsel.set_current_color(colour) + response = colourDialog.run() + + if response == gtk.RESPONSE_OK: + colour = colourDialog.colorsel.get_current_color() + colourName = '#%02x%02x%02x' % ( + int(colour.red *256/65536), + int(colour.green*256/65536), + int(colour.blue *256/65536)) + self.model.set_value(iter, 1, colourName) + + colourDialog.destroy() + + def addSurnameClicked(self, obj): + skipList = set() + iter = self.model.get_iter_first() + while (iter): + surname = self.model.get_value(iter, 0) + skipList.add(surname.encode('iso-8859-1','xmlcharrefreplace')) + iter = self.model.iter_next(iter) + + ln = LastNameDialog(self.db, self.dialog.uistate, self.dialog.track, self.surnames, skipList) + surnameSet = ln.run() + for surname in surnameSet: + self.model.append([surname, '#ffffff']) + + def delSurnameClicked(self, obj): + (path, column) = self.treeView.get_cursor() + if (path): + iter = self.model.get_iter(path) + self.model.remove(iter) + #------------------------------------------------------------------------- # # Menu class diff --git a/src/PluginUtils/__init__.py b/src/PluginUtils/__init__.py index 61ce369b9..f2b5e5959 100644 --- a/src/PluginUtils/__init__.py +++ b/src/PluginUtils/__init__.py @@ -30,7 +30,7 @@ from _MenuOptions import MenuOptions, \ NumberOption, FloatOption, BooleanOption, TextOption, \ EnumeratedListOption, FilterListOption, StringOption, ColourButtonOption, \ - PersonOption, PersonListOption + PersonOption, PersonListOption, SurnameColourOption from _PluginMgr import \ register_export, register_import, \ register_tool, register_report, \ diff --git a/src/ReportBase/_GraphvizReportDialog.py b/src/ReportBase/_GraphvizReportDialog.py index 6194db616..69f0edc4c 100644 --- a/src/ReportBase/_GraphvizReportDialog.py +++ b/src/ReportBase/_GraphvizReportDialog.py @@ -270,6 +270,23 @@ class GVDocBase(BaseDoc.BaseDoc,BaseDoc.GVDoc): self.write('\n') + def add_comment(self, comment): + """ + Add a comment. + + Implementes BaseDoc.GVDoc.add_comment(). + """ + tmp = comment.split('\n') + for line in tmp: + text = line.strip() + print 'text="%s"\n' % text + if text == "": + self.write('\n') + elif text.startswith('#'): + self.write('%s\n' % text) + else: + self.write('# %s\n' % text) + def start_subgraph(self,id): self.write(' subgraph cluster_%s\n' % id) self.write(' {\n') diff --git a/src/plugins/GVFamilyLines.py b/src/plugins/GVFamilyLines.py index 6d1b06692..be4507a2b 100644 --- a/src/plugins/GVFamilyLines.py +++ b/src/plugins/GVFamilyLines.py @@ -67,7 +67,7 @@ from PluginUtils import register_report from ReportBase import Report, ReportUtils, ReportOptions, CATEGORY_CODE, MODE_GUI, MODE_CLI from ReportBase import Report, MenuReportOptions, MODE_GUI, MODE_CLI, CATEGORY_GRAPHVIZ from ReportBase._ReportDialog import ReportDialog -from PluginUtils import register_report, FilterListOption, EnumeratedListOption, BooleanOption, NumberOption, ColourButtonOption, PersonListOption +from PluginUtils import register_report, FilterListOption, EnumeratedListOption, BooleanOption, NumberOption, ColourButtonOption, PersonListOption, SurnameColourOption from QuestionDialog import ErrorDialog, WarningDialog from BasicUtils import name_displayer as _nd from DateHandler import displayer as _dd @@ -109,8 +109,8 @@ class FamilyLinesOptions(MenuReportOptions): category = _('People of Interest') # -------------------------------- - personList = PersonListOption( _('People of interest'), '', dbstate) - personList.set_help( _('People of interest are used as a starting point when determining \"family lines\".')) + personList = PersonListOption( _('People of interest'), '', dbstate) + personList.set_help( _('People of interest are used as a starting point when determining \"family lines\".')) menu.add_option(category, 'FLgidlist', personList) followParents = BooleanOption( _('Follow parents to determine family lines'), True) @@ -129,7 +129,9 @@ class FamilyLinesOptions(MenuReportOptions): category = _('Family Colours') # ---------------------------- - # todo, family colours + surnameColour = SurnameColourOption(_('Family colours'), '', dbstate) + surnameColour.set_help( _('Colours to use for various family lines.')) + menu.add_option(category, 'FLsurnameColours', surnameColour) # ------------------------- category = _('Individuals') @@ -270,7 +272,7 @@ class FamilyLinesReport(Report): # convert the 'surnameColours' string to a dictionary of names and colours self.surnameColours = {} - tmp = '' # TODO, FIXME options.handler.options_dict['FLsurnameColours'].split() + tmp = options.handler.options_dict['FLsurnameColours'].split() while len(tmp) > 1: surname = tmp.pop(0).encode('iso-8859-1','xmlcharrefreplace') colour = tmp.pop(0) @@ -316,19 +318,19 @@ class FamilyLinesReport(Report): # now that begin_report() has done the work, output what we've # obtained into whatever file or format the user expects to use - self.doc.write('# Number of people in database: %d\n' % self.db.get_number_of_people()) - self.doc.write('# Number of people of interest: %d\n' % len(self.peopleToOutput)) - self.doc.write('# Number of families in database: %d\n' % self.db.get_number_of_families()) - self.doc.write('# Number of families of interest: %d\n' % len(self.familiesToOutput)) + self.doc.add_comment('# Number of people in database: %d' % self.db.get_number_of_people()) + self.doc.add_comment('# Number of people of interest: %d' % len(self.peopleToOutput)) + self.doc.add_comment('# Number of families in database: %d' % self.db.get_number_of_families()) + self.doc.add_comment('# Number of families of interest: %d' % len(self.familiesToOutput)) if self.removeExtraPeople: - self.doc.write('# Additional people removed: %d\n' % self.deletedPeople) - self.doc.write('# Additional families removed: %d\n' % self.deletedFamilies) - self.doc.write('# Initial list of people of interest:\n') + self.doc.add_comment('# Additional people removed: %d' % self.deletedPeople) + self.doc.add_comment('# Additional families removed: %d' % self.deletedFamilies) + self.doc.add_comment('# Initial list of people of interest:') for handle in self.interestSet: person = self.db.get_person_from_handle(handle) gid = person.get_gramps_id() name = person.get_primary_name().get_regular_name() - self.doc.write('# -> %s, %s\n' % (gid, name)) + self.doc.add_comment('# -> %s, %s' % (gid, name)) self.writePeople() self.writeFamilies() @@ -609,7 +611,7 @@ class FamilyLinesReport(Report): def writePeople(self): - self.doc.write('\n') + self.doc.add_comment('') # if we're going to attempt to include images, then use the HTML style of .dot file bUseHtmlOutput = False @@ -746,7 +748,7 @@ class FamilyLinesReport(Report): def writeFamilies(self): - self.doc.write('\n') + self.doc.add_comment('') # loop through all the families we need to output for familyHandle in self.familiesToOutput: @@ -804,7 +806,7 @@ class FamilyLinesReport(Report): if label != '': label += '\\n' label += '%s' % childrenStr - self.doc.add_node(fgid,label,"ellipse","","filled",self.colourFamilies) + self.doc.add_node(fgid, label, "ellipse", "", "filled", self.colourFamilies) # now that we have the families written, go ahead and link the parents and children to the families @@ -820,7 +822,7 @@ class FamilyLinesReport(Report): if self.useSubgraphs and fatherHandle and motherHandle: self.doc.start_subgraph(fgid) - self.doc.write('\n') + self.doc.add_comment('') # see if we have a father to link to this family if fatherHandle: @@ -843,7 +845,7 @@ class FamilyLinesReport(Report): for childRef in family.get_child_ref_list(): if childRef.ref in self.peopleToOutput: child = self.db.get_person_from_handle(childRef.ref) - comment = "child: %s" % child.get_primary_name().get_regular_name() + comment = "child: %s" % child.get_primary_name().get_regular_name() self.doc.add_link(child.get_gramps_id(), fgid, comment=comment)