From 3e28ee67ecdaf999332140770e85d9a73c16b207 Mon Sep 17 00:00:00 2001 From: Doug Blank Date: Thu, 22 Jul 2010 02:16:32 +0000 Subject: [PATCH] 2059: need a way to compare and merge all objects, by MD Nauta svn: r15645 --- src/Errors.py | 10 + src/ManagedWindow.py | 2 +- src/Merge/Makefile.am | 11 +- src/Merge/__init__.py | 11 +- src/Merge/mergeevent.py | 259 +++++++++++++ src/Merge/mergefamily.py | 261 +++++++++++++ src/Merge/mergemedia.py | 215 +++++++++++ src/Merge/mergenote.py | 251 ++++++++++++ src/Merge/mergeperson.py | 463 ++++++++++++++++++++++ src/Merge/mergeplace.py | 231 +++++++++++ src/Merge/mergerepository.py | 181 +++++++++ src/Merge/mergesource.py | 238 ++++++++++++ src/gen/db/write.py | 3 +- src/gen/lib/address.py | 35 ++ src/gen/lib/addressbase.py | 22 ++ src/gen/lib/attrbase.py | 22 ++ src/gen/lib/attribute.py | 33 ++ src/gen/lib/baseobj.py | 15 + src/gen/lib/childref.py | 38 ++ src/gen/lib/const.py | 29 ++ src/gen/lib/event.py | 18 +- src/gen/lib/eventref.py | 35 +- src/gen/lib/family.py | 107 +++++- src/gen/lib/ldsord.py | 37 ++ src/gen/lib/ldsordbase.py | 22 ++ src/gen/lib/location.py | 27 ++ src/gen/lib/mediabase.py | 42 +- src/gen/lib/mediaobj.py | 15 + src/gen/lib/mediaref.py | 34 ++ src/gen/lib/name.py | 37 ++ src/gen/lib/note.py | 12 + src/gen/lib/notebase.py | 59 +++ src/gen/lib/person.py | 162 +++++++- src/gen/lib/personref.py | 34 ++ src/gen/lib/place.py | 36 ++ src/gen/lib/privacybase.py | 10 + src/gen/lib/repo.py | 15 +- src/gen/lib/reporef.py | 32 ++ src/gen/lib/secondaryobj.py | 8 + src/gen/lib/src.py | 71 +++- src/gen/lib/srcbase.py | 45 ++- src/gen/lib/srcref.py | 37 ++ src/gen/lib/styledtext.py | 6 + src/gen/lib/url.py | 30 ++ src/gen/lib/urlbase.py | 21 + src/glade/Makefile.am | 10 +- src/glade/mergeevent.glade | 632 +++++++++++++++++++++++++++++++ src/glade/mergefamily.glade | 570 ++++++++++++++++++++++++++++ src/glade/mergemedia.glade | 506 +++++++++++++++++++++++++ src/glade/mergenote.glade | 587 ++++++++++++++++++++++++++++ src/glade/mergeperson.glade | 568 +++++++++++++++++++++++++++ src/glade/mergeplace.glade | 580 ++++++++++++++++++++++++++++ src/glade/mergerepository.glade | 443 ++++++++++++++++++++++ src/glade/mergesource.glade | 569 ++++++++++++++++++++++++++++ src/gui/grampsgui.py | 1 + src/gui/views/listview.py | 8 + src/images/16x16/Makefile.am | 1 + src/images/22x22/Makefile.am | 1 + src/images/48x48/Makefile.am | 1 + src/plugins/lib/libpersonview.py | 55 +-- src/plugins/lib/libplaceview.py | 13 +- src/plugins/view/eventview.py | 20 + src/plugins/view/familyview.py | 21 + src/plugins/view/mediaview.py | 21 + src/plugins/view/noteview.py | 20 + src/plugins/view/repoview.py | 22 ++ src/plugins/view/sourceview.py | 13 +- 67 files changed, 7835 insertions(+), 109 deletions(-) create mode 100644 src/Merge/mergeevent.py create mode 100644 src/Merge/mergefamily.py create mode 100644 src/Merge/mergemedia.py create mode 100644 src/Merge/mergenote.py create mode 100644 src/Merge/mergeperson.py create mode 100644 src/Merge/mergeplace.py create mode 100644 src/Merge/mergerepository.py create mode 100644 src/Merge/mergesource.py create mode 100644 src/gen/lib/const.py create mode 100644 src/glade/mergeevent.glade create mode 100644 src/glade/mergefamily.glade create mode 100644 src/glade/mergemedia.glade create mode 100644 src/glade/mergenote.glade create mode 100644 src/glade/mergeperson.glade create mode 100644 src/glade/mergeplace.glade create mode 100644 src/glade/mergerepository.glade create mode 100644 src/glade/mergesource.glade diff --git a/src/Errors.py b/src/Errors.py index 7b459dd96..e6b62e7c9 100644 --- a/src/Errors.py +++ b/src/Errors.py @@ -142,3 +142,13 @@ class DbError(Exception): def __str__(self): "Return string representation" return self.value + +class MergeError(Exception): + """Error used to report merge errors""" + def __init__(self, value=""): + Exception.__init__(self) + self.value = value + + def __str__(self): + "Return string representation" + return self.value diff --git a/src/ManagedWindow.py b/src/ManagedWindow.py index 5fd50cdcd..4b7d9b0c4 100644 --- a/src/ManagedWindow.py +++ b/src/ManagedWindow.py @@ -428,7 +428,7 @@ class ManagedWindow(object): if glade_file is None: raise TypeError, "ManagedWindow.define_glade: no glade file" glade_file = const.GLADE_FILE - self._gladeobj = Glade(glade_file, "", top_module) + self._gladeobj = Glade(glade_file, None, top_module) return self._gladeobj def get_widget(self, name): diff --git a/src/Merge/Makefile.am b/src/Merge/Makefile.am index 3f0c36d0f..f2e4999c3 100644 --- a/src/Merge/Makefile.am +++ b/src/Merge/Makefile.am @@ -7,9 +7,14 @@ pkgdatadir = $(datadir)/@PACKAGE@/Merge pkgdata_PYTHON = \ __init__.py \ - _MergePerson.py \ - _MergePlace.py \ - _MergeSource.py + mergeperson.py \ + mergefamily.py \ + mergeevent.py \ + mergeplace.py \ + mergesource.py \ + mergerepository.py \ + mergemedia.py \ + mergenote.py pkgpyexecdir = @pkgpyexecdir@/Merge pkgpythondir = @pkgpythondir@/Merge diff --git a/src/Merge/__init__.py b/src/Merge/__init__.py index a07fcbf91..f092240b1 100644 --- a/src/Merge/__init__.py +++ b/src/Merge/__init__.py @@ -23,6 +23,11 @@ """ """ -from _MergePerson import * -from _MergePlace import * -from _MergeSource import * +from mergeperson import * +from mergefamily import * +from mergeevent import * +from mergeplace import * +from mergesource import * +from mergerepository import * +from mergemedia import * +from mergenote import * diff --git a/src/Merge/mergeevent.py b/src/Merge/mergeevent.py new file mode 100644 index 000000000..a24d1d08b --- /dev/null +++ b/src/Merge/mergeevent.py @@ -0,0 +1,259 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Michiel D. Nauta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Provide merge capabilities for events. +""" + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.ggettext import sgettext as _ +import const +import GrampsDisplay +import ManagedWindow +import DateHandler +import Utils + +#------------------------------------------------------------------------- +# +# Gramps constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = '%s_-_Entering_and_Editing_Data:_Detailed_-_part_3' % \ + const.URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Merge_Events') +_GLADE_FILE = 'mergeevent.glade' + +#------------------------------------------------------------------------- +# +# Merge Events +# +#------------------------------------------------------------------------- +class MergeEvents(ManagedWindow.ManagedWindow): + """ + Displays a dialog box that allows the events to be combined into one. + """ + def __init__(self, dbstate, uistate, handle1, handle2): + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + database = dbstate.db + self.ev1 = database.get_event_from_handle(handle1) + self.ev2 = database.get_event_from_handle(handle2) + + self.define_glade('mergeevent', _GLADE_FILE) + self.set_window(self._gladeobj.toplevel, + self.get_widget("event_title"), + _("Merge Events")) + + # Detailed selection widgets + type1 = str(self.ev1.get_type()) + type2 = str(self.ev2.get_type()) + entry1 = self.get_widget("type1") + entry2 = self.get_widget("type2") + entry1.set_text(type1) + entry2.set_text(type2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('type1', 'type2', 'type_btn1', 'type_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("date1") + entry2 = self.get_widget("date2") + entry1.set_text(DateHandler.get_date(self.ev1)) + entry2.set_text(DateHandler.get_date(self.ev2)) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('date1', 'date2', 'date_btn1', 'date_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + place1 = database.get_place_from_handle( + self.ev1.get_place_handle()) + place2 = database.get_place_from_handle( + self.ev2.get_place_handle()) + place1 = place1.get_title() if place1 else "" + place2 = place2.get_title() if place2 else "" + entry1 = self.get_widget("place1") + entry2 = self.get_widget("place2") + entry1.set_text(place1) + entry2.set_text(place2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('place1', 'place2', 'place_btn1', 'place_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("desc1") + entry2 = self.get_widget("desc2") + entry1.set_text(self.ev1.get_description()) + entry2.set_text(self.ev2.get_description()) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('desc1', 'desc2', 'desc_btn1', 'desc_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("marker1") + entry2 = self.get_widget("marker2") + entry1.set_text(str(self.ev1.get_marker())) + entry2.set_text(str(self.ev2.get_marker())) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('marker1', 'marker2', 'marker_btn1', + 'marker_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + gramps1 = self.ev1.get_gramps_id() + gramps2 = self.ev2.get_gramps_id() + entry1 = self.get_widget("gramps1") + entry2 = self.get_widget("gramps2") + entry1.set_text(gramps1) + entry2.set_text(gramps2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('gramps1', 'gramps2', 'gramps_btn1', + 'gramps_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + # Main window widgets that determine which handle survives + ppant1 = Utils.get_participant_from_event(database, handle1) + ppant2 = Utils.get_participant_from_event(database, handle2) + rbutton1 = self.get_widget("handle_btn1") + rbutton_label1 = self.get_widget("label_handle_btn1") + rbutton_label2 = self.get_widget("label_handle_btn2") + rbutton_label1.set_label("%s %s [%s]" % (type1, ppant1, gramps1)) + rbutton_label2.set_label("%s %s [%s]" % (type2, ppant2, gramps2)) + rbutton1.connect("toggled", self.on_handle1_toggled) + + self.connect_button("event_help", self.cb_help) + self.connect_button("event_ok", self.cb_merge) + self.connect_button("event_cancel", self.close) + self.show() + + def on_handle1_toggled(self, obj): + """Preferred event changes""" + if obj.get_active(): + self.get_widget("type_btn1").set_active(True) + self.get_widget("date_btn1").set_active(True) + self.get_widget("place_btn1").set_active(True) + self.get_widget("desc_btn1").set_active(True) + self.get_widget("marker_btn1").set_active(True) + self.get_widget("gramps_btn1").set_active(True) + else: + self.get_widget("type_btn2").set_active(True) + self.get_widget("date_btn2").set_active(True) + self.get_widget("place_btn2").set_active(True) + self.get_widget("desc_btn2").set_active(True) + self.get_widget("marker_btn2").set_active(True) + self.get_widget("gramps_btn2").set_active(True) + + def cb_help(self, obj): + """Display the relevant portion of the Gramps manual""" + GrampsDisplay.help(webpage = WIKI_HELP_PAGE, section = WIKI_HELP_SEC) + + def cb_merge(self, obj): + """ + Perform the merge of the events when the merge button is clicked. + """ + self.uistate.set_busy_cursor(True) + use_handle1 = self.get_widget("handle_btn1").get_active() + if use_handle1: + phoenix = self.ev1 + titanic = self.ev2 + unselect_path = (1,) + else: + phoenix = self.ev2 + titanic = self.ev1 + unselect_path = (0,) + + if self.get_widget("type_btn1").get_active() ^ use_handle1: + phoenix.set_type(titanic.get_type()) + if self.get_widget("date_btn1").get_active() ^ use_handle1: + phoenix.set_date_object(titanic.get_date_object()) + if self.get_widget("place_btn1").get_active() ^ use_handle1: + phoenix.set_place_handle(titanic.get_place_handle()) + if self.get_widget("desc_btn1").get_active() ^ use_handle1: + phoenix.set_description(titanic.get_description()) + if self.get_widget("marker_btn1").get_active() ^ use_handle1: + phoenix.set_marker(titanic.get_marker()) + if self.get_widget("gramps_btn1").get_active() ^ use_handle1: + phoenix.set_gramps_id(titanic.get_gramps_id()) + # cause is deprecated. + + query = MergeEventQuery(self.dbstate, phoenix, titanic) + query.execute() + self.uistate.viewmanager.active_page.selection.unselect_path( + unselect_path) + self.uistate.set_busy_cursor(False) + self.close() + +#------------------------------------------------------------------------- +# +# Merge Event Query +# +#------------------------------------------------------------------------- +class MergeEventQuery(object): + """ + Create database query to merge two events. + """ + def __init__(self, dbstate, phoenix, titanic): + self.database = dbstate.db + self.phoenix = phoenix + self.titanic = titanic + + def execute(self): + """ + Merges two events into a single event. + """ + new_handle = self.phoenix.get_handle() + old_handle = self.titanic.get_handle() + + self.phoenix.merge(self.titanic) + + trans = self.database.transaction_begin() + for person in self.database.iter_people(): + if person.has_handle_reference("Event", old_handle): + bri = person.birth_ref_index + dri = person.death_ref_index + person.replace_handle_reference("Event", old_handle, new_handle) + if person.birth_ref_index != bri and person.birth_ref_index==-1: + for index, ref in enumerate(person.get_event_ref_list()): + if ref.ref == new_handle: + event = self.phoenix + else: + event = self.database.get_event_from_handle(ref.ref) + if event.type.is_birth() and ref.role.is_primary(): + person.birth_ref_index = index + break + if person.death_ref_index != dri and person.death_ref_index==-1: + for index, ref in enumerate(person.get_event_ref_list()): + if ref.ref == new_handle: + event = self.phoenix + else: + event = self.database.get_event_from_handle(ref.ref) + if event.type.is_death() and ref.role.is_primary(): + person.death_ref_index = index + break + self.database.commit_person(person, trans) + + for family in self.database.iter_families(): + if family.has_handle_reference("Event", old_handle): + family.replace_handle_reference("Event", old_handle, new_handle) + self.database.commit_family(family, trans) + + self.database.remove_event(old_handle, trans) + self.database.commit_event(self.phoenix, trans) + self.database.transaction_commit(trans, _("Merge Event Objects")) diff --git a/src/Merge/mergefamily.py b/src/Merge/mergefamily.py new file mode 100644 index 000000000..c24223663 --- /dev/null +++ b/src/Merge/mergefamily.py @@ -0,0 +1,261 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Michiel D. Nauta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Provide merge capabilities for families. +""" + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.ggettext import sgettext as _ +from gen.display.name import displayer as name_displayer +import const +import GrampsDisplay +from QuestionDialog import ErrorDialog +from Errors import MergeError +import ManagedWindow +from Merge.mergeperson import MergePersonQuery + +#------------------------------------------------------------------------- +# +# Gramps constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = '%s_-_Entering_and_Editing_Data:_Detailed_-_part_3' % \ + const.URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Merge_Families') +_GLADE_FILE = 'mergefamily.glade' + +#------------------------------------------------------------------------- +# +# Merge Families +# +#------------------------------------------------------------------------- +class MergeFamilies(ManagedWindow.ManagedWindow): + """ + Merges two families into a single family. Displays a dialog box that allows + the families to be combined into one. + """ + def __init__(self, dbstate, uistate, handle1, handle2): + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + database = dbstate.db + self.fy1 = database.get_family_from_handle(handle1) + self.fy2 = database.get_family_from_handle(handle2) + + self.define_glade('mergefamily', _GLADE_FILE) + self.set_window(self._gladeobj.toplevel, + self.get_widget("family_title"), + _("Merge Families")) + + # Detailed selection widgets + father1 = self.fy1.get_father_handle() + father2 = self.fy2.get_father_handle() + father1 = database.get_person_from_handle(father1) + father2 = database.get_person_from_handle(father2) + father_id1 = father1.get_gramps_id() + father_id2 = father2.get_gramps_id() + father1 = name_displayer.display(father1) if father1 else "" + father2 = name_displayer.display(father2) if father2 else "" + entry1 = self.get_widget("father1") + entry2 = self.get_widget("father2") + entry1.set_text("%s [%s]" % (father1, father_id1)) + entry2.set_text("%s [%s]" % (father2, father_id2)) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('father1', 'father2', 'father_btn1', + 'father_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + mother1 = self.fy1.get_mother_handle() + mother2 = self.fy2.get_mother_handle() + mother1 = database.get_person_from_handle(mother1) + mother2 = database.get_person_from_handle(mother2) + mother_id1 = mother1.get_gramps_id() + mother_id2 = mother2.get_gramps_id() + mother1 = name_displayer.display(mother1) if mother1 else "" + mother2 = name_displayer.display(mother2) if mother2 else "" + entry1 = self.get_widget("mother1") + entry2 = self.get_widget("mother2") + entry1.set_text("%s [%s]" % (mother1, mother_id1)) + entry2.set_text("%s [%s]" % (mother2, mother_id2)) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('mother1', 'mother2', 'mother_btn1', + 'mother_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("rel1") + entry2 = self.get_widget("rel2") + entry1.set_text(str(self.fy1.get_relationship())) + entry2.set_text(str(self.fy2.get_relationship())) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('rel1', 'rel2', 'rel_btn1', 'rel_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("marker1") + entry2 = self.get_widget("marker2") + entry1.set_text(str(self.fy1.get_marker())) + entry2.set_text(str(self.fy2.get_marker())) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('marker1', 'marker2', 'marker_btn1', + 'marker_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + gramps1 = self.fy1.get_gramps_id() + gramps2 = self.fy2.get_gramps_id() + entry1 = self.get_widget("gramps1") + entry2 = self.get_widget("gramps2") + entry1.set_text(gramps1) + entry2.set_text(gramps2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('gramps1', 'gramps2', 'gramps_btn1', + 'gramps_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + # Main window widgets that determine which handle survives + rbutton1 = self.get_widget("handle_btn1") + rbutton_label1 = self.get_widget("label_handle_btn1") + rbutton_label2 = self.get_widget("label_handle_btn2") + rbutton_label1.set_label("%s and %s [%s]" %(father1, mother1, gramps1)) + rbutton_label2.set_label("%s and %s [%s]" %(father2, mother2, gramps2)) + rbutton1.connect("toggled", self.on_handle1_toggled) + + self.connect_button("family_help", self.cb_help) + self.connect_button("family_ok", self.cb_merge) + self.connect_button("family_cancel", self.close) + self.show() + + def on_handle1_toggled(self, obj): + """Preferred family changes""" + if obj.get_active(): + self.get_widget("father_btn1").set_active(True) + self.get_widget("mother_btn1").set_active(True) + self.get_widget("rel_btn1").set_active(True) + self.get_widget("marker_btn1").set_active(True) + self.get_widget("gramps_btn1").set_active(True) + else: + self.get_widget("father_btn2").set_active(True) + self.get_widget("mother_btn2").set_active(True) + self.get_widget("rel_btn2").set_active(True) + self.get_widget("marker_btn2").set_active(True) + self.get_widget("gramps_btn2").set_active(True) + + def cb_help(self, obj): + """Display the relevant portion of the Gramps manual""" + GrampsDisplay.help(webpage = WIKI_HELP_PAGE, section = WIKI_HELP_SEC) + + def cb_merge(self, obj): + """ + Perform the merge of the families when the merge button is clicked. + """ + self.uistate.set_busy_cursor(True) + need_commit = False + database = self.dbstate.db + use_handle1 = self.get_widget("handle_btn1").get_active() + if use_handle1: + phoenix = self.fy1 + titanic = self.fy2 + unselect_path = (1,) + else: + phoenix = self.fy2 + titanic = self.fy1 + unselect_path = (0,) + + phoenix_father = database.get_person_from_handle( + phoenix.get_father_handle()) + phoenix_mother = database.get_person_from_handle( + phoenix.get_mother_handle()) + titanic_father = database.get_person_from_handle( + titanic.get_father_handle()) + titanic_mother = database.get_person_from_handle( + titanic.get_mother_handle()) + + trans = database.transaction_begin("", True) + + # Use merge persons on father and mother to merge a family; The merge + # person routine also merges families if necessary. Merging is not + # an equal operation, there is one preferred family over the other. + # The preferred family is the first listed in a persons + # family_handle_list. Since the GUI allows users to chose the + # preferred father, mother and family independent of each other, while + # the merge person routine fixes preferred family with respect to + # father and mother, the father and mother need first to be swapped + # into the right family, before the merge person routines can be called. + if self.get_widget("father_btn1").get_active() ^ use_handle1: + father_handle = phoenix.get_father_handle() + phoenix.set_father_handle(titanic.get_father_handle()) + titanic.set_father_handle(father_handle) + phoenix_father.replace_handle_reference('Family', + phoenix.get_handle(), titanic.get_handle()) + titanic_father.replace_handle_reference('Family', + titanic.get_handle(), phoenix.get_handle()) + phoenix_father, titanic_father = titanic_father, phoenix_father + database.commit_person(phoenix_father, trans) + database.commit_person(titanic_father, trans) + database.commit_family(phoenix, trans) + database.commit_family(titanic, trans) + if self.get_widget("mother_btn1").get_active() ^ use_handle1: + mother_handle = phoenix.get_mother_handle() + phoenix.set_mother_handle(titanic.get_mother_handle()) + titanic.set_mother_handle(mother_handle) + phoenix_mother.replace_handle_reference('Family', + phoenix.get_handle(), titanic.get_handle()) + titanic_mother.replace_handle_reference('Family', + titanic.get_handle(), phoenix.get_handle()) + phoenix_mother, titanic_mother = titanic_mother, phoenix_mother + database.commit_person(phoenix_mother, trans) + database.commit_person(titanic_mother, trans) + database.commit_family(phoenix, trans) + database.commit_family(titanic, trans) + + if self.get_widget("rel_btn1").get_active() ^ use_handle1: + phoenix.set_relationship(titanic.get_relationship()) + need_commit = True + if self.get_widget("marker_btn1").get_active() ^ use_handle1: + phoenix.set_marker(titanic.get_marker()) + need_commit = True + if self.get_widget("gramps_btn1").get_active() ^ use_handle1: + phoenix.set_gramps_id(titanic.get_gramps_id()) + need_commit = True + if need_commit: + database.commit_family(phoenix, trans) + + try: + if phoenix_father != titanic_father: + query = MergePersonQuery(self.dbstate, phoenix_father, + titanic_father) + query.execute(trans) + if phoenix_mother != titanic_mother: + query = MergePersonQuery(self.dbstate, phoenix_mother, + titanic_mother) + query.execute(trans) + except MergeError, e: + ErrorDialog( _("Cannot merge people"), str(e)) + # TODO: rollback + else: + database.transaction_commit(trans, _('Merge family')) + self.uistate.viewmanager.active_page.selection.unselect_path( + unselect_path) + self.uistate.set_busy_cursor(False) + self.close() diff --git a/src/Merge/mergemedia.py b/src/Merge/mergemedia.py new file mode 100644 index 000000000..e46b44203 --- /dev/null +++ b/src/Merge/mergemedia.py @@ -0,0 +1,215 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Michiel D. Nauta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Provide merge capabilities for media objects. +""" + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.ggettext import sgettext as _ +import const +import GrampsDisplay +import ManagedWindow +import DateHandler + +#------------------------------------------------------------------------- +# +# Gramps constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = '%s_-_Entering_and_Editing_Data:_Detailed_-_part_3' % \ + const.URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Merge_Media_Objects') +_GLADE_FILE = 'mergemedia.glade' + +#------------------------------------------------------------------------- +# +# Merge Media Objects +# +#------------------------------------------------------------------------- +class MergeMediaObjects(ManagedWindow.ManagedWindow): + """ + Displays a dialog box that allows the media objects to be combined into one. + """ + def __init__(self, dbstate, uistate, handle1, handle2): + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + database = dbstate.db + self.mo1 = database.get_object_from_handle(handle1) + self.mo2 = database.get_object_from_handle(handle2) + + self.define_glade('mergeobject', _GLADE_FILE) + self.set_window(self._gladeobj.toplevel, + self.get_widget('object_title'), + _("Merge Media Objects")) + + # Detailed selection Widgets + desc1 = self.mo1.get_description() + desc2 = self.mo2.get_description() + entry1 = self.get_widget("desc1") + entry2 = self.get_widget("desc2") + entry1.set_text(desc1) + entry2.set_text(desc2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('desc1', 'desc2', 'desc_btn1', 'desc_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("path1") + entry2 = self.get_widget("path2") + entry1.set_text(self.mo1.get_path()) + entry2.set_text(self.mo2.get_path()) + entry1.set_position(-1) + entry2.set_position(-1) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('path1', 'path2', 'path_btn1', 'path_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("date1") + entry2 = self.get_widget("date2") + entry1.set_text(DateHandler.get_date(self.mo1)) + entry2.set_text(DateHandler.get_date(self.mo2)) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('date1', 'date2', 'date_btn1', 'date_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + gramps1 = self.mo1.get_gramps_id() + gramps2 = self.mo2.get_gramps_id() + entry1 = self.get_widget("gramps1") + entry2 = self.get_widget("gramps2") + entry1.set_text(gramps1) + entry2.set_text(gramps2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('gramps1', 'gramps2', 'gramps_btn1', + 'gramps_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + # Main window widgets that determine which handle survives + rbutton1 = self.get_widget("handle_btn1") + rbutton_label1 = self.get_widget("label_handle_btn1") + rbutton_label2 = self.get_widget("label_handle_btn2") + rbutton_label1.set_label("%s [%s]" % (desc1, gramps1)) + rbutton_label2.set_label("%s [%s]" % (desc2, gramps2)) + rbutton1.connect('toggled', self.on_handle1_toggled) + + self.connect_button('object_help', self.cb_help) + self.connect_button('object_ok', self.cb_merge) + self.connect_button('object_cancel', self.close) + self.show() + + def on_handle1_toggled(self, obj): + """ first chosen media object changes""" + if obj.get_active(): + self.get_widget("path_btn1").set_active(True) + self.get_widget("desc_btn1").set_active(True) + self.get_widget("date_btn1").set_active(True) + self.get_widget("gramps_btn1").set_active(True) + else: + self.get_widget("path_btn2").set_active(True) + self.get_widget("desc_btn2").set_active(True) + self.get_widget("date_btn2").set_active(True) + self.get_widget("gramps_btn2").set_active(True) + + def cb_help(self, obj): + """Display the relevant portion of the Gramps manual""" + GrampsDisplay.help(webpage = WIKI_HELP_PAGE, section = WIKI_HELP_SEC) + + def cb_merge(self, obj): + """ + Perform the merge of the media objects when the merge button is clicked. + """ + use_handle1 = self.get_widget("handle_btn1").get_active() + if use_handle1: + phoenix = self.mo1 + titanic = self.mo2 + unselect_path = (1,) + else: + phoenix = self.mo2 + titanic = self.mo1 + unselect_path = (0,) + + if self.get_widget("path_btn1").get_active() ^ use_handle1: + phoenix.set_path(titanic.get_path()) + phoenix.set_mime_type(titanic.get_mime_type()) + if self.get_widget("desc_btn1").get_active() ^ use_handle1: + phoenix.set_description(titanic.get_description()) + if self.get_widget("date_btn1").get_active() ^ use_handle1: + phoenix.set_date_object(titanic.get_date_object()) + if self.get_widget("gramps_btn1").get_active() ^ use_handle1: + phoenix.set_gramps_id(titanic.get_gramps_id()) + + query = MergeMediaQuery(self.dbstate, phoenix, titanic) + query.execute() + self.uistate.viewmanager.active_page.selection.unselect_path( + unselect_path) + self.close() + +class MergeMediaQuery(object): + """ + Create datqabase query to merge two media objects. + """ + def __init__(self, dbstate, phoenix, titanic): + self.database = dbstate.db + self.phoenix = phoenix + self.titanic = titanic + + def execute(self): + """ + Merges two media objects into a single object. + """ + new_handle = self.phoenix.get_handle() + old_handle = self.titanic.get_handle() + + self.phoenix.merge(self.titanic) + + trans = self.database.transaction_begin() + for person in self.database.iter_people(): + if person.has_media_reference(old_handle): + person.replace_media_references(old_handle, new_handle) + self.database.commit_person(person, trans) + + for family in self.database.iter_families(): + if family.has_media_reference(old_handle): + family.replace_media_references(old_handle, new_handle) + self.database.commit_family(family, trans) + + for event in self.database.iter_events(): + if event.has_media_reference(old_handle): + event.replace_media_references(old_handle, new_handle) + self.database.commit_event(event, trans) + + for source in self.database.iter_sources(): + if source.has_media_reference(old_handle): + source.replace_media_references(old_handle, new_handle) + self.database.commit_source(source, trans) + + for place in self.database.iter_places(): + if place.has_media_reference(old_handle): + place.replace_media_references(old_handle, new_handle) + self.database.commit_place(place, trans) + + self.database.remove_object(old_handle, trans) + self.database.commit_media_object(self.phoenix, trans) + self.database.transaction_commit(trans, _("Merge Media Objects")) diff --git a/src/Merge/mergenote.py b/src/Merge/mergenote.py new file mode 100644 index 000000000..982da682b --- /dev/null +++ b/src/Merge/mergenote.py @@ -0,0 +1,251 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Michiel D. Nauta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Provide merge capabilities for notes. +""" + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.ggettext import sgettext as _ +import const +import GrampsDisplay +import ManagedWindow +from gui.widgets.styledtextbuffer import StyledTextBuffer + +#------------------------------------------------------------------------- +# +# Gramps constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = '%s_-_Entering_and_Editing_Data:_Detailed_-_part_3' % \ + const.URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Merge_Notes') +_GLADE_FILE = 'mergenote.glade' + +#------------------------------------------------------------------------- +# +# Merge Notes +# +#------------------------------------------------------------------------- +class MergeNotes(ManagedWindow.ManagedWindow): + """ + Displays a dialog box that allows two notes to be combined into one. + """ + def __init__(self, dbstate, uistate, handle1, handle2): + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + database = dbstate.db + self.no1 = database.get_note_from_handle(handle1) + self.no2 = database.get_note_from_handle(handle2) + + self.define_glade('mergenote', _GLADE_FILE) + self.set_window(self._gladeobj.toplevel, + self.get_widget("note_title"), + _("Merge Notes")) + + # Detailed selection widgets + text1 = self.no1.get_styledtext() + tv1 = self.get_widget("text1") + tb1 = StyledTextBuffer() + tv1.set_buffer(tb1) + tb1.set_text(text1) + text2 = self.no2.get_styledtext() + tv2 = self.get_widget("text2") + tb2 = StyledTextBuffer() + tv2.set_buffer(tb2) + tb2.set_text(text2) + if text1 == text2: + for widget_name in ('text1', 'text2', 'text_btn1', 'text_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("type1") + entry2 = self.get_widget("type2") + entry1.set_text(str(self.no1.get_type())) + entry2.set_text(str(self.no2.get_type())) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('type1', 'type2', 'type_btn1', 'type_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + format_names = (_('flowed'), _('preformatted')) + entry1 = self.get_widget("format1") + entry2 = self.get_widget("format2") + entry1.set_text(format_names[self.no1.get_format()]) + entry2.set_text(format_names[self.no2.get_format()]) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('format1', 'format2', 'format_btn1', + 'format_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("marker1") + entry2 = self.get_widget("marker2") + entry1.set_text(str(self.no1.get_marker())) + entry2.set_text(str(self.no2.get_marker())) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('marker1', 'marker2', 'marker_btn1', + 'marker_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + gramps1 = self.no1.get_gramps_id() + gramps2 = self.no2.get_gramps_id() + entry1 = self.get_widget("gramps1") + entry2 = self.get_widget("gramps2") + entry1.set_text(gramps1) + entry2.set_text(gramps2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('gramps1', 'gramps2', 'gramps_btn1', + 'gramps_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + # Main window widgets that determine which handle survives + rbutton1 = self.get_widget("handle_btn1") + rbutton_label1 = self.get_widget("label_handle_btn1") + rbutton_label2 = self.get_widget("label_handle_btn2") + text1short = self.no1.get() + if len(text1short) > 50: + text1short = text1short[0:47] + "..." + text2short = self.no2.get() + if len(text2short) > 50: + text2short = text2short[0:47] + "..." + rbutton_label1.set_label("%s [%s]" % (text1short, gramps1)) + rbutton_label2.set_label("%s [%s]" % (text2short, gramps2)) + rbutton1.connect("toggled", self.on_handle1_toggled) + + self.connect_button("note_help", self.cb_help) + self.connect_button("note_ok", self.cb_merge) + self.connect_button("note_cancel", self.close) + self.show() + + def on_handle1_toggled(self, obj): + """ preferred note changes""" + if obj.get_active(): + self.get_widget("text_btn1").set_active(True) + self.get_widget("type_btn1").set_active(True) + self.get_widget("format_btn1").set_active(True) + self.get_widget("marker_btn1").set_active(True) + self.get_widget("gramps_btn1").set_active(True) + else: + self.get_widget("text_btn2").set_active(True) + self.get_widget("type_btn2").set_active(True) + self.get_widget("format_btn2").set_active(True) + self.get_widget("marker_btn2").set_active(True) + self.get_widget("gramps_btn2").set_active(True) + + def cb_help(self, obj): + """Display the relevant portion of the Gramps manual""" + GrampsDisplay.help(webpage = WIKI_HELP_PAGE, section= WIKI_HELP_SEC) + + def cb_merge(self, obj): + """ + Perform the merge of the notes when the merge button is clicked. + """ + use_handle1 = self.get_widget("handle_btn1").get_active() + if use_handle1: + phoenix = self.no1 + titanic = self.no2 + unselect_path = (1,) + else: + phoenix = self.no2 + titanic = self.no1 + unselect_path = (0,) + + if self.get_widget("text_btn1").get_active() ^ use_handle1: + phoenix.set_styledtext(titanic.get_styledtext()) + if self.get_widget("type_btn1").get_active() ^ use_handle1: + phoenix.set_type(titanic.get_type()) + if self.get_widget("format_btn1").get_active() ^ use_handle1: + phoenix.set_format(titanic.get_format()) + if self.get_widget("marker_btn1").get_active() ^ use_handle1: + phoenix.set_marker(titanic.get_marker()) + if self.get_widget("gramps_btn1").get_active() ^ use_handle1: + phoenix.set_gramps_id(titanic.get_gramps_id()) + + query = MergeNoteQuery(self.dbstate, phoenix, titanic) + query.execute() + self.uistate.viewmanager.active_page.selection.unselect_path( + unselect_path) + self.close() + +#------------------------------------------------------------------------- +# +# Merge Note Query +# +#------------------------------------------------------------------------- +class MergeNoteQuery(object): + """ + Create database query to merge two notes. + """ + def __init__(self, dbstate, phoenix, titanic): + self.database = dbstate.db + self.phoenix = phoenix + self.titanic = titanic + + def execute(self): + """ + Merges two notes into a single note. + """ + new_handle = self.phoenix.get_handle() + old_handle = self.titanic.get_handle() + self.phoenix.merge(self.titanic) + trans = self.database.transaction_begin() + + for person in self.database.iter_people(): + if person.has_note_reference(old_handle): + person.replace_note_references(old_handle, new_handle) + self.database.commit_person(person, trans) + + for family in self.database.iter_families(): + if family.has_note_reference(old_handle): + family.replace_note_references(old_handle, new_handle) + self.database.commit_family(family, trans) + + for event in self.database.iter_events(): + if event.has_note_reference(old_handle): + event.replace_note_references(old_handle, new_handle) + self.database.commit_event(event, trans) + + for source in self.database.iter_sources(): + if source.has_note_reference(old_handle): + source.replace_note_references(old_handle, new_handle) + self.database.commit_source(source, trans) + + for place in self.database.iter_places(): + if place.has_note_reference(old_handle): + place.replace_note_references(old_handle, new_handle) + self.database.commit_place(place, trans) + + for obj in self.database.iter_media_objects(): + if obj.has_note_reference(old_handle): + obj.replace_note_references(old_handle, new_handle) + self.database.commit_media_object(obj, trans) + + for repo in self.database.iter_repositories(): + if repo.has_note_reference(old_handle): + repo.replace_note_references(old_handle, new_handle) + self.database.commit_repository(repo, trans) + + self.database.remove_note(old_handle, trans) + self.database.commit_note(self.phoenix, trans) + self.database.transaction_commit(trans, _("Merge Notes")) diff --git a/src/Merge/mergeperson.py b/src/Merge/mergeperson.py new file mode 100644 index 000000000..643c4f981 --- /dev/null +++ b/src/Merge/mergeperson.py @@ -0,0 +1,463 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta +# Copyright (C) 2010 Jakim Friant +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Provide merge capabilities for persons. +""" + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import pango + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.ggettext import sgettext as _ +from gen.plug.report import utils as ReportUtils +from gen.display.name import displayer as name_displayer +import const +import GrampsDisplay +import DateHandler +from QuestionDialog import ErrorDialog +from Errors import MergeError +import ManagedWindow + +#------------------------------------------------------------------------- +# +# Gramps constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = "%s_-_Entering_and_Editing_Data:_Detailed_-_part_3" % \ + const.URL_MANUAL_PAGE +WIKI_HELP_SEC = _("manual|Merge_People") +_GLADE_FILE = "mergeperson.glade" + +sex = ( _("female"), _("male"), _("unknown") ) + +def name_of(person): + """Return string with name and ID of a person.""" + if not person: + return "" + return "%s [%s]" % (name_displayer.display(person), person.get_gramps_id()) + +class MergePeople(ManagedWindow.ManagedWindow): + """ + Displays a dialog box that allows the persons to be combined into one. + """ + def __init__(self, dbstate, uistate, handle1, handle2): + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + database = dbstate.db + self.pr1 = database.get_person_from_handle(handle1) + self.pr2 = database.get_person_from_handle(handle2) + + self.define_glade('mergeperson', _GLADE_FILE) + self.set_window(self._gladeobj.toplevel, + self.get_widget("person_title"), + _("Merge People")) + + # Detailed selection widgets + name1 = name_displayer.display_name(self.pr1.get_primary_name()) + name2 = name_displayer.display_name(self.pr2.get_primary_name()) + entry1 = self.get_widget("name1") + entry2 = self.get_widget("name2") + entry1.set_text(name1) + entry2.set_text(name2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('name1', 'name2', 'name_btn1', 'name_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("gender1") + entry2 = self.get_widget("gender2") + entry1.set_text(sex[self.pr1.get_gender()]) + entry2.set_text(sex[self.pr2.get_gender()]) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('gender1', 'gender2', 'gender_btn1', + 'gender_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("marker1") + entry2 = self.get_widget("marker2") + entry1.set_text(str(self.pr1.get_marker())) + entry2.set_text(str(self.pr2.get_marker())) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('marker1', 'marker2', 'marker_btn1', + 'marker_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + gramps1 = self.pr1.get_gramps_id() + gramps2 = self.pr2.get_gramps_id() + entry1 = self.get_widget("gramps1") + entry2 = self.get_widget("gramps2") + entry1.set_text(gramps1) + entry2.set_text(gramps2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('gramps1', 'gramps2', 'gramps_btn1', + 'gramps_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + # Main window widgets that determine which handle survives + rbutton1 = self.get_widget("handle_btn1") + rbutton_label1 = self.get_widget("label_handle_btn1") + rbutton_label2 = self.get_widget("label_handle_btn2") + rbutton_label1.set_label(name1 + " [" + gramps1 + "]") + rbutton_label2.set_label(name2 + " [" + gramps2 + "]") + rbutton1.connect("toggled", self.on_handle1_toggled) + expander2 = self.get_widget("expander2") + self.expander_handler = \ + expander2.connect("activate", self.on_expander2_activated) + + self.connect_button("person_help", self.cb_help) + self.connect_button("person_ok", self.cb_merge) + self.connect_button("person_cancel", self.close) + self.show() + + def on_handle1_toggled(self, obj): + """Preferred person changes""" + if obj.get_active(): + self.get_widget("name_btn1").set_active(True) + self.get_widget("gender_btn1").set_active(True) + self.get_widget("marker_btn1").set_active(True) + self.get_widget("gramps_btn1").set_active(True) + else: + self.get_widget("name_btn2").set_active(True) + self.get_widget("gender_btn2").set_active(True) + self.get_widget("marker_btn2").set_active(True) + self.get_widget("gramps_btn2").set_active(True) + + def on_expander2_activated(self, obj): + """Context Information expander is activated""" + text1 = self.get_widget('text1') + text2 = self.get_widget('text2') + self.display(text1.get_buffer(), self.pr1) + self.display(text2.get_buffer(), self.pr2) + expander2 = self.get_widget("expander2") + expander2.disconnect(self.expander_handler) + + def add(self, tobj, tag, text): + """Add text text to text buffer tobj with formatting tag.""" + text += "\n" + tobj.insert_with_tags(tobj.get_end_iter(), text, tag) + + def display(self, tobj, person): + """Fill text buffer tobj with detailed info on person person.""" + database = self.dbstate.db + normal = tobj.create_tag() + normal.set_property('indent', 10) + normal.set_property('pixels-above-lines', 1) + normal.set_property('pixels-below-lines', 1) + indent = tobj.create_tag() + indent.set_property('indent', 30) + indent.set_property('pixels-above-lines', 1) + indent.set_property('pixels-below-lines', 1) + title = tobj.create_tag() + title.set_property('weight', pango.WEIGHT_BOLD) + title.set_property('scale', pango.SCALE_LARGE) + self.add(tobj, title, name_displayer.display(person)) + self.add(tobj, normal, "%s:\t%s" % (_('ID'), + person.get_gramps_id())) + self.add(tobj, normal, "%s:\t%s" % (_('Gender'), + sex[person.get_gender()])) + bref = person.get_birth_ref() + if bref: + self.add(tobj, normal, "%s:\t%s" % (_('Birth'), + self.get_event_info(bref.ref))) + dref = person.get_death_ref() + if dref: + self.add(tobj, normal, "%s:\t%s" % (_('Death'), + self.get_event_info(dref.ref))) + + nlist = person.get_alternate_names() + if len(nlist) > 0: + self.add(tobj, title, _("Alternate Names")) + for name in nlist: + self.add(tobj, normal, + name_displayer.display_name(name)) + + elist = person.get_event_ref_list() + if len(elist) > 0: + self.add(tobj, title, _("Events")) + for event_ref in person.get_event_ref_list(): + event_handle = event_ref.ref + name = str( + database.get_event_from_handle(event_handle).get_type()) + self.add(tobj, normal, "%s:\t%s" % + (name, self.get_event_info(event_handle))) + plist = person.get_parent_family_handle_list() + + if len(plist) > 0: + self.add(tobj, title, _("Parents")) + for fid in person.get_parent_family_handle_list(): + (fname, mname, gid) = self.get_parent_info(fid) + self.add(tobj, normal, "%s:\t%s" % (_('Family ID'), gid)) + if fname: + self.add(tobj, indent, "%s:\t%s" % (_('Father'), fname)) + if mname: + self.add(tobj, indent, "%s:\t%s" % (_('Mother'), mname)) + else: + self.add(tobj, normal, _("No parents found")) + + self.add(tobj, title, _("Spouses")) + slist = person.get_family_handle_list() + if len(slist) > 0: + for fid in slist: + (fname, mname, pid) = self.get_parent_info(fid) + family = database.get_family_from_handle(fid) + self.add(tobj, normal, "%s:\t%s" % (_('Family ID'), pid)) + spouse_id = ReportUtils.find_spouse(person, family) + if spouse_id: + spouse = database.get_person_from_handle(spouse_id) + self.add(tobj, indent, "%s:\t%s" % (_('Spouse'), + name_of(spouse))) + relstr = str(family.get_relationship()) + self.add(tobj, indent, "%s:\t%s" % (_('Type'), relstr)) + event = ReportUtils.find_marriage(database, family) + if event: + self.add(tobj, indent, "%s:\t%s" % ( + _('Marriage'), + self.get_event_info(event.get_handle()))) + for child_ref in family.get_child_ref_list(): + child = database.get_person_from_handle(child_ref.ref) + self.add(tobj, indent, "%s:\t%s" % (_('Child'), + name_of(child))) + else: + self.add(tobj, normal, _("No spouses or children found")) + + alist = person.get_address_list() + if len(alist) > 0: + self.add(tobj, title, _("Addresses")) + for addr in alist: + location = ", ".join([addr.get_street(), addr.get_city(), + addr.get_state(), addr.get_country(), + addr.get_postal_code(), addr.get_phone()]) + self.add(tobj, normal, location.strip()) + + def get_parent_info(self, fid): + """Return tuple of father name, mother name and family ID""" + database = self.dbstate.db + family = database.get_family_from_handle(fid) + father_id = family.get_father_handle() + mother_id = family.get_mother_handle() + if father_id: + father = database.get_person_from_handle(father_id) + fname = name_of(father) + else: + fname = u"" + if mother_id: + mother = database.get_person_from_handle(mother_id) + mname = name_of(mother) + else: + mname = u"" + return (fname, mname, family.get_gramps_id()) + + def get_event_info(self, handle): + """Return date and place of an event as string.""" + date = "" + place = "" + if handle: + event = self.dbstate.db.get_event_from_handle(handle) + date = DateHandler.get_date(event) + place = self.place_name(event) + if date: + return ("%s, %s" % (date, place)) if place else date + else: + return place or "" + else: + return "" + + def place_name(self, event): + """Return place name of an event as string.""" + place_id = event.get_place_handle() + if place_id: + place = self.dbstate.db.get_place_from_handle(place_id) + return place.get_title() + else: + return "" + + def cb_help(self, obj): + """Display the relevant portion of Gramps manual""" + GrampsDisplay.help(webpage = WIKI_HELP_PAGE, section = WIKI_HELP_SEC) + + def cb_merge(self, obj): + """ + Perform the merge of the persons when the merge button is clicked. + """ + self.uistate.set_busy_cursor(True) + use_handle1 = self.get_widget("handle_btn1").get_active() + if use_handle1: + phoenix = self.pr1 + titanic = self.pr2 + unselect_path = (1,) + else: + phoenix = self.pr2 + titanic = self.pr1 + unselect_path = (0,) + + if self.get_widget("name_btn1").get_active() ^ use_handle1: + swapname = phoenix.get_primary_name() + phoenix.set_primary_name(titanic.get_primary_name()) + titanic.set_primary_name(swapname) + if self.get_widget("gender_btn1").get_active() ^ use_handle1: + phoenix.set_gender(titanic.get_gender()) + if self.get_widget("marker_btn1").get_active() ^ use_handle1: + phoenix.set_marker(titanic.get_marker()) + if self.get_widget("gramps_btn1").get_active() ^ use_handle1: + swapid = phoenix.get_gramps_id() + phoenix.set_gramps_id(titanic.get_gramps_id()) + titanic.set_gramps_id(swapid) + + try: + query = MergePersonQuery(self.dbstate, phoenix, titanic) + query.execute() + except MergeError, errstr: + ErrorDialog( _("Cannot merge people"), errstr) + self.uistate.viewmanager.active_page.selection.unselect_path( + unselect_path) + self.uistate.set_busy_cursor(False) + self.close() + +class MergePersonQuery(object): + """ + Create database query to merge two persons. + """ + def __init__(self, dbstate, phoenix, titanic): + self.database = dbstate.db + self.phoenix = phoenix + self.titanic = titanic + if self.check_for_spouse(self.phoenix, self.titanic): + raise MergeError(_("Spouses cannot be merged. To merge these " + "people, you must first break the relationship between them.")) + if self.check_for_child(self.phoenix, self.titanic): + raise MergeError(_("A parent and child cannot be merged. To merge " + "these people, you must first break the relationship between " + "them")) + + def check_for_spouse(self, person1, person2): + """Return if person1 and person2 are spouses of eachother.""" + fs1 = set(person1.get_family_handle_list()) + fs2 = set(person2.get_family_handle_list()) + return len(fs1.intersection(fs2)) != 0 + + def check_for_child(self, person1, person2): + """Return if person1 and person2 have a child-parent relationship.""" + fs1 = set(person1.get_family_handle_list()) + fp1 = set(person1.get_parent_family_handle_list()) + fs2 = set(person2.get_family_handle_list()) + fp2 = set(person2.get_parent_family_handle_list()) + return len(fs1.intersection(fp2)) != 0 or len(fs2.intersection(fp1)) + + def merge_families(self, main_family_handle, family, trans): + new_handle = self.phoenix.get_handle() + family_handle = family.get_handle() + main_family = self.database.get_family_from_handle(main_family_handle) + main_family.merge(family) + for childref in family.get_child_ref_list(): + child = self.database.get_person_from_handle( + childref.get_reference_handle()) + if main_family_handle in child.parent_family_list: + child.remove_handle_references('Family', [family_handle]) + else: + child.replace_handle_reference('Family', family_handle, + main_family_handle) + self.database.commit_person(child, trans) + self.phoenix.remove_family_handle(family_handle) + family_father_handle = family.get_father_handle() + spouse_handle = family.get_mother_handle() if \ + new_handle == family_father_handle else family_father_handle + spouse = self.database.get_person_from_handle(spouse_handle) + spouse.remove_family_handle(family_handle) + self.database.commit_person(spouse, trans) + self.database.remove_family(family_handle, trans) + self.database.commit_family(main_family, trans) + + def execute(self, trans=None): + """ + Merges two persons into a single person. + """ + new_handle = self.phoenix.get_handle() + old_handle = self.titanic.get_handle() + + self.phoenix.merge(self.titanic) + + # For now use a batch transaction, because merger of persons is + # complicated, thus is done in several steps and the database should + # be updated after each step for the calculation of the next step. + # Normal Gramps transactions only touch the database upon + # transaction_commit, not after each commit_person/commit_family. + # Unfortunately batch transactions are no transactions at all, so there + # is not possibility of rollback in case of trouble. + if trans is None: + need_commit = True + trans = self.database.transaction_begin("", True) + else: + need_commit = False + + commit_persons = [] + for person in self.database.iter_people(): + if person.has_handle_reference('Person', old_handle): + person.replace_handle_reference('Person', old_handle,new_handle) + #self.database.commit_person(person, trans) # DEADLOCK + person_handle = person.get_handle() + if person_handle == new_handle: + self.phoenix.replace_handle_reference('Person', old_handle, + new_handle) + elif person_handle != old_handle: + commit_persons.append(person) + for person in commit_persons: + self.database.commit_person(person, trans) + + for family_handle in self.phoenix.get_parent_family_handle_list(): + family = self.database.get_family_from_handle(family_handle) + if family.has_handle_reference('Person', old_handle): + family.replace_handle_reference('Person', old_handle,new_handle) + self.database.commit_family(family, trans) + + parent_list = [] + family_handle_list = self.phoenix.get_family_handle_list()[:] + for family_handle in family_handle_list: + family = self.database.get_family_from_handle(family_handle) + parents = (family.get_father_handle(), family.get_mother_handle()) + if family.has_handle_reference('Person', old_handle): + family.replace_handle_reference('Person', old_handle,new_handle) + parents = (family.get_father_handle(), + family.get_mother_handle()) + # prune means merging families in this case. + if parents in parent_list: + # also merge when father_handle or mother_handle == None! + idx = parent_list.index(parents) + main_family_handle = family_handle_list[idx] + self.merge_families(main_family_handle, family, trans) + continue + self.database.commit_family(family, trans) + parent_list.append(parents) + + self.database.remove_person(old_handle, trans) + self.database.commit_person(self.phoenix, trans) + if need_commit: + self.database.transaction_commit(trans, _('Merge Person')) diff --git a/src/Merge/mergeplace.py b/src/Merge/mergeplace.py new file mode 100644 index 000000000..85eb65151 --- /dev/null +++ b/src/Merge/mergeplace.py @@ -0,0 +1,231 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: _MergePlace.py 14135 2010-01-25 17:45:21Z gbritton $ + +""" +Provide merge capabilities for places. +""" + +#------------------------------------------------------------------------- +# +# GTK/Gnome modules +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.ggettext import sgettext as _ +import const +import GrampsDisplay +import ManagedWindow + +#------------------------------------------------------------------------- +# +# Gramps constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = '%s_-_Entering_and_Editing_Data:_Detailed_-_part_3' % \ + const.URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Merge_Places') +_GLADE_FILE = 'mergeplace.glade' + +#------------------------------------------------------------------------- +# +# Merge Places +# +#------------------------------------------------------------------------- +class MergePlaces(ManagedWindow.ManagedWindow): + """ + Displays a dialog box that allows the places to be combined into one. + """ + def __init__(self, dbstate, uistate, handle1, handle2): + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + database = dbstate.db + self.pl1 = database.get_place_from_handle(handle1) + self.pl2 = database.get_place_from_handle(handle2) + + self.define_glade('mergeplace', _GLADE_FILE) + self.set_window(self._gladeobj.toplevel, + self.get_widget('place_title'), + _("Merge Places")) + + # Detailed selection widgets + title1 = self.pl1.get_title() + title2 = self.pl2.get_title() + entry1 = self.get_widget("title1") + entry2 = self.get_widget("title2") + entry1.set_text(title1) + entry2.set_text(title2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('title1', 'title2', 'title_btn1', 'title_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("lat1") + entry2 = self.get_widget("lat2") + entry1.set_text(self.pl1.get_latitude()) + entry2.set_text(self.pl2.get_latitude()) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('lat1', 'lat2', 'lat_btn1', 'lat_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("long1") + entry2 = self.get_widget("long2") + entry1.set_text(self.pl1.get_longitude()) + entry2.set_text(self.pl2.get_longitude()) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('long1', 'long2', 'long_btn1', 'long_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + loc1 = self.pl1.get_main_location().get_text_data_list() + loc2 = self.pl2.get_main_location().get_text_data_list() + tv1 = self.get_widget("loc1") + tv2 = self.get_widget("loc2") + tb1 = gtk.TextBuffer() + tb2 = gtk.TextBuffer() + tv1.set_buffer(tb1) + tv2.set_buffer(tb2) + tb1.set_text("\n".join(loc1)) + tb2.set_text("\n".join(loc2)) + if loc1 == loc2: + for widget_name in ('loc1', 'loc2', 'loc_btn1', 'loc_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + gramps1 = self.pl1.get_gramps_id() + gramps2 = self.pl2.get_gramps_id() + entry1 = self.get_widget("gramps1") + entry2 = self.get_widget("gramps2") + entry1.set_text(gramps1) + entry2.set_text(gramps2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('gramps1', 'gramps2', 'gramps_btn1', + 'gramps_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + # Main window widgets that determine which handle survives + rbutton1 = self.get_widget("handle_btn1") + rbutton_label1 = self.get_widget("label_handle_btn1") + rbutton_label2 = self.get_widget("label_handle_btn2") + rbutton_label1.set_label(title1 + " [" + gramps1 + "]") + rbutton_label2.set_label(title2 + " [" + gramps2 + "]") + rbutton1.connect("toggled", self.on_handle1_toggled) + + self.connect_button('place_help', self.cb_help) + self.connect_button('place_ok', self.cb_merge) + self.connect_button('place_cancel', self.close) + self.show() + + def on_handle1_toggled(self, obj): + """first chosen place changes""" + if obj.get_active(): + self.get_widget("title_btn1").set_active(True) + self.get_widget("lat_btn1").set_active(True) + self.get_widget("long_btn1").set_active(True) + self.get_widget("loc_btn1").set_active(True) + self.get_widget("gramps_btn1").set_active(True) + else: + self.get_widget("title_btn2").set_active(True) + self.get_widget("lat_btn2").set_active(True) + self.get_widget("long_btn2").set_active(True) + self.get_widget("loc_btn2").set_active(True) + self.get_widget("gramps_btn2").set_active(True) + + def cb_help(self, obj): + """Display the relevant portion of Gramps manual""" + GrampsDisplay.help(webpage = WIKI_HELP_PAGE, section = WIKI_HELP_SEC) + + def cb_merge(self, obj): + """ + Performs the merge of the places when the merge button is clicked. + """ + self.uistate.set_busy_cursor(True) + use_handle1 = self.get_widget("handle_btn1").get_active() + if use_handle1: + phoenix = self.pl1 + titanic = self.pl2 + unselect_path = (1,) + else: + phoenix = self.pl2 + titanic = self.pl1 + unselect_path = (0,) + + if self.get_widget("title_btn1").get_active() ^ use_handle1: + phoenix.set_title(titanic.get_title()) + if self.get_widget("lat_btn1").get_active() ^ use_handle1: + phoenix.set_latitude(titanic.get_latitude()) + if self.get_widget("long_btn1").get_active() ^ use_handle1: + phoenix.set_longitude(titanic.get_longitude()) + if self.get_widget("loc_btn1").get_active() ^ use_handle1: + swaploc = phoenix.get_main_location() + phoenix.set_main_location(titanic.get_main_location()) + titanic.set_main_location(swaploc) + if self.get_widget("gramps_btn1").get_active() ^ use_handle1: + phoenix.set_gramps_id(titanic.get_gramps_id()) + + query = MergePlaceQuery(self.dbstate, phoenix, titanic) + query.execute() + self.uistate.viewmanager.active_page.selection.unselect_path( + unselect_path) + self.uistate.set_busy_cursor(False) + self.close() + +class MergePlaceQuery(object): + """ + Create database query to merge two places. + """ + def __init__(self, dbstate, phoenix, titanic): + self.database = dbstate.db + self.phoenix = phoenix + self.titanic = titanic + + def execute(self): + """ + Merges to places into a single place. + """ + new_handle = self.phoenix.get_handle() + old_handle = self.titanic.get_handle() + + self.phoenix.merge(self.titanic) + + trans = self.database.transaction_begin() + for person in self.database.iter_people(): + if person.has_handle_reference('Place', old_handle): + person.replace_handle_reference('Place', old_handle, new_handle) + self.database.commit_person(person, trans) + + for family in self.database.iter_families(): + if family.has_handle_reference('Place', old_handle): + family.replace_handle_reference('Place', old_handle, new_handle) + self.database.commit_family(family, trans) + + for event in self.database.iter_events(): + if event.has_handle_reference('Place', old_handle): + event.replace_handle_reference('Place', old_handle, new_handle) + self.database.commit_event(event, trans) + + self.database.remove_place(old_handle, trans) + self.database.commit_place(self.phoenix, trans) + self.database.transaction_commit(trans, _("Merge Places")) diff --git a/src/Merge/mergerepository.py b/src/Merge/mergerepository.py new file mode 100644 index 000000000..5bb91916d --- /dev/null +++ b/src/Merge/mergerepository.py @@ -0,0 +1,181 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Michiel D. Nauta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Provide merge capabilities for repositories. +""" + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.ggettext import sgettext as _ +import const +import GrampsDisplay +import ManagedWindow + +#------------------------------------------------------------------------- +# +# Gramps constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = '%s_-_Entering_and_Editing_Data:_Detailed_-_part_3' % \ + const.URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Merge_Repositories') +_GLADE_FILE = 'mergerepository.glade' + +#------------------------------------------------------------------------- +# +# Merge Repositories +# +#------------------------------------------------------------------------- +class MergeRepositories(ManagedWindow.ManagedWindow): + """ + Displays a dialog box that allows two repositories to be combined into one. + """ + def __init__(self, dbstate, uistate, handle1, handle2): + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + database = dbstate.db + self.rp1 = database.get_repository_from_handle(handle1) + self.rp2 = database.get_repository_from_handle(handle2) + + self.define_glade('mergerepository', _GLADE_FILE) + self.set_window(self._gladeobj.toplevel, + self.get_widget('repository_title'), + _("Merge Repositories")) + + # Detailed selection widgets + name1 = self.rp1.get_name() + name2 = self.rp2.get_name() + entry1 = self.get_widget('name1') + entry2 = self.get_widget('name2') + entry1.set_text(name1) + entry2.set_text(name2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('name1', 'name2', 'name_btn1', 'name_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget('type1') + entry2 = self.get_widget('type2') + entry1.set_text(str(self.rp1.get_type())) + entry2.set_text(str(self.rp2.get_type())) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('type1', 'type2', 'type_btn1', 'type_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + gramps1 = self.rp1.get_gramps_id() + gramps2 = self.rp2.get_gramps_id() + entry1 = self.get_widget('gramps1') + entry2 = self.get_widget('gramps2') + entry1.set_text(gramps1) + entry2.set_text(gramps2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('gramps1', 'gramps2', 'gramps_btn1', + 'gramps_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + # Main window widgets that determine which handle survives + rbutton1 = self.get_widget("handle_btn1") + rbutton_label1 = self.get_widget("label_handle_btn1") + rbutton_label2 = self.get_widget("label_handle_btn2") + rbutton_label1.set_label("%s [%s]" % (name1, gramps1)) + rbutton_label2.set_label("%s [%s]" % (name2, gramps2)) + rbutton1.connect('toggled', self.on_handle1_toggled) + + self.connect_button('repository_help', self.cb_help) + self.connect_button('repository_ok', self.cb_merge) + self.connect_button('repository_cancel', self.close) + + self.show() + + def on_handle1_toggled(self, obj): + """ preferred repository changes""" + if obj.get_active(): + self.get_widget('name_btn1').set_active(True) + self.get_widget('type_btn1').set_active(True) + self.get_widget('gramps_btn1').set_active(True) + else: + self.get_widget('name_btn2').set_active(True) + self.get_widget('type_btn2').set_active(True) + self.get_widget('gramps_btn2').set_active(True) + + def cb_help(self, obj): + """Display the relevant portion of the Gramps manual""" + GrampsDisplay.help(webpage = WIKI_HELP_PAGE, section = WIKI_HELP_SEC) + + def cb_merge(self, obj): + """ + Perform the merge of the repositories when the merge button is clicked. + """ + self.uistate.set_busy_cursor(True) + use_handle1 = self.get_widget("handle_btn1").get_active() + if use_handle1: + phoenix = self.rp1 + titanic = self.rp2 + unselect_path = (1,) + else: + phoenix = self.rp2 + titanic = self.rp1 + unselect_path = (0,) + + if self.get_widget("name_btn1").get_active() ^ use_handle1: + phoenix.set_name(titanic.get_name()) + if self.get_widget("type_btn1").get_active() ^ use_handle1: + phoenix.set_type(titanic.get_type()) + if self.get_widget("gramps_btn1").get_active() ^ use_handle1: + phoenix.set_gramps_id(titanic.get_gramps_id()) + + query = MergeRepoQuery(self.dbstate, phoenix, titanic) + query.execute() + self.uistate.viewmanager.active_page.selection.unselect_path( + unselect_path) + self.uistate.set_busy_cursor(False) + self.close() + +class MergeRepoQuery(object): + """ + Create database query to merge two repositories. + """ + def __init__(self, dbstate, phoenix, titanic): + self.database = dbstate.db + self.phoenix = phoenix + self.titanic = titanic + + def execute(self): + """ + Merges two repositories into a single repository. + """ + new_handle = self.phoenix.get_handle() + old_handle = self.titanic.get_handle() + + self.phoenix.merge(self.titanic) + + trans = self.database.transaction_begin() + for source in self.database.iter_sources(): + if source.has_repo_reference(old_handle): + source.replace_repo_references(old_handle, new_handle) + self.database.commit_source(source, trans) + self.database.remove_repository(old_handle, trans) + self.database.commit_repository(self.phoenix, trans) + self.database.transaction_commit(trans, _("Merge Repositories")) diff --git a/src/Merge/mergesource.py b/src/Merge/mergesource.py new file mode 100644 index 000000000..8d14f3db3 --- /dev/null +++ b/src/Merge/mergesource.py @@ -0,0 +1,238 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2000-2005 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: _MergeSource.py 14135 2010-01-25 17:45:21Z gbritton $ + +""" +Provide merge capabilities for sources. +""" + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.ggettext import sgettext as _ +import const +import GrampsDisplay +import ManagedWindow + +#------------------------------------------------------------------------- +# +# Gramps constants +# +#------------------------------------------------------------------------- +WIKI_HELP_PAGE = '%s_-_Entering_and_Editing_Data:_Detailed_-_part_3' % \ + const.URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Merge_Sources') +_GLADE_FILE = 'mergesource.glade' + +#------------------------------------------------------------------------- +# +# Merge Sources +# +#------------------------------------------------------------------------- +class MergeSources(ManagedWindow.ManagedWindow): + """ + Displays a dialog box that allows the sources to be combined into one. + """ + def __init__(self, dbstate, uistate, handle1, handle2): + ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + database = dbstate.db + self.src1 = database.get_source_from_handle(handle1) + self.src2 = database.get_source_from_handle(handle2) + + self.define_glade('mergesource', _GLADE_FILE) + self.set_window(self._gladeobj.toplevel, + self.get_widget('source_title'), + _("Merge Sources")) + + # Detailed Selection widgets + title1 = self.src1.get_title() + title2 = self.src2.get_title() + entry1 = self.get_widget("title1") + entry2 = self.get_widget("title2") + entry1.set_text(title1) + entry2.set_text(title2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('title1', 'title2', 'title_btn1', 'title_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("author1") + entry2 = self.get_widget("author2") + entry1.set_text(self.src1.get_author()) + entry2.set_text(self.src2.get_author()) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('author1', 'author2', 'author_btn1', + 'author_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("abbrev1") + entry2 = self.get_widget("abbrev2") + entry1.set_text(self.src1.get_abbreviation()) + entry2.set_text(self.src2.get_abbreviation()) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('abbrev1', 'abbrev2', 'abbrev_btn1', + 'abbrev_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + entry1 = self.get_widget("pub1") + entry2 = self.get_widget("pub2") + entry1.set_text(self.src1.get_publication_info()) + entry2.set_text(self.src2.get_publication_info()) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('pub1', 'pub2', 'pub_btn1', 'pub_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + gramps1 = self.src1.get_gramps_id() + gramps2 = self.src2.get_gramps_id() + entry1 = self.get_widget("gramps1") + entry2 = self.get_widget("gramps2") + entry1.set_text(gramps1) + entry2.set_text(gramps2) + if entry1.get_text() == entry2.get_text(): + for widget_name in ('gramps1', 'gramps2', 'gramps_btn1', + 'gramps_btn2'): + self.get_widget(widget_name).set_sensitive(False) + + # Main window widgets that determine which handle survives + rbutton1 = self.get_widget("handle_btn1") + rbutton_label1 = self.get_widget("label_handle_btn1") + rbutton_label2 = self.get_widget("label_handle_btn2") + rbutton_label1.set_label(title1 + " [" + gramps1 + "]") + rbutton_label2.set_label(title2 + " [" + gramps2 + "]") + rbutton1.connect("toggled", self.on_handle1_toggled) + + self.connect_button('source_help', self.cb_help) + self.connect_button('source_ok', self.cb_merge) + self.connect_button('source_cancel', self.close) + self.show() + + def on_handle1_toggled(self, obj): + """first chosen source changes""" + if obj.get_active(): + self.get_widget("title_btn1").set_active(True) + self.get_widget("author_btn1").set_active(True) + self.get_widget("abbrev_btn1").set_active(True) + self.get_widget("pub_btn1").set_active(True) + self.get_widget("gramps_btn1").set_active(True) + else: + self.get_widget("title_btn2").set_active(True) + self.get_widget("author_btn2").set_active(True) + self.get_widget("abbrev_btn2").set_active(True) + self.get_widget("pub_btn2").set_active(True) + self.get_widget("gramps_btn2").set_active(True) + + def cb_help(self, obj): + """Display the relevant portion of Gramps manual""" + GrampsDisplay.help(webpage = WIKI_HELP_PAGE, section = WIKI_HELP_SEC) + + def cb_merge(self, obj): + """ + Performs the merge of the sources when the merge button is clicked. + """ + self.uistate.set_busy_cursor(True) + use_handle1 = self.get_widget("handle_btn1").get_active() + if use_handle1: + phoenix = self.src1 + titanic = self.src2 + unselect_path = (1,) + else: + phoenix = self.src2 + titanic = self.src1 + unselect_path = (0,) + + if self.get_widget("title_btn1").get_active() ^ use_handle1: + phoenix.set_title(titanic.get_title()) + if self.get_widget("author_btn1").get_active() ^ use_handle1: + phoenix.set_author(titanic.get_author()) + if self.get_widget("abbrev_btn1").get_active() ^ use_handle1: + phoenix.set_abbreviation(titanic.get_abbreviation()) + if self.get_widget("pub_btn1").get_active() ^ use_handle1: + phoenix.set_publication_info(titanic.get_publication_info()) + if self.get_widget("gramps_btn1").get_active() ^ use_handle1: + phoenix.set_gramps_id(titanic.get_gramps_id()) + + query = MergeSourceQuery(self.dbstate, phoenix, titanic) + query.execute() + self.uistate.viewmanager.active_page.selection.unselect_path( + unselect_path) + self.uistate.set_busy_cursor(False) + self.close() + +class MergeSourceQuery(object): + """ + Create database query to merge two sources. + """ + def __init__(self, dbstate, phoenix, titanic): + self.database = dbstate.db + self.phoenix = phoenix + self.titanic = titanic + + def execute(self): + """ + Merges to sources into a single source. + """ + new_handle = self.phoenix.get_handle() + old_handle = self.titanic.get_handle() + + self.phoenix.merge(self.titanic) + + trans = self.database.transaction_begin() + for person in self.database.iter_people(): + if person.has_source_reference(old_handle): + person.replace_source_references(old_handle, new_handle) + self.database.commit_person(person, trans) + + for family in self.database.iter_families(): + if family.has_source_reference(old_handle): + family.replace_source_references(old_handle, new_handle) + self.database.commit_family(family, trans) + + for event in self.database.iter_events(): + if event.has_source_reference(old_handle): + event.replace_source_references(old_handle, new_handle) + self.database.commit_event(event, trans) + + for source in self.database.iter_sources(): + if source.has_source_reference(old_handle): + source.replace_source_references(old_handle, new_handle) + self.database.commit_source(source, trans) + + for place in self.database.iter_places(): + if place.has_source_reference(old_handle): + place.replace_source_references(old_handle, new_handle) + self.database.commit_place(place, trans) + + for obj in self.database.iter_media_objects(): + if obj.has_source_reference(old_handle): + obj.replace_source_references(old_handle, new_handle) + self.database.commit_media_object(obj, trans) + + for repo in self.database.iter_repositories(): + if repo.has_source_reference(old_handle): + repo.replace_source_references(old_handle, new_handle) + self.database.commit_repository(repo, trans) + + self.database.remove_source(old_handle, trans) + self.database.commit_source(self.phoenix, trans) + self.database.transaction_commit(trans, _("Merge Sources")) diff --git a/src/gen/db/write.py b/src/gen/db/write.py index 71ff4fc3c..bdb843338 100644 --- a/src/gen/db/write.py +++ b/src/gen/db/write.py @@ -1322,7 +1322,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback): except: pass finally: - cursor.close() + if 'cursor' in locals(): + cursor.close() def commit_base(self, obj, data_map, key, transaction, change_time): """ diff --git a/src/gen/lib/address.py b/src/gen/lib/address.py index ec2604643..6b6d9898b 100644 --- a/src/gen/lib/address.py +++ b/src/gen/lib/address.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -35,6 +36,7 @@ from gen.lib.srcbase import SourceBase from gen.lib.notebase import NoteBase from gen.lib.datebase import DateBase from gen.lib.locationbase import LocationBase +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -124,3 +126,36 @@ class Address(SecondaryObject, PrivacyBase, SourceBase, NoteBase, DateBase, :rtype: list """ return self.get_referenced_note_handles() + + def is_equivalent(self, other): + """ + Return if this address is equivalent, that is agrees in location and + date, to other. + + :param other: The address to compare this one to. + :rtype other: Address + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.get_text_data_list() != other.get_text_data_list() or \ + self.get_date_object() != other.get_date_object(): + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this address. + + Lost: date, street, city, county, state, country, postal and phone of + acquisition. + + :param acquisition: The address to merge with the present address. + :rtype acquisition: Address + """ + self._merge_privacy(acquisition) + self._merge_note_list(acquisition) + self._merge_source_reference_list(acquisition) diff --git a/src/gen/lib/addressbase.py b/src/gen/lib/addressbase.py index 48cd5d8be..5d5fd9088 100644 --- a/src/gen/lib/addressbase.py +++ b/src/gen/lib/addressbase.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,6 +31,7 @@ AddressBase class for GRAMPS. # #------------------------------------------------------------------------- from gen.lib.address import Address +from gen.lib.const import IDENTICAL, EQUAL #------------------------------------------------------------------------- # @@ -111,3 +113,23 @@ class AddressBase(object): :type address_list: list """ self.address_list = address_list + + def _merge_address_list(self, acquisition): + """ + Merge the list of addresses from acquisition with our own. + + :param acquisition: the address list of this object will be merged with + the current address list. + :rtype acquisition: AddressBase + """ + address_list = self.address_list[:] + for addendum in acquisition.get_address_list(): + for address in address_list: + equi = address.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + address.merge(addendum) + break + else: + self.address_list.append(addendum) diff --git a/src/gen/lib/attrbase.py b/src/gen/lib/attrbase.py index c7cb06a67..8d95a4e70 100644 --- a/src/gen/lib/attrbase.py +++ b/src/gen/lib/attrbase.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,6 +31,7 @@ AttributeBase class for GRAMPS. # #------------------------------------------------------------------------- from gen.lib.attribute import Attribute +from gen.lib.const import IDENTICAL, EQUAL #------------------------------------------------------------------------- # @@ -117,3 +119,23 @@ class AttributeBase(object): :type attribute_list: list """ self.attribute_list = attribute_list + + def _merge_attribute_list(self, acquisition): + """ + Merge the list of attributes from acquisition with our own. + + :param acquisition: the attribute list of this object will be merged + with the current attribute list. + :rtype acquisition: AttributeBase + """ + attr_list = self.attribute_list[:] + for addendum in acquisition.get_attribute_list(): + for attr in attr_list: + equi = attr.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + attr.merge(addendum) + break + else: + self.attribute_list.append(addendum) diff --git a/src/gen/lib/attribute.py b/src/gen/lib/attribute.py index 31371725a..723d342c3 100644 --- a/src/gen/lib/attribute.py +++ b/src/gen/lib/attribute.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,6 +35,7 @@ from gen.lib.privacybase import PrivacyBase from gen.lib.srcbase import SourceBase from gen.lib.notebase import NoteBase from gen.lib.attrtype import AttributeType +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -139,6 +141,37 @@ class Attribute(SecondaryObject, PrivacyBase, SourceBase, NoteBase): """ return self.get_referenced_note_handles() + def is_equivalent(self, other): + """ + Return if this attribute is equivalent, that is agrees in type and + value, to other. + + :param other: The attribute to compare this one to. + :rtype other: Attribute + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.type != other.type or self.value != other.value: + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this attribute. + + Lost: type and value of acquisition. + + :param acquisition: the attribute to merge with the present attribute. + :rtype acquisition: Attribute + """ + self._merge_privacy(acquisition) + self._merge_source_reference_list(acquisition) + self._merge_note_list(acquisition) + def set_type(self, val): """Set the type (or key) of the Attribute instance.""" self.type.set(val) diff --git a/src/gen/lib/baseobj.py b/src/gen/lib/baseobj.py index a3c093a62..0d6fa25da 100644 --- a/src/gen/lib/baseobj.py +++ b/src/gen/lib/baseobj.py @@ -171,3 +171,18 @@ class BaseObject(object): for obj in self.get_handle_referents(): ret += obj.get_referenced_handles_recursively() return ret + + def merge(self, acquisition): + """ + Merge content of this object with that of acquisition. + + There are two sides to merger. First, the content of acquisition needs + to be incorporated. Second, handles that reference acquisition (if + there are any) need to be updated. Only the first part is handled in + gen.lib, the second part needs access to the database and should be + done in its own routines. + + :param acquisition: The object to incorporate. + :type acquisition: BaseObject + """ + pass diff --git a/src/gen/lib/childref.py b/src/gen/lib/childref.py index 4b563a7a9..d2a8688a2 100644 --- a/src/gen/lib/childref.py +++ b/src/gen/lib/childref.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2006-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,6 +35,7 @@ from gen.lib.srcbase import SourceBase from gen.lib.notebase import NoteBase from gen.lib.refbase import RefBase from gen.lib.childreftype import ChildRefType +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -138,6 +140,42 @@ class ChildRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase): """ return self.source_list + def is_equivalent(self, other): + """ + Return if this child reference is equivalent, that is agrees in hlink, + to other. + + :param other: The childref to compare this one to. + :rtype other: ChildRef + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.ref != other.ref: + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this child reference. + + Lost: hlink, mrel and frel of acquisition. + + :param acquisition: The childref to merge with the present childref. + :rtype acquisition: ChildRef + """ + self._merge_privacy(acquisition) + self._merge_note_list(acquisition) + self._merge_source_reference_list(acquisition) + if (self.mrel != acquisition.mrel) or (self.frel != acquisition.frel): + if self.mrel == ChildRefType.UNKNOWN: + self.set_mother_relation(acquisition.mrel) + if self.frel == ChildRefType.UNKNOWN: + self.set_father_relation(acquisition.frel) + def set_mother_relation(self, rel): """Set relation between the person and mother.""" self.mrel.set(rel) diff --git a/src/gen/lib/const.py b/src/gen/lib/const.py new file mode 100644 index 000000000..df8b61f5f --- /dev/null +++ b/src/gen/lib/const.py @@ -0,0 +1,29 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Michiel D. Nauta +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Constants +""" + +DIFFERENT = 0 +EQUAL = 1 +IDENTICAL = 2 diff --git a/src/gen/lib/event.py b/src/gen/lib/event.py index c829b685a..da367f254 100644 --- a/src/gen/lib/event.py +++ b/src/gen/lib/event.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -278,7 +279,22 @@ class Event(SourceBase, NoteBase, MediaBase, AttributeBase, index += 1 return True - + + def merge(self, acquisition): + """ + Merge the content of acquisition into this event. + + Lost: handle, id, marker, type, date, place, description of acquisition. + + :param acquisition: The event to merge with the present event. + :rtype acquisition: Event + """ + self._merge_privacy(acquisition) + self._merge_attribute_list(acquisition) + self._merge_note_list(acquisition) + self._merge_source_reference_list(acquisition) + self._merge_media_list(acquisition) + def set_type(self, the_type): """ Set the type of the Event to the passed (int,str) tuple. diff --git a/src/gen/lib/eventref.py b/src/gen/lib/eventref.py index b45b69df3..e3dec30c8 100644 --- a/src/gen/lib/eventref.py +++ b/src/gen/lib/eventref.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -35,6 +36,7 @@ from gen.lib.notebase import NoteBase from gen.lib.attrbase import AttributeBase from gen.lib.refbase import RefBase from gen.lib.eventroletype import EventRoleType +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -180,7 +182,7 @@ class EventRef(SecondaryObject, PrivacyBase, NoteBase, AttributeBase, RefBase): def replace_source_references(self, old_handle, new_handle): """ Replace references to source handles in the list in this object and - all child objects. + all child objects and merge equivalent entries. :param old_handle: The source handle to be replaced. :type old_handle: str @@ -190,6 +192,37 @@ class EventRef(SecondaryObject, PrivacyBase, NoteBase, AttributeBase, RefBase): for item in self.get_sourcref_child_list(): item.replace_source_references(old_handle, new_handle) + def is_equivalent(self, other): + """ + Return if this eventref is equivalent, that is agrees in handle and + role, to other. + + :param other: The eventref to compare this one to. + :rtype other: EventRef + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.ref != other.ref or self.role != other.role: + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this eventref. + + Lost: hlink and role of acquisition. + + :param acquisition: The eventref to merge with the present eventref. + :param acquisition: EventRef + """ + self._merge_privacy(acquisition) + self._merge_attribute_list(acquisition) + self._merge_note_list(acquisition) + def get_role(self): """ Return the tuple corresponding to the preset role. diff --git a/src/gen/lib/family.py b/src/gen/lib/family.py index 464ce3486..28952643a 100644 --- a/src/gen/lib/family.py +++ b/src/gen/lib/family.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -46,6 +47,7 @@ from gen.lib.ldsordbase import LdsOrdBase from gen.lib.childref import ChildRef from gen.lib.familyreltype import FamilyRelType from gen.lib.markertype import MarkerType +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -205,17 +207,41 @@ class Family(SourceBase, NoteBase, MediaBase, AttributeBase, LdsOrdBase, :type new_handle: str """ if classname == 'Event': - handle_list = [ref.ref for ref in self.event_ref_list] - while old_handle in handle_list: - ix = handle_list.index(old_handle) - self.event_ref_list[ix].ref = new_handle - handle_list[ix] = '' + refs_list = [ ref.ref for ref in self.event_ref_list ] + new_ref = None + if new_handle in refs_list: + new_ref = self.event_ref_list[refs_list.index(new_handle)] + n_replace = refs_list.count(old_handle) + for ix_replace in xrange(n_replace): + idx = refs_list.index(old_handle) + self.event_ref_list[idx].ref = new_handle + refs_list[idx] = new_handle + if new_ref: + evt_ref = self.event_ref_list[idx] + equi = new_ref.is_equivalent(evt_ref) + if equi != DIFFERENT: + if equi == EQUAL: + new_ref.merge(evt_ref) + self.event_ref_list.pop(idx) + refs_list.pop(idx) elif classname == 'Person': - handle_list = [ref.ref for ref in self.child_ref_list] - while old_handle in handle_list: - ix = handle_list.index(old_handle) - self.child_ref_list[ix].ref = new_handle - handle_list[ix] = '' + refs_list = [ ref.ref for ref in self.child_ref_list ] + new_ref = None + if new_handle in refs_list: + new_ref = self.child_ref_list[refs_list.index(new_handle)] + n_replace = refs_list.count(old_handle) + for ix_replace in xrange(n_replace): + idx = refs_list.index(old_handle) + self.child_ref_list[idx].ref = new_handle + refs_list[idx] = new_handle + if new_ref: + child_ref = self.child_ref_list[idx] + equi = new_ref.is_equivalent(child_ref) + if equi != DIFFERENT: + if equi == EQUAL: + new_ref.merge(child_ref) + self.child_ref_list.pop(idx) + refs_list.pop(idx) if self.father_handle == old_handle: self.father_handle = new_handle if self.mother_handle == old_handle: @@ -296,6 +322,26 @@ class Family(SourceBase, NoteBase, MediaBase, AttributeBase, LdsOrdBase, """ return self.get_sourcref_child_list() + self.source_list + def merge(self, acquisition): + """ + Merge the content of acquisition into this family. + + Lost: handle, id, marker, relation, father, mother of acquisition. + + :param acquisition: The family to merge with the present family. + :rtype acquisition: Family + """ + if self.type != acquisition.type and self.type == FamilyRelType.UNKNOWN: + self.set_relationship(acquisition.get_relationship()) + self._merge_privacy(acquisition) + self._merge_event_ref_list(acquisition) + self._merge_lds_ord_list(acquisition) + self._merge_media_list(acquisition) + self._merge_child_ref_list(acquisition) + self._merge_attribute_list(acquisition) + self._merge_note_list(acquisition) + self._merge_source_reference_list(acquisition) + def set_relationship(self, relationship_type): """ Set the relationship type between the people identified as the @@ -441,6 +487,26 @@ class Family(SourceBase, NoteBase, MediaBase, AttributeBase, LdsOrdBase, """ self.child_ref_list = child_ref_list + def _merge_child_ref_list(self, acquisition): + """ + Merge the list of child references from acquisition with our own. + + :param acquisition: the childref list of this family will be merged + with the current childref list. + :rtype acquisition: Family + """ + childref_list = self.child_ref_list[:] + for addendum in acquisition.get_child_ref_list(): + for childref in childref_list: + equi = childref.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + childref.merge(addendum) + break + else: + self.child_ref_list.append(addendum) + def add_event_ref(self, event_ref): """ Add the :class:`~gen.lib.eventref.EventRef` to the Family instance's :class:`~gen.lib.eventref.EventRef` list. @@ -485,3 +551,24 @@ class Family(SourceBase, NoteBase, MediaBase, AttributeBase, LdsOrdBase, :type event_ref_list: list """ self.event_ref_list = event_ref_list + + def _merge_event_ref_list(self, acquisition): + """ + Merge the list of event references from acquisition with our own. + + :param acquisition: the event references list of this object will be + merged with the current event references list. + :rtype acquisition: Person + """ + eventref_list = self.event_ref_list[:] + for addendum in acquisition.get_event_ref_list(): + for eventref in eventref_list: + equi = eventref.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + eventref.merge(addendum) + break + else: + self.event_ref_list.append(addendum) + diff --git a/src/gen/lib/ldsord.py b/src/gen/lib/ldsord.py index 7d045476b..78dcade69 100644 --- a/src/gen/lib/ldsord.py +++ b/src/gen/lib/ldsord.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -43,6 +44,7 @@ from gen.lib.notebase import NoteBase from gen.lib.datebase import DateBase from gen.lib.placebase import PlaceBase from gen.lib.privacybase import PrivacyBase +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -204,6 +206,41 @@ class LdsOrd(SecondaryObject, SourceBase, NoteBase, """ return self.source_list + def is_equivalent(self, other): + """ + Return if this ldsord is equivalent, that is agrees in date, temple, + place, status, sealed_to, to other. + + :param other: The ldsord to compare this one to. + :rtype other: LdsOrd + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.type != other.type or \ + self.get_date_object() != other.get_date_object() or \ + self.temple != other.temple or \ + self.status != other.status or \ + self.famc != other.famc: + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this ldsord. + + Lost: type, date, temple, place, status, sealed_to of acquistion. + + :param acquisition: The ldsord to merge with the present ldsord. + :rtype acquisition: LdsOrd + """ + self._merge_privacy(acquisition) + self._merge_note_list(acquisition) + self._merge_source_reference_list(acquisition) + def get_type(self): """ Return the type of the Event. diff --git a/src/gen/lib/ldsordbase.py b/src/gen/lib/ldsordbase.py index d56e7e8b0..cd6c9d7cc 100644 --- a/src/gen/lib/ldsordbase.py +++ b/src/gen/lib/ldsordbase.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,6 +31,7 @@ LdsOrdBase class for GRAMPS. # #------------------------------------------------------------------------- from gen.lib.ldsord import LdsOrd +from gen.lib.const import IDENTICAL, EQUAL #------------------------------------------------------------------------- # @@ -115,3 +117,23 @@ class LdsOrdBase(object): :type lds_ord_list: list """ self.lds_ord_list = lds_ord_list + + def _merge_lds_ord_list(self, acquisition): + """ + Merge the list of ldsord from acquisition with our own. + + :param acquisition: the ldsord list of this object will be merged with + the current ldsord list. + :rtype acquisition: LdsOrdBase + """ + ldsord_list = self.lds_ord_list[:] + for addendum in acquisition.get_lds_ord_list(): + for ldsord in ldsord_list: + equi = ldsord.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + ldsord.merge(addendum) + break + else: + self.lds_ord_list.append(addendum) diff --git a/src/gen/lib/location.py b/src/gen/lib/location.py index 9f697a65d..06d855a71 100644 --- a/src/gen/lib/location.py +++ b/src/gen/lib/location.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -31,6 +32,7 @@ Location class for GRAMPS. #------------------------------------------------------------------------- from gen.lib.secondaryobj import SecondaryObject from gen.lib.locationbase import LocationBase +from gen.lib.const import IDENTICAL, DIFFERENT #------------------------------------------------------------------------- # @@ -79,6 +81,31 @@ class Location(SecondaryObject, LocationBase): """ return [self.parish] + LocationBase.get_text_data_list(self) + def is_equivalent(self, other): + """ + Return if this location is equivalent to other. + + :param other: The location to compare this one to. + :rtype other: Location + :returns: Constant inidicating degree of equivalence. + :rtype: int + """ + if self.is_equal(other): + return IDENTICAL + else: + return DIFFERENT + + def merge(self, acquisition): + """ + Merge the content of acquisition into this location. + + Lost: everything of acquisition. + + :param acquisition: The location to merge with the present location. + :rtype acquisition: Location + """ + pass + def is_empty(self): return not self.city and not self.county and not self.state and \ not self.country and not self.postal and not self.phone diff --git a/src/gen/lib/mediabase.py b/src/gen/lib/mediabase.py index e85c1a94c..c4153c817 100644 --- a/src/gen/lib/mediabase.py +++ b/src/gen/lib/mediabase.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2006 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,6 +31,7 @@ MediaBase class for GRAMPS. # #------------------------------------------------------------------------- from gen.lib.mediaref import MediaRef +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -92,6 +94,26 @@ class MediaBase(object): """ self.media_list = media_ref_list + def _merge_media_list(self, acquisition): + """ + Merge the list of media references from acquisition with our own. + + :param acquisition: the media list of this object will be merged with + the current media reference list. + :rtype acquisition: MediaBase + """ + media_list = self.media_list[:] + for addendum in acquisition.get_media_list(): + for obj in media_list: + equi = obj.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + obj.merge(addendum) + break + else: + self.media_list.append(addendum) + def has_media_reference(self, obj_handle) : """ Return True if the object or any of it's child objects has reference @@ -118,7 +140,8 @@ class MediaBase(object): def replace_media_references(self, old_handle, new_handle): """ - Replace all references to old media handle with the new handle. + Replace all references to old media handle with the new handle and + merge equivalent entries. :param old_handle: The media handle to be replaced. :type old_handle: str @@ -126,8 +149,19 @@ class MediaBase(object): :type new_handle: str """ refs_list = [ media_ref.ref for media_ref in self.media_list ] + new_ref = None + if new_handle in refs_list: + new_ref = self.media_list[refs_list.index(new_handle)] n_replace = refs_list.count(old_handle) for ix_replace in xrange(n_replace): - ix = refs_list.index(old_handle) - self.media_list[ix].ref = new_handle - refs_list[ix] = new_handle + idx = refs_list.index(old_handle) + self.media_list[idx].ref = new_handle + refs_list[idx] = new_handle + if new_ref: + media_ref = self.media_list[idx] + equi = new_ref.is_equivalent(media_ref) + if equi != DIFFERENT: + if equi == EQUAL: + new_ref.merge(media_ref) + self.media_list.pop(idx) + refs_list.pop(idx) diff --git a/src/gen/lib/mediaobj.py b/src/gen/lib/mediaobj.py index 5ffeb9ef7..cda875e76 100644 --- a/src/gen/lib/mediaobj.py +++ b/src/gen/lib/mediaobj.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -186,6 +187,20 @@ class MediaObject(SourceBase, NoteBase, DateBase, AttributeBase, """ return self.attribute_list + self.source_list + def merge(self, acquisition): + """ + Merge the content of acquisition into this media object. + + Lost: handle, id, marker, file, date of acquisition. + + :param acquisition: The media object to merge with the present object. + :rtype acquisition: MediaObject + """ + self._merge_privacy(acquisition) + self._merge_attribute_list(acquisition) + self._merge_note_list(acquisition) + self._merge_source_reference_list(acquisition) + def set_mime_type(self, mime_type): """ Set the MIME type associated with the MediaObject. diff --git a/src/gen/lib/mediaref.py b/src/gen/lib/mediaref.py index 12353fcb2..428e5505c 100644 --- a/src/gen/lib/mediaref.py +++ b/src/gen/lib/mediaref.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -35,6 +36,7 @@ from gen.lib.srcbase import SourceBase from gen.lib.notebase import NoteBase from gen.lib.refbase import RefBase from gen.lib.attrbase import AttributeBase +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -131,6 +133,38 @@ class MediaRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase, """ return self.attribute_list + self.source_list + def is_equivalent(self, other): + """ + Return if this object reference is equivalent, that is agrees in + reference and region, to other. + + :param other: The object reference to compare this one to. + :rtype other: MediaRef + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.ref != other.ref or self.rect != other.rect: + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this object reference. + + Lost: hlink and region or acquisition. + + :param acquisition: The object reference to merge with the present one. + :rtype acquisition: MediaRef + """ + self._merge_privacy(acquisition) + self._merge_attribute_list(acquisition) + self._merge_source_reference_list(acquisition) + self._merge_note_list(acquisition) + def set_rectangle(self, coord): """Set subsection of an image.""" self.rect = coord diff --git a/src/gen/lib/name.py b/src/gen/lib/name.py index 5f52b0d42..e138adf7c 100644 --- a/src/gen/lib/name.py +++ b/src/gen/lib/name.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -35,6 +36,7 @@ from gen.lib.srcbase import SourceBase from gen.lib.notebase import NoteBase from gen.lib.datebase import DateBase from gen.lib.nametype import NameType +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -185,6 +187,41 @@ class Name(SecondaryObject, PrivacyBase, SourceBase, NoteBase, DateBase): """ return self.get_referenced_note_handles() + def is_equivalent(self, other): + """ + Return if this name is equivalent, that is agrees in type, first + call, last, suffix, patronymic, title and date, to other. + + :param other: The name to compare this name to. + :rtype other: Name + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + # TODO what to do with sort and display? + if self.get_text_data_list() != other.get_text_data_list() or \ + self.get_date_object() != other.get_date_object(): + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this name. + + Lost: type, first, call, last, suffix, patronymic, title and date of + acquisition. + + :param acquisition: The name to merge with the present name. + :rtype acquisition: Name + """ + # TODO what to do with sort and display? + self._merge_privacy(acquisition) + self._merge_note_list(acquisition) + self._merge_source_reference_list(acquisition) + def set_group_as(self, name): """ Set the grouping name for a person. diff --git a/src/gen/lib/note.py b/src/gen/lib/note.py index 2c457a82b..4bc86da12 100644 --- a/src/gen/lib/note.py +++ b/src/gen/lib/note.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -120,6 +121,17 @@ class Note(BasicPrimaryObject): """ return [str(self.text)] + def merge(self, acquisition): + """ + Merge the content of acquisition into this note. + + Lost: handle, id, marker, type, format, text and tags of acquisition. + + :param acquisition: The note to merge with the present note. + :rtype acquisition: Note + """ + self._merge_privacy(acquisition) + def set(self, text): """Set the text associated with the note to the passed string. diff --git a/src/gen/lib/notebase.py b/src/gen/lib/notebase.py index 06c6c5f87..1e01763f9 100644 --- a/src/gen/lib/notebase.py +++ b/src/gen/lib/notebase.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -110,6 +111,27 @@ class NoteBase(object): """ return self.note_list + def has_note_reference(self, note_handle): + """ + Return True if the object or any of its child objects has reference + to this note handle. + + :param note_handle: The note handle to be checked. + :type note_handle: str + :returns: Returns whether the object or any of its child objects has + reference to this note handle. + :rtype: bool + """ + for note_ref in self.note_list: + if note_ref == note_handle: + return True + + for item in self.get_note_child_list(): + if item.has_note_reference(note_handle): + return True + + return False + def set_note_list(self, note_list): """ Assign the passed list to be object's list of :class:`~gen.lib.note.Note` handles. @@ -119,6 +141,17 @@ class NoteBase(object): """ self.note_list = note_list + def _merge_note_list(self, acquisition): + """ + Merge the list of notes from acquisition with our own. + + :param acquisition: The note list of this object will be merged with + the current note list. + :rtype acquisition: NoteBase + """ + for addendum in acquisition.note_list: + self.add_note(addendum) + def get_referenced_note_handles(self): """ Return the list of (classname, handle) tuples for all referenced notes. @@ -130,3 +163,29 @@ class NoteBase(object): :rtype: list """ return [('Note', handle) for handle in self.note_list] + + def replace_note_references(self, old_handle, new_handle): + """ + Replace references to note handles in the list of this object and + all child objects and merge equivalent entries. + + :param old_handle: The note handle to be replaced. + :type old_handle: str + :param new_handle: The note handle to replace the old one with. + :type new_handle: str + """ + refs_list = self.note_list[:] + new_ref = None + if new_handle in self.note_list: + new_ref = new_handle + n_replace = refs_list.count(old_handle) + for ix_replace in xrange(n_replace): + idx = refs_list.index(old_handle) + if new_ref: + self.note_list.pop(idx) + refs_list.pop(idx) + else: + self.note_list[idx] = new_handle + + for item in self.get_note_child_list(): + item.replace_note_references(old_handle, new_handle) diff --git a/src/gen/lib/person.py b/src/gen/lib/person.py index b357c6c18..301367ab9 100644 --- a/src/gen/lib/person.py +++ b/src/gen/lib/person.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -43,6 +44,8 @@ from gen.lib.personref import PersonRef from gen.lib.attrtype import AttributeType from gen.lib.eventroletype import EventRoleType from gen.lib.markertype import MarkerType +from gen.lib.attribute import Attribute +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -107,6 +110,9 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase, def __eq__(self, other): return isinstance(other, Person) and self.handle == other.handle + + def __ne__(self, other): + return not self == other def serialize(self): """ @@ -269,17 +275,53 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase, def _replace_handle_reference(self, classname, old_handle, new_handle): if classname == 'Event': - handle_list = [ref.ref for ref in self.event_ref_list] - while old_handle in handle_list: - ix = handle_list.index(old_handle) - self.event_ref_list[ix].ref = new_handle - handle_list[ix] = '' + refs_list = [ ref.ref for ref in self.event_ref_list ] + new_ref = None + if new_handle in refs_list: + new_ref = self.event_ref_list[refs_list.index(new_handle)] + n_replace = refs_list.count(old_handle) + for ix_replace in xrange(n_replace): + idx = refs_list.index(old_handle) + self.event_ref_list[idx].ref = new_handle + refs_list[idx] = new_handle + if new_ref: + evt_ref = self.event_ref_list[idx] + equi = new_ref.is_equivalent(evt_ref) + if equi != DIFFERENT: + if equi == EQUAL: + new_ref.merge(evt_ref) + self.event_ref_list.pop(idx) + refs_list.pop(idx) + if idx < self.birth_ref_index: + self.birth_ref_index -= 1 + elif idx == self.birth_ref_index: + self.birth_ref_index = -1 + # birth_ref_index should be recalculated which + # needs database access! + if idx < self.death_ref_index: + self.death_ref_index -= 1 + elif idx == self.death_ref_index: + self.death_ref_index = -1 + # death_ref_index should be recalculated which + # needs database access! elif classname == 'Person': - handle_list = [ref.ref for ref in self.person_ref_list] - while old_handle in handle_list: - ix = handle_list.index(old_handle) - self.person_ref_list[ix].ref = new_handle - handle_list[ix] = '' + refs_list = [ ref.ref for ref in self.person_ref_list ] + new_ref = None + if new_handle in refs_list: + new_ref = self.person_ref_list[refs_list.index(new_handle)] + n_replace = refs_list.count(old_handle) + for ix_replace in xrange(n_replace): + idx = refs_list.index(old_handle) + self.person_ref_list[idx].ref = new_handle + refs_list[idx] = new_handle + if new_ref: + person_ref = self.person_ref_list[idx] + equi = new_ref.is_equivalent(person_ref) + if equi != DIFFERENT: + if equi == EQUAL: + new_ref.merge(person_ref) + self.person_ref_list.pop(idx) + refs_list.pop(idx) elif classname == 'Family': while old_handle in self.family_list: ix = self.family_list.index(old_handle) @@ -369,6 +411,37 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase, #don't count double, notes can be found in sourcref return self.get_sourcref_child_list() + self.source_list + def merge(self, acquisition): + """ + Merge the content of acquisition into this person. + + :param acquisition: The person to merge with the present person. + :rtype acquisition: Person + """ + acquisition_id = acquisition.get_gramps_id() + if acquisition_id: + attr = Attribute() + attr.set_type("Merged Gramps ID") + attr.set_value(acquisition.get_gramps_id()) + self.add_attribute(attr) + + self._merge_privacy(acquisition) + acquisition.alternate_names.insert(0, acquisition.get_primary_name()) + self._merge_alternate_names(acquisition) + self._merge_event_ref_list(acquisition) + self._merge_lds_ord_list(acquisition) + self._merge_media_list(acquisition) + self._merge_address_list(acquisition) + self._merge_attribute_list(acquisition) + self._merge_url_list(acquisition) + self._merge_person_ref_list(acquisition) + self._merge_note_list(acquisition) + self._merge_source_reference_list(acquisition) + + map(self.add_parent_family_handle, + acquisition.get_parent_family_handle_list()) + map(self.add_family_handle, acquisition.get_family_handle_list()) + def set_primary_name(self, name): """ Set the primary name of the Person to the specified :class:`~gen.lib.name.Name` instance. @@ -405,6 +478,29 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase, """ self.alternate_names = alt_name_list + def _merge_alternate_names(self, acquisition): + """ + Merge the list of alternate names from acquisition with our own. + + :param acquisition: the list of alternate names of this object will be + merged with the current alternate name list. + :rtype acquisition: Person + """ + name_list = self.alternate_names[:] + primary_name = self.get_primary_name() + if primary_name and not primary_name.is_empty(): + name_list.insert(0, primary_name) + for addendum in acquisition.get_alternate_names(): + for name in name_list: + equi = name.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + name.merge(addendum) + break + else: + self.alternate_names.append(addendum) + def add_alternate_name(self, name): """ Add a :class:`~gen.lib.name.Name` instance to the list of alternative names. @@ -579,6 +675,32 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase, """ self.event_ref_list = event_ref_list + def _merge_event_ref_list(self, acquisition): + """ + Merge the list of event references from acquisition with our own. + + :param acquisition: the event references list of this object will be + merged with the current event references list. + :rtype acquisition: Person + """ + eventref_list = self.event_ref_list[:] + for idx, addendum in enumerate(acquisition.get_event_ref_list()): + for eventref in eventref_list: + equi = eventref.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + eventref.merge(addendum) + break + else: + self.event_ref_list.append(addendum) + if self.birth_ref_index == -1 and \ + idx == acquisition.birth_ref_index: + self.birth_ref_index = len(self.event_ref_list) - 1 + if self.death_ref_index == -1 and \ + idx == acquisition.death_ref_index: + self.death_ref_index = len(self.event_ref_list) - 1 + def add_family_handle(self, family_handle): """ Add the :class:`~gen.lib.family.Family` handle to the Person instance's :class:`~gen.lib.family.Family` list. @@ -807,3 +929,23 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase, :type person_ref_list: list """ self.person_ref_list = person_ref_list + + def _merge_person_ref_list(self, acquisition): + """ + Merge the list of person references from acquisition with our own. + + :param acquisition: the list of person references of this person will b + merged with the current person references list. + :rtype acquisition: Person + """ + personref_list = self.person_ref_list[:] + for addendum in acquisition.get_person_ref_list(): + for personref in personref_list: + equi = personref.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + personref.merge(addendum) + break + else: + self.person_ref_list.append(addendum) diff --git a/src/gen/lib/personref.py b/src/gen/lib/personref.py index 042dbc175..7fcda1d02 100644 --- a/src/gen/lib/personref.py +++ b/src/gen/lib/personref.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2006-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,6 +35,7 @@ from gen.lib.privacybase import PrivacyBase from gen.lib.srcbase import SourceBase from gen.lib.notebase import NoteBase from gen.lib.refbase import RefBase +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -131,6 +133,38 @@ class PersonRef(SecondaryObject, PrivacyBase, SourceBase, NoteBase, RefBase): """ return self.source_list + def is_equivalent(self, other): + """ + Return if this person reference is equivalent, that is agrees in handle + and relation, to other. + + :param other: The personref to compare this one to. + :rtype other: PersonRef + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.ref != other.ref or \ + self.get_text_data_list() != other.get_text_data_list(): + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this person reference. + + Lost: hlink and relation of acquisition. + + :param acquisition: The personref to merge with the present personref. + :param acquisition: PersonRef + """ + self._merge_privacy(acquisition) + self._merge_source_reference_list(acquisition) + self._merge_note_list(acquisition) + def set_relation(self, rel): """Set relation to a person.""" self.rel = rel diff --git a/src/gen/lib/place.py b/src/gen/lib/place.py index b378f6902..6a26e9d54 100644 --- a/src/gen/lib/place.py +++ b/src/gen/lib/place.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -194,6 +195,19 @@ class Place(SourceBase, NoteBase, MediaBase, UrlBase, PrimaryObject): """ return self.get_referenced_note_handles() + def merge(self, acquisition): + """ Merge the content of acquisition into this place. + + :param acquisition: The place to merge with the present place. + :rtype acquisition: Place + """ + self._merge_privacy(acquisition) + self._merge_locations(acquisition) + self._merge_media_list(acquisition) + self._merge_url_list(acquisition) + self._merge_note_list(acquisition) + self._merge_source_reference_list(acquisition) + def set_title(self, title): """ Set the descriptive title of the Place object. @@ -307,6 +321,28 @@ class Place(SourceBase, NoteBase, MediaBase, UrlBase, PrimaryObject): if location not in self.alt_loc: self.alt_loc.append(location) + def _merge_locations(self, acquisition): + """ + Add the main and alternate locations of acquisition to the alternate + location list. + + :param acquisition: instance to merge + :type acquisition: :class:'~gen.lib.place.Place + """ + altloc_list = self.alt_loc[:] + if self.main_loc and not self.main_loc.is_empty(): + altloc_list.insert(0, self.main_loc) + add_list = acquisition.get_alternate_locations() + acq_main_loc = acquisition.get_main_location() + if acq_main_loc and not acq_main_loc.is_empty(): + add_list.insert(0, acquisition.get_main_location()) + for addendum in add_list: + for altloc in altloc_list: + if altloc.is_equal(addendum): + break + else: + self.alt_loc.append(addendum) + def get_display_info(self): """ Get the display information associated with the object. diff --git a/src/gen/lib/privacybase.py b/src/gen/lib/privacybase.py index 16a3b97ad..3e0b0446d 100644 --- a/src/gen/lib/privacybase.py +++ b/src/gen/lib/privacybase.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2005 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -81,3 +82,12 @@ class PrivacyBase(object): :rtype: bool """ return self.private + + def _merge_privacy(self, other): + """ + Merge the privacy level of this object with that of other. + + :returns: Privacy of merged objects. + :rtype: bool + """ + self.private = self.private or other.private diff --git a/src/gen/lib/repo.py b/src/gen/lib/repo.py index 3028dd205..0da5bb062 100644 --- a/src/gen/lib/repo.py +++ b/src/gen/lib/repo.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -171,7 +172,7 @@ class Repository(NoteBase, AddressBase, UrlBase, PrimaryObject): def replace_source_references(self, old_handle, new_handle): """ Replace references to source handles in the list in this object and - all child objects. + all child objects and merge equivalent entries. :param old_handle: The source handle to be replaced. :type old_handle: str @@ -181,6 +182,18 @@ class Repository(NoteBase, AddressBase, UrlBase, PrimaryObject): for item in self.get_sourcref_child_list(): item.replace_source_references(old_handle, new_handle) + def merge(self, acquisition): + """ + Merge the content of acquisition into this repository. + + :param acquisition: The repository to merge with the present repository. + :rtype acquisition: Repository + """ + self._merge_privacy(acquisition) + self._merge_address_list(acquisition) + self._merge_url_list(acquisition) + self._merge_note_list(acquisition) + def set_type(self, the_type): """ :param the_type: descriptive type of the Repository diff --git a/src/gen/lib/reporef.py b/src/gen/lib/reporef.py index b3a147261..0d8f3066a 100644 --- a/src/gen/lib/reporef.py +++ b/src/gen/lib/reporef.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,6 +35,7 @@ from gen.lib.privacybase import PrivacyBase from gen.lib.notebase import NoteBase from gen.lib.refbase import RefBase from gen.lib.srcmediatype import SourceMediaType +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -101,6 +103,36 @@ class RepoRef(SecondaryObject, PrivacyBase, NoteBase, RefBase): ret += [('Repository', self.ref)] return ret + def is_equivalent(self, other): + """ + Return if this repository reference is equivalent, that is agrees in + reference, call number and medium, to other. + + :param other: The repository reference to compare this one to. + :rtype other: RepoRef + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.ref != other.ref or \ + self.get_text_data_list() != other.get_text_data_list(): + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this repository reference. + + :param acquisition: The repository reference to merge with the present + repository reference. + :rtype acquisition: RepoRef + """ + self._merge_privacy(acquisition) + self._merge_note_list(acquisition) + def set_call_number(self, number): self.call_number = number diff --git a/src/gen/lib/secondaryobj.py b/src/gen/lib/secondaryobj.py index 74e001eac..ffb32d41b 100644 --- a/src/gen/lib/secondaryobj.py +++ b/src/gen/lib/secondaryobj.py @@ -44,3 +44,11 @@ class SecondaryObject(BaseObject): def is_equal(self, source): return cmp(self.serialize(), source.serialize()) == 0 + + def is_equivalent(self, other): + """ + Return if this object is equivalent to other. + + Should be overwritten by objects that inherit from this class. + """ + pass diff --git a/src/gen/lib/src.py b/src/gen/lib/src.py index 75c64a8c2..b6e9b193f 100644 --- a/src/gen/lib/src.py +++ b/src/gen/lib/src.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -34,6 +35,7 @@ from gen.lib.mediabase import MediaBase from gen.lib.notebase import NoteBase from gen.lib.reporef import RepoRef from gen.lib.markertype import MarkerType +from gen.lib.const import DIFFERENT, EQUAL, IDENTICAL #------------------------------------------------------------------------- # @@ -129,9 +131,9 @@ class Source(MediaBase, NoteBase, PrimaryObject): if classname == 'Repository': handle_list = [ref.ref for ref in self.reporef_list] while old_handle in handle_list: - ix = handle_list.index(old_handle) - self.reporef_list[ix].ref = new_handle - handle_list[ix] = '' + idx = handle_list.index(old_handle) + self.reporef_list[idx].ref = new_handle + handle_list[idx] = '' def get_text_data_list(self): """ @@ -222,8 +224,8 @@ class Source(MediaBase, NoteBase, PrimaryObject): def replace_source_references(self, old_handle, new_handle): """ - Replace references to source handles in the list in this object and - all child objects. + Replace references to source_handles in the list in this object and + all child objects and merge equivalent entries. :param old_handle: The source handle to be replaced. :type old_handle: str @@ -233,6 +235,23 @@ class Source(MediaBase, NoteBase, PrimaryObject): for item in self.get_sourcref_child_list(): item.replace_source_references(old_handle, new_handle) + def merge(self, acquisition): + """ + Merge the content of acquisition into this source. + + :param acquisition: The source to merge with the present source. + :rtype acquisition: Source + """ + self._merge_privacy(acquisition) + self._merge_note_list(acquisition) + self._merge_media_list(acquisition) + my_datamap = self.get_data_map() + acquisition_map = acquisition.get_data_map() + for key in acquisition.get_data_map(): + if key not in my_datamap: + self.datamap[key] = acquisition_map[key] + self._merge_reporef_list(acquisition) + def get_data_map(self): """Return the data map of attributes for the source.""" return self.datamap @@ -317,6 +336,26 @@ class Source(MediaBase, NoteBase, PrimaryObject): """ self.reporef_list = reporef_list + def _merge_reporef_list(self, acquisition): + """ + Merge the list of repository references from acquisition with our own. + + :param acquisition: the repository references list of this object will + be merged with the current repository references list. + :rtype acquisition: RepoRef + """ + reporef_list = self.reporef_list[:] + for addendum in acquisition.get_reporef_list(): + for reporef in reporef_list: + equi = reporef.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + reporef.merge(addendum) + break + else: + self.reporef_list.append(addendum) + def has_repo_reference(self, repo_handle): """ Return True if the Source has reference to this Repository handle. @@ -342,17 +381,29 @@ class Source(MediaBase, NoteBase, PrimaryObject): def replace_repo_references(self, old_handle, new_handle): """ - Replace all references to old Repository handle with the new handle. + Replace all references to old Repository handle with the new handle + and merge equivalent entries. :param old_handle: The Repository handle to be replaced. :type old_handle: str :param new_handle: The Repository handle to replace the old one with. :type new_handle: str + indikken """ refs_list = [ repo_ref.ref for repo_ref in self.reporef_list ] + new_ref = None + if new_handle in refs_list: + new_ref = self.reporef_list[refs_list.index(new_handle)] n_replace = refs_list.count(old_handle) for ix_replace in xrange(n_replace): - ix = refs_list.index(old_handle) - self.reporef_list[ix].ref = new_handle - refs_list.pop(ix) - + idx = refs_list.index(old_handle) + self.reporef_list[idx].ref = new_handle + refs_list[idx] = new_handle + if new_ref: + repo_ref = self.reporef_list[idx] + equi = new_ref.is_equivalent(repo_ref) + if equi != DIFFERENT: + if equi == EQUAL: + new_ref.merge(repo_ref) + self.reporef_list.pop(idx) + refs_list.pop(idx) diff --git a/src/gen/lib/srcbase.py b/src/gen/lib/srcbase.py index 27a88e5c4..33f64a91e 100644 --- a/src/gen/lib/srcbase.py +++ b/src/gen/lib/srcbase.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2006 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -30,6 +31,7 @@ SourceBase class for GRAMPS. # #------------------------------------------------------------------------- from gen.lib.srcref import SourceRef +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -132,8 +134,8 @@ class SourceBase(object): def replace_source_references(self, old_handle, new_handle): """ - Replace references to source handles in the list in this object and - all child objects. + Replace references to source handles in the list in this object and + all child objects and merge equivalent entries. :param old_handle: The source handle to be replaced. :type old_handle: str @@ -141,12 +143,23 @@ class SourceBase(object): :type new_handle: str """ refs_list = [ src_ref.ref for src_ref in self.source_list ] + new_ref = None + if new_handle in refs_list: + new_ref = self.source_list[refs_list.index(new_handle)] n_replace = refs_list.count(old_handle) for ix_replace in xrange(n_replace): - ix = refs_list.index(old_handle) - self.source_list[ix].ref = new_handle - refs_list[ix] = new_handle - + idx = refs_list.index(old_handle) + self.source_list[idx].ref = new_handle + refs_list[idx] = new_handle + if new_ref: + src_ref = self.source_list[idx] + equi = new_ref.is_equivalent(src_ref) + if equi != DIFFERENT: + if equi == EQUAL: + new_ref.merge(src_ref) + self.source_list.pop(idx) + refs_list.pop(idx) + for item in self.get_sourcref_child_list(): item.replace_source_references(old_handle, new_handle) @@ -159,3 +172,23 @@ class SourceBase(object): :type src_ref_list: list of :class:`~gen.lib.srcref.SourceRef` instances """ self.source_list = src_ref_list + + def _merge_source_reference_list(self, acquisition): + """ + Merge the list of source references from acquisition with our own. + + :param acquisition: the source references list of this object will be + merged with the current source references list. + :rtype acquisition: SourceRef + """ + srcref_list = self.source_list[:] + for addendum in acquisition.get_source_references(): + for srcref in srcref_list: + equi = srcref.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + srcref.merge(addendum) + break + else: + self.source_list.append(addendum) diff --git a/src/gen/lib/srcref.py b/src/gen/lib/srcref.py index 60d41918c..a9d8e7256 100644 --- a/src/gen/lib/srcref.py +++ b/src/gen/lib/srcref.py @@ -2,6 +2,7 @@ # Gramps - a GTK+/GNOME based genealogy program # # Copyright (C) 2000-2007 Donald N. Allingham +# Copyright (C) 2010 Michiel D. Nauta # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -41,6 +42,7 @@ from gen.lib.datebase import DateBase from gen.lib.privacybase import PrivacyBase from gen.lib.notebase import NoteBase from gen.lib.refbase import RefBase +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -117,6 +119,41 @@ class SourceRef(SecondaryObject, DateBase, PrivacyBase, NoteBase, RefBase): ret += [('Source', self.ref)] return ret + def is_equivalent(self, other): + """ + Return if this source reference is equivalent, that is agreees in + reference, source page and date, to other. + + :param other: The source reference to compare this one to. + :rtype other: SourceRef + ;returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.ref != other.ref or \ + self.page != other.page or \ + self.get_date_object() != other.get_date_object(): + return DIFFERENT + else: + if self.is_equal(other): + return IDENTICAL + else: + return EQUAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this source reference. + + :param acquisition: The source reference to merge with the present one. + :rtype acquisition: SourceRef + """ + self._merge_privacy(acquisition) + self._merge_note_list(acquisition) + # merge confidence + level_priority = [0, 4, 1, 3, 2] + idx = min(level_priority.index(self.confidence), + level_priority.index(acquisition.confidence)) + self.confidence = level_priority[idx] + def set_confidence_level(self, val): """Set the confidence level.""" self.confidence = val diff --git a/src/gen/lib/styledtext.py b/src/gen/lib/styledtext.py index 8e23f9bfc..63253528d 100644 --- a/src/gen/lib/styledtext.py +++ b/src/gen/lib/styledtext.py @@ -120,6 +120,12 @@ class StyledText(object): return self.__class__("".join([self._string, str(other)]), self._tags) + def __eq__(self, other): + return self._string == other._string and self._tags == other._tags + + def __ne__(self, other): + return self._string != other._string or self._tags != other._tags + # private methods diff --git a/src/gen/lib/url.py b/src/gen/lib/url.py index 47e1d53c0..6714acfcb 100644 --- a/src/gen/lib/url.py +++ b/src/gen/lib/url.py @@ -41,6 +41,7 @@ from urlparse import urlparse from gen.lib.secondaryobj import SecondaryObject from gen.lib.privacybase import PrivacyBase from gen.lib.urltype import UrlType +from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT #------------------------------------------------------------------------- # @@ -82,6 +83,35 @@ class Url(SecondaryObject, PrivacyBase): """ return [self.path, self.desc] + def is_equivalent(self, other): + """ + Return if this url is equivalent, that is agrees in type, full path + name and description, to other. + + :param other: The url to compare this one to. + :rtype other: Url + :returns: Constant indicating degree of equivalence. + :rtype: int + """ + if self.type != other.type or \ + self.get_full_path() != other.get_full_path() or \ + self.desc != other.desc: + return DIFFERENT + else: + if self.get_privacy() != other.get_privacy(): + return EQUAL + else: + return IDENTICAL + + def merge(self, acquisition): + """ + Merge the content of acquisition into this url. + + :param acquisition: The url to merge with the present url. + :rtype acquisition: Url + """ + self._merge_privacy(acquisition) + def set_path(self, path): """Set the URL path.""" self.path = path diff --git a/src/gen/lib/urlbase.py b/src/gen/lib/urlbase.py index 8f5f33181..f7201f1c8 100644 --- a/src/gen/lib/urlbase.py +++ b/src/gen/lib/urlbase.py @@ -30,6 +30,7 @@ UrlBase class for GRAMPS. # #------------------------------------------------------------------------- from gen.lib.url import Url +from gen.lib.const import IDENTICAL, EQUAL #------------------------------------------------------------------------- # @@ -83,6 +84,26 @@ class UrlBase(object): """ self.urls = url_list + def _merge_url_list(self, acquisition): + """ + Merge the list of urls from acquisition with our own. + + :param acquisition: The url list of this object will be merged with + the current url list. + :rtype acquisition: UrlBase + """ + url_list = self.urls[:] + for addendum in acquisition.get_url_list(): + for url in url_list: + equi = url.is_equivalent(addendum) + if equi == IDENTICAL: + break + elif equi == EQUAL: + url.merge(addendum) + break + else: + self.urls.append(addendum) + def add_url(self, url): """ Add a :class:`~gen.lib.url.Url` instance to the object's list of :class:`~gen.lib.url.Url` instances. diff --git a/src/glade/Makefile.am b/src/glade/Makefile.am index 7943b977c..c94c7f3c7 100644 --- a/src/glade/Makefile.am +++ b/src/glade/Makefile.am @@ -41,4 +41,12 @@ dist_pkgdata_DATA = \ editplace.glade \ editsourceref.glade \ editname.glade \ - editevent.glade + editevent.glade \ + mergeperson.glade \ + mergefamily.glade \ + mergeevent.glade \ + mergeplace.glade \ + mergesource.glade \ + mergerepository.glade \ + mergemedia.glade \ + mergenote.glade diff --git a/src/glade/mergeevent.glade b/src/glade/mergeevent.glade new file mode 100644 index 000000000..1aa557d4b --- /dev/null +++ b/src/glade/mergeevent.glade @@ -0,0 +1,632 @@ + + + + True + 500 + dialog + False + + + True + vertical + + + True + + + True + True + + + False + False + 15 + + + + + True + Select the event that will provide the +primary data for the merged event. + + + False + 1 + + + + + True + + + True + True + True + + + True + True + + + + + False + False + + + + + True + True + True + handle_btn1 + + + True + True + + + + + False + False + 1 + + + + + False + 5 + 2 + + + + + True + True + + + True + + + True + 6 + 7 + 4 + 6 + 6 + + + True + <b>Event 1</b> + True + + + GTK_FILL + + + + + + True + <b>Event 2</b> + True + + + 2 + 3 + GTK_FILL + + + + + + Type: + True + True + False + True + True + + + 1 + 2 + GTK_FILL + + + + + + Type: + True + True + False + True + True + type_btn1 + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Date: + True + True + False + True + True + + + 2 + 3 + GTK_FILL + + + + + + Date: + True + True + False + True + True + date_btn1 + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Place: + True + True + False + True + True + + + 3 + 4 + GTK_FILL + + + + + + Place: + True + True + False + True + True + place_btn1 + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + Description: + True + True + False + True + True + + + 4 + 5 + GTK_FILL + + + + + + Description: + True + True + False + True + True + desc_btn1 + + + 2 + 3 + 4 + 5 + GTK_FILL + + + + + + Marker: + True + True + False + True + True + + + 5 + 6 + GTK_FILL + + + + + + Marker: + True + True + False + True + True + marker_btn1 + + + 2 + 3 + 5 + 6 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + + + 6 + 7 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + gramps_btn1 + + + 2 + 3 + 6 + 7 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 1 + 2 + + + + + + True + True + False + + + 3 + 4 + 1 + 2 + + + + + + True + True + False + + + 1 + 2 + 2 + 3 + + + + + + True + True + False + + + 3 + 4 + 2 + 3 + + + + + + True + True + False + + + 1 + 2 + 3 + 4 + + + + + + True + True + False + + + 3 + 4 + 3 + 4 + + + + + + True + True + False + + + 1 + 2 + 4 + 5 + + + + + + True + True + False + + + 3 + 4 + 4 + 5 + + + + + + True + True + False + + + 1 + 2 + 5 + 6 + + + + + + True + True + False + + + 3 + 4 + 5 + 6 + + + + + + True + True + False + + + 1 + 2 + 6 + 7 + + + + + + True + True + False + + + 3 + 4 + 6 + 7 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + Attributes, notes, sources and media objects of both events will be combined. + True + + + 6 + 1 + + + + + + + True + Detailed Selection + + + + + 3 + False + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + gtk-help + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + event_cancel + event_ok + event_help + + + diff --git a/src/glade/mergefamily.glade b/src/glade/mergefamily.glade new file mode 100644 index 000000000..7f3814320 --- /dev/null +++ b/src/glade/mergefamily.glade @@ -0,0 +1,570 @@ + + + + True + 500 + dialog + False + + + True + vertical + + + True + vertical + + + True + True + + + False + False + 15 + + + + + True + Select the family that will provide the +primary data for the merged family. + + + False + 1 + + + + + True + + + True + True + True + + + True + True + + + + + False + False + + + + + True + True + True + handle_btn1 + + + True + True + + + + + False + False + 1 + + + + + False + 5 + 2 + + + + + True + True + + + True + + + True + 6 + 6 + 4 + 6 + 6 + + + True + <b>Family 1</b> + True + + + GTK_FILL + + + + + + True + <b>Family 2</b> + True + + + 2 + 3 + GTK_FILL + + + + + + Father: + True + True + False + True + True + + + 1 + 2 + GTK_FILL + + + + + + Father: + True + True + False + True + True + father_btn1 + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Mother: + True + True + False + True + True + + + 2 + 3 + GTK_FILL + + + + + + Mother: + True + True + False + True + True + mother_btn1 + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Relationship: + True + True + False + True + True + + + 3 + 4 + GTK_FILL + + + + + + Relationship: + True + True + False + True + True + rel_btn1 + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + Marker: + True + True + False + True + True + + + 4 + 5 + GTK_FILL + + + + + + Marker: + True + True + False + True + True + marker_btn1 + + + 2 + 3 + 4 + 5 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + + + 5 + 6 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + gramps_btn1 + + + 2 + 3 + 5 + 6 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 1 + 2 + + + + + + True + True + False + + + 3 + 4 + 1 + 2 + + + + + + True + True + False + + + 1 + 2 + 2 + 3 + + + + + + True + True + False + + + 3 + 4 + 2 + 3 + + + + + + True + True + False + + + 1 + 2 + 3 + 4 + + + + + + True + True + False + + + 3 + 4 + 3 + 4 + + + + + + True + True + False + + + 1 + 2 + 4 + 5 + + + + + + True + True + False + + + 3 + 4 + 4 + 5 + + + + + + True + True + False + + + 1 + 2 + 5 + 6 + + + + + + True + True + False + + + 3 + 4 + 5 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + Events, lds_ord, media objects, attributes, notes and sources of both families will be combined. + True + + + 6 + 1 + + + + + + + True + Detailed Selection + + + + + 3 + False + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + gtk-help + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + family_cancel + family_ok + family_help + + + diff --git a/src/glade/mergemedia.glade b/src/glade/mergemedia.glade new file mode 100644 index 000000000..64805a109 --- /dev/null +++ b/src/glade/mergemedia.glade @@ -0,0 +1,506 @@ + + + + True + 500 + dialog + False + + + True + vertical + + + True + + + True + True + + + False + False + 15 + + + + + True + Select the object that will provide the +primary data for the merged object. + + + False + 1 + + + + + True + + + True + True + True + + + True + True + + + + + False + False + + + + + True + True + True + handle_btn1 + + + True + True + + + + + False + False + 1 + + + + + False + 5 + 2 + + + + + True + True + + + True + + + True + 6 + 5 + 4 + 6 + 6 + + + True + <b>Object 1</b> + True + + + GTK_FILL + + + + + + True + <b>Object 2</b> + True + + + 2 + 3 + GTK_FILL + + + + + + Title: + True + True + False + True + True + + + 1 + 2 + GTK_FILL + + + + + + Title: + True + True + False + True + True + desc_btn1 + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Path: + True + True + False + True + True + + + 2 + 3 + GTK_FILL + + + + + + Path: + True + True + False + True + True + path_btn1 + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Date: + True + True + False + True + True + + + 3 + 4 + GTK_FILL + + + + + + Date: + True + True + False + True + True + date_btn1 + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + + + 4 + 5 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + gramps_btn1 + + + 2 + 3 + 4 + 5 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 1 + 2 + + + + + + True + True + False + + + 3 + 4 + 1 + 2 + + + + + + True + True + False + + + 1 + 2 + 2 + 3 + + + + + + True + True + False + + + 3 + 4 + 2 + 3 + + + + + + True + True + False + + + 1 + 2 + 3 + 4 + + + + + + True + True + False + + + 3 + 4 + 3 + 4 + + + + + + True + True + False + + + 1 + 2 + 4 + 5 + + + + + + True + True + False + + + 3 + 4 + 4 + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + Attributes, sources and notes of both objects will be combined. + True + + + 6 + 1 + + + + + + + True + Detailed Selection + + + + + 3 + False + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + gtk-help + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + object_cancel + object_ok + object_help + + + diff --git a/src/glade/mergenote.glade b/src/glade/mergenote.glade new file mode 100644 index 000000000..4a22634e5 --- /dev/null +++ b/src/glade/mergenote.glade @@ -0,0 +1,587 @@ + + + + True + 600 + dialog + False + + + True + vertical + + + True + + + True + True + + + False + False + 15 + + + + + True + Select the note that will provide the +primary data for the merged note. + + + False + 1 + + + + + True + + + True + True + True + + + True + True + + + + + False + False + + + + + True + True + True + handle_btn1 + + + True + True + + + + + False + False + 1 + + + + + False + 5 + 2 + + + + + True + True + + + True + + + True + 6 + 6 + 4 + 6 + 6 + + + True + <b>Note 1</b> + True + + + GTK_FILL + + + + + + True + <b>Note 2</b> + True + + + 2 + 3 + GTK_FILL + + + + + + Text: + True + True + False + True + True + + + 1 + 2 + GTK_FILL + + + + + + Text: + True + True + False + True + True + text_btn1 + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Type: + True + True + False + True + True + + + 2 + 3 + GTK_FILL + + + + + + Type: + True + True + False + True + True + type_btn1 + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Format: + True + True + False + True + True + + + 3 + 4 + GTK_FILL + + + + + + Format: + True + True + False + True + True + format_btn1 + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + Marker: + True + True + False + True + True + + + 4 + 5 + GTK_FILL + + + + + + Marker: + True + True + False + True + True + marker_btn1 + + + 2 + 3 + 4 + 5 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + + + 5 + 6 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + gramps_btn1 + + + 2 + 3 + 5 + 6 + GTK_FILL + + + + + + True + True + automatic + automatic + in + + + True + True + False + + + + + 1 + 2 + 1 + 2 + + + + + + True + True + automatic + automatic + in + + + True + True + False + word + + + + + 3 + 4 + 1 + 2 + + + + + + True + True + False + + + 1 + 2 + 2 + 3 + + + + + + True + True + False + + + 3 + 4 + 2 + 3 + + + + + + True + True + False + + + 1 + 2 + 3 + 4 + + + + + + True + True + False + + + 3 + 4 + 3 + 4 + + + + + + True + True + False + + + 1 + 2 + 4 + 5 + + + + + + True + True + False + + + 3 + 4 + 4 + 5 + + + + + + True + True + False + + + 1 + 2 + 5 + 6 + + + + + + True + True + False + + + 3 + 4 + 5 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + + True + + + 6 + 1 + + + + + + + True + Detailed Selection + + + + + 3 + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + gtk-help + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + note_cancel + note_ok + note_help + + + diff --git a/src/glade/mergeperson.glade b/src/glade/mergeperson.glade new file mode 100644 index 000000000..49c0d4641 --- /dev/null +++ b/src/glade/mergeperson.glade @@ -0,0 +1,568 @@ + + + + True + 700 + dialog + False + + + True + vertical + + + True + + + True + True + + + False + False + 15 + + + + + True + Select the person that will provide the +primary data for the merged person. + + + False + 1 + + + + + True + + + True + True + True + + + True + True + + + + + False + False + + + + + True + True + True + handle_btn1 + + + True + True + + + + + False + False + 1 + + + + + False + 5 + 2 + + + + + True + True + + + True + + + True + 6 + 5 + 4 + 6 + 6 + + + True + <b>Person 1</b> + True + + + GTK_FILL + + + + + + True + <b>Person 2</b> + True + + + 2 + 3 + GTK_FILL + + + + + + Name: + True + True + False + True + True + + + 1 + 2 + GTK_FILL + + + + + + Name: + True + True + False + True + True + name_btn1 + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Gender: + True + True + False + True + True + + + 2 + 3 + GTK_FILL + + + + + + Gender: + True + True + False + True + True + gender_btn1 + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Marker: + True + True + False + True + True + + + 3 + 4 + GTK_FILL + + + + + + Marker: + True + True + False + True + True + marker_btn1 + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + + + 4 + 5 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + gramps_btn1 + + + 2 + 3 + 4 + 5 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 1 + 2 + + + + + + True + True + False + + + 3 + 4 + 1 + 2 + + + + + + True + True + False + + + 1 + 2 + 2 + 3 + + + + + + True + True + False + + + 3 + 4 + 2 + 3 + + + + + + True + True + False + + + 1 + 2 + 3 + 4 + + + + + + True + True + False + + + 3 + 4 + 3 + 4 + + + + + + True + True + False + + + 1 + 2 + 4 + 5 + + + + + + True + True + False + + + 3 + 4 + 4 + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + Events, media objects, addresses, attributes, urls, notes and sources of both persons will be combined. + True + + + 6 + 1 + + + + + + + True + Detailed Selection + + + + + 3 + False + + + + + True + True + + + True + horizontal + 5 + + + True + True + automatic + automatic + in + + + True + True + 10 + 10 + False + 10 + 10 + + + + + 1 + + + + + True + True + automatic + automatic + in + + + True + True + 10 + 10 + False + 10 + 10 + + + + + 2 + + + + + + + True + Context Information + + + + + 4 + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + gtk-help + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + person_cancel + person_ok + person_help + + + diff --git a/src/glade/mergeplace.glade b/src/glade/mergeplace.glade new file mode 100644 index 000000000..8d53fc0ac --- /dev/null +++ b/src/glade/mergeplace.glade @@ -0,0 +1,580 @@ + + + + True + 500 + dialog + False + + + True + vertical + + + True + vertical + + + True + True + + + False + False + 15 + + + + + True + Select the place that will provide the +primary data for the merged place. + + + False + 1 + + + + + True + + + True + True + True + + + True + True + + + + + False + False + + + + + True + True + True + handle_btn1 + + + True + True + + + + + False + False + 1 + + + + + False + 5 + 2 + + + + + True + True + + + True + + + True + 6 + 6 + 4 + 6 + 6 + + + True + <b>Place 1</b> + True + + + GTK_FILL + + + + + + True + <b>Place 2</b> + True + + + 2 + 3 + GTK_FILL + + + + + + Title: + True + True + False + True + True + + + 1 + 2 + GTK_FILL + + + + + + Title: + True + True + False + True + True + title_btn1 + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Latitude: + True + True + False + True + True + + + 2 + 3 + GTK_FILL + + + + + + Latitude: + True + True + False + True + True + lat_btn1 + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Longitude: + True + True + False + True + True + + + 3 + 4 + GTK_FILL + + + + + + Longitude: + True + True + False + True + True + long_btn1 + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + Location: + True + True + False + True + True + + + 4 + 5 + GTK_FILL + + + + + + Location: + True + True + False + True + True + loc_btn1 + + + 2 + 3 + 4 + 5 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + + + 5 + 6 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + gramps_btn1 + + + 2 + 3 + 5 + 6 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 1 + 2 + + + + + + True + True + False + + + 3 + 4 + 1 + 2 + + + + + + True + True + False + + + 1 + 2 + 2 + 3 + + + + + + True + True + False + + + 3 + 4 + 2 + 3 + + + + + + True + True + False + + + 1 + 2 + 3 + 4 + + + + + + True + True + False + + + 3 + 4 + 3 + 4 + + + + + + True + + + True + True + False + + + + + 1 + 2 + 4 + 5 + + + + + + True + + + True + True + False + + + + + 3 + 4 + 4 + 5 + + + + + + True + True + False + + + 1 + 2 + 5 + 6 + + + + + + True + True + False + + + 3 + 4 + 5 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + Alternate locations, sources, urls, media objects and notes of both places will be combined. + True + + + 6 + 1 + + + + + + + True + Detailed Selection + + + + + 3 + False + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + gtk-help + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + place_cancel + place_ok + place_help + + + diff --git a/src/glade/mergerepository.glade b/src/glade/mergerepository.glade new file mode 100644 index 000000000..c90466c7d --- /dev/null +++ b/src/glade/mergerepository.glade @@ -0,0 +1,443 @@ + + + + True + 500 + dialog + False + + + True + vertical + + + True + + + True + True + + + False + False + 15 + + + + + True + Select the repository that will provide the +primary data for the merged repository. + + + False + 1 + + + + + True + + + True + True + True + + + True + True + + + + + False + False + + + + + True + True + True + handle_btn1 + + + True + True + + + + + False + False + 1 + + + + + False + 5 + 2 + + + + + True + True + + + True + + + True + 6 + 4 + 4 + 6 + 6 + + + True + <b>Repository 1</b> + True + + + GTK_FILL + + + + + + True + <b>Repository 2</b> + True + + + 2 + 3 + GTK_FILL + + + + + + Name: + True + True + False + True + True + + + 1 + 2 + GTK_FILL + + + + + + Name: + True + True + False + True + True + name_btn1 + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Type: + True + True + False + True + True + + + 2 + 3 + GTK_FILL + + + + + + Type: + True + True + False + True + True + type_btn1 + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + + + 3 + 4 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + gramps_btn1 + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 1 + 2 + + + + + + True + True + False + + + 3 + 4 + 1 + 2 + + + + + + True + True + False + + + 1 + 2 + 2 + 3 + + + + + + True + True + False + + + 3 + 4 + 2 + 3 + + + + + + True + True + False + + + 1 + 2 + 3 + 4 + + + + + + True + True + False + + + 3 + 4 + 3 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + Addresses, urls and notes of both repositories will be combined. + True + + + 6 + 1 + + + + + + + True + Detailed Selection + + + + + 3 + False + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + gtk-help + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + repository_cancel + repository_ok + repository_help + + + diff --git a/src/glade/mergesource.glade b/src/glade/mergesource.glade new file mode 100644 index 000000000..fc78d7ae4 --- /dev/null +++ b/src/glade/mergesource.glade @@ -0,0 +1,569 @@ + + + + True + 500 + dialog + False + + + True + vertical + + + True + + + True + True + + + False + False + 15 + + + + + True + Select the source that will provide the +primary data for the merged source. + + + False + 1 + + + + + True + + + True + True + True + + + True + True + + + + + False + False + + + + + True + True + True + handle_btn1 + + + True + True + + + + + False + False + 1 + + + + + False + 5 + 2 + + + + + True + True + + + True + + + True + 6 + 6 + 4 + 6 + 6 + + + True + <b>Source 1</b> + True + + + GTK_FILL + + + + + + True + <b>Source 2</b> + True + + + 2 + 3 + GTK_FILL + + + + + + Title: + True + True + False + True + True + + + 1 + 2 + GTK_FILL + + + + + + Title: + True + True + False + True + True + title_btn1 + + + 2 + 3 + 1 + 2 + GTK_FILL + + + + + + Author: + True + True + False + True + True + + + 2 + 3 + GTK_FILL + + + + + + Author: + True + True + False + True + True + author_btn1 + + + 2 + 3 + 2 + 3 + GTK_FILL + + + + + + Abbreviation: + True + True + False + True + True + + + 3 + 4 + GTK_FILL + + + + + + Abbreviation: + True + True + False + True + True + abbrev_btn1 + + + 2 + 3 + 3 + 4 + GTK_FILL + + + + + + Publication: + True + True + False + True + True + + + 4 + 5 + GTK_FILL + + + + + + Publication: + True + True + False + True + True + pub_btn1 + + + 2 + 3 + 4 + 5 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + + + 5 + 6 + GTK_FILL + + + + + + Gramps ID: + True + True + False + True + True + gramps_btn1 + + + 2 + 3 + 5 + 6 + GTK_FILL + + + + + + True + True + False + + + 1 + 2 + 1 + 2 + + + + + + True + True + False + + + 3 + 4 + 1 + 2 + + + + + + True + True + False + + + 1 + 2 + 2 + 3 + + + + + + True + True + False + + + 3 + 4 + 2 + 3 + + + + + + True + True + False + + + 1 + 2 + 3 + 4 + + + + + + True + True + False + + + 3 + 4 + 3 + 4 + + + + + + True + True + False + + + 1 + 2 + 4 + 5 + + + + + + True + True + False + + + 3 + 4 + 4 + 5 + + + + + + True + True + False + + + 1 + 2 + 5 + 6 + + + + + + True + True + False + + + 3 + 4 + 5 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + Notes, media objects, data-items and repository references of both sources will be combined. + True + + + 6 + 1 + + + + + + + True + Detailed Selection + + + + + 3 + False + + + + + 1 + + + + + True + end + + + gtk-cancel + True + True + True + False + True + + + False + False + 0 + + + + + gtk-ok + True + True + True + False + True + + + False + False + 1 + + + + + gtk-help + True + True + True + False + True + + + False + False + 2 + + + + + False + end + 0 + + + + + + source_cancel + source_ok + source_help + + + diff --git a/src/gui/grampsgui.py b/src/gui/grampsgui.py index 59a57c3e7..774a0c1f8 100644 --- a/src/gui/grampsgui.py +++ b/src/gui/grampsgui.py @@ -123,6 +123,7 @@ def register_stock_icons (): ('gramps-geo-altmap', _('GeoView'), gtk.gdk.CONTROL_MASK, 0, ''), ('gramps-lock', _('Public'), gtk.gdk.CONTROL_MASK, 0, ''), ('gramps-media', _('Media'), gtk.gdk.CONTROL_MASK, 0, ''), + ('gramps-merge', _('Merge'), gtk.gdk.CONTROL_MASK, 0, ''), ('gramps-notes', _('Notes'), gtk.gdk.CONTROL_MASK, 0, ''), ('gramps-parents', _('Parents'), gtk.gdk.CONTROL_MASK, 0, ''), ('gramps-parents-add', _('Add Parents'), gtk.gdk.CONTROL_MASK, 0, ''), diff --git a/src/gui/views/listview.py b/src/gui/views/listview.py index 267475527..50cbcdffa 100644 --- a/src/gui/views/listview.py +++ b/src/gui/views/listview.py @@ -199,6 +199,8 @@ class ListView(NavigationView): self.ADD_MSG, self.add), ('Remove', gtk.STOCK_REMOVE, _("_Remove"), "Delete", self.DEL_MSG, self.remove), + ('Merge', 'gramps-merge', _('_Merge...'), None, None, + self.merge), ('ExportTab', None, _('Export View...'), None, None, self.export), ]) @@ -1017,6 +1019,12 @@ class ListView(NavigationView): """ raise NotImplementedError + def merge(self, obj): + """ + Template function to allow the merger of two objects. + """ + raise NotImplementedError + def remove_object_from_handle(self, handle): """ Template function to allow the removal of an object by its handle diff --git a/src/images/16x16/Makefile.am b/src/images/16x16/Makefile.am index 86cc2e6f8..a7806f8c3 100644 --- a/src/images/16x16/Makefile.am +++ b/src/images/16x16/Makefile.am @@ -34,6 +34,7 @@ dist_pkgdata_DATA = \ gramps-gramplet.png \ gramps-lock.png \ gramps-media.png \ + gramps-merge.png \ gramps-notes.png \ gramps-parents.png \ gramps-parents-add.png \ diff --git a/src/images/22x22/Makefile.am b/src/images/22x22/Makefile.am index 972e3662a..c160c74ae 100644 --- a/src/images/22x22/Makefile.am +++ b/src/images/22x22/Makefile.am @@ -34,6 +34,7 @@ dist_pkgdata_DATA = \ gramps-gramplet.png \ gramps-lock.png \ gramps-media.png \ + gramps-merge.png \ gramps-notes.png \ gramps-parents.png \ gramps-parents-add.png \ diff --git a/src/images/48x48/Makefile.am b/src/images/48x48/Makefile.am index 2c0da0cde..890975e92 100644 --- a/src/images/48x48/Makefile.am +++ b/src/images/48x48/Makefile.am @@ -34,6 +34,7 @@ dist_pkgdata_DATA = \ gramps-gramplet.png \ gramps-lock.png \ gramps-media.png \ + gramps-merge.png \ gramps-notes.png \ gramps-parents.png \ gramps-parents-add.png \ diff --git a/src/plugins/lib/libpersonview.py b/src/plugins/lib/libpersonview.py index e4ff9c41c..48e1eca6a 100644 --- a/src/plugins/lib/libpersonview.py +++ b/src/plugins/lib/libpersonview.py @@ -204,13 +204,10 @@ class BasePersonView(ListView): + - - - - @@ -223,6 +220,7 @@ class BasePersonView(ListView): + @@ -233,6 +231,7 @@ class BasePersonView(ListView): + @@ -343,10 +342,8 @@ class BasePersonView(ListView): _("Add a new person"), self.add), ('Remove', gtk.STOCK_REMOVE, _("_Remove"), "Delete", _("Remove the Selected Person"), self.remove), - ('CmpMerge', None, _('Compare and _Merge...'), None, None, - self.cmp_merge), - ('FastMerge', None, _('_Fast Merge...'), None, None, - self.fast_merge), + ('Merge', 'gramps-merge', _('_Merge...'), None, None, + self.merge), ('ExportTab', None, _('Export View...'), None, None, self.export), ]) @@ -365,31 +362,10 @@ class BasePersonView(ListView): self.all_action.set_visible(False) self.edit_action.set_visible(False) - def cmp_merge(self, obj): - mlist = self.selected_handles() - - if len(mlist) != 2: - ErrorDialog( - _("Cannot merge people"), - _("Exactly two people must be selected to perform a merge. " - "A second person can be selected by holding down the " - "control key while clicking on the desired person.")) - else: - import Merge - person1 = self.dbstate.db.get_person_from_handle(mlist[0]) - person2 = self.dbstate.db.get_person_from_handle(mlist[1]) - if person1 and person2: - Merge.PersonCompare(self.dbstate, self.uistate, person1, - person2, self.build_tree) - else: - ErrorDialog( - _("Cannot merge people"), - _("Exactly two people must be selected to perform a " - "merge. A second person can be selected by holding " - "down the control key while clicking on the desired " - "person.")) - - def fast_merge(self, obj): + def merge(self, obj): + """ + Merge the selected people. + """ mlist = self.selected_handles() if len(mlist) != 2: @@ -400,15 +376,4 @@ class BasePersonView(ListView): "control key while clicking on the desired person.")) else: import Merge - - person1 = self.dbstate.db.get_person_from_handle(mlist[0]) - person2 = self.dbstate.db.get_person_from_handle(mlist[1]) - if person1 and person2: - Merge.MergePeopleUI(self.dbstate, self.uistate, person1, - person2, self.build_tree) - else: - ErrorDialog( - _("Cannot merge people"), - _("Exactly two people must be selected to perform a merge. " - "A second person can be selected by holding down the " - "control key while clicking on the desired person.")) + Merge.MergePeople(self.dbstate, self.uistate, mlist[0], mlist[1]) diff --git a/src/plugins/lib/libplaceview.py b/src/plugins/lib/libplaceview.py index 4c28a0292..2505b3c47 100644 --- a/src/plugins/lib/libplaceview.py +++ b/src/plugins/lib/libplaceview.py @@ -155,8 +155,6 @@ class PlaceBaseView(ListView): def define_actions(self): ListView.define_actions(self) - self._add_action('FastMerge', None, _('_Merge...'), - callback=self.fast_merge) self._add_toolmenu_action('MapsList', _('Loading...'), _("Attempt to see selected locations with a Map " "Service (OpenstreetMap, Google Maps, ...)"), @@ -317,11 +315,9 @@ class PlaceBaseView(ListView): + - - - @@ -333,6 +329,7 @@ class PlaceBaseView(ListView): + @@ -344,6 +341,7 @@ class PlaceBaseView(ListView): + @@ -397,7 +395,10 @@ class PlaceBaseView(ListView): except Errors.WindowActiveError: pass - def fast_merge(self, obj): + def merge(self, obj): + """ + Merge the selected places. + """ mlist = self.selected_handles() if len(mlist) != 2: diff --git a/src/plugins/view/eventview.py b/src/plugins/view/eventview.py index 99b2a9a94..88245c236 100644 --- a/src/plugins/view/eventview.py +++ b/src/plugins/view/eventview.py @@ -53,6 +53,7 @@ import Errors import Bookmarks import config from DdTargets import DdTargets +from QuestionDialog import ErrorDialog from gui.editors import EditEvent, DeleteEventQuery from Filters.SideBar import EventSidebarFilter from gen.plug import CATEGORY_QR_EVENT @@ -178,6 +179,7 @@ class EventView(ListView): + @@ -191,6 +193,7 @@ class EventView(ListView): + @@ -200,6 +203,7 @@ class EventView(ListView): + @@ -256,6 +260,22 @@ class EventView(ListView): except Errors.WindowActiveError: pass + def merge(self, obj): + """ + Merge the selected events. + """ + mlist = self.selected_handles() + + if len(mlist) != 2: + msg = _("Cannot merge event objects.") + msg2 = _("Exactly two events must be selected to perform a merge. " + "A second object can be selected by holding down the " + "control key while clicking on the desired event.") + ErrorDialog(msg, msg2) + else: + import Merge + Merge.MergeEvents(self.dbstate, self.uistate, mlist[0], mlist[1]) + def dummy_report(self, obj): """ For the xml UI definition of popup to work, the submenu Quick Report must have an entry in the xml diff --git a/src/plugins/view/familyview.py b/src/plugins/view/familyview.py index 6b79b5f3b..ed820ad83 100644 --- a/src/plugins/view/familyview.py +++ b/src/plugins/view/familyview.py @@ -50,6 +50,7 @@ from gui.editors import EditFamily import Bookmarks import Errors import config +from QuestionDialog import ErrorDialog from Filters.SideBar import FamilySidebarFilter from gen.plug import CATEGORY_QR_FAMILY @@ -107,6 +108,7 @@ class FamilyView(ListView): FamilyModel, signal_map, dbstate.db.get_family_bookmarks(), Bookmarks.FamilyBookmarks, nav_group, + multiple=True, filter_class=FamilySidebarFilter) self.func_list = { @@ -143,6 +145,7 @@ class FamilyView(ListView): + @@ -162,6 +165,7 @@ class FamilyView(ListView): + @@ -171,6 +175,7 @@ class FamilyView(ListView): + @@ -236,6 +241,22 @@ class FamilyView(ListView): except Errors.WindowActiveError: pass + def merge(self, obj): + """ + Merge the selected families. + """ + mlist = self.selected_handles() + + if len(mlist) != 2: + msg = _("Cannot merge families.") + msg2 = _("Exactly two families must be selected to perform a merge." + " A second family can be selected by holding down the " + "control key while clicking on the desired family.") + ErrorDialog(msg, msg2) + else: + import Merge + Merge.MergeFamilies(self.dbstate, self.uistate, mlist[0], mlist[1]) + def dummy_report(self, obj): """ For the xml UI definition of popup to work, the submenu Quick Report must have an entry in the xml diff --git a/src/plugins/view/mediaview.py b/src/plugins/view/mediaview.py index 654881de5..905ba39c3 100644 --- a/src/plugins/view/mediaview.py +++ b/src/plugins/view/mediaview.py @@ -62,6 +62,7 @@ from gui.editors import EditMedia, DeleteMediaQuery import Errors from Filters.SideBar import MediaSidebarFilter from DdTargets import DdTargets +from QuestionDialog import ErrorDialog from gen.plug import CATEGORY_QR_MEDIA #------------------------------------------------------------------------- @@ -355,6 +356,7 @@ class MediaView(ListView): + @@ -381,6 +383,7 @@ class MediaView(ListView): + @@ -395,6 +398,7 @@ class MediaView(ListView): + @@ -441,6 +445,23 @@ class MediaView(ListView): except Errors.WindowActiveError: pass + def merge(self, obj): + """ + Merge the selected objects. + """ + mlist = self.selected_handles() + + if len(mlist) != 2: + msg = _("Cannot merge media objects.") + msg2 = _("Exactly two media objects must be selected to perform a " + "merge. A second object can be selected by holding down the " + "control key while clicking on the desired object.") + ErrorDialog(msg, msg2) + else: + import Merge + Merge.MergeMediaObjects(self.dbstate, self.uistate, mlist[0], + mlist[1]) + def get_handle_from_gramps_id(self, gid): """ returns the handle of the specified object diff --git a/src/plugins/view/noteview.py b/src/plugins/view/noteview.py index 28a0ed3d7..8fda27aeb 100644 --- a/src/plugins/view/noteview.py +++ b/src/plugins/view/noteview.py @@ -52,6 +52,7 @@ import Bookmarks import config from gen.lib import Note from DdTargets import DdTargets +from QuestionDialog import ErrorDialog from Filters.SideBar import NoteSidebarFilter from gui.editors import EditNote, DeleteNoteQuery from gen.plug import CATEGORY_QR_NOTE @@ -166,6 +167,7 @@ class NoteView(ListView): + @@ -179,6 +181,7 @@ class NoteView(ListView): + @@ -188,6 +191,7 @@ class NoteView(ListView): + @@ -239,3 +243,19 @@ class NoteView(ListView): EditNote(self.dbstate, self.uistate, [], note) except Errors.WindowActiveError: pass + + def merge(self, obj): + """ + Merge the selected notes. + """ + mlist = self.selected_handles() + + if len(mlist) != 2: + msg = _("Cannot merge notes.") + msg2 = _("Exactly two notes must be selected to perform a merge. " + "A second note can be selected by holding down the " + "control key while clicking on the desired note.") + ErrorDialog(msg, msg2) + else: + import Merge + Merge.MergeNotes(self.dbstate, self.uistate, mlist[0], mlist[1]) diff --git a/src/plugins/view/repoview.py b/src/plugins/view/repoview.py index bb062fde6..203e3c14d 100644 --- a/src/plugins/view/repoview.py +++ b/src/plugins/view/repoview.py @@ -44,6 +44,7 @@ import Errors import config from gui.editors import EditRepository, DeleteRepositoryQuery from DdTargets import DdTargets +from QuestionDialog import ErrorDialog from Filters.SideBar import RepoSidebarFilter from gen.plug import CATEGORY_QR_REPOSITORY @@ -182,6 +183,7 @@ class RepositoryView(ListView): + @@ -195,6 +197,7 @@ class RepositoryView(ListView): + @@ -204,6 +207,7 @@ class RepositoryView(ListView): + @@ -235,6 +239,24 @@ class RepositoryView(ListView): except Errors.WindowActiveError: pass + def merge(self, obj): + """ + Merge the selected repositories. + """ + mlist = self.selected_handles() + + if len(mlist) != 2: + msg = _("Cannot merge repositories.") + msg2 = _("Exactly two repositories must be selected to perform a " + "merge. A second repository can be selected by holding " + "down the control key while clicking on the desired " + "repository.") + ErrorDialog(msg, msg2) + else: + import Merge + Merge.MergeRepositories(self.dbstate, self.uistate, mlist[0], + mlist[1]) + def get_handle_from_gramps_id(self, gid): obj = self.dbstate.db.get_repository_from_gramps_id(gid) if obj: diff --git a/src/plugins/view/sourceview.py b/src/plugins/view/sourceview.py index 6059c5ab3..9f89da36e 100644 --- a/src/plugins/view/sourceview.py +++ b/src/plugins/view/sourceview.py @@ -130,8 +130,6 @@ class SourceView(ListView): def define_actions(self): ListView.define_actions(self) - self._add_action('FastMerge', None, _('_Merge'), - callback=self.fast_merge) self._add_action('FilterEdit', None, _('Source Filter Editor'), callback=self.filter_editor,) self._add_action('QuickReport', None, _("Quick View"), None, None, None) @@ -166,11 +164,9 @@ class SourceView(ListView): + - - - @@ -182,6 +178,7 @@ class SourceView(ListView): + @@ -191,6 +188,7 @@ class SourceView(ListView): + @@ -226,7 +224,10 @@ class SourceView(ListView): except Errors.WindowActiveError: pass - def fast_merge(self, obj): + def merge(self, obj): + """ + Merge the selected sources. + """ mlist = self.selected_handles() if len(mlist) != 2: