class ExtractCity(tool.BatchTool, ManagedWindow.ManagedWindow): """ Extracts city, state, and zip code information from an place description if the title is empty and the description falls into the category of: New York, NY 10000 Sorry for those not in the US or Canada. I doubt this will work for any other locales. Works for Sweden if the decriptions is like Stockholm (A) where the letter A is the abbreviation letter for laen. Works for France if the description is like Paris, IDF 75000, FRA or Paris, ILE DE FRANCE 75000, FRA """ def __init__(self, dbstate, uistate, options_class, name, callback=None): self.label = _('Extract Place data') ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) self.set_window(gtk.Window(), gtk.Label(), '') tool.BatchTool.__init__(self, dbstate, uistate, options_class, name) if not self.fail: uistate.set_busy_cursor(True) self.run(dbstate.db) uistate.set_busy_cursor(False) def run(self, db): """ Performs the actual extraction of information """ self.progress = ProgressMeter(_('Checking Place Titles'), '') self.progress.set_pass(_('Looking for place fields'), self.db.get_number_of_places()) self.name_list = [] for place in db.iter_places(): descr = place.get_title() loc = place.get_main_location() self.progress.step() if loc.get_street() == loc.get_city() == \ loc.get_state() == loc.get_postal_code() == "": match = CITY_STATE_ZIP.match(descr.strip()) if match: data = match.groups() city = data[0] state = data[2] postal = data[5] val = " ".join(state.strip().split()).upper() if state: new_state = STATE_MAP.get(val.upper()) if new_state: self.name_list.append( (place.handle, (city, new_state[0], postal, COUNTRY[new_state[1]]))) continue # Check if there is a left parant. in the string, might be Swedish laen. match = CITY_LAEN.match(descr.strip().replace(","," ")) if match: data = match.groups() city = data[0] state = '(' + data[1] + ')' postal = None val = " ".join(state.strip().split()).upper() if state: new_state = STATE_MAP.get(val.upper()) if new_state: self.name_list.append( (place.handle, (city, new_state[0], postal, COUNTRY[new_state[1]]))) continue match = CITY_STATE.match(descr.strip()) if match: data = match.groups() city = data[0] state = data[1] postal = None if state: m0 = STATE_ZIP.match(state) if m0: (state, postal) = m0.groups() val = " ".join(state.strip().split()).upper() if state: new_state = STATE_MAP.get(val.upper()) if new_state: self.name_list.append( (place.handle, (city, new_state[0], postal, COUNTRY[new_state[1]]))) continue val = " ".join(descr.strip().split()).upper() new_state = STATE_MAP.get(val) if new_state: self.name_list.append( (place.handle, (None, new_state[0], None, COUNTRY[new_state[1]]))) self.progress.close() if self.name_list: self.display() else: self.close() from QuestionDialog import OkDialog OkDialog(_('No modifications made'), _("No place information could be extracted.")) def display(self): self.top = Glade("changenames.glade") window = self.top.toplevel self.top.connect_signals({ "destroy_passed_object" : self.close, "on_ok_clicked" : self.on_ok_clicked, "on_help_clicked" : self.on_help_clicked, "on_delete_event" : self.close, }) self.list = self.top.get_object("list") self.set_window(window, self.top.get_object('title'), self.label) lbl = self.top.get_object('info') lbl.set_line_wrap(True) lbl.set_text( _('Below is a list of Places with the possible data that can ' 'be extracted from the place title. Select the places you ' 'wish Gramps to convert.')) self.model = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) r = gtk.CellRendererToggle() r.connect('toggled', self.toggled) c = gtk.TreeViewColumn(_('Select'), r, active=0) self.list.append_column(c) for (title, col) in COLS: render = gtk.CellRendererText() if col > 1: render.set_property('editable', True) render.connect('edited', self.__change_name, col) self.list.append_column( gtk.TreeViewColumn(title, render, text=col)) self.list.set_model(self.model) self.iter_list = [] self.progress.set_pass(_('Building display'), len(self.name_list)) for (id, data) in self.name_list: place = self.db.get_place_from_handle(id) descr = place.get_title() handle = self.model.append() self.model.set_value(handle, 0, True) self.model.set_value(handle, 1, descr) if data[0]: self.model.set_value(handle, 2, data[0]) if data[1]: self.model.set_value(handle, 3, data[1]) if data[2]: self.model.set_value(handle, 4, data[2]) if data[3]: self.model.set_value(handle, 5, data[3]) self.model.set_value(handle, 6, id) self.iter_list.append(handle) self.progress.step() self.progress.close() self.show() def __change_name(self, text, path, new_text, col): self.model[path][col] = new_text return def toggled(self, cell, path_string): path = tuple(map(int, path_string.split(':'))) row = self.model[path] row[0] = not row[0] def build_menu_names(self, obj): return (self.label, None) def on_help_clicked(self, obj): """Display the relevant portion of GRAMPS manual""" GrampsDisplay.help() def on_ok_clicked(self, obj): with DbTxn(_("Extract Place data"), self.db, batch=True) as self.trans: self.db.disable_signals() changelist = [node for node in self.iter_list if self.model.get_value(node, 0)] for change in changelist: row = self.model[change] place = self.db.get_place_from_handle(row[6]) (city, state, postal, country) = (row[2], row[3], row[4], row[5]) if city: place.get_main_location().set_city(city) if state: place.get_main_location().set_state(state) if postal: place.get_main_location().set_postal_code(postal) if country: place.get_main_location().set_country(country) self.db.commit_place(place, self.trans) self.db.enable_signals() self.db.request_rebuild() self.close()
def display(self): self.top = Glade("changenames.glade") window = self.top.toplevel self.top.connect_signals({ "destroy_passed_object" : self.close, "on_ok_clicked" : self.on_ok_clicked, "on_help_clicked" : self.on_help_clicked, "on_delete_event" : self.close, }) self.list = self.top.get_object("list") self.set_window(window, self.top.get_object('title'), self.label) lbl = self.top.get_object('info') lbl.set_line_wrap(True) lbl.set_text( _('Below is a list of Places with the possible data that can ' 'be extracted from the place title. Select the places you ' 'wish Gramps to convert.')) self.model = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING) r = gtk.CellRendererToggle() r.connect('toggled', self.toggled) c = gtk.TreeViewColumn(_('Select'), r, active=0) self.list.append_column(c) for (title, col) in COLS: render = gtk.CellRendererText() if col > 1: render.set_property('editable', True) render.connect('edited', self.__change_name, col) self.list.append_column( gtk.TreeViewColumn(title, render, text=col)) self.list.set_model(self.model) self.iter_list = [] self.progress.set_pass(_('Building display'), len(self.name_list)) for (id, data) in self.name_list: place = self.db.get_place_from_handle(id) descr = place.get_title() handle = self.model.append() self.model.set_value(handle, 0, True) self.model.set_value(handle, 1, descr) if data[0]: self.model.set_value(handle, 2, data[0]) if data[1]: self.model.set_value(handle, 3, data[1]) if data[2]: self.model.set_value(handle, 4, data[2]) if data[3]: self.model.set_value(handle, 5, data[3]) self.model.set_value(handle, 6, id) self.iter_list.append(handle) self.progress.step() self.progress.close() self.show()
def __init__(self, dbstate, uistate, options_class, name, callback=None): """ Relationship calculator class. """ tool.Tool.__init__(self, dbstate, options_class, name) ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) #set the columns to see for data in BasePersonView.CONFIGSETTINGS: if data[0] == 'columns.rank': colord = data[1] elif data[0] == 'columns.visible': colvis = data[1] elif data[0] == 'columns.size': colsize = data[1] self.colord = [] for col, size in zip(colord, colsize): if col in colvis: self.colord.append((1, col, size)) else: self.colord.append((0, col, size)) self.dbstate = dbstate self.relationship = Relationship.get_relationship_calculator() self.relationship.connect_db_signals(dbstate) self.glade = Glade() self.person = self.db.get_person_from_handle( uistate.get_active('Person')) name = '' if self.person: name = name_displayer.display(self.person) self.title = _('Relationship calculator: %(person_name)s') % { 'person_name': name } window = self.glade.toplevel self.titlelabel = self.glade.get_object('title') self.set_window( window, self.titlelabel, _('Relationship to %(person_name)s') % {'person_name': name}, self.title) self.tree = self.glade.get_object("peopleList") self.text = self.glade.get_object("text1") self.textbuffer = gtk.TextBuffer() self.text.set_buffer(self.textbuffer) self.model = PersonTreeModel(self.db) self.tree.set_model(self.model) self.tree.connect('key-press-event', self._key_press) self.selection = self.tree.get_selection() self.selection.set_mode(gtk.SELECTION_SINGLE) #keep reference of column so garbage collection works self.columns = [] for pair in self.colord: if not pair[0]: continue name = column_names[pair[1]] column = gtk.TreeViewColumn(name, gtk.CellRendererText(), markup=pair[1]) column.set_resizable(True) column.set_min_width(60) column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY) self.tree.append_column(column) #keep reference of column so garbage collection works self.columns.append(column) self.sel = self.tree.get_selection() self.changedkey = self.sel.connect('changed', self.on_apply_clicked) self.closebtn = self.glade.get_object("button5") self.closebtn.connect('clicked', self.close) if not self.person: self.window.hide() ErrorDialog( _('Active person has not been set'), _('You must select an active person for this ' 'tool to work properly.')) self.close() return self.show()
class ChangeTypes(tool.BatchTool, ManagedWindow.ManagedWindow): def __init__(self, dbstate, uistate, options_class, name, callback=None): tool.BatchTool.__init__(self, dbstate, uistate, options_class, name) if self.fail: return if uistate: self.title = _('Change Event Types') ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) self.init_gui() else: self.run_tool(cli=True) def init_gui(self): # Draw dialog and make it handle everything self.glade = Glade() self.auto1 = self.glade.get_object("original") self.auto2 = self.glade.get_object("new") # Need to display localized event names etype = EventType() event_names = sorted(etype.get_standard_names(), key=locale.strxfrm) AutoComp.fill_combo(self.auto1, event_names) AutoComp.fill_combo(self.auto2, event_names) etype.set_from_xml_str(self.options.handler.options_dict['fromtype']) self.auto1.child.set_text(str(etype)) etype.set_from_xml_str(self.options.handler.options_dict['totype']) self.auto2.child.set_text(str(etype)) window = self.glade.toplevel self.set_window(window, self.glade.get_object('title'), self.title) self.glade.connect_signals({ "on_close_clicked": self.close, "on_apply_clicked": self.on_apply_clicked, "on_delete_event": self.close, }) self.show() def build_menu_names(self, obj): return (self.title, None) def run_tool(self, cli=False): # Run tool and return results # These are English names, no conversion needed fromtype = self.options.handler.options_dict['fromtype'] totype = self.options.handler.options_dict['totype'] modified = 0 with DbTxn(_('Change types'), self.db, batch=True) as self.trans: self.db.disable_signals() if not cli: progress = ProgressMeter(_('Analyzing Events'), '') progress.set_pass('', self.db.get_number_of_events()) for event_handle in self.db.get_event_handles(): event = self.db.get_event_from_handle(event_handle) if event.get_type().xml_str() == fromtype: event.type.set_from_xml_str(totype) modified += 1 self.db.commit_event(event, self.trans) if not cli: progress.step() if not cli: progress.close() self.db.enable_signals() self.db.request_rebuild() if modified == 0: msg = _("No event record was modified.") else: msg = ngettext("%d event record was modified.", "%d event records were modified.", modified) % modified if cli: print "Done: ", msg return (bool(modified), msg) def on_apply_clicked(self, obj): # Need to store English names for later comparison the_type = EventType() the_type.set(self.auto1.child.get_text()) self.options.handler.options_dict['fromtype'] = the_type.xml_str() the_type.set(self.auto2.child.get_text()) self.options.handler.options_dict['totype'] = the_type.xml_str() modified, msg = self.run_tool(cli=False) OkDialog(_('Change types'), msg, self.window) # Save options self.options.handler.save_options() self.close()
class EditSource(EditPrimary): def __init__(self, dbstate, uistate, track, source, callback=None): EditPrimary.__init__(self, dbstate, uistate, track, source, dbstate.db.get_source_from_handle, dbstate.db.get_source_from_gramps_id, callback) def empty_object(self): return gen.lib.Source() def get_menu_title(self): title = self.obj.get_title() if title: title = _('Source') + ": " + title else: title = _('New Source') return title def _local_init(self): self.width_key = 'interface.source-width' self.height_key = 'interface.source-height' assert (self.obj) self.glade = Glade() self.set_window(self.glade.toplevel, None, self.get_menu_title()) def _connect_signals(self): self.define_ok_button(self.glade.get_object('ok'), self.save) self.define_cancel_button(self.glade.get_object('cancel')) self.define_help_button(self.glade.get_object('help')) def _connect_db_signals(self): """ Connect any signals that need to be connected. Called by the init routine of the base class (_EditPrimary). """ self._add_db_signal('source-rebuild', self._do_close) self._add_db_signal('source-delete', self.check_for_close) def _setup_fields(self): self.author = MonitoredEntry(self.glade.get_object("author"), self.obj.set_author, self.obj.get_author, self.db.readonly) self.pubinfo = MonitoredEntry(self.glade.get_object("pubinfo"), self.obj.set_publication_info, self.obj.get_publication_info, self.db.readonly) self.gid = MonitoredEntry(self.glade.get_object("gid"), self.obj.set_gramps_id, self.obj.get_gramps_id, self.db.readonly) self.priv = PrivacyButton(self.glade.get_object("private"), self.obj, self.db.readonly) self.abbrev = MonitoredEntry(self.glade.get_object("abbrev"), self.obj.set_abbreviation, self.obj.get_abbreviation, self.db.readonly) self.title = MonitoredEntry(self.glade.get_object("source_title"), self.obj.set_title, self.obj.get_title, self.db.readonly) def _create_tabbed_pages(self): notebook = gtk.Notebook() self.note_tab = NoteTab(self.dbstate, self.uistate, self.track, self.obj.get_note_list(), self.get_menu_title(), gen.lib.NoteType.SOURCE) self._add_tab(notebook, self.note_tab) self.track_ref_for_deletion("note_tab") self.gallery_tab = GalleryTab(self.dbstate, self.uistate, self.track, self.obj.get_media_list()) self._add_tab(notebook, self.gallery_tab) self.track_ref_for_deletion("gallery_tab") self.data_tab = DataEmbedList(self.dbstate, self.uistate, self.track, self.obj) self._add_tab(notebook, self.data_tab) self.track_ref_for_deletion("data_tab") self.repo_tab = RepoEmbedList(self.dbstate, self.uistate, self.track, self.obj.get_reporef_list()) self._add_tab(notebook, self.repo_tab) self.track_ref_for_deletion("repo_tab") self.backref_list = CitationBackRefList( self.dbstate, self.uistate, self.track, self.db.find_backlink_handles(self.obj.handle)) self.backref_tab = self._add_tab(notebook, self.backref_list) self.track_ref_for_deletion("backref_tab") self.track_ref_for_deletion("backref_list") self._setup_notebook_tabs(notebook) notebook.show_all() self.glade.get_object('vbox').pack_start(notebook, True) def build_menu_names(self, source): return (_('Edit Source'), self.get_menu_title()) def save(self, *obj): self.ok_button.set_sensitive(False) if self.object_is_empty(): ErrorDialog( _("Cannot save source"), _("No data exists for this source. Please " "enter data or cancel the edit.")) self.ok_button.set_sensitive(True) return (uses_dupe_id, id) = self._uses_duplicate_id() if uses_dupe_id: prim_object = self.get_from_gramps_id(id) name = prim_object.get_title() msg1 = _("Cannot save source. ID already exists.") msg2 = _("You have attempted to use the existing Gramps ID with " "value %(id)s. This value is already used by '" "%(prim_object)s'. Please enter a different ID or leave " "blank to get the next available ID value.") % { 'id': id, 'prim_object': name } ErrorDialog(msg1, msg2) self.ok_button.set_sensitive(True) return with DbTxn('', self.db) as trans: if not self.obj.get_handle(): self.db.add_source(self.obj, trans) msg = _("Add Source (%s)") % self.obj.get_title() else: if not self.obj.get_gramps_id(): self.obj.set_gramps_id( self.db.find_next_source_gramps_id()) self.db.commit_source(self.obj, trans) msg = _("Edit Source (%s)") % self.obj.get_title() trans.set_description(msg) self.close() if self.callback: self.callback(self.obj)
class RelCalc(tool.Tool, ManagedWindow.ManagedWindow): def __init__(self, dbstate, uistate, options_class, name, callback=None): """ Relationship calculator class. """ tool.Tool.__init__(self, dbstate, options_class, name) ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) #set the columns to see for data in BasePersonView.CONFIGSETTINGS: if data[0] == 'columns.rank': colord = data[1] elif data[0] == 'columns.visible': colvis = data[1] elif data[0] == 'columns.size': colsize = data[1] self.colord = [] for col, size in zip(colord, colsize): if col in colvis: self.colord.append((1, col, size)) else: self.colord.append((0, col, size)) self.dbstate = dbstate self.relationship = Relationship.get_relationship_calculator() self.relationship.connect_db_signals(dbstate) self.glade = Glade() self.person = self.db.get_person_from_handle( uistate.get_active('Person')) name = '' if self.person: name = name_displayer.display(self.person) self.title = _('Relationship calculator: %(person_name)s') % { 'person_name': name } window = self.glade.toplevel self.titlelabel = self.glade.get_object('title') self.set_window( window, self.titlelabel, _('Relationship to %(person_name)s') % {'person_name': name}, self.title) self.tree = self.glade.get_object("peopleList") self.text = self.glade.get_object("text1") self.textbuffer = gtk.TextBuffer() self.text.set_buffer(self.textbuffer) self.model = PersonTreeModel(self.db) self.tree.set_model(self.model) self.tree.connect('key-press-event', self._key_press) self.selection = self.tree.get_selection() self.selection.set_mode(gtk.SELECTION_SINGLE) #keep reference of column so garbage collection works self.columns = [] for pair in self.colord: if not pair[0]: continue name = column_names[pair[1]] column = gtk.TreeViewColumn(name, gtk.CellRendererText(), markup=pair[1]) column.set_resizable(True) column.set_min_width(60) column.set_sizing(gtk.TREE_VIEW_COLUMN_GROW_ONLY) self.tree.append_column(column) #keep reference of column so garbage collection works self.columns.append(column) self.sel = self.tree.get_selection() self.changedkey = self.sel.connect('changed', self.on_apply_clicked) self.closebtn = self.glade.get_object("button5") self.closebtn.connect('clicked', self.close) if not self.person: self.window.hide() ErrorDialog( _('Active person has not been set'), _('You must select an active person for this ' 'tool to work properly.')) self.close() return self.show() def close(self, *obj): """ Close relcalc tool. Remove non-gtk connections so garbage collection can do its magic. """ self.relationship.disconnect_db_signals(self.dbstate) self.sel.disconnect(self.changedkey) ManagedWindow.ManagedWindow.close(self, *obj) def build_menu_names(self, obj): return (_("Relationship Calculator tool"), None) def on_apply_clicked(self, obj): model, node = self.tree.get_selection().get_selected() if not node: return handle = model.get_value(node, PeopleBaseModel.COLUMN_INT_ID) other_person = self.db.get_person_from_handle(handle) if other_person is None: self.textbuffer.set_text("") return #now determine the relation, and print it out rel_strings, common_an = self.relationship.get_all_relationships( self.db, self.person, other_person) p1 = name_displayer.display(self.person) p2 = name_displayer.display(other_person) text = [] if other_person is None: pass elif self.person.handle == other_person.handle: rstr = _( "%(person)s and %(active_person)s are the same person.") % { 'person': p1, 'active_person': p2 } text.append((rstr, "")) elif len(rel_strings) == 0: rstr = _("%(person)s and %(active_person)s are not related.") % { 'person': p2, 'active_person': p1 } text.append((rstr, "")) for rel_string, common in zip(rel_strings, common_an): rstr = _( "%(person)s is the %(relationship)s of %(active_person)s.") % { 'person': p2, 'relationship': rel_string, 'active_person': p1 } length = len(common) if length == 1: person = self.db.get_person_from_handle(common[0]) if common[0] in [other_person.handle, self.person.handle]: commontext = '' else: name = name_displayer.display(person) commontext = " " + _("Their common ancestor is %s.") % name elif length == 2: p1c = self.db.get_person_from_handle(common[0]) p2c = self.db.get_person_from_handle(common[1]) p1str = name_displayer.display(p1c) p2str = name_displayer.display(p2c) commontext = " " + _( "Their common ancestors are %(ancestor1)s and %(ancestor2)s." ) % { 'ancestor1': p1str, 'ancestor2': p2str } elif length > 2: index = 0 commontext = " " + _("Their common ancestors are: ") for person_handle in common: person = self.db.get_person_from_handle(person_handle) if index: commontext += ", " commontext += name_displayer.display(person) index += 1 commontext += "." else: commontext = "" text.append((rstr, commontext)) textval = "" for val in text: textval += "%s %s\n" % (val[0], val[1]) self.textbuffer.set_text(textval) def _key_press(self, obj, event): if event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter): store, paths = self.selection.get_selected_rows() if paths and len(paths[0]) == 1: if self.tree.row_expanded(paths[0]): self.tree.collapse_row(paths[0]) else: self.tree.expand_row(paths[0], 0) return True return False
def define_glade(self, top_module, glade_file=None): if glade_file is None: raise TypeError, "ManagedWindow.define_glade: no glade file" glade_file = const.GLADE_FILE self._gladeobj = Glade(glade_file, None, top_module) return self._gladeobj
class EditPerson(EditPrimary): """ The EditPerson dialog is derived from the EditPrimary class. It allows for the editing of the primary object type of Person. """ QR_CATEGORY = CATEGORY_QR_PERSON def __init__(self, dbstate, uistate, track, person, callback=None): """ Create an EditPerson window. Associate a person with the window. """ EditPrimary.__init__(self, dbstate, uistate, track, person, dbstate.db.get_person_from_handle, dbstate.db.get_person_from_gramps_id, callback) def empty_object(self): """ Return an empty Person object for comparison for changes. This is used by the base class (EditPrimary). """ person = gen.lib.Person() #the editor requires a surname person.primary_name.add_surname(gen.lib.Surname()) person.primary_name.set_primary_surname(0) return person def get_menu_title(self): if self.obj and self.obj.get_handle(): name = name_displayer.display(self.obj) title = _('Person: %(name)s') % {'name': name} else: name = name_displayer.display(self.obj) if name: title = _('New Person: %(name)s') % {'name': name} else: title = _('New Person') return title def get_preview_name(self): prevname = name_displayer.display(self.obj) return prevname def _local_init(self): """ Performs basic initialization, including setting up widgets and the glade interface. Local initialization function. This is called by the base class of EditPrimary, and overridden here. """ self.width_key = 'interface.person-width' self.height_key = 'interface.person-height' self.pname = self.obj.get_primary_name() self.should_guess_gender = (not self.obj.get_gramps_id() and self.obj.get_gender() == gen.lib.Person.UNKNOWN) self.top = Glade() self.set_window(self.top.toplevel, None, self.get_menu_title()) self.obj_photo = self.top.get_object("personPix") self.frame_photo = self.top.get_object("frame5") self.eventbox = self.top.get_object("eventbox1") self.singsurnfr = SingSurn(self.top) self.multsurnfr = self.top.get_object("hboxmultsurnames") self.singlesurn_active = True self.surntab = SurnameTab(self.dbstate, self.uistate, self.track, self.obj.get_primary_name(), on_change=self._changed_name) self.set_contexteventbox(self.top.get_object("eventboxtop")) def _post_init(self): """ Handle any initialization that needs to be done after the interface is brought up. Post initalization function. This is called by _EditPrimary's init routine, and overridden in the derived class (this class). """ self.load_person_image() self.given.grab_focus() self._changed_name(None) self.top.get_object("hboxmultsurnames").pack_start(self.surntab) if len(self.obj.get_primary_name().get_surname_list()) > 1: self.multsurnfr.set_size_request( -1, int(config.get('interface.surname-box-height'))) self.singsurnfr.hide_all() self.multsurnfr.show_all() self.singlesurn_active = False else: self.multsurnfr.hide_all() self.singsurnfr.show_all() self.singlesurn_active = True #if self.pname.get_surname() and not self.pname.get_first_name(): # self.given.grab_focus() #else: # self.surname_field.grab_focus() def _connect_signals(self): """ Connect any signals that need to be connected. Called by the init routine of the base class (_EditPrimary). """ self.define_cancel_button(self.top.get_object("button15")) self.define_ok_button(self.top.get_object("ok"), self.save) self.define_help_button( self.top.get_object("button134"), _('Gramps_3.4_Wiki_Manual_-_Entering_and_editing_data:_detailed_-_part_1' ), _('manpage section id|Editing_information_about_people')) self.given.connect("focus_out_event", self._given_focus_out_event) self.top.get_object("editnamebtn").connect("clicked", self._edit_name_clicked) self.top.get_object("multsurnamebtn").connect("clicked", self._mult_surn_clicked) self.eventbox.connect('button-press-event', self._image_button_press) def _connect_db_signals(self): """ Connect any signals that need to be connected. Called by the init routine of the base class (_EditPrimary). """ self._add_db_signal('person-rebuild', self._do_close) self._add_db_signal('person-delete', self.check_for_close) self._add_db_signal('family-rebuild', self.family_change) self._add_db_signal('family-delete', self.family_change) self._add_db_signal('family-update', self.family_change) self._add_db_signal('family-add', self.family_change) self._add_db_signal('event-update', self.event_updated) self._add_db_signal('event-rebuild', self.event_updated) self._add_db_signal('event-delete', self.event_updated) def family_change(self, handle_list=[]): """ Callback for family change signals. This should rebuild the backreferences to family in person when: 1)a family the person is parent of changes. Person could have been removed 2)a family the person is child in changes. Child could have been removed 3)a family is changed. The person could be added as child or parent """ #As this would be an extensive check, we choose the easy path and # rebuild family backreferences on all family changes self._update_families() def _update_families(self): phandle = self.obj.get_handle() if phandle: #new person has no handle yet and cannot be in a family. person = self.dbstate.db.get_person_from_handle(phandle) self.obj.set_family_handle_list(person.get_family_handle_list()) self.obj.set_parent_family_handle_list( person.get_parent_family_handle_list()) #a family groupname in event_list might need to be changed # we just rebuild the view always self.event_list.rebuild_callback() def event_updated(self, obj): #place in event might have changed, or person event shown in the list # we just rebuild the view always self.event_list.rebuild_callback() def _validate_call(self, widget, text): """ a callname must be a part of the given name, see if this is the case """ validcall = self.given.obj.get_text().split() dummy = copy(validcall) for item in dummy: validcall += item.split('-') if text in validcall: return return ValidationError( _("Call name must be the given name that " "is normally used.")) def _setup_fields(self): """ Connect the GrampsWidget objects to field in the interface. This allows the widgets to keep the data in the attached Person object up to date at all times, eliminating a lot of need in 'save' routine. """ self.private = widgets.PrivacyButton(self.top.get_object('private'), self.obj, self.db.readonly) self.gender = widgets.MonitoredMenu( self.top.get_object('gender'), self.obj.set_gender, self.obj.get_gender, ((_('female'), gen.lib.Person.FEMALE), (_('male'), gen.lib.Person.MALE), (_('unknown'), gen.lib.Person.UNKNOWN)), self.db.readonly) self.ntype_field = widgets.MonitoredDataType( self.top.get_object("ntype"), self.pname.set_type, self.pname.get_type, self.db.readonly, self.db.get_name_types()) #part of Given Name section self.given = widgets.MonitoredEntry(self.top.get_object("given_name"), self.pname.set_first_name, self.pname.get_first_name, self.db.readonly) self.call = widgets.MonitoredEntry(self.top.get_object("call"), self.pname.set_call_name, self.pname.get_call_name, self.db.readonly) self.call.connect("validate", self._validate_call) #force validation now with initial entry self.call.obj.validate(force=True) self.title = widgets.MonitoredEntry(self.top.get_object("title"), self.pname.set_title, self.pname.get_title, self.db.readonly) self.suffix = widgets.MonitoredEntryIndicator( self.top.get_object("suffix"), self.pname.set_suffix, self.pname.get_suffix, _('suffix'), self.db.readonly) self.nick = widgets.MonitoredEntry(self.top.get_object("nickname"), self.pname.set_nick_name, self.pname.get_nick_name, self.db.readonly) #part of Single Surname section self.surname_field = widgets.MonitoredEntry( self.top.get_object("surname"), self.pname.get_primary_surname().set_surname, self.pname.get_primary_surname().get_surname, self.db.readonly, autolist=self.db.get_surname_list() if not self.db.readonly else []) self.prefix = widgets.MonitoredEntryIndicator( self.top.get_object("prefix"), self.pname.get_primary_surname().set_prefix, self.pname.get_primary_surname().get_prefix, _('prefix'), self.db.readonly) self.ortype_field = widgets.MonitoredDataType( self.top.get_object("cmborigin"), self.pname.get_primary_surname().set_origintype, self.pname.get_primary_surname().get_origintype, self.db.readonly, self.db.get_origin_types()) #other fields self.tags = widgets.MonitoredTagList(self.top.get_object("tag_label"), self.top.get_object("tag_button"), self.obj.set_tag_list, self.obj.get_tag_list, self.db, self.uistate, self.track, self.db.readonly) self.gid = widgets.MonitoredEntry(self.top.get_object("gid"), self.obj.set_gramps_id, self.obj.get_gramps_id, self.db.readonly) #make sure title updates automatically for obj in [ self.top.get_object("given_name"), self.top.get_object("call"), self.top.get_object("suffix"), self.top.get_object("prefix"), self.top.get_object("surname"), ]: obj.connect('changed', self._changed_name) self.preview_name = self.top.get_object("full_name") self.preview_name.modify_font(pango.FontDescription('sans bold 12')) def _create_tabbed_pages(self): """ Create the notebook tabs and insert them into the main window. """ notebook = gtk.Notebook() notebook.set_scrollable(True) self.event_list = PersonEventEmbedList(self.dbstate, self.uistate, self.track, self.obj) self._add_tab(notebook, self.event_list) self.track_ref_for_deletion("event_list") self.name_list = NameEmbedList(self.dbstate, self.uistate, self.track, self.obj.get_alternate_names(), self.obj, self.name_callback) self._add_tab(notebook, self.name_list) self.track_ref_for_deletion("name_list") self.srcref_list = CitationEmbedList(self.dbstate, self.uistate, self.track, self.obj.get_citation_list(), self.get_menu_title()) self._add_tab(notebook, self.srcref_list) self.track_ref_for_deletion("srcref_list") self.attr_list = AttrEmbedList(self.dbstate, self.uistate, self.track, self.obj.get_attribute_list()) self._add_tab(notebook, self.attr_list) self.track_ref_for_deletion("attr_list") self.addr_list = AddrEmbedList(self.dbstate, self.uistate, self.track, self.obj.get_address_list()) self._add_tab(notebook, self.addr_list) self.track_ref_for_deletion("addr_list") self.note_tab = NoteTab(self.dbstate, self.uistate, self.track, self.obj.get_note_list(), self.get_menu_title(), notetype=gen.lib.NoteType.PERSON) self._add_tab(notebook, self.note_tab) self.track_ref_for_deletion("note_tab") self.gallery_tab = GalleryTab(self.dbstate, self.uistate, self.track, self.obj.get_media_list(), self.load_person_image) self._add_tab(notebook, self.gallery_tab) self.track_ref_for_deletion("gallery_tab") self.web_list = WebEmbedList(self.dbstate, self.uistate, self.track, self.obj.get_url_list()) self._add_tab(notebook, self.web_list) self.track_ref_for_deletion("web_list") self.person_ref_list = PersonRefEmbedList( self.dbstate, self.uistate, self.track, self.obj.get_person_ref_list()) self._add_tab(notebook, self.person_ref_list) self.track_ref_for_deletion("person_ref_list") self.lds_list = LdsEmbedList(self.dbstate, self.uistate, self.track, self.obj.get_lds_ord_list()) self._add_tab(notebook, self.lds_list) self.track_ref_for_deletion("lds_list") self.backref_tab = PersonBackRefList( self.dbstate, self.uistate, self.track, self.db.find_backlink_handles(self.obj.handle)) self._add_tab(notebook, self.backref_tab) self.track_ref_for_deletion("backref_tab") self._setup_notebook_tabs(notebook) notebook.show_all() self.top.get_object('vbox').pack_start(notebook, True) def _changed_name(self, *obj): """ callback to changes typed by user to the person name. Update the window title, and default name in name tab """ self.update_title(self.get_menu_title()) self.preview_name.set_text(self.get_preview_name()) self.name_list.update_defname() def name_callback(self): """ Callback if changes happen in the name tab that impact the preferred name. """ self.pname = self.obj.get_primary_name() self.ntype_field.reinit(self.pname.set_type, self.pname.get_type) self.given.reinit(self.pname.set_first_name, self.pname.get_first_name) self.call.reinit(self.pname.set_call_name, self.pname.get_call_name) self.title.reinit(self.pname.set_title, self.pname.get_title) self.suffix.reinit(self.pname.set_suffix, self.pname.get_suffix) self.nick.reinit(self.pname.set_nick_name, self.pname.get_nick_name) #part of Single Surname section self.surname_field.reinit(self.pname.get_primary_surname().set_surname, self.pname.get_primary_surname().get_surname) self.prefix.reinit(self.pname.get_primary_surname().set_prefix, self.pname.get_primary_surname().get_prefix) self.ortype_field.reinit( self.pname.get_primary_surname().set_origintype, self.pname.get_primary_surname().get_origintype) self.__renewmultsurnames() if len(self.pname.get_surname_list()) == 1: self.singlesurn_active = True else: self.singlesurn_active = False if self.singlesurn_active: self.multsurnfr.hide_all() self.singsurnfr.show_all() else: self.singsurnfr.hide_all() self.multsurnfr.show_all() def build_menu_names(self, person): """ Provide the information needed by the base class to define the window management menu entries. """ return (_('Edit Person'), self.get_menu_title()) def _image_button_press(self, obj, event): """ Button press event that is caught when a button has been pressed while on the image on the main form. This does not apply to the images in galleries, just the image on the main form. """ if event.type == gtk.gdk._2BUTTON_PRESS and event.button == 1: media_list = self.obj.get_media_list() if media_list: media_ref = media_list[0] object_handle = media_ref.get_reference_handle() media_obj = self.db.get_object_from_handle(object_handle) try: EditMediaRef(self.dbstate, self.uistate, self.track, media_obj, media_ref, self.load_photo) except Errors.WindowActiveError: pass elif gui.utils.is_right_click(event): media_list = self.obj.get_media_list() if media_list: photo = media_list[0] self._show_popup(photo, event) #do not propagate further: return True def _show_popup(self, photo, event): """ Look for right-clicks on a picture and create a popup menu of the available actions. """ menu = gtk.Menu() menu.set_title(_("Media Object")) obj = self.db.get_object_from_handle(photo.get_reference_handle()) if obj: gui.utils.add_menuitem(menu, _("View"), photo, self._popup_view_photo) gui.utils.add_menuitem(menu, _("Edit Object Properties"), photo, self._popup_change_description) menu.popup(None, None, None, event.button, event.time) def _popup_view_photo(self, obj): """ Open this picture in the default picture viewer. """ media_list = self.obj.get_media_list() if media_list: photo = media_list[0] object_handle = photo.get_reference_handle() ref_obj = self.db.get_object_from_handle(object_handle) photo_path = Utils.media_path_full(self.db, ref_obj.get_path()) gui.utils.open_file_with_default_application(photo_path) def _popup_change_description(self, obj): """ Bring up the EditMediaRef dialog for the image on the main form. """ media_list = self.obj.get_media_list() if media_list: media_ref = media_list[0] object_handle = media_ref.get_reference_handle() media_obj = self.db.get_object_from_handle(object_handle) EditMediaRef(self.dbstate, self.uistate, self.track, media_obj, media_ref, self.load_photo) def _top_contextmenu(self): """ Override from base class, the menuitems and actiongroups for the top of context menu. """ self.all_action = gtk.ActionGroup("/PersonAll") self.home_action = gtk.ActionGroup("/PersonHome") self.track_ref_for_deletion("all_action") self.track_ref_for_deletion("home_action") self.all_action.add_actions([ ('ActivePerson', gtk.STOCK_APPLY, _("Make Active Person"), None, None, self._make_active), ]) self.home_action.add_actions([ ('HomePerson', gtk.STOCK_HOME, _("Make Home Person"), None, None, self._make_home_person), ]) self.all_action.set_visible(True) self.home_action.set_visible(True) ui_top_cm = ''' <menuitem action="ActivePerson"/> <menuitem action="HomePerson"/>''' return ui_top_cm, [self.all_action, self.home_action] def _post_build_popup_ui(self): """ Override base class, make inactive home action if not needed. """ if (self.dbstate.db.get_default_person() and self.obj.get_handle() == self.dbstate.db.get_default_person().get_handle()): self.home_action.set_sensitive(False) else: self.home_action.set_sensitive(True) def _make_active(self, obj): self.uistate.set_active(self.obj.get_handle(), 'Person') def _make_home_person(self, obj): handle = self.obj.get_handle() if handle: self.dbstate.db.set_default_person_handle(handle) def _given_focus_out_event(self, entry, event): """ Callback that occurs when the user leaves the given name field, allowing us to attempt to guess the gender of the person if so requested. """ if not self.should_guess_gender: return False try: gender_type = self.db.genderStats.guess_gender( unicode(entry.get_text())) self.gender.force(gender_type) except: return False return False def _check_for_unknown_gender(self): if self.obj.get_gender() == gen.lib.Person.UNKNOWN: d = GenderDialog(self.window) gender = d.run() d.destroy() if gender >= 0: self.obj.set_gender(gender) def _update_family_ids(self): # Update each of the families child lists to reflect any # change in ordering due to the new birth date family = self.obj.get_main_parents_family_handle() if (family): f = self.db.get_family_from_handle(family) new_order = self.reorder_child_ref_list(self.obj, f.get_child_ref_list()) f.set_child_ref_list(new_order) for family in self.obj.get_parent_family_handle_list(): f = self.db.get_family_from_handle(family) new_order = self.reorder_child_ref_list(self.obj, f.get_child_ref_list()) f.set_child_ref_list(new_order) error = False original = self.db.get_person_from_handle(self.obj.handle) if original: (female, male, unknown) = _select_gender[self.obj.get_gender()] if male and original.get_gender() != gen.lib.Person.MALE: for tmp_handle in self.obj.get_family_handle_list(): temp_family = self.db.get_family_from_handle(tmp_handle) if self.obj == temp_family.get_mother_handle(): if temp_family.get_father_handle() is not None: error = True else: temp_family.set_mother_handle(None) temp_family.set_father_handle(self.obj) elif female and original != gen.lib.Person.FEMALE: for tmp_handle in self.obj.get_family_handle_list(): temp_family = self.db.get_family_from_handle(tmp_handle) if self.obj == temp_family.get_father_handle(): if temp_family.get_mother_handle() is not None: error = True else: temp_family.set_father_handle(None) temp_family.set_mother_handle(self.obj) elif unknown and original.get_gender() != gen.lib.Person.UNKNOWN: for tmp_handle in self.obj.get_family_handle_list(): temp_family = self.db.get_family_from_handle(tmp_handle) if self.obj == temp_family.get_father_handle(): if temp_family.get_mother_handle() is not None: error = True else: temp_family.set_father_handle(None) temp_family.set_mother_handle(self.obj) if self.obj == temp_family.get_mother_handle(): if temp_family.get_father_handle() is not None: error = True else: temp_family.set_mother_handle(None) temp_family.set_father_handle(self.obj) if error: msg2 = _("Problem changing the gender") msg = _("Changing the gender caused problems " "with marriage information.\nPlease check " "the person's marriages.") ErrorDialog(msg2, msg) def save(self, *obj): """ Save the data. """ self.ok_button.set_sensitive(False) if self.object_is_empty(): ErrorDialog( _("Cannot save person"), _("No data exists for this person. Please " "enter data or cancel the edit.")) self.ok_button.set_sensitive(True) return # fix surname problems for name in [self.obj.get_primary_name() ] + self.obj.get_alternate_names(): if len(name.get_surname_list()) > 1: newlist = [ surn for surn in name.get_surname_list() if not surn.is_empty() ] if len(newlist) != len(name.get_surname_list()): name.set_surname_list(newlist) if len(name.get_surname_list()) == 0: name.set_surname_list([gen.lib.Surname()]) try: primind = [ surn.get_primary() for surn in name.get_surname_list() ].index(True) except ValueError: primind = 0 # no match name.set_primary_surname(primind) # fix ide problems (uses_dupe_id, id) = self._uses_duplicate_id() if uses_dupe_id: prim_object = self.get_from_gramps_id(id) name = self.name_displayer.display(prim_object) msg1 = _("Cannot save person. ID already exists.") msg2 = _("You have attempted to use the existing Gramps ID with " "value %(id)s. This value is already used by '" "%(prim_object)s'. Please enter a different ID or leave " "blank to get the next available ID value.") % { 'id': id, 'prim_object': name } ErrorDialog(msg1, msg2) self.ok_button.set_sensitive(True) return self._check_for_unknown_gender() self.db.set_birth_death_index(self.obj) with DbTxn('', self.db) as trans: self._update_family_ids() if not self.obj.get_handle(): self.db.add_person(self.obj, trans) msg = _("Add Person (%s)") % \ self.name_displayer.display(self.obj) else: if not self.obj.get_gramps_id(): self.obj.set_gramps_id( self.db.find_next_person_gramps_id()) self.db.commit_person(self.obj, trans) msg = _("Edit Person (%s)") % \ self.name_displayer.display(self.obj) trans.set_description(msg) self.close() if self.callback: self.callback(self.obj) self.callback = None def _edit_name_clicked(self, obj): """ Bring up the EditName dialog for this name. Called when the edit name button is clicked for the primary name on the main form (not in the names tab). """ try: EditName(self.dbstate, self.uistate, self.track, self.pname, self._update_name) except Errors.WindowActiveError: pass def _mult_surn_clicked(self, obj): """ Show the list entry of multiple surnames """ self.singsurnfr.hide_all() self.singlesurn_active = False #update multsurnfr for possible changes self.__renewmultsurnames() self.multsurnfr.show_all() def _update_name(self, name): """ Called when the primary name has been changed by the EditName dialog. This allows us to update the main form in response to any changes. """ for obj in (self.ntype_field, self.given, self.call, self.title, self.suffix, self.nick, self.surname_field, self.prefix, self.ortype_field): obj.update() self.__renewmultsurnames() if len(self.obj.get_primary_name().get_surname_list()) == 1: self.singlesurn_active = True else: self.singlesurn_active = False if self.singlesurn_active: self.multsurnfr.hide_all() self.singsurnfr.show_all() else: self.singsurnfr.hide_all() self.multsurnfr.show_all() def __renewmultsurnames(self): """Update mult surnames section with what is presently the correct surname. It is easier to recreate the entire mult surnames GUI than changing what has changed visually. """ #remove present surname tab, and put new one msurhbox = self.top.get_object("hboxmultsurnames") msurhbox.remove(self.surntab) self.surntab = SurnameTab(self.dbstate, self.uistate, self.track, self.obj.get_primary_name()) self.multsurnfr.set_size_request( -1, int(config.get('interface.surname-box-height'))) msurhbox.pack_start(self.surntab) def load_person_image(self): """ Load the primary image into the main form if it exists. Used as callback on Gallery Tab too. """ media_list = self.obj.get_media_list() if media_list: ref = media_list[0] handle = ref.get_reference_handle() obj = self.dbstate.db.get_object_from_handle(handle) if obj is None: #notify user of error from QuestionDialog import RunDatabaseRepair RunDatabaseRepair(_('Non existing media found in the Gallery')) else: self.load_photo(ref, obj) else: self.obj_photo.hide() self.frame_photo.hide_all() def load_photo(self, ref, obj): """ Load the person's main photo using the Thumbnailer. """ pixbuf = ThumbNails.get_thumbnail_image( Utils.media_path_full(self.dbstate.db, obj.get_path()), obj.get_mime_type(), ref.get_rectangle()) self.obj_photo.set_from_pixbuf(pixbuf) self.obj_photo.show() self.frame_photo.show_all() def birth_dates_in_order(self, child_ref_list): """ Check any *valid* birthdates in the list to insure that they are in numerically increasing order. """ inorder = True prev_date = 0 handle_list = [ref.ref for ref in child_ref_list] for i in range(len(handle_list)): child_handle = handle_list[i] child = self.db.get_person_from_handle(child_handle) if child.get_birth_ref(): event_handle = child.get_birth_ref().ref event = self.db.get_event_from_handle(event_handle) child_date = event.get_date_object().get_sort_value() else: continue if (prev_date <= child_date): # <= allows for twins prev_date = child_date else: inorder = False return inorder def reorder_child_ref_list(self, person, child_ref_list): """ Reorder the child list to put the specified person in his/her correct birth order. Only check *valid* birthdates. Move the person as short a distance as possible. """ if self.birth_dates_in_order(child_ref_list): return (child_ref_list) # Build the person's date string once event_ref = person.get_birth_ref() if event_ref: event = self.db.get_event_from_handle(event_ref.ref) person_bday = event.get_date_object().get_sort_value() else: person_bday = 0 # First, see if the person needs to be moved forward in the list handle_list = [ref.ref for ref in child_ref_list] index = handle_list.index(person.get_handle()) target = index for i in range(index - 1, -1, -1): other = self.db.get_person_from_handle(handle_list[i]) event_ref = other.get_birth_ref() if event_ref: event = self.db.get_event_from_handle(event_ref.ref) other_bday = event.get_date_object().get_sort_value() if other_bday == 0: continue if person_bday < other_bday: target = i else: continue # Now try moving to a later position in the list if (target == index): for i in range(index, len(handle_list)): other = self.db.get_person_from_handle(handle_list[i]) event_ref = other.get_birth_ref() if event_ref: event = self.db.get_event_from_handle(event_ref.ref) other_bday = event.get_date_object().get_sort_value() if other_bday == "99999999": continue if person_bday > other_bday: target = i else: continue # Actually need to move? Do it now. if (target != index): ref = child_ref_list.pop(index) child_ref_list.insert(target, ref) return child_ref_list def _cleanup_on_exit(self): """Unset all things that can block garbage collection. Finalize rest """ ## self.private.destroy() ## self.gender.destroy() ## self.ntype_field.destroy() ## self.given.destroy() ## self.call.destroy() ## self.title.destroy() ## self.suffix.destroy() ## self.nick.destroy() ## self.surname_field.destroy() ## self.prefix.destroy() ## self.ortype_field.destroy() ## self.tags.destroy() ## self.gid.destroy() EditPrimary._cleanup_on_exit(self)
class EditPersonRef(EditSecondary): """ Displays a dialog that allows the user to edit a person reference. """ def __init__(self, dbstate, uistate, track, addr, callback): """ Displays the dialog box. parent - The class that called the PersonRef editor. addr - The address that is to be edited """ EditSecondary.__init__(self, dbstate, uistate, track, addr, callback) def _local_init(self): self.width_key = 'interface.person-ref-width' self.height_key = 'interface.person-ref-height' self.top = Glade() self.set_window(self.top.toplevel, self.top.get_object("title"), _('Person Reference Editor')) self.person_label = self.top.get_object('person') def _setup_fields(self): if self.obj.ref: p = self.dbstate.db.get_person_from_handle(self.obj.ref) self.person_label.set_text(name_displayer.display(p)) self.street = MonitoredEntry( self.top.get_object("relationship"), self.obj.set_relation, self.obj.get_relation, self.db.readonly) self.priv = PrivacyButton( self.top.get_object("private"), self.obj, self.db.readonly) def _connect_signals(self): #self.define_help_button(self.top.get_object('help')) self.define_cancel_button(self.top.get_object('cancel')) self.define_ok_button(self.top.get_object('ok'),self.save) self.top.get_object('select').connect('clicked',self._select_person) def _connect_db_signals(self): """ Connect any signals that need to be connected. Called by the init routine of the base class (_EditPrimary). """ self._add_db_signal('person-rebuild', self.close) self._add_db_signal('person-delete', self.check_for_close) def check_for_close(self, handles): """ Callback method for delete signals. If there is a delete signal of the primary object we are editing, the editor (and all child windows spawned) should be closed """ if self.obj.ref in handles: self.close() def _select_person(self, obj): SelectPerson = SelectorFactory('Person') sel = SelectPerson(self.dbstate, self.uistate, self.track) person = sel.run() if person: self.obj.ref = person.get_handle() self.person_label.set_text(name_displayer.display(person)) def _create_tabbed_pages(self): """ Create the notebook tabs and inserts them into the main window. """ notebook = gtk.Notebook() self.srcref_list = CitationEmbedList(self.dbstate, self.uistate, self.track, self.obj.get_citation_list()) self._add_tab(notebook, self.srcref_list) self.track_ref_for_deletion("srcref_list") self.note_tab = NoteTab(self.dbstate, self.uistate, self.track, self.obj.get_note_list(), notetype=NoteType.ASSOCIATION) self._add_tab(notebook, self.note_tab) self.track_ref_for_deletion("note_tab") self._setup_notebook_tabs(notebook) notebook.show_all() self.top.get_object('vbox').pack_start(notebook, True) def build_menu_names(self, obj): return (_('Person Reference'),_('Person Reference Editor')) def save(self,*obj): """ Called when the OK button is pressed. Gets data from the form and updates the Address data structure. """ if self.obj.ref: if self.callback: self.callback(self.obj) self.callback = None self.close() else: from QuestionDialog import ErrorDialog ErrorDialog( _('No person selected'), _('You must either select a person or Cancel ' 'the edit'))
class ManagedWindow(object): """ Managed window base class. This class provides all the goodies necessary for user-friendly window management in GRAMPS: registering the menu item under the Windows menu, keeping track of child windows, closing them on close/delete event, and presenting itself when selected or attempted to create again. """ def __init__(self, uistate, track, obj): """ Create child windows and add itself to menu, if not there already. The usage from derived classes is envisioned as follows: import ManagedWindow class SomeWindowClass(ManagedWindow.ManagedWindow): def __init__(self,uistate,dbstate,track): window_id = self # Or e.g. window_id = person.handle submenu_label = None # This window cannot have children menu_label = 'Menu label for this window' ManagedWindow.ManagedWindow.__init__(self, uistate, track, window_id, submenu_label, menu_label) # Proceed with the class. ... :param uistate: gramps uistate :param track: {list of parent windows, [] if the main GRAMPS window is the parent} :param obj: The object that is used to id the managed window, The inheriting object needs a method build_menu_names(self, obj) which works on this obj and creates menu labels for use in the Gramps Window Menu. If self.submenu_label ='' then leaf, otherwise branch """ window_key = self.build_window_key(obj) menu_label, submenu_label = self.build_menu_names(obj) self._gladeobj = None self.isWindow = None self.width_key = None self.height_key = None self.__refs_for_deletion = [] if uistate and uistate.gwm.get_item_from_id(window_key): uistate.gwm.get_item_from_id(window_key).present() raise Errors.WindowActiveError('This window is already active') else: self.window_id = window_key self.submenu_label = submenu_label self.menu_label = menu_label self.uistate = uistate if uistate: self.track = self.uistate.gwm.add_item(track, self) else: self.track = [] # Work out parent_window if len(self.track) > 1: # We don't belong to the lop level if self.track[-1] > 0: # If we are not the first in the group, # then first in that same group is our parent parent_item_track = self.track[:-1] parent_item_track.append(0) else: # If we're first in the group, then our parent # is the first in the group one level up parent_item_track = self.track[:-2] parent_item_track.append(0) # Based on the track, get item and then window object self.parent_window = self.uistate.gwm.get_item_from_track( parent_item_track).window else: # On the top level: we use gramps top window if self.uistate: self.parent_window = self.uistate.window else: self.parent_window = None def set_window(self, window, title, text, msg=None, isWindow=False): """ Set the window that is managed. :param window: if isWindow=False window must be a gtk.Window() object, otherwise None :param title: a label widget in which to write the title, None if not needed :param text: text to use as title of window and in title param :param msg: if not None, use msg as title of window instead of text :param isWindow: {if isWindow than self is the window (so self inherits from gtk.Window and from ManagedWindow.ManagedWindow) if not isWindow, than window is the Window to manage, and after this method self.window stores it. } """ self.isWindow = isWindow self.msg = msg self.titlelabel = title if self.isWindow: set_titles(self, title, text, msg) else: set_titles(window, title, text, msg) #closing the gtk.Window must also close ManagedWindow self.window = window self.window.connect('delete-event', self.close) def update_title(self, text): if self.isWindow: set_titles(self, self.titlelabel, text, self.msg) else: set_titles(self.window, self.titlelabel, text, self.msg) def build_menu_names(self, obj): return ('Undefined Menu', 'Undefined Submenu') def build_window_key(self, obj): return id(obj) def define_glade(self, top_module, glade_file=None): if glade_file is None: raise TypeError, "ManagedWindow.define_glade: no glade file" glade_file = const.GLADE_FILE self._gladeobj = Glade(glade_file, None, top_module) return self._gladeobj def get_widget(self, name): assert (self._gladeobj) object = self._gladeobj.get_child_object(name) if object is not None: return object raise ValueError, ( 'ManagedWindow.get_widget: "%s" widget not found in "%s/%s"' % (name, self._gladeobj.dirname, self._gladeobj.filename)) return object def connect_button(self, button_name, function): assert (self._gladeobj) self.get_widget(button_name).connect('clicked', function) def show(self): if self.isWindow: self.set_transient_for(self.parent_window) self.opened = True self.show_all() else: assert self.window, "ManagedWindow: self.window does not exist!" self.window.set_transient_for(self.parent_window) self.opened = True self.window.show_all() def modal_call(self, after_ok_func=None): """ Method to do modal run of the ManagedWindow. Connect the OK button to a method that checks if all is ok, Do not call close, close is called here. (if not ok, do self.window.run() to obtain new response ) TODO: remove close here and do close in ReportDialog, this can only be done, once all methods use modal_call() instead of their own implementation Connect Cancel to do close, delete event is connected to close here in ManagedWindow. Do not generete RESPONSE_OK/CANCEL/DELETE_EVENT on button clicks of other buttons after_ok_func is called on ok click in this method """ #self.show() while True: response = self.window.run() if response == gtk.RESPONSE_OK: # dialog will be closed by connect, now continue work while # rest of dialog is unresponsive, release when finished self.close() if after_ok_func is not None: after_ok_func() break elif (response == gtk.RESPONSE_DELETE_EVENT or response == gtk.RESPONSE_CANCEL): # connect buttons generating this to a close call break def close(self, *obj): """ Close itself. Takes care of closing children and removing itself from menu. """ self._save_size() self.clean_up() self.uistate.gwm.close_track(self.track) self.opened = False self.parent_window.present() def present(self): """ Present window (unroll/unminimize/bring to top). """ if self.isWindow: self.present(self) else: assert hasattr(self, 'window'), \ "ManagedWindow: self.window does not exist!" self.window.present() def _set_size(self): """ Set the dimensions of the window """ if self.width_key is not None: width = config.get(self.width_key) height = config.get(self.height_key) self.window.resize(width, height) def _save_size(self): """ Save the dimensions of the window to the config file """ if self.width_key is not None: (width, height) = self.window.get_size() config.set(self.width_key, width) config.set(self.height_key, height) config.save() def track_ref_for_deletion(self, ref): """ Record references of instance variables that need to be removed from scope so that the class can be garbage collected """ if ref not in self.__refs_for_deletion: self.__refs_for_deletion.append(ref) def clean_up(self): """ Remove any instance variables from scope which point to non-glade GTK objects so that the class can be garbage collected. If the object is a Gramps widget then it should have a clean_up method which can be called that removes any other GTK object it contains. """ while len(self.__refs_for_deletion): attr = self.__refs_for_deletion.pop() obj = getattr(self, attr) if hasattr(obj, 'clean_up'): obj.clean_up() delattr(self, attr)
class EditMediaRef(EditReference): def __init__(self, state, uistate, track, media, media_ref, update): EditReference.__init__(self, state, uistate, track, media, media_ref, update) if not self.source.get_handle(): #show the addmedia dialog immediately, with track of parent. AddMediaObject(state, self.uistate, self.track, self.source, self._update_addmedia) def _local_init(self): self.width_key = 'interface.media-ref-width' self.height_key = 'interface.media-ref-height' self.top = Glade() self.set_window(self.top.toplevel, self.top.get_object('title'), _('Media Reference Editor')) self.define_warn_box(self.top.get_object("warn_box")) self.top.get_object("label427").set_text(_("Y coordinate|Y")) self.top.get_object("label428").set_text(_("Y coordinate|Y")) tblref = self.top.get_object('table50') self.notebook_ref = self.top.get_object('notebook_ref') self.track_ref_for_deletion("notebook_ref") #recreate start page as GrampsTab self.notebook_ref.remove_page(0) self.reftab = RefTab(self.dbstate, self.uistate, self.track, _('General'), tblref) self.track_ref_for_deletion("reftab") tblref = self.top.get_object('table2') self.notebook_shared = self.top.get_object('notebook_shared') #recreate start page as GrampsTab self.notebook_shared.remove_page(0) self.track_ref_for_deletion("notebook_shared") self.primtab = RefTab(self.dbstate, self.uistate, self.track, _('_General'), tblref) self.track_ref_for_deletion("primtab") self.rect_pixbuf = None def setup_filepath(self): self.select = self.top.get_object('file_select') self.track_ref_for_deletion("select") self.file_path = self.top.get_object("path") self.track_ref_for_deletion("file_path") self.file_path.set_text(self.source.get_path()) self.select.connect('clicked', self.select_file) def determine_mime(self): descr = gen.mime.get_description(self.source.get_mime_type()) if descr: self.mimetext.set_text(descr) path = self.file_path.get_text() path_full = Utils.media_path_full(self.db, path) if path != self.source.get_path( ) and path_full != self.source.get_path(): #redetermine mime mime = gen.mime.get_type(Utils.find_file(path_full)) self.source.set_mime_type(mime) descr = gen.mime.get_description(mime) if descr: self.mimetext.set_text(descr) else: self.mimetext.set_text(_('Unknown')) #if mime type not set, is note if not self.source.get_mime_type(): self.mimetext.set_text(_('Note')) def draw_preview(self): """ Draw the two preview images. This method can be called on eg change of the path. """ mtype = self.source.get_mime_type() if mtype: fullpath = Utils.media_path_full(self.db, self.source.get_path()) pb = ThumbNails.get_thumbnail_image(fullpath, mtype) self.pixmap.set_from_pixbuf(pb) subpix = ThumbNails.get_thumbnail_image(fullpath, mtype, self.rectangle) self.subpixmap.set_from_pixbuf(subpix) else: pb = ThumbNails.find_mime_type_pixbuf('text/plain') self.pixmap.set_from_pixbuf(pb) self.subpixmap.set_from_pixbuf(pb) def _setup_fields(self): ebox_shared = self.top.get_object('eventbox') ebox_shared.connect('button-press-event', self.button_press_event) if not self.dbstate.db.readonly: self.button_press_coords = (0, 0) ebox_ref = self.top.get_object('eventbox1') ebox_ref.connect('button-press-event', self.button_press_event_ref) ebox_ref.connect('button-release-event', self.button_release_event_ref) ebox_ref.connect('motion-notify-event', self.motion_notify_event_ref) ebox_ref.add_events(gtk.gdk.BUTTON_PRESS_MASK) ebox_ref.add_events(gtk.gdk.BUTTON_RELEASE_MASK) self.pixmap = self.top.get_object("pixmap") self.mimetext = self.top.get_object("type") self.track_ref_for_deletion("mimetext") coord = self.source_ref.get_rectangle() #upgrade path: set invalid (from eg old db) to none if coord is not None and coord in ((None, ) * 4, (0, 0, 100, 100), (coord[0], coord[1]) * 2): coord = None self.rectangle = coord self.subpixmap = self.top.get_object("subpixmap") self.track_ref_for_deletion("subpixmap") self.setup_filepath() self.determine_mime() self.draw_preview() corners = ["corner1_x", "corner1_y", "corner2_x", "corner2_y"] if coord and isinstance(coord, tuple): for index, corner in enumerate(corners): self.top.get_object(corner).set_value(coord[index]) else: for corner, value in zip(corners, [0, 0, 100, 100]): self.top.get_object(corner).set_value(value) if self.dbstate.db.readonly: for corner in corners: self.top.get_object(corner).set_sensitive(False) self.corner1_x_spinbutton = MonitoredSpinButton( self.top.get_object("corner1_x"), self.set_corner1_x, self.get_corner1_x, self.db.readonly) self.track_ref_for_deletion("corner1_x_spinbutton") self.corner1_y_spinbutton = MonitoredSpinButton( self.top.get_object("corner1_y"), self.set_corner1_y, self.get_corner1_y, self.db.readonly) self.track_ref_for_deletion("corner1_y_spinbutton") self.corner2_x_spinbutton = MonitoredSpinButton( self.top.get_object("corner2_x"), self.set_corner2_x, self.get_corner2_x, self.db.readonly) self.track_ref_for_deletion("corner2_x_spinbutton") self.corner2_y_spinbutton = MonitoredSpinButton( self.top.get_object("corner2_y"), self.set_corner2_y, self.get_corner2_y, self.db.readonly) self.track_ref_for_deletion("corner2_y_spinbutton") self.descr_window = MonitoredEntry(self.top.get_object("description"), self.source.set_description, self.source.get_description, self.db.readonly) self.ref_privacy = PrivacyButton(self.top.get_object("private"), self.source_ref, self.db.readonly) self.gid = MonitoredEntry(self.top.get_object("gid"), self.source.set_gramps_id, self.source.get_gramps_id, self.db.readonly) self.privacy = PrivacyButton(self.top.get_object("privacy"), self.source, self.db.readonly) self.path_obj = MonitoredEntry(self.top.get_object("path"), self.source.set_path, self.source.get_path, self.db.readonly) self.date_field = MonitoredDate(self.top.get_object("date_entry"), self.top.get_object("date_edit"), self.source.get_date_object(), self.uistate, self.track, self.db.readonly) self.tags = MonitoredTagList(self.top.get_object("tag_label"), self.top.get_object("tag_button"), self.source.set_tag_list, self.source.get_tag_list, self.db, self.uistate, self.track, self.db.readonly) def set_corner1_x(self, value): """ Callback for the signal handling of the spinbutton for the first corner x coordinate of the subsection. Updates the subsection thumbnail using the given value @param value: the first corner x coordinate of the subsection in int """ if self.rectangle is None: self.rectangle = (0, 0, 100, 100) self.rectangle = (value, ) + self.rectangle[1:] self.update_subpixmap() def set_corner1_y(self, value): """ Callback for the signal handling of the spinbutton for the first corner y coordinate of the subsection. Updates the subsection thumbnail using the given value @param value: the first corner y coordinate of the subsection in int """ if self.rectangle is None: self.rectangle = (0, 0, 100, 100) self.rectangle = self.rectangle[:1] + (value, ) + self.rectangle[2:] self.update_subpixmap() def set_corner2_x(self, value): """ Callback for the signal handling of the spinbutton for the second corner x coordinate of the subsection. Updates the subsection thumbnail using the given value @param value: the second corner x coordinate of the subsection in int """ if self.rectangle is None: self.rectangle = (0, 0, 100, 100) self.rectangle = self.rectangle[:2] + (value, ) + self.rectangle[3:] self.update_subpixmap() def set_corner2_y(self, value): """ Callback for the signal handling of the spinbutton for the second corner y coordinate of the subsection. Updates the subsection thumbnail using the given value @param value: the second corner y coordinate of the subsection in int """ if self.rectangle is None: self.rectangle = (0, 0, 100, 100) self.rectangle = self.rectangle[:3] + (value, ) self.update_subpixmap() def get_corner1_x(self): """ Callback for the signal handling of the spinbutton for the first corner x coordinate of the subsection. @returns: the first corner x coordinate of the subsection or 0 if there is no selection """ if self.rectangle is not None: return self.rectangle[0] else: return 0 def get_corner1_y(self): """ Callback for the signal handling of the spinbutton for the first corner y coordinate of the subsection. @returns: the first corner y coordinate of the subsection or 0 if there is no selection """ if self.rectangle is not None: return self.rectangle[1] else: return 0 def get_corner2_x(self): """ Callback for the signal handling of the spinbutton for the second corner x coordinate of the subsection. @returns: the second corner x coordinate of the subsection or 100 if there is no selection """ if self.rectangle is not None: return self.rectangle[2] else: return 100 def get_corner2_y(self): """ Callback for the signal handling of the spinbutton for the second corner x coordinate of the subsection. @returns: the second corner x coordinate of the subsection or 100 if there is no selection """ if self.rectangle is not None: return self.rectangle[3] else: return 100 def update_subpixmap(self): """ Updates the thumbnail of the specified subsection """ path = self.source.get_path() if path is None: self.subpixmap.hide() else: try: fullpath = Utils.media_path_full(self.db, path) pixbuf = gtk.gdk.pixbuf_new_from_file(fullpath) width = pixbuf.get_width() height = pixbuf.get_height() upper_x = min(self.rectangle[0], self.rectangle[2]) / 100. lower_x = max(self.rectangle[0], self.rectangle[2]) / 100. upper_y = min(self.rectangle[1], self.rectangle[3]) / 100. lower_y = max(self.rectangle[1], self.rectangle[3]) / 100. sub_x = int(upper_x * width) sub_y = int(upper_y * height) sub_width = int((lower_x - upper_x) * width) sub_height = int((lower_y - upper_y) * height) if sub_width > 0 and sub_height > 0: pixbuf = pixbuf.subpixbuf(sub_x, sub_y, sub_width, sub_height) width = sub_width height = sub_height ratio = float(max(height, width)) scale = const.THUMBSCALE / ratio x = int(scale * width) y = int(scale * height) pixbuf = pixbuf.scale_simple(x, y, gtk.gdk.INTERP_BILINEAR) self.subpixmap.set_from_pixbuf(pixbuf) self.subpixmap.show() except: self.subpixmap.hide() def build_menu_names(self, person): """ Provide the information needed by the base class to define the window management menu entries. """ if self.source: submenu_label = _('Media: %s') % self.source.get_gramps_id() else: submenu_label = _('New Media') return (_('Media Reference Editor'), submenu_label) def button_press_event(self, obj, event): if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: photo_path = Utils.media_path_full(self.db, self.source.get_path()) open_file_with_default_application(photo_path) def button_press_event_ref(self, widget, event): """ Handle the button-press-event generated by the eventbox parent of the subpixmap. Remember these coordinates so we can crop the picture when button-release-event is received. """ self.button_press_coords = (event.x, event.y) # prepare drawing of a feedback rectangle self.rect_pixbuf = self.subpixmap.get_pixbuf() w, h = self.rect_pixbuf.get_width(), self.rect_pixbuf.get_height() self.rect_pixbuf_render = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, w, h) cm = gtk.gdk.colormap_get_system() color = cm.alloc_color(gtk.gdk.Color("blue")) self.rect_pixmap = gtk.gdk.Pixmap(None, w, h, cm.get_visual().depth) self.rect_pixmap.set_colormap(cm) self.rect_gc = self.rect_pixmap.new_gc() self.rect_gc.set_foreground(color) def motion_notify_event_ref(self, widget, event): # get the image size and calculate the X and Y offsets # (image is centered *horizontally* when smaller than const.THUMBSCALE) w, h = self.rect_pixbuf.get_width(), self.rect_pixbuf.get_height() offset_x = (const.THUMBSCALE - w) / 2 offset_y = 0 self.rect_pixmap.draw_pixbuf(self.rect_gc, self.rect_pixbuf, 0, 0, 0, 0) # get coordinates of the rectangle, so that x1 < x2 and y1 < y2 x1 = min(self.button_press_coords[0], event.x) x2 = max(self.button_press_coords[0], event.x) y1 = min(self.button_press_coords[1], event.y) y2 = max(self.button_press_coords[1], event.y) width = int(x2 - x1) height = int(y2 - y1) x1 = int(x1 - offset_x) y1 = int(y1 - offset_y) self.rect_pixmap.draw_rectangle(self.rect_gc, False, x1, y1, width, height) self.rect_pixbuf_render.get_from_drawable( self.rect_pixmap, gtk.gdk.colormap_get_system(), 0, 0, 0, 0, w, h) self.subpixmap.set_from_pixbuf(self.rect_pixbuf_render) def button_release_event_ref(self, widget, event): """ Handle the button-release-event generated by the eventbox parent of the subpixmap. Crop the picture accordingly. """ # reset the crop on double-click or click+CTRL if (event.button==1 and event.type == gtk.gdk._2BUTTON_PRESS) or \ (event.button==1 and (event.state & gtk.gdk.CONTROL_MASK) ): self.corner1_x_spinbutton.set_value(0) self.corner1_y_spinbutton.set_value(0) self.corner2_x_spinbutton.set_value(100) self.corner2_y_spinbutton.set_value(100) else: if (self.rect_pixbuf == None): return self.subpixmap.set_from_pixbuf(self.rect_pixbuf) # ensure the clicks happened at least 5 pixels away from each other new_x1 = min(self.button_press_coords[0], event.x) new_y1 = min(self.button_press_coords[1], event.y) new_x2 = max(self.button_press_coords[0], event.x) new_y2 = max(self.button_press_coords[1], event.y) if new_x2 - new_x1 >= 5 and new_y2 - new_y1 >= 5: # get the image size and calculate the X and Y offsets # (image is centered *horizontally* when smaller than const.THUMBSCALE) w = self.rect_pixbuf.get_width() h = self.rect_pixbuf.get_height() x = (const.THUMBSCALE - w) / 2 y = 0 # if the click was outside of the image, # bring it within the boundaries if new_x1 < x: new_x1 = x if new_y1 < y: new_y1 = y if new_x2 >= x + w: new_x2 = x + w - 1 if new_y2 >= y + h: new_y2 = y + h - 1 # get the old spinbutton % values old_x1 = self.corner1_x_spinbutton.get_value() old_y1 = self.corner1_y_spinbutton.get_value() old_x2 = self.corner2_x_spinbutton.get_value() old_y2 = self.corner2_y_spinbutton.get_value() delta_x = old_x2 - old_x1 # horizontal scale delta_y = old_y2 - old_y1 # vertical scale # Took a while to figure out the math here. # # 1) figure out the current crop % values # by doing the following: # # xp = click_location_x / width * 100 # yp = click_location_y / height * 100 # # but remember that click_location_x and _y # might not be zero-based for non-rectangular # images, so subtract the pixbuf "x" and "y" # to bring the values back to zero-based # # 2) the minimum value cannot be less than the # existing crop value, so add the current # minimum to the new values new_x1 = old_x1 + delta_x * (new_x1 - x) / w new_y1 = old_y1 + delta_y * (new_y1 - y) / h new_x2 = old_x1 + delta_x * (new_x2 - x) / w new_y2 = old_y1 + delta_y * (new_y2 - y) / h # set the new values self.corner1_x_spinbutton.set_value(new_x1) self.corner1_y_spinbutton.set_value(new_y1) self.corner2_x_spinbutton.set_value(new_x2) self.corner2_y_spinbutton.set_value(new_y2) # Free the pixbuf as it is not needed anymore self.rect_pixbuf = None def _update_addmedia(self, obj): """ Called when the add media dialog has been called. This allows us to update the main form in response to any changes: Redraw relevant fields: description, mimetype and path """ for obj in (self.descr_window, self.path_obj): obj.update() self.determine_mime() self.draw_preview() def select_file(self, val): self.determine_mime() path = self.file_path.get_text() self.source.set_path(Utils.get_unicode_path_from_file_chooser(path)) AddMediaObject(self.dbstate, self.uistate, self.track, self.source, self._update_addmedia) def _connect_signals(self): self.define_cancel_button(self.top.get_object('button84')) self.define_ok_button(self.top.get_object('button82'), self.save) def _connect_db_signals(self): """ Connect any signals that need to be connected. Called by the init routine of the base class (_EditPrimary). """ self._add_db_signal('media-rebuild', self.close) self._add_db_signal('media-delete', self.check_for_close) def _create_tabbed_pages(self): """ Create the notebook tabs and inserts them into the main window. """ notebook_ref = self.top.get_object('notebook_ref') notebook_src = self.top.get_object('notebook_shared') self._add_tab(notebook_src, self.primtab) self._add_tab(notebook_ref, self.reftab) self.srcref_list = CitationEmbedList( self.dbstate, self.uistate, self.track, self.source_ref.get_citation_list()) self._add_tab(notebook_ref, self.srcref_list) self.track_ref_for_deletion("srcref_list") self.attr_list = MediaAttrEmbedList( self.dbstate, self.uistate, self.track, self.source_ref.get_attribute_list()) self._add_tab(notebook_ref, self.attr_list) self.track_ref_for_deletion("attr_list") self.backref_list = MediaBackRefList( self.dbstate, self.uistate, self.track, self.db.find_backlink_handles(self.source.handle), self.enable_warnbox) self._add_tab(notebook_src, self.backref_list) self.track_ref_for_deletion("backref_list") self.note_ref_tab = NoteTab(self.dbstate, self.uistate, self.track, self.source_ref.get_note_list(), notetype=NoteType.MEDIAREF) self._add_tab(notebook_ref, self.note_ref_tab) self.track_ref_for_deletion("note_ref_tab") self.src_srcref_list = CitationEmbedList( self.dbstate, self.uistate, self.track, self.source.get_citation_list()) self._add_tab(notebook_src, self.src_srcref_list) self.track_ref_for_deletion("src_srcref_list") self.src_attr_list = MediaAttrEmbedList( self.dbstate, self.uistate, self.track, self.source.get_attribute_list()) self._add_tab(notebook_src, self.src_attr_list) self.track_ref_for_deletion("src_attr_list") self.src_note_ref_tab = NoteTab(self.dbstate, self.uistate, self.track, self.source.get_note_list(), notetype=NoteType.MEDIA) self._add_tab(notebook_src, self.src_note_ref_tab) self.track_ref_for_deletion("src_note_ref_tab") self._setup_notebook_tabs(notebook_src) self._setup_notebook_tabs(notebook_ref) def save(self, *obj): #first save primary object if self.source.handle: with DbTxn( _("Edit Media Object (%s)") % self.source.get_description(), self.db) as trans: self.db.commit_media_object(self.source, trans) else: if self.check_for_duplicate_id('Media'): return with DbTxn( _("Add Media Object (%s)") % self.source.get_description(), self.db) as trans: self.db.add_object(self.source, trans) #save reference object in memory coord = ( self.top.get_object("corner1_x").get_value_as_int(), self.top.get_object("corner1_y").get_value_as_int(), self.top.get_object("corner2_x").get_value_as_int(), self.top.get_object("corner2_y").get_value_as_int(), ) #do not set unset or invalid coord if coord is not None and coord in ((None, ) * 4, (0, 0, 100, 100), (coord[0], coord[1]) * 2): coord = None self.source_ref.set_rectangle(coord) #call callback if given if self.update: self.update(self.source_ref, self.source) self.update = None self.close()
def __init__(self, dbstate, uistate, track=[], filter=None, skip=set(), show_search_bar=True, default=None): """Set up the dialog with the dbstate and uistate, track of parent windows for ManagedWindow, initial filter for the model, skip with set of handles to skip in the view, and search_bar to show the SearchBar at the top or not. """ self.filter = (2, filter, False) # Set window title, some selectors may set self.title in their __init__ if not hasattr(self, 'title'): self.title = self.get_window_title() ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) self.renderer = gtk.CellRendererText() self.track_ref_for_deletion("renderer") self.renderer.set_property('ellipsize', pango.ELLIPSIZE_END) self.db = dbstate.db self.tree = None self.model = None self.glade = Glade() window = self.glade.toplevel self.showall = self.glade.get_object('showall') title_label = self.glade.get_object('title') vbox = self.glade.get_object('select_person_vbox') self.tree = self.glade.get_object('plist') self.tree.set_headers_visible(True) self.tree.set_headers_clickable(True) self.tree.connect('row-activated', self._on_row_activated) self.tree.grab_focus() #add the search bar self.search_bar = SearchBar(dbstate, uistate, self.build_tree) filter_box = self.search_bar.build() self.setup_filter() vbox.pack_start(filter_box, False, False) vbox.reorder_child(filter_box, 1) self.set_window(window, title_label, self.title) #set up sorting self.sort_col = 0 self.setupcols = True self.columns = [] self.sortorder = gtk.SORT_ASCENDING self.skip_list = skip self.build_tree() self.selection = self.tree.get_selection() self.track_ref_for_deletion("selection") self._local_init() self._set_size() self.show() #show or hide search bar? self.set_show_search_bar(show_search_bar) #Hide showall if no filter is specified if self.filter[1] is not None: self.showall.connect('toggled', self.show_toggle) self.showall.show() else: self.showall.hide() if default: self.goto_handle(default)
class BaseSelector(ManagedWindow.ManagedWindow): """Base class for the selectors, showing a dialog from which to select one of the primary objects """ NONE = -1 TEXT = 0 MARKUP = 1 IMAGE = 2 def __init__(self, dbstate, uistate, track=[], filter=None, skip=set(), show_search_bar=True, default=None): """Set up the dialog with the dbstate and uistate, track of parent windows for ManagedWindow, initial filter for the model, skip with set of handles to skip in the view, and search_bar to show the SearchBar at the top or not. """ self.filter = (2, filter, False) # Set window title, some selectors may set self.title in their __init__ if not hasattr(self, 'title'): self.title = self.get_window_title() ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) self.renderer = gtk.CellRendererText() self.track_ref_for_deletion("renderer") self.renderer.set_property('ellipsize', pango.ELLIPSIZE_END) self.db = dbstate.db self.tree = None self.model = None self.glade = Glade() window = self.glade.toplevel self.showall = self.glade.get_object('showall') title_label = self.glade.get_object('title') vbox = self.glade.get_object('select_person_vbox') self.tree = self.glade.get_object('plist') self.tree.set_headers_visible(True) self.tree.set_headers_clickable(True) self.tree.connect('row-activated', self._on_row_activated) self.tree.grab_focus() #add the search bar self.search_bar = SearchBar(dbstate, uistate, self.build_tree) filter_box = self.search_bar.build() self.setup_filter() vbox.pack_start(filter_box, False, False) vbox.reorder_child(filter_box, 1) self.set_window(window, title_label, self.title) #set up sorting self.sort_col = 0 self.setupcols = True self.columns = [] self.sortorder = gtk.SORT_ASCENDING self.skip_list = skip self.build_tree() self.selection = self.tree.get_selection() self.track_ref_for_deletion("selection") self._local_init() self._set_size() self.show() #show or hide search bar? self.set_show_search_bar(show_search_bar) #Hide showall if no filter is specified if self.filter[1] is not None: self.showall.connect('toggled', self.show_toggle) self.showall.show() else: self.showall.hide() if default: self.goto_handle(default) def goto_handle(self, handle): """ Goto the correct row. """ try: # tree: path = None node = self.model.get_node(handle) if node: parent_node = self.model.on_iter_parent(node) if parent_node: parent_path = self.model.on_get_path(parent_node) if parent_path: for i in range(len(parent_path)): expand_path = tuple( [x for x in parent_path[:i + 1]]) self.tree.expand_row(expand_path, False) path = self.model.on_get_path(node) except: # flat: try: path = self.model.on_get_path(handle) except: path = None if path is not None: self.selection.unselect_all() self.selection.select_path(path) self.tree.scroll_to_cell(path, None, 1, 0.5, 0) else: # not in list self.selection.unselect_all() def add_columns(self, tree): tree.set_fixed_height_mode(True) titles = self.get_column_titles() for ix in range(len(titles)): item = titles[ix] if item[2] == BaseSelector.NONE: continue elif item[2] == BaseSelector.TEXT: column = gtk.TreeViewColumn(item[0], self.renderer, text=item[3]) elif item[2] == BaseSelector.MARKUP: column = gtk.TreeViewColumn(item[0], self.renderer, markup=item[3]) column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED) column.set_fixed_width(item[1]) column.set_resizable(True) #connect click column.connect('clicked', self.column_clicked, ix) column.set_clickable(True) ##column.set_sort_column_id(ix) # model has its own sort implemented self.columns.append(column) tree.append_column(column) def build_menu_names(self, obj): return (self.title, None) def get_selected_ids(self): mlist = [] self.selection.selected_foreach(self.select_function, mlist) return mlist def select_function(self, store, path, iter, id_list): handle_column = self.get_handle_column() id_list.append(self.model.get_value(iter, handle_column)) def run(self): val = self.window.run() result = None if val == gtk.RESPONSE_OK: id_list = self.get_selected_ids() if id_list and id_list[0]: result = self.get_from_handle_func()(id_list[0]) if result is None and self.get_from_handle_func2: result = self.get_from_handle_func2()(id_list[0]) self.close() elif val != gtk.RESPONSE_DELETE_EVENT: self.close() return result def _on_row_activated(self, treeview, path, view_col): self.window.response(gtk.RESPONSE_OK) def _local_init(self): # define selector-specific init routine pass def get_window_title(self): assert False, "Must be defined in the subclass" def get_model_class(self): assert False, "Must be defined in the subclass" def get_column_titles(self): """ Defines the columns to show in the selector. Must be defined in the subclasses. :returns: a list of tuples with four entries. The four entries should be 0: column header string, 1: column width, 2: TEXT, MARKUP or IMAGE, 3: column in the model that must be used. """ raise NotImplementedError def get_from_handle_func(self): assert False, "Must be defined in the subclass" def get_from_handle_func2(self): return None def get_handle_column(self): # return 3 assert False, "Must be defined in the subclass" def set_show_search_bar(self, value): """make the search bar at the top shown """ self.show_search_bar = value if not self.search_bar: return if self.show_search_bar: self.search_bar.show() else: self.search_bar.hide() def begintree(self, store, path, node, sel_list): handle_column = self.get_handle_column() handle = store.get_value(node, handle_column) sel_list.append(handle) def first_selected(self): """ first selected entry in the Selector tree """ mlist = [] self.selection.selected_foreach(self.begintree, mlist) return mlist[0] if mlist else None def column_order(self): """ returns a tuple indicating the column order of the model """ return [(1, row[3], row[1], row[0]) for row in self.get_column_titles()] def exact_search(self): """ Returns a tuple indicating columns requiring an exact search """ return () def setup_filter(self): """ Builds the default filters and add them to the filter bar. """ cols = [(pair[3], pair[1], pair[0] in self.exact_search()) for pair in self.column_order() if pair[0]] self.search_bar.setup_filter(cols) def build_tree(self): """ Builds the selection people see in the Selector """ if self.filter[1]: filter_info = self.filter else: #search info for the if self.search_bar.get_value()[0] in self.exact_search(): filter_info = (0, self.search_bar.get_value(), True) else: filter_info = (0, self.search_bar.get_value(), False) #set up cols the first time if self.setupcols: self.add_columns(self.tree) #reset the model with correct sorting self.clear_model() self.model = self.get_model_class()(self.db, self.sort_col, self.sortorder, sort_map=self.column_order(), skip=self.skip_list, search=filter_info) self.tree.set_model(self.model) #sorting arrow in column header (not on start, only on click) if not self.setupcols: for i in xrange(len(self.columns)): enable_sort_flag = (i == self.sort_col) self.columns[i].set_sort_indicator(enable_sort_flag) self.columns[self.sort_col].set_sort_order(self.sortorder) # set the search column to be the sorted column search_col = self.column_order()[self.sort_col][1] self.tree.set_search_column(search_col) self.setupcols = False def column_clicked(self, obj, data): if self.sort_col != data: self.sortorder = gtk.SORT_ASCENDING self.sort_col = data else: if (self.columns[data].get_sort_order() == gtk.SORT_DESCENDING or not self.columns[data].get_sort_indicator()): self.sortorder = gtk.SORT_ASCENDING else: self.sortorder = gtk.SORT_DESCENDING self.model.reverse_order() self.build_tree() handle = self.first_selected() if handle: path = self.model.on_get_path(handle) self.selection.select_path(path) self.tree.scroll_to_cell(path, None, 1, 0.5, 0) return True def show_toggle(self, obj): filter_info = None if obj.get_active() else self.filter self.clear_model() self.model = self.get_model_class()(self.db, self.sort_col, self.sortorder, sort_map=self.column_order(), skip=self.skip_list, search=filter_info) self.tree.set_model(self.model) self.tree.grab_focus() def clear_model(self): if self.model: self.tree.set_model(None) if hasattr(self.model, 'destroy'): self.model.destroy() self.model = None def _cleanup_on_exit(self): """Unset all things that can block garbage collection. Finalize rest """ self.clear_model() self.db = None self.tree = None self.columns = None self.search_bar.destroy() def close(self, *obj): ManagedWindow.ManagedWindow.close(self) self._cleanup_on_exit()
def __init__(self, date, uistate, track): """ Initiate and display the dialog. """ ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) # Create self.date as a copy of the given Date object. self.date = Date(date) self.top = Glade() self.set_window(self.top.toplevel, self.top.get_object('title'), _('Date selection')) self.statusbar = self.top.get_object('statusbar') self.ok_button = self.top.get_object('ok_button') self.calendar_box = self.top.get_object('calendar_box') for name in Date.ui_calendar_names: self.calendar_box.get_model().append([name]) self.calendar_box.set_active(self.date.get_calendar()) self.calendar_box.connect('changed', self.switch_calendar) self.quality_box = self.top.get_object('quality_box') for item_number in range(len(QUAL_TEXT)): self.quality_box.append_text(QUAL_TEXT[item_number][1]) if self.date.get_quality() == QUAL_TEXT[item_number][0]: self.quality_box.set_active(item_number) self.type_box = self.top.get_object('type_box') for item_number in range(len(MOD_TEXT)): self.type_box.append_text(MOD_TEXT[item_number][1]) if self.date.get_modifier() == MOD_TEXT[item_number][0]: self.type_box.set_active(item_number) self.type_box.connect('changed', self.switch_type) self.start_month_box = self.top.get_object('start_month_box') self.stop_month_box = self.top.get_object('stop_month_box') month_names = CAL_TO_MONTHS_NAMES[self.date.get_calendar()] for name in month_names: self.start_month_box.append_text(name) self.stop_month_box.append_text(name) self.start_month_box.set_active(self.date.get_month()) self.stop_month_box.set_active(self.date.get_stop_month()) self.start_day = self.top.get_object('start_day') self.start_day.set_value(self.date.get_day()) self.start_year = self.top.get_object('start_year') self.start_year.set_value(self.date.get_year()) self.stop_day = self.top.get_object('stop_day') self.stop_day.set_value(self.date.get_stop_day()) self.stop_year = self.top.get_object('stop_year') self.stop_year.set_value(self.date.get_stop_year()) self.dual_dated = self.top.get_object('dualdated') self.new_year = self.top.get_object('newyear') self.new_year.set_text(self.date.newyear_to_str()) # Disable second date controls if not compound date if not self.date.is_compound(): self.stop_day.set_sensitive(0) self.stop_month_box.set_sensitive(0) self.stop_year.set_sensitive(0) # Disable the rest of controls if a text-only date if self.date.get_modifier() == Date.MOD_TEXTONLY: self.start_day.set_sensitive(0) self.start_month_box.set_sensitive(0) self.start_year.set_sensitive(0) self.calendar_box.set_sensitive(0) self.quality_box.set_sensitive(0) self.dual_dated.set_sensitive(0) self.new_year.set_sensitive(0) self.text_entry = self.top.get_object('date_text_entry') self.text_entry.set_text(self.date.get_text()) if self.date.get_slash(): self.dual_dated.set_active(1) self.calendar_box.set_sensitive(0) self.calendar_box.set_active(Date.CAL_JULIAN) self.dual_dated.connect('toggled', self.switch_dual_dated) # The dialog is modal -- since dates don't have names, we don't # want to have several open dialogs, since then the user will # loose track of which is which. Much like opening files. self.validated_date = self.return_date = None for o in self.top.get_objects(): try: if o != self.ok_button: o.connect_after('changed', self.revalidate) except TypeError: pass # some of them don't support the signal, ignore them... self.revalidate() self.show() while True: response = self.window.run() if response == gtk.RESPONSE_HELP: GrampsDisplay.help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) elif response == gtk.RESPONSE_DELETE_EVENT: break else: if response == gtk.RESPONSE_OK: # if the user pressed OK/enter while inside an edit field, # e.g., the year, # build_date_from_ui won't pick up the new text in the # run of revalidate that allowed the OK! if not self.revalidate(): continue self.return_date = Date() self.return_date.copy(self.validated_date) self.close() break
class DateEditorDialog(ManagedWindow.ManagedWindow): """ Dialog allowing to build the date precisely, to correct possible limitations of parsing and/or underlying structure of Date. """ def __init__(self, date, uistate, track): """ Initiate and display the dialog. """ ManagedWindow.ManagedWindow.__init__(self, uistate, track, self) # Create self.date as a copy of the given Date object. self.date = Date(date) self.top = Glade() self.set_window(self.top.toplevel, self.top.get_object('title'), _('Date selection')) self.statusbar = self.top.get_object('statusbar') self.ok_button = self.top.get_object('ok_button') self.calendar_box = self.top.get_object('calendar_box') for name in Date.ui_calendar_names: self.calendar_box.get_model().append([name]) self.calendar_box.set_active(self.date.get_calendar()) self.calendar_box.connect('changed', self.switch_calendar) self.quality_box = self.top.get_object('quality_box') for item_number in range(len(QUAL_TEXT)): self.quality_box.append_text(QUAL_TEXT[item_number][1]) if self.date.get_quality() == QUAL_TEXT[item_number][0]: self.quality_box.set_active(item_number) self.type_box = self.top.get_object('type_box') for item_number in range(len(MOD_TEXT)): self.type_box.append_text(MOD_TEXT[item_number][1]) if self.date.get_modifier() == MOD_TEXT[item_number][0]: self.type_box.set_active(item_number) self.type_box.connect('changed', self.switch_type) self.start_month_box = self.top.get_object('start_month_box') self.stop_month_box = self.top.get_object('stop_month_box') month_names = CAL_TO_MONTHS_NAMES[self.date.get_calendar()] for name in month_names: self.start_month_box.append_text(name) self.stop_month_box.append_text(name) self.start_month_box.set_active(self.date.get_month()) self.stop_month_box.set_active(self.date.get_stop_month()) self.start_day = self.top.get_object('start_day') self.start_day.set_value(self.date.get_day()) self.start_year = self.top.get_object('start_year') self.start_year.set_value(self.date.get_year()) self.stop_day = self.top.get_object('stop_day') self.stop_day.set_value(self.date.get_stop_day()) self.stop_year = self.top.get_object('stop_year') self.stop_year.set_value(self.date.get_stop_year()) self.dual_dated = self.top.get_object('dualdated') self.new_year = self.top.get_object('newyear') self.new_year.set_text(self.date.newyear_to_str()) # Disable second date controls if not compound date if not self.date.is_compound(): self.stop_day.set_sensitive(0) self.stop_month_box.set_sensitive(0) self.stop_year.set_sensitive(0) # Disable the rest of controls if a text-only date if self.date.get_modifier() == Date.MOD_TEXTONLY: self.start_day.set_sensitive(0) self.start_month_box.set_sensitive(0) self.start_year.set_sensitive(0) self.calendar_box.set_sensitive(0) self.quality_box.set_sensitive(0) self.dual_dated.set_sensitive(0) self.new_year.set_sensitive(0) self.text_entry = self.top.get_object('date_text_entry') self.text_entry.set_text(self.date.get_text()) if self.date.get_slash(): self.dual_dated.set_active(1) self.calendar_box.set_sensitive(0) self.calendar_box.set_active(Date.CAL_JULIAN) self.dual_dated.connect('toggled', self.switch_dual_dated) # The dialog is modal -- since dates don't have names, we don't # want to have several open dialogs, since then the user will # loose track of which is which. Much like opening files. self.validated_date = self.return_date = None for o in self.top.get_objects(): try: if o != self.ok_button: o.connect_after('changed', self.revalidate) except TypeError: pass # some of them don't support the signal, ignore them... self.revalidate() self.show() while True: response = self.window.run() if response == gtk.RESPONSE_HELP: GrampsDisplay.help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) elif response == gtk.RESPONSE_DELETE_EVENT: break else: if response == gtk.RESPONSE_OK: # if the user pressed OK/enter while inside an edit field, # e.g., the year, # build_date_from_ui won't pick up the new text in the # run of revalidate that allowed the OK! if not self.revalidate(): continue self.return_date = Date() self.return_date.copy(self.validated_date) self.close() break def revalidate(self, obj=None): """ If anything changed, revalidate the date and enable/disable the "OK" button based on the result. """ (the_quality, the_modifier, the_calendar, the_value, the_text, the_newyear) = self.build_date_from_ui() LOG.debug("revalidate: {0} changed, value: {1}".format(obj, the_value)) d = Date(self.date) if not self.ok_button.get_sensitive(): self.statusbar.pop(1) try: d.set(quality=the_quality, modifier=the_modifier, calendar=the_calendar, value=the_value, text=the_text, newyear=the_newyear) # didn't throw yet? self.validated_date = d LOG.debug("validated_date set to: {0}".format(d.dateval)) self.ok_button.set_sensitive(1) self.calendar_box.set_sensitive(1) return True except DateError as e: self.ok_button.set_sensitive(0) self.calendar_box.set_sensitive(0) self.statusbar.push( 1, _("Correct the date or switch from `{cur_mode}' to `{text_mode}'" ).format(cur_mode=MOD_TEXT[self.type_box.get_active()][1], text_mode=MOD_TEXT[-1][1])) return False def build_menu_names(self, obj): """ Define the menu entry for the ManagedWindows """ return (_("Date selection"), None) def build_date_from_ui(self): """ Collect information from the UI controls and return 5-tuple of (quality,modifier,calendar,value,text) """ # It is important to not set date based on these controls. # For example, changing the caledar makes the date inconsistent # until the callback of the calendar menu is finished. # We need to be able to use this function from that callback, # so here we just report on the state of all widgets, without # actually modifying the date yet. modifier = MOD_TEXT[self.type_box.get_active()][0] text = self.text_entry.get_text() if modifier == Date.MOD_TEXTONLY: return (Date.QUAL_NONE, Date.MOD_TEXTONLY, Date.CAL_GREGORIAN, Date.EMPTY, text, Date.NEWYEAR_JAN1) quality = QUAL_TEXT[self.quality_box.get_active()][0] if modifier in (Date.MOD_RANGE, Date.MOD_SPAN): value = (self.start_day.get_value_as_int(), self.start_month_box.get_active(), self.start_year.get_value_as_int(), self.dual_dated.get_active(), self.stop_day.get_value_as_int(), self.stop_month_box.get_active(), self.stop_year.get_value_as_int(), self.dual_dated.get_active()) else: value = (self.start_day.get_value_as_int(), self.start_month_box.get_active(), self.start_year.get_value_as_int(), self.dual_dated.get_active()) calendar = self.calendar_box.get_active() newyear = Date.newyear_to_code(self.new_year.get_text()) return (quality, modifier, calendar, value, text, newyear) def switch_type(self, obj): """ Disable/enable various date controls depending on the date type selected via the menu. """ the_modifier = MOD_TEXT[self.type_box.get_active()][0] # Disable/enable second date controls based on whether # the type allows compound dates if the_modifier in (Date.MOD_RANGE, Date.MOD_SPAN): stop_date_sensitivity = 1 else: stop_date_sensitivity = 0 self.stop_day.set_sensitive(stop_date_sensitivity) self.stop_month_box.set_sensitive(stop_date_sensitivity) self.stop_year.set_sensitive(stop_date_sensitivity) # Disable/enable the rest of the controls if the type is text-only. date_sensitivity = not the_modifier == Date.MOD_TEXTONLY self.start_day.set_sensitive(date_sensitivity) self.start_month_box.set_sensitive(date_sensitivity) self.start_year.set_sensitive(date_sensitivity) self.calendar_box.set_sensitive(date_sensitivity) self.quality_box.set_sensitive(date_sensitivity) self.dual_dated.set_sensitive(date_sensitivity) self.new_year.set_sensitive(date_sensitivity) def switch_dual_dated(self, obj): """ Changed whether this is a dual dated year, or not. Dual dated years are represented in the Julian calendar so that the day/months don't changed in the Text representation. """ if self.dual_dated.get_active(): self.calendar_box.set_active(Date.CAL_JULIAN) self.calendar_box.set_sensitive(0) else: self.calendar_box.set_sensitive(1) def switch_calendar(self, obj): """ Change month names and convert the date based on the calendar selected via the menu. """ old_cal = self.date.get_calendar() new_cal = self.calendar_box.get_active() LOG.debug(">>>switch_calendar: {0} changed, {1} -> {2}".format( obj, old_cal, new_cal)) (the_quality, the_modifier, the_calendar, the_value, the_text, the_newyear) = self.build_date_from_ui() try: self.date.set(quality=the_quality, modifier=the_modifier, calendar=old_cal, value=the_value, text=the_text, newyear=the_newyear) except DateError: pass else: if not self.date.is_empty(): self.date.convert_calendar(new_cal) self.start_month_box.get_model().clear() self.stop_month_box.get_model().clear() month_names = CAL_TO_MONTHS_NAMES[new_cal] for name in month_names: self.start_month_box.append_text(name) self.stop_month_box.append_text(name) self.start_day.set_value(self.date.get_day()) self.start_month_box.set_active(self.date.get_month()) self.start_year.set_value(self.date.get_year()) self.stop_day.set_value(self.date.get_stop_day()) self.stop_month_box.set_active(self.date.get_stop_month()) self.stop_year.set_value(self.date.get_stop_year()) LOG.debug("<<<switch_calendar: {0} changed, {1} -> {2}".format( obj, old_cal, new_cal))