# # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2007-2008 Brian G. Matherly # Copyright (C) 2008,2010 Gary Burton # Copyright (C) 2008 Craig J. Anderson # Copyright (C) 2009 Nick Hall # Copyright (C) 2010 Jakim Friant # # 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$ """ Specific option handling for a GUI. """ #------------------------------------------------------------------------ # # python modules # #------------------------------------------------------------------------ from gen.ggettext import gettext as _ import os import sys #------------------------------------------------------------------------- # # gtk modules # #------------------------------------------------------------------------- import gtk import gobject #------------------------------------------------------------------------- # # gramps modules # #------------------------------------------------------------------------- import Utils from gui.utils import ProgressMeter from gui.pluginmanager import GuiPluginManager from gui import widgets import ManagedWindow from QuestionDialog import OptionDialog from gui.selectors import SelectorFactory from gen.display.name import displayer as _nd from Filters import GenericFilterFactory, GenericFilter, Rules import gen #------------------------------------------------------------------------ # # Dialog window used to select a surname # #------------------------------------------------------------------------ class LastNameDialog(ManagedWindow.ManagedWindow): """ A dialog that allows the selection of a surname from the database. """ def __init__(self, database, uistate, track, surnames, skip_list=set()): ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT | \ gtk.DIALOG_NO_SEPARATOR buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT) self.__dlg = gtk.Dialog(None, uistate.window, flags, buttons) self.__dlg.set_position(gtk.WIN_POS_CENTER_ON_PARENT) self.set_window(self.__dlg, None, _('Select surname')) 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.__tree_view = 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.__tree_view.append_column(col1) self.__tree_view.append_column(col2) scrolled_window = gtk.ScrolledWindow() scrolled_window.add(self.__tree_view) scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrolled_window.set_shadow_type(gtk.SHADOW_OUT) self.__dlg.vbox.pack_start(scrolled_window, expand=True, fill=True) scrolled_window.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 = ProgressMeter(_('Finding Surnames')) progress.set_pass(_('Finding surnames'), database.get_number_of_people()) for person in database.iter_people(): progress.step() 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 skip_list: 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.__tree_selection = self.__tree_view.get_selection() self.__tree_selection.set_mode(gtk.SELECTION_MULTIPLE) self.__tree_selection.select_path(0) def run(self): """ Display the dialog and return the selected surnames when done. """ response = self.__dlg.run() surname_set = set() if response == gtk.RESPONSE_ACCEPT: (mode, paths) = self.__tree_selection.get_selected_rows() for path in paths: i = self.__model.get_iter(path) surname = self.__model.get_value(i, 0) surname_set.add(surname) self.__dlg.destroy() return surname_set #------------------------------------------------------------------------- # # GuiStringOption class # #------------------------------------------------------------------------- class GuiStringOption(gtk.Entry): """ This class displays an option that is a simple one-line string. """ def __init__(self, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.StringOption @return: nothing """ gtk.Entry.__init__(self) self.__option = option self.set_text( self.__option.get_value() ) self.connect('changed', self.__text_changed) self.set_tooltip_text(self.__option.get_help()) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __text_changed(self, obj): # IGNORE:W0613 - obj is unused """ Handle the change of the value. """ self.__option.set_value( self.get_text() ) def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------- # # GuiColorOption class # #------------------------------------------------------------------------- class GuiColorOption(gtk.ColorButton): """ This class displays an option that allows the selection of a colour. """ def __init__(self, option, dbstate, uistate, track): self.__option = option value = self.__option.get_value() gtk.ColorButton.__init__( self, gtk.gdk.color_parse(value) ) self.connect('color-set', self.__value_changed) self.set_tooltip_text(self.__option.get_help()) def __value_changed(self, obj): # IGNORE:W0613 - obj is unused """ Handle the change of color. """ colour = self.get_color() value = '#%02x%02x%02x' % ( int(colour.red * 256 / 65536), int(colour.green * 256 / 65536), int(colour.blue * 256 / 65536)) self.__option.set_value(value) #------------------------------------------------------------------------- # # GuiNumberOption class # #------------------------------------------------------------------------- class GuiNumberOption(gtk.SpinButton): """ This class displays an option that is a simple number with defined maximum and minimum values. """ def __init__(self, option, dbstate, uistate, track): self.__option = option decimals = 0 step = self.__option.get_step() adj = gtk.Adjustment(1, self.__option.get_min(), self.__option.get_max(), step) # Calculate the number of decimal places if necessary if step < 1: import math decimals = int(math.log10(step) * -1) gtk.SpinButton.__init__(self, adj, digits=decimals) gtk.SpinButton.set_numeric(self, True) self.set_value(self.__option.get_value()) self.connect('value_changed', self.__value_changed) self.set_tooltip_text(self.__option.get_help()) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __value_changed(self, obj): # IGNORE:W0613 - obj is unused """ Handle the change of the value. """ vtype = type(self.__option.get_value()) self.__option.set_value( vtype(self.get_value()) ) def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------- # # GuiTextOption class # #------------------------------------------------------------------------- class GuiTextOption(gtk.ScrolledWindow): """ This class displays an option that is a multi-line string. """ def __init__(self, option, dbstate, uistate, track): self.__option = option gtk.ScrolledWindow.__init__(self) self.set_shadow_type(gtk.SHADOW_IN) self.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) # Add a TextView value = self.__option.get_value() gtext = gtk.TextView() gtext.get_buffer().set_text("\n".join(value)) gtext.set_editable(1) self.add(gtext) # Required for tooltip gtext.add_events(gtk.gdk.ENTER_NOTIFY_MASK) gtext.add_events(gtk.gdk.LEAVE_NOTIFY_MASK) gtext.set_tooltip_text(self.__option.get_help()) self.__buff = gtext.get_buffer() self.__buff.connect('changed', self.__value_changed) def __value_changed(self, obj): # IGNORE:W0613 - obj is unused """ Handle the change of the value. """ text_val = unicode( self.__buff.get_text( self.__buff.get_start_iter(), self.__buff.get_end_iter(), False) ) self.__option.set_value( text_val.split('\n') ) #------------------------------------------------------------------------- # # GuiBooleanOption class # #------------------------------------------------------------------------- class GuiBooleanOption(gtk.CheckButton): """ This class displays an option that is a boolean (True or False). """ def __init__(self, option, dbstate, uistate, track): self.__option = option gtk.CheckButton.__init__(self, self.__option.get_label()) self.set_active(self.__option.get_value()) self.connect('toggled', self.__value_changed) self.set_tooltip_text(self.__option.get_help()) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __value_changed(self, obj): # IGNORE:W0613 - obj is unused """ Handle the change of the value. """ self.__option.set_value( self.get_active() ) def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------- # # GuiEnumeratedListOption class # #------------------------------------------------------------------------- class GuiEnumeratedListOption(gtk.HBox): """ This class displays an option that provides a finite number of values. Each possible value is assigned a value and a description. """ def __init__(self, option, dbstate, uistate, track): gtk.HBox.__init__(self) evtBox = gtk.EventBox() self.__option = option self.__combo = gtk.combo_box_new_text() evtBox.add(self.__combo) self.pack_start(evtBox, True, True) self.__update_options() self.set_tooltip_text(self.__option.get_help()) self.__combo.connect('changed', self.__value_changed) self.__option.connect('options-changed', self.__update_options) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __value_changed(self, obj): # IGNORE:W0613 - obj is unused """ Handle the change of the value. """ index = self.__combo.get_active() if index < 0: return items = self.__option.get_items() value, description = items[index] # IGNORE:W0612 - description is unused self.__option.set_value( value ) self.value_changed() def value_changed(self): pass def __update_options(self): """ Handle the change of the available options. """ self.__combo.get_model().clear() cur_val = self.__option.get_value() active_index = 0 current_index = 0 for (value, description) in self.__option.get_items(): self.__combo.append_text(description) if value == cur_val: active_index = current_index current_index += 1 self.__combo.set_active( active_index ) def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------- # # GuiPersonOption class # #------------------------------------------------------------------------- class GuiPersonOption(gtk.HBox): """ This class displays an option that allows a person from the database to be selected. """ def __init__(self, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.PersonOption @return: nothing """ gtk.HBox.__init__(self) self.__option = option self.__dbstate = dbstate self.__db = dbstate.get_database() self.__uistate = uistate self.__track = track self.__person_label = gtk.Label() self.__person_label.set_alignment(0.0, 0.5) pevt = gtk.EventBox() pevt.add(self.__person_label) person_button = widgets.SimpleButton(gtk.STOCK_INDEX, self.__get_person_clicked) person_button.set_relief(gtk.RELIEF_NORMAL) self.pack_start(pevt, False) self.pack_end(person_button, False) person_handle = self.__uistate.get_active('Person') person = self.__dbstate.db.get_person_from_handle(person_handle) if not person: person = self.__db.get_default_person() self.__update_person(person) pevt.set_tooltip_text(self.__option.get_help()) person_button.set_tooltip_text(_('Select a different person')) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __get_person_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the button to choose a different person. """ # Create a filter for the person selector. rfilter = GenericFilter() rfilter.set_logical_op('or') rfilter.add_rule(Rules.Person.IsBookmarked([])) rfilter.add_rule(Rules.Person.HasIdOf([self.__option.get_value()])) # Add the database home person if one exists. default_person = self.__db.get_default_person() if default_person: gid = default_person.get_gramps_id() rfilter.add_rule(Rules.Person.HasIdOf([gid])) # Add the selected person if one exists. person_handle = self.__uistate.get_active('Person') active_person = self.__dbstate.db.get_person_from_handle(person_handle) if active_person: gid = active_person.get_gramps_id() rfilter.add_rule(Rules.Person.HasIdOf([gid])) select_class = SelectorFactory('Person') sel = select_class(self.__dbstate, self.__uistate, self.__track, title=_('Select a person for the report'), filter=rfilter ) person = sel.run() self.__update_person(person) def __update_person(self, person): """ Update the currently selected person. """ if person: name = _nd.display(person) gid = person.get_gramps_id() self.__person_label.set_text( "%s (%s)" % (name, gid) ) self.__option.set_value(gid) def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------- # # GuiFamilyOption class # #------------------------------------------------------------------------- class GuiFamilyOption(gtk.HBox): """ This class displays an option that allows a family from the database to be selected. """ def __init__(self, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.FamilyOption @return: nothing """ gtk.HBox.__init__(self) self.__option = option self.__dbstate = dbstate self.__db = dbstate.get_database() self.__uistate = uistate self.__track = track self.__family_label = gtk.Label() self.__family_label.set_alignment(0.0, 0.5) pevt = gtk.EventBox() pevt.add(self.__family_label) family_button = widgets.SimpleButton(gtk.STOCK_INDEX, self.__get_family_clicked) family_button.set_relief(gtk.RELIEF_NORMAL) self.pack_start(pevt, False) self.pack_end(family_button, False) self.__initialize_family() pevt.set_tooltip_text(self.__option.get_help()) family_button.set_tooltip_text(_('Select a different family')) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __initialize_family(self): """ Find a family to initialize the option with. Any family will do, but try to find a family that the user is likely interested in. """ family_list = [] # Use the active family if one is selected family = self.__uistate.get_active('Family') if family: family_list = [family] if not family_list: # Next try the family of the active person person_handle = self.__uistate.get_active('Person') person = self.__dbstate.db.get_person_from_handle(person_handle) if person: family_list = person.get_family_handle_list() if not family_list: # Next try the family of the default person in the database. person = self.__db.get_default_person() if person: family_list = person.get_family_handle_list() if not family_list: # Finally, take any family you can find. for family in self.__db.iter_family_handles(): self.__update_family(family) break else: self.__update_family(family_list[0]) def __get_family_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the button to choose a different family. """ # Create a filter for the person selector. rfilter = GenericFilterFactory('Family')() rfilter.set_logical_op('or') # Add the current family rfilter.add_rule(Rules.Family.HasIdOf([self.__option.get_value()])) # Add all bookmarked families rfilter.add_rule(Rules.Family.IsBookmarked([])) # Add the families of the database home person if one exists. default_person = self.__db.get_default_person() if default_person: family_list = default_person.get_family_handle_list() for family_handle in family_list: family = self.__db.get_family_from_handle(family_handle) gid = family.get_gramps_id() rfilter.add_rule(Rules.Family.HasIdOf([gid])) # Add the families of the selected person if one exists. active_person = self.__db.get_default_person() if active_person: family_list = active_person.get_family_handle_list() for family_handle in family_list: family = self.__db.get_family_from_handle(family_handle) gid = family.get_gramps_id() rfilter.add_rule(Rules.Family.HasIdOf([gid])) select_class = SelectorFactory('Family') sel = select_class(self.__dbstate, self.__uistate, self.__track, filter=rfilter ) family = sel.run() if family: self.__update_family(family.get_handle()) def __update_family(self, handle): """ Update the currently selected family. """ if handle: family = self.__dbstate.db.get_family_from_handle(handle) family_id = family.get_gramps_id() fhandle = family.get_father_handle() mhandle = family.get_mother_handle() if fhandle: father = self.__db.get_person_from_handle(fhandle) father_name = _nd.display(father) else: father_name = _("unknown father") if mhandle: mother = self.__db.get_person_from_handle(mhandle) mother_name = _nd.display(mother) else: mother_name = _("unknown mother") name = _("%s and %s (%s)") % (father_name, mother_name, family_id) self.__family_label.set_text( name ) self.__option.set_value(family_id) def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------- # # GuiNoteOption class # #------------------------------------------------------------------------- class GuiNoteOption(gtk.HBox): """ This class displays an option that allows a note from the database to be selected. """ def __init__(self, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.NoteOption @return: nothing """ gtk.HBox.__init__(self) self.__option = option self.__dbstate = dbstate self.__db = dbstate.get_database() self.__uistate = uistate self.__track = track self.__note_label = gtk.Label() self.__note_label.set_alignment(0.0, 0.5) pevt = gtk.EventBox() pevt.add(self.__note_label) note_button = widgets.SimpleButton(gtk.STOCK_INDEX, self.__get_note_clicked) note_button.set_relief(gtk.RELIEF_NORMAL) self.pack_start(pevt, False) self.pack_end(note_button, False) # Initialize to the current value nid = self.__option.get_value() note = self.__db.get_note_from_gramps_id(nid) self.__update_note(note) pevt.set_tooltip_text(self.__option.get_help()) note_button.set_tooltip_text(_('Select an existing note')) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __get_note_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the button to choose a different note. """ select_class = SelectorFactory('Note') sel = select_class(self.__dbstate, self.__uistate, self.__track) note = sel.run() self.__update_note(note) def __update_note(self, note): """ Update the currently selected note. """ if note: note_id = note.get_gramps_id() txt = " ".join(note.get().split()) #String must be unicode for truncation to work for non ascii characters txt = unicode(txt) if len(txt) > 35: txt = txt[:35] + "..." txt = "%s [%s]" % (txt, note_id) self.__note_label.set_text( txt ) self.__option.set_value(note_id) else: txt = "%s" % _('No note given, click button to select one') self.__note_label.set_text( txt ) self.__note_label.set_use_markup(True) self.__option.set_value("") def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------- # # GuiMediaOption class # #------------------------------------------------------------------------- class GuiMediaOption(gtk.HBox): """ This class displays an option that allows a media object from the database to be selected. """ def __init__(self, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.MediaOption @return: nothing """ gtk.HBox.__init__(self) self.__option = option self.__dbstate = dbstate self.__db = dbstate.get_database() self.__uistate = uistate self.__track = track self.__media_label = gtk.Label() self.__media_label.set_alignment(0.0, 0.5) pevt = gtk.EventBox() pevt.add(self.__media_label) media_button = widgets.SimpleButton(gtk.STOCK_INDEX, self.__get_media_clicked) media_button.set_relief(gtk.RELIEF_NORMAL) self.pack_start(pevt, False) self.pack_end(media_button, False) # Initialize to the current value mid = self.__option.get_value() media = self.__db.get_object_from_gramps_id(mid) self.__update_media(media) pevt.set_tooltip_text(self.__option.get_help()) media_button.set_tooltip_text(_('Select an existing media object')) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __get_media_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the button to choose a different note. """ select_class = SelectorFactory('MediaObject') sel = select_class(self.__dbstate, self.__uistate, self.__track) media = sel.run() self.__update_media(media) def __update_media(self, media): """ Update the currently selected media. """ if media: media_id = media.get_gramps_id() txt = "%s [%s]" % (media.get_description(), media_id) self.__media_label.set_text( txt ) self.__option.set_value(media_id) else: txt = "%s" % _('No image given, click button to select one') self.__media_label.set_text( txt ) self.__media_label.set_use_markup(True) self.__option.set_value("") def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------- # # GuiPersonListOption class # #------------------------------------------------------------------------- class GuiPersonListOption(gtk.HBox): """ This class displays a widget that allows multiple people from the database to be selected. """ def __init__(self, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.PersonListOption @return: nothing """ gtk.HBox.__init__(self) self.__option = option self.__dbstate = dbstate self.__db = dbstate.get_database() self.__uistate = uistate self.__track = track self.__model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) self.__tree_view = gtk.TreeView(self.__model) self.__tree_view.set_size_request(150, 150) col1 = gtk.TreeViewColumn(_('Name' ), gtk.CellRendererText(), text=0) col2 = gtk.TreeViewColumn(_('ID' ), 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.__tree_view.append_column(col1) self.__tree_view.append_column(col2) self.__scrolled_window = gtk.ScrolledWindow() self.__scrolled_window.add(self.__tree_view) self.__scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.__scrolled_window.set_shadow_type(gtk.SHADOW_OUT) self.pack_start(self.__scrolled_window, expand=True, fill=True) value = self.__option.get_value() for gid in value.split(): person = self.__db.get_person_from_gramps_id(gid) if person: name = _nd.display(person) self.__model.append([name, gid]) # now setup the '+' and '-' pushbutton for adding/removing people from # the container self.__add_person = widgets.SimpleButton(gtk.STOCK_ADD, self.__add_person_clicked) self.__del_person = widgets.SimpleButton(gtk.STOCK_REMOVE, self.__del_person_clicked) self.__vbbox = gtk.VButtonBox() self.__vbbox.add(self.__add_person) self.__vbbox.add(self.__del_person) self.__vbbox.set_layout(gtk.BUTTONBOX_SPREAD) self.pack_end(self.__vbbox, expand=False) self.__tree_view.set_tooltip_text(self.__option.get_help()) def __update_value(self): """ Parse the object and return. """ gidlist = '' i = self.__model.get_iter_first() while (i): gid = self.__model.get_value(i, 1) gidlist = gidlist + gid + ' ' i = self.__model.iter_next(i) self.__option.set_value(gidlist) def __add_person_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the add person button. """ # people we already have must be excluded # so we don't list them multiple times skip_list = set() i = self.__model.get_iter_first() while (i): gid = self.__model.get_value(i, 1) # get the GID stored in column #1 person = self.__db.get_person_from_gramps_id(gid) skip_list.add(person.get_handle()) i = self.__model.iter_next(i) select_class = SelectorFactory('Person') sel = select_class(self.__dbstate, self.__uistate, self.__track, skip=skip_list) person = sel.run() if person: name = _nd.display(person) gid = person.get_gramps_id() self.__model.append([name, gid]) # 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 family_list = person.get_family_handle_list() for family_handle in family_list: family = self.__db.get_family_from_handle(family_handle) if person.get_handle() == family.get_father_handle(): spouse_handle = family.get_mother_handle() else: spouse_handle = family.get_father_handle() if spouse_handle and (spouse_handle not in skip_list): spouse = self.__db.get_person_from_handle( spouse_handle) spouse_name = _nd.display(spouse) text = _('Also include %s?') % spouse_name prompt = OptionDialog(_('Select Person'), text, _('No'), None, _('Yes'), None) if prompt.get_response() == gtk.RESPONSE_YES: gid = spouse.get_gramps_id() self.__model.append([spouse_name, gid]) self.__update_value() def __del_person_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the delete person button. """ (path, column) = self.__tree_view.get_cursor() if (path): i = self.__model.get_iter(path) self.__model.remove(i) self.__update_value() #------------------------------------------------------------------------- # # GuiPlaceListOption class # #------------------------------------------------------------------------- class GuiPlaceListOption(gtk.HBox): """ This class displays a widget that allows multiple places from the database to be selected. """ def __init__(self, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.PlaceListOption @return: nothing """ gtk.HBox.__init__(self) self.__option = option self.__dbstate = dbstate self.__db = dbstate.get_database() self.__uistate = uistate self.__track = track self.__model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) self.__tree_view = gtk.TreeView(self.__model) self.__tree_view.set_size_request(150, 150) col1 = gtk.TreeViewColumn(_('Place' ), gtk.CellRendererText(), text=0) col2 = gtk.TreeViewColumn(_('ID' ), 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.__tree_view.append_column(col1) self.__tree_view.append_column(col2) self.__scrolled_window = gtk.ScrolledWindow() self.__scrolled_window.add(self.__tree_view) self.__scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.__scrolled_window.set_shadow_type(gtk.SHADOW_OUT) self.pack_start(self.__scrolled_window, expand=True, fill=True) value = self.__option.get_value() for gid in value.split(): place = self.__db.get_place_from_gramps_id(gid) if place: place_name = place.get_title() self.__model.append([place_name, gid]) # now setup the '+' and '-' pushbutton for adding/removing places from # the container self.__add_place = widgets.SimpleButton(gtk.STOCK_ADD, self.__add_place_clicked) self.__del_place = widgets.SimpleButton(gtk.STOCK_REMOVE, self.__del_place_clicked) self.__vbbox = gtk.VButtonBox() self.__vbbox.add(self.__add_place) self.__vbbox.add(self.__del_place) self.__vbbox.set_layout(gtk.BUTTONBOX_SPREAD) self.pack_end(self.__vbbox, expand=False) self.__tree_view.set_tooltip_text(self.__option.get_help()) def __update_value(self): """ Parse the object and return. """ gidlist = '' i = self.__model.get_iter_first() while (i): gid = self.__model.get_value(i, 1) gidlist = gidlist + gid + ' ' i = self.__model.iter_next(i) self.__option.set_value(gidlist) def __add_place_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the add place button. """ # places we already have must be excluded # so we don't list them multiple times skip_list = set() i = self.__model.get_iter_first() while (i): gid = self.__model.get_value(i, 1) # get the GID stored in column #1 place = self.__db.get_place_from_gramps_id(gid) skip_list.add(place.get_handle()) i = self.__model.iter_next(i) select_class = SelectorFactory('Place') sel = select_class(self.__dbstate, self.__uistate, self.__track, skip=skip_list) place = sel.run() if place: place_name = place.get_title() gid = place.get_gramps_id() self.__model.append([place_name, gid]) self.__update_value() def __del_place_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the delete place button. """ (path, column) = self.__tree_view.get_cursor() if (path): i = self.__model.get_iter(path) self.__model.remove(i) self.__update_value() #------------------------------------------------------------------------- # # GuiSurnameColorOption class # #------------------------------------------------------------------------- class GuiSurnameColorOption(gtk.HBox): """ This class displays 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, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.SurnameColorOption @return: nothing """ gtk.HBox.__init__(self) self.__option = option self.__dbstate = dbstate self.__db = dbstate.get_database() self.__uistate = uistate self.__track = track # This will get populated the first time the dialog is run, # and used each time after. self.__surnames = {} # list of surnames and count self.__model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING) self.__tree_view = gtk.TreeView(self.__model) self.__tree_view.set_size_request(150, 150) self.__tree_view.connect('row-activated', self.__row_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.__tree_view.append_column(col1) self.__tree_view.append_column(col2) self.scrolled_window = gtk.ScrolledWindow() self.scrolled_window.add(self.__tree_view) self.scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) self.scrolled_window.set_shadow_type(gtk.SHADOW_OUT) self.pack_start(self.scrolled_window, expand=True, fill=True) self.add_surname = widgets.SimpleButton(gtk.STOCK_ADD, self.__add_clicked) self.del_surname = widgets.SimpleButton(gtk.STOCK_REMOVE, self.__del_clicked) self.vbbox = gtk.VButtonBox() self.vbbox.add(self.add_surname) self.vbbox.add(self.del_surname) self.vbbox.set_layout(gtk.BUTTONBOX_SPREAD) self.pack_end(self.vbbox, expand=False) # populate the surname/colour treeview # # For versions prior to 3.0.2, the fields were delimited with # whitespace. However, this causes problems when the surname # also has a space within it. When populating the control, # support both the new and old format -- look for the \xb0 # delimiter, and if it isn't there, assume this is the old- # style space-delimited format. (Bug #2162.) if (self.__option.get_value().find(u'\xb0') >= 0): tmp = self.__option.get_value().split(u'\xb0') else: tmp = self.__option.get_value().split(' ') while len(tmp) > 1: surname = tmp.pop(0) colour = tmp.pop(0) self.__model.append([surname, colour]) self.__tree_view.set_tooltip_text(self.__option.get_help()) def __value_changed(self): """ Parse the object and return. """ surname_colours = '' i = self.__model.get_iter_first() while (i): surname = self.__model.get_value(i, 0) #surname = surname.encode('iso-8859-1','xmlcharrefreplace') colour = self.__model.get_value(i, 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 # # Hmmm...putting whitespace between the fields causes # problems when the surname has whitespace -- for example, # with surnames like "Del Monte". So now we insert a non- # whitespace character which is unlikely to appear in # a surname. (See bug report #2162.) surname_colours += surname + u'\xb0' + colour + u'\xb0' i = self.__model.iter_next(i) self.__option.set_value( surname_colours ) def __row_clicked(self, treeview, path, column): """ Handle the case of a row being clicked on. """ # get the surname and colour value for this family i = self.__model.get_iter(path) surname = self.__model.get_value(i, 0) colour = gtk.gdk.color_parse(self.__model.get_value(i, 1)) title = 'Select colour for %s' % surname colour_dialog = gtk.ColorSelectionDialog(title) colorsel = colour_dialog.colorsel colorsel.set_current_color(colour) response = colour_dialog.run() if response == gtk.RESPONSE_OK: colour = colorsel.get_current_color() colour_name = '#%02x%02x%02x' % ( int(colour.red *256/65536), int(colour.green*256/65536), int(colour.blue *256/65536)) self.__model.set_value(i, 1, colour_name) colour_dialog.destroy() self.__value_changed() def __add_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the the add surname button. """ skip_list = set() i = self.__model.get_iter_first() while (i): surname = self.__model.get_value(i, 0) skip_list.add(surname.encode('iso-8859-1','xmlcharrefreplace')) i = self.__model.iter_next(i) ln_dialog = LastNameDialog(self.__db, self.__uistate, self.__track, self.__surnames, skip_list) surname_set = ln_dialog.run() for surname in surname_set: self.__model.append([surname, '#ffffff']) self.__value_changed() def __del_clicked(self, obj): # IGNORE:W0613 - obj is unused """ Handle the the delete surname button. """ (path, column) = self.__tree_view.get_cursor() if (path): i = self.__model.get_iter(path) self.__model.remove(i) self.__value_changed() #------------------------------------------------------------------------- # # GuiDestinationOption class # #------------------------------------------------------------------------- class GuiDestinationOption(gtk.HBox): """ This class displays an option that allows the user to select a DestinationOption. """ def __init__(self, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.DestinationOption @return: nothing """ gtk.HBox.__init__(self) self.__option = option self.__entry = gtk.Entry() self.__entry.set_text( self.__option.get_value() ) self.__entry.connect('changed', self.__text_changed) self.__button = gtk.Button() img = gtk.Image() img.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON) self.__button.add(img) self.__button.connect('clicked', self.__select_file) self.pack_start(self.__entry, True, True) self.pack_end(self.__button, False, False) self.set_tooltip_text(self.__option.get_help()) self.__option.connect('options-changed', self.__option_changed) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __text_changed(self, obj): # IGNORE:W0613 - obj is unused """ Handle the change of the value. """ self.__option.set_value( self.__entry.get_text() ) def __select_file(self, obj): """ Handle the user's request to select a file (or directory). """ if self.__option.get_directory_entry(): my_action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER else: my_action = gtk.FILE_CHOOSER_ACTION_SAVE fcd = gtk.FileChooserDialog(_("Save As"), action=my_action, buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK)) name = os.path.abspath(self.__option.get_value()) if self.__option.get_directory_entry(): while not os.path.isdir(name): # Keep looking up levels to find a valid drive. name, tail = os.path.split(name) if not name: # Avoid infinite loops name = os.getcwd() fcd.set_current_folder(name) else: fcd.set_current_name(name) status = fcd.run() if status == gtk.RESPONSE_OK: path = Utils.get_unicode_path(fcd.get_filename()) if path: if not self.__option.get_directory_entry() and \ not path.endswith(self.__option.get_extension()): path = path + self.__option.get_extension() self.__entry.set_text(path) self.__option.set_value(path) fcd.destroy() def __option_changed(self): """ Handle a change of the option. """ extension = self.__option.get_extension() directory = self.__option.get_directory_entry() value = self.__option.get_value() if not directory and not value.endswith(extension): value = value + extension self.__option.set_value(value) elif directory and value.endswith(extension): value = value[:-len(extension)] self.__option.set_value(value) self.__entry.set_text( self.__option.get_value() ) def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------- # # GuiStyleOption class # #------------------------------------------------------------------------- class GuiStyleOption(GuiEnumeratedListOption): """ This class displays a StyleOption. """ def __init__(self, option, dbstate, uistate, track): """ @param option: The option to display. @type option: gen.plug.menu.StyleOption @return: nothing """ GuiEnumeratedListOption.__init__(self, option, dbstate, uistate, track) self.__option = option self.__button = gtk.Button("%s..." % _("Style Editor")) self.__button.connect('clicked', self.__on_style_edit_clicked) self.pack_end(self.__button, False, False) def __on_style_edit_clicked(self, *obj): """The user has clicked on the 'Edit Styles' button. Create a style sheet editor object and let them play. When they are done, update the displayed styles.""" from gen.plug.docgen import StyleSheetList from gui.plug.report._styleeditor import StyleListDisplay style_list = StyleSheetList(self.__option.get_style_file(), self.__option.get_default_style()) StyleListDisplay(style_list, None, None) new_items = [] for style_name in style_list.get_style_names(): new_items.append( (style_name, style_name) ) self.__option.set_items(new_items) #------------------------------------------------------------------------- # # GuiBooleanListOption class # #------------------------------------------------------------------------- class GuiBooleanListOption(gtk.HBox): """ This class displays an option that provides a list of check boxes. Each possible value is assigned a value and a description. """ def __init__(self, option, dbstate, uistate, track): gtk.HBox.__init__(self) self.__option = option self.__cbutton = [] COLUMNS = 2 # Number of checkbox columns column = [] for i in range(COLUMNS): vbox = gtk.VBox() self.pack_start(vbox, True, True) column.append(vbox) vbox.show() counter = 0 default = option.get_value().split(',') for description in option.get_descriptions(): button = gtk.CheckButton(description) self.__cbutton.append(button) if default[counter] == 'True': button.set_active(True) button.connect("toggled", self.__value_changed) column[counter % COLUMNS].pack_start(button, True, True) button.show() counter += 1 self.set_tooltip_text(self.__option.get_help()) self.__option.connect('avail-changed', self.__update_avail) self.__update_avail() def __value_changed(self, button): """ Handle the change of the value. """ value = '' for button in self.__cbutton: value = value + str(button.get_active()) + ',' value = value[:len(value)-1] self.__option.set_value(value) def __update_avail(self): """ Update the availability (sensitivity) of this widget. """ avail = self.__option.get_available() self.set_sensitive(avail) #------------------------------------------------------------------------ # # GuiMenuOptions class # #------------------------------------------------------------------------ class GuiMenuOptions(object): """ Introduction ============ A GuiMenuOptions is used to implement the necessary funtions for adding options to a GTK dialog. """ def __init__(self): self.menu = gen.plug.menu.Menu() # Fill options_dict with report/tool defaults: self.options_dict = {} self.options_help = {} self.add_menu_options(self.menu) for name in self.menu.get_all_option_names(): option = self.menu.get_option_by_name(name) self.options_dict[name] = option.get_value() self.options_help[name] = [ "", option.get_help() ] def make_default_style(self, default_style): """ This function is currently required by some reports. """ pass def add_menu_options(self, menu): """ Add the user defined options to the menu. @param menu: A menu class for the options to belong to. @type menu: Menu @return: nothing """ raise NotImplementedError def add_menu_option(self, category, name, option): """ Add a single option to the menu. """ self.menu.add_option(category, name, option) self.options_dict[name] = option.get_value() self.options_help[name] = [ "", option.get_help() ] def add_user_options(self, dialog): """ Generic method to add user options to the gui. """ for category in self.menu.get_categories(): for name in self.menu.get_option_names(category): option = self.menu.get_option(category, name) # override option default with xml-saved value: if name in self.options_dict: option.set_value(self.options_dict[name]) widget, label = make_gui_option(option, dialog.dbstate, dialog.uistate, dialog.track) if widget is None: print "UNKNOWN OPTION: ", option else: if label: dialog.add_frame_option(category, option.get_label(), widget) else: dialog.add_frame_option(category, "", widget) def parse_user_options(self, dialog): # IGNORE:W0613 - dialog is unused """ Load the changed values into the saved options. """ for name in self.menu.get_all_option_names(): option = self.menu.get_option_by_name(name) self.options_dict[name] = option.get_value() def make_gui_option(option, dbstate, uistate, track): """ Stand-alone function so that Options can be used in other ways, too. Takes an Option and returns a GuiOption. """ widget = None label = True pmgr = GuiPluginManager.get_instance() external_options = pmgr.get_external_opt_dict() if isinstance(option, gen.plug.menu.PersonOption): widget = GuiPersonOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.FamilyOption): widget = GuiFamilyOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.NoteOption): widget = GuiNoteOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.MediaOption): widget = GuiMediaOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.PersonListOption): widget = GuiPersonListOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.NumberOption): widget = GuiNumberOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.BooleanOption): widget = GuiBooleanOption(option, dbstate, uistate, track) label = False elif isinstance(option, gen.plug.menu.DestinationOption): widget = GuiDestinationOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.StringOption): widget = GuiStringOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.StyleOption): widget = GuiStyleOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.EnumeratedListOption): widget = GuiEnumeratedListOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.TextOption): widget = GuiTextOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.ColorOption): widget = GuiColorOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.SurnameColorOption): widget = GuiSurnameColorOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.PlaceListOption): widget = GuiPlaceListOption(option, dbstate, uistate, track) elif isinstance(option, gen.plug.menu.BooleanListOption): widget = GuiBooleanListOption(option, dbstate, uistate, track) elif option.__class__ in external_options: widget = external_options[option.__class__](option, dbstate, uistate, track) else: raise AttributeError( "can't make GuiOption: unknown option type: '%s'" % option) return widget, label