gramps/src/MergeData.py
Alex Roitman e11c8d99cc Update
svn: r4248
2005-03-28 05:05:38 +00:00

1589 lines
56 KiB
Python

#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2005 Donald N. Allingham
#
# 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$
#-------------------------------------------------------------------------
#
# Standard python modules
#
#-------------------------------------------------------------------------
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# GNOME
#
#-------------------------------------------------------------------------
import gtk
import gnome
from gnome.ui import Druid, DruidPageEdge, DruidPageStandard
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
import RelLib
import Utils
import ListModel
import NameDisplay
import const
#-------------------------------------------------------------------------
#
# Merge People
#
#-------------------------------------------------------------------------
class MergePeople:
def __init__(self,parent,db,person1,person2,update,ep_update=None):
self.parent = parent
self.db = db
self.p1 = person1
self.p2 = person2
self.update = update
self.ep_update = ep_update
self.parent = parent
self.need_birth = self.p1.get_birth_handle() and self.p2.get_birth_handle()
self.need_death = self.p1.get_death_handle() and self.p2.get_death_handle()
self.need_spouse = len(self.p1.get_family_handle_list()) + \
len(self.p2.get_family_handle_list()) > 1
self.need_parents = len(self.p1.get_parent_family_handle_list()) + \
len(self.p2.get_parent_family_handle_list()) > 1
self.build_interface()
def build_interface(self):
self.w = gtk.Window()
self.fg_color = gtk.gdk.color_parse('#7d684a')
self.bg_color = gtk.gdk.color_parse('#e1dbc5')
self.logo = gtk.gdk.pixbuf_new_from_file("%s/gramps.png" % const.rootDir)
self.splash = gtk.gdk.pixbuf_new_from_file("%s/splash.jpg" % const.rootDir)
self.d = Druid()
self.w.add(self.d)
self.w.set_title(_('GRAMPS - Merge People'))
self.d.add(self.build_info_page())
self.d.add(self.build_name_page())
self.d.add(self.build_id_page())
if self.need_birth:
self.d.add(self.build_birth_page())
if self.need_death:
self.d.add(self.build_death_page())
if self.need_parents:
self.d.add(self.build_parents_page())
if self.need_spouse:
self.d.add(self.build_spouse_page())
self.last_page = self.build_last_page()
self.d.add(self.last_page)
self.d.set_show_help(True)
self.d.connect('cancel',self.close)
#self.d.connect('help',self.help)
self.w.connect("destroy_event",self.close)
self.w.show_all()
def close(self,obj,obj2=None):
"""
Close and delete handler.
"""
self.w.destroy()
def build_last_page(self):
"""
Build the last druid page. The actual text will be added after the
save is performed and the success status us known.
"""
p = DruidPageEdge(1)
p.set_title_color(self.fg_color)
p.set_bg_color(self.bg_color)
p.set_logo(self.logo)
p.set_watermark(self.splash)
p.set_text(_('Pressing APPLY will merge the two selected people '
'into a single person. If you do not wish to do this, '
'press CANCEL'))
p.connect('finish',self.close)
return p
def build_name_page(self):
"""
Build a page with the table of format radio buttons and
their descriptions.
"""
self.format_buttons = []
p = DruidPageStandard()
p.set_title(_('Select primary name'))
p.set_title_foreground(self.fg_color)
p.set_background(self.bg_color)
p.set_logo(self.logo)
box = gtk.VBox()
box.set_spacing(12)
p.append_item("",box,"")
fname = NameDisplay.displayer.display(self.p1)
mname = NameDisplay.displayer.display(self.p2)
self.name1 = gtk.RadioButton(None,fname)
self.name2 = gtk.RadioButton(self.name1,mname)
self.altnames = gtk.CheckButton(_('Keep unselected name as an alternate name'))
table = gtk.Table(6,2)
table.set_row_spacings(6)
table.set_col_spacings(6)
label = gtk.Label('<b>%s</b>' % _('Primary name'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,5,0,1,xoptions=gtk.EXPAND|gtk.FILL)
label = gtk.Label('<b>%s</b>' % _('Options'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,5,3,4,xoptions=gtk.EXPAND|gtk.FILL)
table.attach(self.name1,1,2,1,2)
table.attach(self.name2,1,2,2,3)
table.attach(self.altnames,1,2,4,5)
box.add(table)
box.show_all()
return p
def build_id_page(self):
"""
Build a page with the table of format radio buttons and
their descriptions.
"""
p = DruidPageStandard()
p.set_title(_('Select GRAMPS ID'))
p.set_title_foreground(self.fg_color)
p.set_background(self.bg_color)
p.set_logo(self.logo)
box = gtk.VBox()
box.set_spacing(12)
p.append_item("",box,"")
self.id1 = gtk.RadioButton(None,self.p1.get_gramps_id())
self.id2 = gtk.RadioButton(self.id1,self.p2.get_gramps_id())
self.keepid = gtk.CheckButton(_('Keep old ID as an Attribute'))
table = gtk.Table(6,2)
table.set_row_spacings(6)
table.set_col_spacings(6)
label = gtk.Label('<b>%s</b>' % _('GRAMPS ID'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,5,0,1,xoptions=gtk.EXPAND|gtk.FILL)
label = gtk.Label('<b>%s</b>' % _('Options'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,5,3,4,xoptions=gtk.EXPAND|gtk.FILL)
table.attach(self.id1,1,2,1,2)
table.attach(self.id2,1,2,2,3)
table.attach(self.keepid,1,2,4,5)
box.add(table)
box.show_all()
return p
def build_birth_page(self):
"""
Build a page with the table of format radio buttons and
their descriptions.
"""
p = DruidPageStandard()
p.set_title(_('Select Birth Event'))
p.set_title_foreground(self.fg_color)
p.set_background(self.bg_color)
p.set_logo(self.logo)
box = gtk.VBox()
box.set_spacing(12)
p.append_item("",box,"")
(birth1,bplace1) = self.get_event_info(self.p1,
self.p1.get_birth_handle())
(birth2,bplace2) = self.get_event_info(self.p2,
self.p2.get_birth_handle())
bstr1 = ", ".join([birth1,bplace1])
bstr2 = ", ".join([birth2,bplace2])
self.birth1 = gtk.RadioButton(None,bstr1)
self.birth2 = gtk.RadioButton(self.birth1,bstr2)
self.keepbirth = gtk.CheckButton(_('Add unselected birth event as '
'an Alternate Birth event'))
table = gtk.Table(6,2)
table.set_row_spacings(6)
table.set_col_spacings(6)
label = gtk.Label('<b>%s</b>' % _('Birth Event'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,5,0,1,xoptions=gtk.EXPAND|gtk.FILL)
label = gtk.Label('<b>%s</b>' % _('Options'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,5,3,4,xoptions=gtk.EXPAND|gtk.FILL)
table.attach(self.birth1,1,2,1,2)
table.attach(self.birth2,1,2,2,3)
table.attach(self.keepbirth,1,2,4,5)
box.add(table)
box.show_all()
return p
def build_death_page(self):
"""
Build a page with the table of format radio buttons and
their descriptions.
"""
p = DruidPageStandard()
p.set_title(_('Select Death Event'))
p.set_title_foreground(self.fg_color)
p.set_background(self.bg_color)
p.set_logo(self.logo)
box = gtk.VBox()
box.set_spacing(12)
p.append_item("",box,"")
(birth1,bplace1) = self.get_event_info(self.p1,
self.p1.get_death_handle())
(birth2,bplace2) = self.get_event_info(self.p2,
self.p2.get_death_handle())
bstr1 = ", ".join([birth1,bplace1])
bstr2 = ", ".join([birth2,bplace2])
self.death1 = gtk.RadioButton(None,bstr1)
self.death2 = gtk.RadioButton(self.death1,bstr2)
self.keepdeath = gtk.CheckButton(_('Add unselected death event as '
'an Alternate Death event'))
table = gtk.Table(6,2)
table.set_row_spacings(6)
table.set_col_spacings(6)
label = gtk.Label('<b>%s</b>' % _('Death Event'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,5,0,1,xoptions=gtk.EXPAND|gtk.FILL)
label = gtk.Label('<b>%s</b>' % _('Options'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,5,3,4,xoptions=gtk.EXPAND|gtk.FILL)
table.attach(self.death1,1,2,1,2)
table.attach(self.death2,1,2,2,3)
table.attach(self.keepdeath,1,2,4,5)
box.add(table)
box.show_all()
return p
def build_spouse_page(self):
"""
Build a page with the table of format radio buttons and
their descriptions.
"""
p = DruidPageStandard()
p.set_title(_('Select Spouses'))
p.set_title_foreground(self.fg_color)
p.set_background(self.bg_color)
p.set_logo(self.logo)
box = gtk.VBox()
box.set_spacing(12)
p.append_item("",box,"")
import ReportUtils
table = gtk.Table(3,3)
table.set_row_spacings(6)
table.set_col_spacings(6)
label = gtk.Label('<b>%s</b>' %
_('Select spouse relationships'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,3,0,1,xoptions=gtk.EXPAND|gtk.FILL)
self.spouse_view = gtk.TreeView()
self.spouse_list = gtk.ListStore(bool,str,str,str,str,str)
self.spouse_view.set_model(self.spouse_list)
self.spouse_view.set_reorderable(True)
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_AUTOMATIC,
gtk.POLICY_AUTOMATIC)
scroll.add(self.spouse_view)
table.attach(scroll,0,3,2,3,xoptions=gtk.EXPAND|gtk.FILL,
yoptions=gtk.EXPAND|gtk.FILL)
celltoggle = gtk.CellRendererToggle()
celltoggle.set_property('activatable',True)
celltoggle.connect('toggled',self.parent_toggle,
(self.spouse_list,0))
celltext = gtk.CellRendererText()
col0 = gtk.TreeViewColumn(_('Select'),celltoggle,active=0)
col1 = gtk.TreeViewColumn(_('ID'),celltext,text=1)
col2 = gtk.TreeViewColumn(_('Spouse'),celltext,text=2)
col3 = gtk.TreeViewColumn(_('Children'),celltext,text=3)
col4 = gtk.TreeViewColumn(_('From'),celltext,text=4)
self.spouse_view.append_column(col0)
self.spouse_view.append_column(col1)
self.spouse_view.append_column(col2)
self.spouse_view.append_column(col3)
self.spouse_view.append_column(col4)
for fid in self.p1.get_family_handle_list():
family = self.db.get_family_from_handle(fid)
fgid = family.get_gramps_id()
spouse_id = ReportUtils.find_spouse(self.p1,family)
sname = name_of(self.db.get_person_from_handle(spouse_id))
children = str(len(family.get_child_handle_list()))
self.spouse_list.append(row=[True,fgid,sname,
children,self.p1.get_gramps_id(),fid])
for fid in self.p2.get_family_handle_list():
family = self.db.get_family_from_handle(fid)
fgid = family.get_gramps_id()
spouse_id = ReportUtils.find_spouse(self.p1,family)
sname = name_of(self.db.get_person_from_handle(spouse_id))
children = str(len(family.get_child_handle_list()))
self.spouse_list.append(row=[True,fgid,sname,
children,self.p2.get_gramps_id(),fid])
box.add(table)
box.show_all()
return p
def build_parents_page(self):
"""
Build a page with the table of format radio buttons and
their descriptions.
"""
p = DruidPageStandard()
p.set_title(_('Select parents'))
p.set_title_foreground(self.fg_color)
p.set_background(self.bg_color)
p.set_logo(self.logo)
box = gtk.VBox()
box.set_spacing(12)
p.append_item("",box,"")
table = gtk.Table(3,3)
table.set_row_spacings(6)
table.set_col_spacings(6)
label = gtk.Label('<b>%s</b>' %
_('Select parent relationships'))
label.set_use_markup(True)
label.set_alignment(0.0,0.5)
table.attach(label,0,3,0,1,xoptions=gtk.EXPAND|gtk.FILL)
label = gtk.Label(_('You can choose which sets of parents '
'are included in the merged person by selecting '
'the checkboxes. You can order the sets of parents '
'by dragging and dropping the rows. The first '
'set of parents in the list is considered to be '
'the primary set of parents used for reporting.'))
label.set_use_markup(True)
label.set_line_wrap(True)
label.set_justify(gtk.JUSTIFY_LEFT)
label.set_alignment(0.0,0.5)
table.attach(label,1,3,1,2,xoptions=gtk.EXPAND|gtk.FILL)
self.parent_view = gtk.TreeView()
self.parent_view.set_reorderable(True)
self.parent_list = gtk.ListStore(bool,str,str,str,str,str)
self.parent_view.set_model(self.parent_list)
scroll = gtk.ScrolledWindow()
scroll.set_policy(gtk.POLICY_AUTOMATIC,
gtk.POLICY_AUTOMATIC)
scroll.add(self.parent_view)
table.attach(scroll,0,3,2,3,xoptions=gtk.EXPAND|gtk.FILL,
yoptions=gtk.EXPAND|gtk.FILL)
celltoggle = gtk.CellRendererToggle()
celltoggle.set_property('activatable',True)
celltoggle.connect('toggled',self.parent_toggle,
(self.parent_list,0))
celltext = gtk.CellRendererText()
col0 = gtk.TreeViewColumn(_('Select'),celltoggle,active=0)
col1 = gtk.TreeViewColumn(_('ID'),celltext,text=1)
col2 = gtk.TreeViewColumn(_('Father'),celltext,text=2)
col3 = gtk.TreeViewColumn(_('Mother'),celltext,text=3)
col4 = gtk.TreeViewColumn(_('From'),celltext,text=4)
self.parent_view.append_column(col0)
self.parent_view.append_column(col1)
self.parent_view.append_column(col2)
self.parent_view.append_column(col3)
self.parent_view.append_column(col4)
for fid in self.p1.get_parent_family_handle_list():
fname,mname,fgid = self.get_parent_info(fid[0])
self.parent_list.append(row=[True, fgid, fname, mname,
self.p1.get_gramps_id(), fid[0]])
for fid in self.p2.get_parent_family_handle_list():
fname,mname,fgid = self.get_parent_info(fid[0])
self.parent_list.append(row=[True, fgid, fname, mname,
self.p2.get_gramps_id(), fid[0]])
box.add(table)
box.show_all()
return p
def get_parent_info(self,fid):
family = self.db.get_family_from_handle(fid)
father_id = family.get_father_handle()
mother_id = family.get_mother_handle()
if father_id:
father = self.db.get_person_from_handle(father_id)
fname = name_of(father)
else:
fname = u""
if mother_id:
mother = self.db.get_person_from_handle(mother_id)
mname = name_of(mother)
else:
mname = u""
return (fname,mname,family.get_gramps_id())
def parent_toggle(self,celltoggle,path,data):
model, column = data
if column == 0:
model[path][column] = not model[path][column]
else:
if not model[path][column]:
for index in range(0,len(model)):
model[index][column] = int(path)==int(index)
def build_info_page(self):
"""
Build initial druid page with the overall information about the process.
This is a static page, nothing fun here :-)
"""
p = DruidPageEdge(0)
p.set_title(_('Merge people'))
p.set_title_color(self.fg_color)
p.set_bg_color(self.bg_color)
p.set_logo(self.logo)
p.set_watermark(self.splash)
p.set_text(_('This document will help you through the '
'the process of merging two people.'))
return p
def get_event_info(self,person,handle):
date = ""
place = ""
if handle:
event = self.db.get_event_from_handle(handle)
return (event.get_date(),self.place_name(event))
else:
return (u"",u"")
def build_spouse_list(self,person,widget):
widget.clear()
for fam_handle in person.get_family_handle_list():
fam = self.db.get_family_from_handle(fam_handle)
if person.get_gender() == RelLib.Person.MALE:
spouse_handle = fam.get_mother_handle()
else:
spouse_handle = fam.get_father_handle()
if spouse_handle:
spouse = self.db.get_person_from_handle(spouse_handle)
else:
spouse = None
if spouse == None:
name = "unknown"
else:
sname = NameDisplay.displayer.display(spouse)
name = "%s [%s]" % (sname,spouse.get_gramps_id())
widget.add([name])
def set_field(self,widget,value):
"""Sets the string of the entry field at positions it a space 0"""
widget.set_text(value)
def place_name(self,event):
place_id = event.get_place_handle()
if place_id:
place = self.db.get_place_from_handle(place_id)
return "%s [%s]" % (place.get_title(),place.get_gramps_id())
else:
return ""
def on_merge_edit_clicked(self,obj):
import EditPerson
self.on_merge_clicked(obj)
# This needs to be fixed to provide an update call
EditPerson.EditPerson(self.parent,self.p1,self.db,self.ep_update)
def copy_note(self,one,two):
if one.get_note() != two.get_note():
one.set_note("%s\n\n%s" % (one.get_note(),two.get_note()))
def copy_sources(self,one,two):
slist = one.get_source_references()[:]
for xsrc in two.get_source_references():
for src in slist:
if src.are_equal(xsrc):
break
else:
one.add_source_reference(xsrc)
def merge_person_information(self,new,trans):
self.old_handle = self.p2.get_handle()
self.new_handle = self.p1.get_handle()
new.set_handle(new_handle)
new.set_gender(self.p1.get_gender())
self.merge_gramps_ids(new)
self.merge_names(new)
self.merge_birth(new)
self.merge_death(new)
self.merge_event_lists(new)
# copy attributes
new.set_attribute_list(self.p1.get_attribute_list() +
self.p2.get_attribute_list())
# copy addresses
new.set_address_list(self.p1.get_address_list() + self.p2.get_address_list())
# copy urls
new.set_url_list(self.p1.get_url_list() + self.p2.get_url_list())
# privacy
new.set_privacy(self.p1.get_privacy() or self.p2.get_privacy())
# sources
new.set_source_reference_list(self.p1.get_source_references() +
self.p2.get_source_references())
# note
note1 = self.p1.get_note_object()
note2 = self.p2.get_note_object()
new.set_note_object(self.merge_notes(note1,note2))
def on_merge_clicked(self,obj):
new = RelLib.Person()
trans = self.db.transaction_begin()
self.merge_person_information(new,trans)
self.merge_family_information(new,trans)
def convert_child_ids(self, family_id, id1, id2, trans):
new_list = []
change = False
family = self.db.get_family_from_handle(family_id)
for child_id in family.get_child_handle_list():
if child_id == id2:
new_list.append(id1)
change = True
else:
new_list.append(id2)
if change:
family.set_child_handle_list(new_list)
self.db.commit_family(family,trans)
def merge_parents(self, new, trans):
"""
Process for merging parents:
Case 1: Person 1 has parents, Person 2 doesn't
- Assign Person 1 list to new person
- Assign Person 1 al
Case 2: Person 2 has parents, Person 2 doesn't
- Assign Person 2 list to new person
- Loop through family list of Person 2, replacing ID2
with ID2 and recommitting the data
Case 3: Person 1 and Person 2 have parents, One select, the
other is not.
- Assign selected to list, changed ID references
- Remove ID from other family
Case 4: Person 1 and Person 2 have parents, one selected, other kept
- Assign selected to list, change IDs
- Assign second to list, change IDs
"""
f1_list = self.p1.get_parent_family_handle_list()
f2_list = self.p2.get_parent_family_handle_list()
if self.need_parents: # cases 1 and 2
if len(f1_list) == 0 and len(f2_list) > 0:
new.add_parent_family_handle(f2_list[0])
self.convert_child_ids(f2_list[0],self.new_handle,
self.old_handle, trans)
el
else: # cases 3 and 4
if len(f1_list) > 0:
new.set_parent_family_handle_list(f1_list)
elif len(f2_list) > 0:
new.set_parent_family_handle_list(f2_list)
for fid in f2:
family = self.db.get_family_from_id(fid)
if family.get_father_handle() == self.old_handle:
family.set_father_handle(self.new_handle)
if family.get_mother_handle() == self.old_handle:
family.set_mother_handle(self.new_handle)
self.db.commit_family(family, trans)
def merge_family_information(self, new, trans):
self.merge_parents(new, trans)
return
if self.glade.get_widget("bfather2").get_active():
orig_family_handle = self.p1.get_main_parents_family_handle()
if orig_family_handle:
orig_family = self.db.get_family_from_handle(orig_family_handle)
orig_family.remove_child_handle(new_handle)
self.db.commit_family(orig_family,trans)
(src_handle,mrel,frel) = self.p2.get_main_parents_family_handle()
if src_handle:
source_family = self.db.get_family_from_handle(src_handle)
if self.old_handle in source_family.get_child_handle_list():
source_family.remove_child_handle(self.old_handle)
self.p2.remove_parent_family_handle(src_handle)
if new_handle not in source_family.get_child_handle_list():
source_family.add_child_handle(new_handle)
new.add_parent_family_handle(src_handle,mrel,frel)
self.db.commit_family(source_family,trans)
new.set_main_parent_family_handle(src_handle)
else:
src_handle = self.p2.get_main_parents_family_handle()
if src_handle:
source_family = self.db.get_family_from_handle(src_handle)
source_family.remove_child_handle(self.p2)
self.p2.set_main_parent_family_handle(None)
self.db.commit_family(source_family,trans)
self.merge_families(trans)
for photo in self.p2.get_media_list():
self.p1.add_media_reference(photo)
if self.p1.get_nick_name() == "":
self.p1.set_nick_name(self.p2.get_nick_name())
if self.p2.get_note() != "":
old_note = self.p1.get_note()
if old_note:
old_note = old_note + "\n\n"
self.p1.set_note(old_note + self.p2.get_note())
self.copy_sources(self.p1,self.p2)
self.db.remove_person(self.p2.get_handle(),trans)
self.db.commit_person(self.p1,trans)
self.update(self.p1,self.p2,old_id)
self.db.transaction_commit(trans,_("Merge Person"))
Utils.destroy_passed_object(self.top)
def find_family(self,family):
if self.p1.get_gender() == RelLib.Person.MALE:
mother = family.get_mother_handle()
father = self.p1.get_handle()
else:
father = family.get_father_handle()
mother = self.p1.get_handle()
for myfamily_handle in self.db.get_family_handles():
myfamily = self.db.get_family_from_handle(myfamily_handle)
if (myfamily.get_father_handle() == father and
myfamily.get_mother_handle() == mother):
return myfamily
return None
def merge_family_pair(self,tgt_family,src_family,trans):
# copy children from source to target
for child_handle in src_family.get_child_handle_list():
if child_handle not in tgt_family.get_child_handle_list():
child = self.db.get_person_from_handle(child_handle)
parents = child.get_parent_family_handle_list()
tgt_family.add_child_handle(child)
if child.get_main_parents_family_handle() == src_family:
child.set_main_parent_family_handle(tgt_family)
i = 0
for fam in parents[:]:
if fam[0] == src_family.get_handle():
parents[i] = (tgt_family,fam[1],fam[2])
i += 1
self.db.commit_person(child,trans)
# merge family events
lst = tgt_family.get_event_list()[:]
for xdata in src_family.get_event_list():
for data in lst:
if data.are_equal(xdata):
self.copy_note(data,xdata)
self.copy_sources(data,xdata)
break
else:
tgt_family.add_event(xdata)
# merge family attributes
lst = tgt_family.get_attribute_list()[:]
for xdata in src_family.get_attribute_list():
for data in lst:
if data.get_type() == xdata.get_type() and \
data.getValue() == xdata.get_value():
self.copy_note(data,xdata)
self.copy_sources(data,xdata)
break
else:
tgt_family.add_attribute(xdata)
# merge family notes
if src_family.get_note() != "":
old_note = tgt_family.get_note()
if old_note:
old_note = old_note + "\n\n"
tgt_family.set_note(old_note + src_family.get_note())
# merge family top-level sources
self.copy_sources(tgt_family,src_family)
# merge multimedia objects
for photo in src_family.get_media_list():
tgt_family.add_media_reference(photo)
def merge_families(self,trans):
family_num = 0
for src_family_handle in self.p2.get_family_handle_list():
src_family = self.db.get_family_from_handle(src_family_handle)
family_num += 1
if not src_family:
continue
if src_family in self.p1.get_family_handle_list():
continue
tgt_family = self.find_family(src_family)
#
# This is the case where a new family to be added to the
# p1 as a result of the merge already exists as a
# family. In this case, we need to remove the old source
# family (with the pre-merge identity of the p1) from
# both the parents
#
if tgt_family in self.p1.get_family_handle_list():
if tgt_family.get_father_handle() != None and \
src_family in tgt_family.get_family_handle_list():
tgt_family.get_father_handle().remove_family_handle(src_family)
if tgt_family.get_mother_handle() != None and \
src_family in tgt_family.get_mother_handle().get_family_handle_list():
tgt_family.get_mother_handle().remove_family_handle(src_family)
self.merge_family_pair(tgt_family,src_family,trans)
# delete the old source family
self.db.remove_family(src_family,trans)
self.db.commit_family(tgt_family,trans)
continue
# This is the case where a new family to be added
# and it is not already in the list.
if tgt_family:
# tgt_family a duplicate family, transfer children from
# the p2 family, and delete the family. Not sure
# what to do about marriage/divorce date/place yet.
# transfer child to new family, alter children to
# point to the correct family
self.merge_family_pair(tgt_family,src_family,trans)
# change parents of the family to point to the new
# family
father_handle = src_family.get_father_handle()
if father_handle:
father = self.db.get_father_from_handle(father_handle)
father.remove_family_handle(src_family.get_handle())
father.add_family_handle(tgt_family.get_handle())
self.db.commit_person(father,trans)
mother_handle = src_family.get_mother_handle()
if mother_handle:
mother = self.db.get_mother_from_handle(mother_handle)
mother.remove_family_handle(src_family.get_handle())
mother.add_family_handle(tgt_family.get_handle())
self.db.commit_person(mother,trans)
self.db.remove_family(src_family.get_handle())
else:
if src_family not in self.p1.get_family_handle_list():
self.p1.add_family_handle(src_family)
if self.p1.get_gender() == RelLib.Person.MALE:
src_family.set_father_handle(self.p1)
else:
src_family.set_mother_handle(self.p1)
self.remove_marriage(src_family,self.p2,trans)
# a little debugging here
cursor = self.db.get_family_cursor()
data = cursor.first()
while data:
fam = RelLib.Family()
fam.unserialize(data[1])
if self.p2 in fam.get_child_handle_list():
fam.remove_child_handle(self.p2)
fam.add_child_handle(self.p1)
if self.p2 == fam.get_father_handle():
fam.set_father_handle(self.p1)
if self.p2 == fam.get_mother_handle():
fam.set_mother_handle(self.p1)
if fam.get_father_handle() == None and fam.get_mother_handle() == None:
self.delete_empty_family(fam)
data = cursor.next()
def remove_marriage(self,family,person,trans):
if person:
person.remove_family_handle(family)
if family.get_father_handle() == None and family.get_mother_handle() == None:
self.delete_empty_family(family,trans)
def delete_empty_family(self,family,trans):
for child_handle in family.get_child_handle_list():
child = self.db.get_person_from_handle(child_handle)
if child.get_main_parents_family_handle() == family_handle:
child.set_main_parent_family_handle(None)
else:
child.remove_parent_family_handle(family_handle)
self.db.commit_person(child,trans)
self.db.remove_family(family_handle,trans)
def merge_gramps_ids(self,new):
if self.id1.get_active():
new.set_gramps_id(self.p1.get_gramps_id())
other_id = self.p2.get_gramps_id()
else:
new.set_gramps_id(self.p2.get_gramps_id())
other_id = self.p1.get_gramps_id()
if self.keepid:
attr = RelLib.Attribute()
attr.set_type('Merged GRAMPS ID')
attr.set_value(other_id)
new.add_attribute(attr)
def merge_notes(self, note1, note2):
if note1 and not note2:
return note1
elif not note1 and note2:
return note2
elif note1 and note2:
note1.append("\n" + note2.get())
note1.set_format(note1.get_format() or note2.get_format())
return note1
return None
def merge_names(self, new):
if self.name1.get_active():
new.set_primary_name(self.p1.get_primary_name())
alt = self.p2.get_primary_name()
else:
new.set_primary_name(self.p2.get_primary_name())
alt = self.p1.get_primary_name()
if self.altnames.get_active():
new.add_alternate_name(alt)
for name in self.p1.get_alternate_names():
new.add_alternate_name(name)
for name in self.p2.get_alternate_names():
new.add_alternate_name(name)
def merge_death(self, new):
handle1 = self.p1.get_death_handle()
handle2 = self.p2.get_death_handle()
if not self.need_death:
if handle1:
new.set_death_handle(handle1)
if handle2:
new.set_death_handle(handle2)
else:
if self.death1.get_active():
new.set_death_handle(handle1)
alt_handle = handle2
else:
new.set_death_handle(handle2)
alt_handle = handle1
if self.keepdeath:
event = self.db.get_event_from_handle(alt_handle)
event.set_handle(None)
event.db.set_name('Alternate Death')
self.db.add_event(event,trans)
new.add_event_handle(event.get_handle())
def merge_birth(self, new):
handle1 = self.p1.get_birth_handle()
handle2 = self.p2.get_birth_handle()
if not self.need_birth:
if handle1:
new.set_birth_handle(handle1)
if handle2:
new.set_birth_handle(handle2)
else:
if self.birth1.get_active():
new.set_birth_handle(handle1)
alt_handle = handle2
else:
new.set_birth_handle(handle2)
alt_handle = handle1
if self.keepbirth:
event = self.db.get_event_from_handle(alt_handle)
event.set_handle(None)
event.set_name('Alternate Birth')
self.db.add_event(event,trans)
new.add_event_handle(event.get_handle())
def merge_event_lists(self, new):
data_list = self.p1.get_event_list()
for handle in self.p2.get_event_list():
if handle not in data_list:
events.append(handle)
new.set_event_list(data_list)
def compare_people(p1,p2):
name1 = p1.get_primary_name()
name2 = p2.get_primary_name()
chance = name_match(name1,name2)
if chance == -1.0 :
return -1.0
birth1 = p1.get_birth_handle()
death1 = p1.get_death_handle()
birth2 = p2.get_birth_handle()
death2 = p2.get_death_handle()
value = date_match(birth1.get_date_object(),birth2.get_date_object())
if value == -1.0 :
return -1.0
chance = chance + value
value = date_match(death1.get_date_object(),death2.get_date_object())
if value == -1.0 :
return -1.0
chance = chance + value
value = place_match(birth1.get_place_handle(),birth2.get_place_handle())
if value == -1.0 :
return -1.0
chance = chance + value
value = place_match(death1.get_place_handle(),death2.get_place_handle())
if value == -1.0 :
return -1.0
chance = chance + value
ancestors = []
ancestors_of(p1,ancestors)
if p2 in ancestors:
return -1.0
ancestors = []
ancestors_of(p2,ancestors)
if p1 in ancestors:
return -1.0
f1 = p1.get_main_parents_family_handle()
f2 = p2.get_main_parents_family_handle()
if f1 and f1.get_father_handle():
dad1 = f1.get_father_handle().get_primary_name()
else:
dad1 = None
if f2 and f2.get_father_handle():
dad2 = f2.get_father_handle().get_primary_name()
else:
dad2 = None
value = name_match(dad1,dad2)
if value == -1.0:
return -1.0
chance = chance + value
if f1 and f1.get_mother_handle():
mom1 = f1.get_mother_handle().get_primary_name()
else:
mom1 = None
if f2 and f2.get_mother_handle():
mom2 = f2.get_mother_handle().get_primary_name()
else:
mom2 = None
value = name_match(mom1,mom2)
if value == -1.0:
return -1.0
chance = chance + value
for f1 in p1.get_family_handle_list():
for f2 in p2.get_family_handle_list():
if p1.get_gender() == RelLib.Person.FEMALE:
father1 = f1.get_father_handle()
father2 = f2.get_father_handle()
if father1 and father2:
if father1 == father2:
chance = chance + 1.0
else:
fname1 = NameDisplay.displayer.display(father1)
fname2 = NameDisplay.displayer.display(father2)
value = name_match(fname1,fname2)
if value != -1.0:
chance = chance + value
else:
mother1 = f1.get_mother_handle()
mother2 = f2.get_mother_handle()
if mother1 and mother2:
if mother1 == mother2:
chance = chance + 1.0
else:
mname1 = NameDisplay.displayer.display(mother1)
mname2 = NameDisplay.displayer.display(mother2)
value = name_match(mname1,mname2)
if value != -1.0:
chance = chance + value
return chance
#-----------------------------------------------------------------
#
#
#
#-----------------------------------------------------------------
def name_compare(s1,s2):
return s1 == s2
#-----------------------------------------------------------------
#
#
#
#-----------------------------------------------------------------
def date_match(date1,date2):
if date1.get_date() == "" or date2.get_date() == "":
return 0.0
if date1.get_date() == date2.get_date():
return 1.0
if date1.isRange() or date2.isRange():
return range_compare(date1,date2)
date1 = date1.get_start_date()
date2 = date2.get_start_date()
if date1.getYear() == date2.getYear():
if date1.getMonth() == date2.getMonth():
return 0.75
if not date1.getMonthValid() or not date2.getMonthValid():
return 0.75
else:
return -1.0
else:
return -1.0
#-----------------------------------------------------------------
#
#
#
#-----------------------------------------------------------------
def range_compare(date1,date2):
d1_start = date1.get_start_date()
d2_start = date2.get_start_date()
d1_stop = date1.get_stop_date()
d2_stop = date2.get_stop_date()
if date1.isRange() and date2.isRange():
if d1_start >= d2_start and d1_start <= d2_stop or \
d2_start >= d1_start and d2_start <= d1_stop or \
d1_stop >= d2_start and d1_stop <= d2_stop or \
d2_stop >= d1_start and d2_stop <= d1_stop:
return 0.5
else:
return -1.0
elif date2.isRange():
if d1_start >= d2_start and d1_start <= d2_stop:
return 0.5
else:
return -1.0
else:
if d2_start >= d1_start and d2_start <= d1_stop:
return 0.5
else:
return -1.0
#---------------------------------------------------------------------
#
#
#
#---------------------------------------------------------------------
def name_match(name,name1):
if not name1 or not name:
return 0
srn1 = name.get_surname()
sfx1 = name.get_suffix()
srn2 = name1.get_surname()
sfx2 = name1.get_suffix()
if not name_compare(srn1,srn2):
return -1
if sfx1 != sfx2:
if sfx1 != "" and sfx2 != "":
return -1
if name.get_first_name() == name1.get_first_name():
return 1
else:
list1 = name.get_first_name().split()
list2 = name1.get_first_name().split()
if len(list1) < len(list2):
return list_reduce(list1,list2)
else:
return list_reduce(list2,list1)
#---------------------------------------------------------------------
#
#
#
#---------------------------------------------------------------------
def list_reduce(list1,list2):
value = 0
for name in list1:
for name2 in list2:
if is_initial(name) and name[0] == name2[0]:
value = value + 0.25
break
if is_initial(name2) and name2[0] == name[0]:
value = value + 0.25
break
if name == name2:
value = value + 0.5
break
if name[0] == name2[0] and name_compare(name,name2):
value = value + 0.25
break
if value == 0:
return -1
else:
return min(value,1)
#---------------------------------------------------------------------
#
#
#
#---------------------------------------------------------------------
def place_match(p1,p2):
if p1 == p2:
return 1
if p1 == None:
name1 = ""
else:
name1 = p1.get_title()
if p2 == None:
name2 = ""
else:
name2 = p2.get_title()
if name1 == "" or name2 == "":
return 0
if name1 == name2:
return 1
list1 = name1.replace(","," ").split()
list2 = name2.replace(","," ").split()
value = 0
for name in list1:
for name2 in list2:
if name == name2:
value = value + 0.5
break
if name[0] == name2[0] and name_compare(name,name2):
value = value + 0.25
break
if value == 0:
return -1
else:
return min(value,1)
#-------------------------------------------------------------------------
#
#
#
#-------------------------------------------------------------------------
def is_initial(name):
if len(name) > 2:
return 0
elif len(name) == 2:
if name[0].isupper() and name[1] == '.':
return 1
else:
return name[0].isupper()
#---------------------------------------------------------------------
#
#
#
#---------------------------------------------------------------------
def ancestors_of(p1,lst):
if p1 == None:
return
lst.append(p1)
f1 = p1.get_main_parents_family_handle()
if f1 != None:
ancestors_of(f1.get_father_handle(),lst)
ancestors_of(f1.get_mother_handle(),lst)
#---------------------------------------------------------------------
#
#
#
#---------------------------------------------------------------------
def name_of(p):
if not p:
return ""
return "%s [%s]" % (NameDisplay.displayer.display(p),p.get_gramps_id())
#-------------------------------------------------------------------------
#
# Merge Places
#
#-------------------------------------------------------------------------
class MergePlaces:
"""
Merges to places into a single place. Displays a dialog box that
allows the places to be combined into one.
"""
def __init__(self,database,new_handle,old_handle,update):
self.db = database
self.new_handle = new_handle
self.old_handle = old_handle
self.p1 = self.db.get_place_from_handle(self.new_handle)
self.p2 = self.db.get_place_from_handle(self.old_handle)
self.update = update
self.trans = self.db.transaction_begin()
self.glade = gtk.glade.XML(const.mergeFile,"merge_places","gramps")
self.top = self.glade.get_widget("merge_places")
Utils.set_titles(self.top,self.glade.get_widget('title'),
_("Select title"))
self.glade.get_widget("title1_text").set_text(self.p1.get_title())
self.glade.get_widget("title2_text").set_text(self.p2.get_title())
self.t3 = self.glade.get_widget("title3_text")
self.t3.set_text(self.p1.get_title())
self.glade.signal_autoconnect({
"destroy_passed_object" : Utils.destroy_passed_object,
"on_merge_places_clicked" : self.on_merge_places_clicked,
})
self.top.show()
def on_merge_places_clicked(self,obj):
"""
Performs the merge of the places when the merge button is clicked.
"""
t2active = self.glade.get_widget("title2").get_active()
if t2active:
self.p1.set_title(self.p2.get_title())
elif self.glade.get_widget("title3").get_active():
self.p1.set_title(unicode(self.t3.get_text()))
# Set longitude
if self.p1.get_longitude() == "" and self.p2.get_longitude() != "":
self.p1.set_longitude(self.p2.get_longitude())
# Set latitude
if self.p1.get_latitude() == "" and self.p2.get_latitude() != "":
self.p1.set_latitude(self.p2.get_latitude())
# Add URLs from P2 to P1
for url in self.p2.get_url_list():
self.p1.add_url(url)
# Copy photos from P2 to P1
for photo in self.p2.get_media_list():
self.p1.add_media_reference(photo)
# Copy sources from P2 to P1
for source in self.p2.get_source_references():
self.p1.add_source(source)
# Add notes from P2 to P1
note = self.p2.get_note()
if note != "":
if self.p1.get_note() == "":
self.p1.set_note(note)
elif self.p1.get_note() != note:
self.p1.set_note("%s\n\n%s" % (self.p1.get_note(),note))
if t2active:
lst = [self.p1.get_main_location()] + self.p1.get_alternate_locations()
self.p1.set_main_location(self.p2.get_main_location())
for l in lst:
if not l.is_empty():
self.p1.add_alternate_locations(l)
else:
lst = [self.p2.get_main_location()] + self.p2.get_alternate_locations()
for l in lst:
if not l.is_empty():
self.p1.add_alternate_locations(l)
# remove old and commit new source
self.db.remove_place(self.old_handle,self.trans)
self.db.commit_place(self.p1,self.trans)
# replace references in other objetcs
# events
for handle in self.db.get_event_handles():
event = self.db.get_event_from_handle(handle)
if event.get_place_handle() == self.old_handle:
event.set_place_handle(self.new_handle)
# personal LDS ordinances
for handle in self.db.get_person_handles(sort_handles=False):
person = self.db.get_person_from_handle(handle)
ord_list = [ordinance for ordinance \
in [person.lds_bapt,person.lds_endow,person.lds_seal]
if (ordinance and \
ordinance.get_place_handle() == self.old_handle)
]
if ord_list:
for ordinance in ord_list:
ordinance.set_place_handle(self.new_handle)
self.db.commit_person(person,self.trans)
# family LDS ordinance
for handle in self.db.get_family_handles():
family = self.db.get_family_from_handle(handle)
if family.lds_seal and \
family.lds_seal.get_place_handle() == self.old_handle:
family.lds_seal.set_place_handle(self.new_handle)
self.db.commit_family(family,self.trans)
self.db.transaction_commit(self.trans,_("Merge Places"))
self.update()
Utils.destroy_passed_object(obj)
#-------------------------------------------------------------------------
#
# Merge Sources
#
#-------------------------------------------------------------------------
class MergeSources:
"""
Merges to places into a single place. Displays a dialog box that
allows the places to be combined into one.
"""
def __init__(self,database,new_handle,old_handle,update):
self.db = database
self.new_handle = new_handle
self.old_handle = old_handle
self.src1 = self.db.get_source_from_handle(self.new_handle)
self.src2 = self.db.get_source_from_handle(self.old_handle)
self.update = update
self.glade = gtk.glade.XML(const.mergeFile,"merge_sources","gramps")
self.top = self.glade.get_widget("merge_sources")
self.title1 = self.glade.get_widget("title1")
self.title2 = self.glade.get_widget("title2")
self.title1.set_text(self.src1.get_title())
self.title2.set_text(self.src2.get_title())
self.author1 = self.glade.get_widget("author1")
self.author2 = self.glade.get_widget("author2")
self.author1.set_text(self.src1.get_author())
self.author2.set_text(self.src2.get_author())
self.abbrev1 = self.glade.get_widget("abbrev1")
self.abbrev2 = self.glade.get_widget("abbrev2")
self.abbrev1.set_text(self.src1.get_abbreviation())
self.abbrev2.set_text(self.src2.get_abbreviation())
self.pub1 = self.glade.get_widget("pub1")
self.pub2 = self.glade.get_widget("pub2")
self.pub1.set_text(self.src1.get_publication_info())
self.pub2.set_text(self.src2.get_publication_info())
self.gramps1 = self.glade.get_widget("gramps1")
self.gramps2 = self.glade.get_widget("gramps2")
self.gramps1.set_text(self.src1.get_gramps_id())
self.gramps2.set_text(self.src2.get_gramps_id())
self.glade.get_widget('ok').connect('clicked',self.merge)
self.glade.get_widget('cancel').connect('clicked',self.close)
self.trans = self.db.transaction_begin()
self.top.show()
def close(self,obj):
self.top.destroy()
def merge(self,obj):
"""
Performs the merge of the sources when the merge button is clicked.
"""
use_title1 = self.glade.get_widget("title_btn1").get_active()
use_author1 = self.glade.get_widget("author_btn1").get_active()
use_abbrev1 = self.glade.get_widget("abbrev_btn1").get_active()
use_pub1 = self.glade.get_widget("pub_btn1").get_active()
use_gramps1 = self.glade.get_widget("gramps_btn1").get_active()
if not use_title1:
self.src1.set_title(self.src2.get_title())
if not use_author1:
self.src1.set_author(self.src2.get_author())
if not use_abbrev1:
self.src1.set_abbreviation(self.src2.get_abbreviation())
if not use_pub1:
self.src1.set_publication_info(self.src2.get_publication_info())
if not use_gramps1:
self.src1.set_gramps_id(self.src2.get_gramps_id())
# Copy photos from src2 to src1
for photo in self.src2.get_media_list():
self.src1.add_media_reference(photo)
# Add notes from P2 to P1
note = self.src2.get_note()
if note != "":
if self.src1.get_note() == "":
self.src1.set_note(note)
elif self.src1.get_note() != note:
self.src1.set_note("%s\n\n%s" % (self.src1.get_note(),note))
src2_map = self.src2.get_data_map()
src1_map = self.src1.get_data_map()
for key in src2_map.keys():
if not src1_map.has_key(key):
src1_map[key] = src2_map[key]
# replace references in other objetcs
self.db.remove_source(self.old_handle,self.trans)
self.db.commit_source(self.src1,self.trans)
# replace handles
# people
for handle in self.db.get_person_handles(sort_handles=False):
person = self.db.get_person_from_handle(handle)
if person.has_source_reference(self.old_handle):
person.replace_source_references(self.old_handle,self.new_handle)
self.db.commit_person(person,self.trans)
# family
for handle in self.db.get_family_handles():
family = self.db.get_family_from_handle(handle)
if family.has_source_reference(self.old_handle):
family.replace_source_references(self.old_handle,self.new_handle)
self.db.commit_family(family,self.trans)
# events
for handle in self.db.get_event_handles():
event = self.db.get_event_from_handle(handle)
if event.has_source_reference(self.old_handle):
event.replace_source_references(self.old_handle,self.new_handle)
self.db.commit_event(event,self.trans)
# sources
for handle in self.db.get_source_handles():
source = self.db.get_source_from_handle(handle)
if source.has_source_reference(self.old_handle):
source.replace_source_references(self.old_handle,self.new_handle)
self.db.commit_source(source,self.trans)
# places
for handle in self.db.get_place_handles():
place = self.db.get_place_from_handle(handle)
if place.has_source_reference(self.old_handle):
place.replace_source_references(self.old_handle,self.new_handle)
self.db.commit_place(place,self.trans)
# media
for handle in self.db.get_media_object_handles():
obj = self.db.get_object_from_handle(handle)
if obj.has_source_reference(self.old_handle):
obj.replace_source_references(self.old_handle,self.new_handle)
self.db.commit_media_object(obj,self.trans)
self.db.transaction_commit(self.trans,_("Merge Sources"))
self.top.destroy()