From 9bc91af2de58d292bb71ff82aaabecfc4f22ba93 Mon Sep 17 00:00:00 2001 From: SNoiraud Date: Mon, 22 Feb 2016 15:27:33 +0100 Subject: [PATCH 1/8] 5449: Pedigree view crashes if you happen to choose a child as ancestor --- gramps/gen/utils/alive.py | 8 ++ gramps/plugins/tool/findloop.glade | 106 ++++++++++++++ gramps/plugins/tool/findloop.py | 215 +++++++++++++++++++++++++++++ gramps/plugins/tool/tools.gpr.py | 23 +++ 4 files changed, 352 insertions(+) create mode 100644 gramps/plugins/tool/findloop.glade create mode 100644 gramps/plugins/tool/findloop.py diff --git a/gramps/gen/utils/alive.py b/gramps/gen/utils/alive.py index 3f6e5f045..ba47375ad 100644 --- a/gramps/gen/utils/alive.py +++ b/gramps/gen/utils/alive.py @@ -86,6 +86,7 @@ class ProbablyAlive(object): self.MAX_SIB_AGE_DIFF = max_sib_age_diff self.MAX_AGE_PROB_ALIVE = max_age_prob_alive self.AVG_GENERATION_GAP = avg_generation_gap + self.plist = [] def probably_alive_range(self, person, is_spouse=False): # FIXME: some of these computed dates need to be a span. For @@ -95,6 +96,7 @@ class ProbablyAlive(object): # "between 1930 and 1940") if person is None: return (None, None, "", None) + self.plist = [] birth_ref = person.get_birth_ref() death_ref = person.get_death_ref() death_date = None @@ -272,6 +274,9 @@ class ProbablyAlive(object): # ago. def descendants_too_old (person, years): + if person in self.plist: + return (None, None, "", None) + self.plist.append(person) for family_handle in person.get_family_handle_list(): family = self.db.get_family_from_handle(family_handle) if not family: @@ -343,6 +348,9 @@ class ProbablyAlive(object): return (date1, date2, explain, other) def ancestors_too_old(person, year): + if person in self.plist: + return (None, None, "", None) + self.plist.append(person) LOG.debug("ancestors_too_old('%s', %s)".format( name_displayer.display(person), year) ) family_handle = person.get_main_parents_family_handle() diff --git a/gramps/plugins/tool/findloop.glade b/gramps/plugins/tool/findloop.glade new file mode 100644 index 000000000..0bb214452 --- /dev/null +++ b/gramps/plugins/tool/findloop.glade @@ -0,0 +1,106 @@ + + + + + + False + 450 + 400 + dialog + + + + True + False + vertical + + + True + False + end + + + gtk-close + False + True + True + True + False + True + + + + False + False + 0 + + + + + + + + False + True + end + 0 + + + + + True + False + 6 + vertical + 6 + + + True + False + center + + + False + False + 8 + 0 + + + + + True + True + out + + + True + True + + + + + + + + True + True + 1 + + + + + True + True + 1 + + + + + + + + + close + + + diff --git a/gramps/plugins/tool/findloop.py b/gramps/plugins/tool/findloop.py new file mode 100644 index 000000000..9a947feae --- /dev/null +++ b/gramps/plugins/tool/findloop.py @@ -0,0 +1,215 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2007-2009 Stephane Charette +# Copyright (C) 2016- Serge Noiraud +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +"Find possible loop in a people descendance" + +#------------------------------------------------------------------------ +# +# GNOME/GTK modules +# +#------------------------------------------------------------------------ +from gi.repository import Gtk +from gi.repository import GObject + +#------------------------------------------------------------------------ +# +# Gramps modules +# +#------------------------------------------------------------------------ +from gramps.gen.const import GRAMPS_LOCALE as glocale +_ = glocale.translation.sgettext +ngettext = glocale.translation.ngettext # else "nearby" comments are ignored +from gramps.gen.const import URL_MANUAL_PAGE +from gramps.gui.plug import tool +from gramps.gen.plug.report import utils as ReportUtils +from gramps.gui.editors import EditPerson, EditFamily +from gramps.gui.managedwindow import ManagedWindow +from gramps.gui.utils import ProgressMeter +from gramps.gui.display import display_help +from gramps.gui.glade import Glade +from gramps.gen.lib import Tag +from gramps.gen.db import DbTxn +from gramps.gen.display.name import displayer as _nd +from gramps.gui.editors import EditFamily + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + +#------------------------------------------------------------------------ +# +# FindLoop class +# +#------------------------------------------------------------------------ +class FindLoop(ManagedWindow) : + + def __init__(self, dbstate, user, options_class, name, callback=None): + uistate = user.uistate + + self.title = _('Find possible loop') + ManagedWindow.__init__(self, uistate, [], self.__class__) + self.dbstate = dbstate + self.uistate = uistate + self.db = dbstate.db + + topDialog = Glade() + + topDialog.connect_signals({ + "destroy_passed_object" : self.close, + "on_help_clicked" : self.on_help_clicked, + "on_delete_event" : self.close, + }) + + window = topDialog.toplevel + title = topDialog.get_object("title") + self.set_window(window, title, self.title) + + # start the progress indicator + self.progress = ProgressMeter(self.title,_('Starting'), + parent=self.window) + self.progress.set_pass(_('Looking for possible loop for each person'), + self.db.get_number_of_people()) + + self.model = Gtk.ListStore( + GObject.TYPE_STRING, # 0==father id + GObject.TYPE_STRING, # 1==father + GObject.TYPE_STRING, # 2==son id + GObject.TYPE_STRING, # 3==son + GObject.TYPE_STRING) # 4==family gid + + self.treeView = topDialog.get_object("treeview") + self.treeView.set_model(self.model) + col1 = Gtk.TreeViewColumn(_('Gramps ID'), Gtk.CellRendererText(), text=0) + col2 = Gtk.TreeViewColumn(_('Ancestor'), Gtk.CellRendererText(), text=1) + col3 = Gtk.TreeViewColumn(_('Gramps ID'), Gtk.CellRendererText(), text=2) + col4 = Gtk.TreeViewColumn(_('Descendant'), Gtk.CellRendererText(), text=3) + col5 = Gtk.TreeViewColumn(_('Family ID'), Gtk.CellRendererText(), text=4) + col1.set_resizable(True) + col2.set_resizable(True) + col3.set_resizable(True) + col4.set_resizable(True) + col5.set_resizable(True) + col1.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col2.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col3.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col4.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col5.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) + col1.set_sort_column_id(0) + col2.set_sort_column_id(1) + col3.set_sort_column_id(2) + col4.set_sort_column_id(3) + col5.set_sort_column_id(4) + self.treeView.append_column(col1) + self.treeView.append_column(col2) + self.treeView.append_column(col3) + self.treeView.append_column(col4) + self.treeView.append_column(col5) + self.treeSelection = self.treeView.get_selection() + self.treeView.connect('row-activated', self.rowActivated) + + people = self.db.get_person_handles() + count = 0 + for person_handle in people: + person = self.db.get_person_from_handle(person_handle) + count += 1 + self.current = person + self.parent = None + self.descendants(person_handle, []) + self.progress.set_header("%d/%d" % (count, len(people))) + self.progress.step() + + # close the progress bar + self.progress.close() + + self.show() + + def descendants(self, person_handle, new_list): + person = self.db.get_person_from_handle(person_handle) + plist = [] + for item in new_list: + plist.append(item) + if person in plist: + # We found one loop + father_id = self.current.get_gramps_id() + father = _nd.display(self.current) + son_id = self.parent.get_gramps_id() + son = _nd.display(self.parent) + value = (father_id, father, son_id, son, self.curr_fam) + found = False + for pth in range(len(self.model)): + path = Gtk.TreePath(pth) + treeiter = self.model.get_iter(path) + find = (self.model.get_value(treeiter, 0), + self.model.get_value(treeiter, 1), + self.model.get_value(treeiter, 2), + self.model.get_value(treeiter, 3), + self.model.get_value(treeiter, 4)) + if find == value: + found = True + if not found: + self.model.append(value) + return + plist.append(person) + for family_handle in person.get_family_handle_list(): + family = self.db.get_family_from_handle(family_handle) + self.curr_fam = family.get_gramps_id() + if not family: + # can happen with LivingProxyDb(PrivateProxyDb(db)) + continue + for child_ref in family.get_child_ref_list(): + child_handle = child_ref.ref + self.parent = person + self.descendants(child_handle, plist) + + def rowActivated(self, treeView, path, column) : + # first we need to check that the row corresponds to a person + iter = self.model.get_iter(path) + From_id = self.model.get_value(iter, 0) + To_id = self.model.get_value(iter, 2) + Fam_id = self.model.get_value(iter, 4) + fam = self.dbstate.db.get_family_from_gramps_id(Fam_id) + if fam: + try: + EditFamily(self.dbstate, self.uistate, [], fam) + except WindowActiveError: + pass + return True + return False + + def on_help_clicked(self, obj): + """Display the relevant portion of GRAMPS manual""" + pass + + +#------------------------------------------------------------------------ +# +# FindLoopOptions +# +#------------------------------------------------------------------------ +class FindLoopOptions(tool.ToolOptions): + """ + Defines options and provides handling interface. + """ + def __init__(self, name, person_id=None): + """ Initialize the options class """ + tool.ToolOptions.__init__(self, name, person_id) diff --git a/gramps/plugins/tool/tools.gpr.py b/gramps/plugins/tool/tools.gpr.py index 8ff1f9696..5db7edd31 100644 --- a/gramps/plugins/tool/tools.gpr.py +++ b/gramps/plugins/tool/tools.gpr.py @@ -474,3 +474,26 @@ optionclass = 'MergeCitationsOptions', tool_modes = [TOOL_MODE_GUI] ) +#------------------------------------------------------------------------ +# +# Find Possible Loop +# +#------------------------------------------------------------------------ + +register(TOOL, +id = 'loop', +name = _("Find Possible Loop"), +description = _("Searches the entire database, looking for " + "a possible loop."), +version = '1.0', +gramps_target_version = MODULE_VERSION, +status = STABLE, +fname = 'findloop.py', +authors = ["Serge Noiraud"], +authors_email = ["serge.noiraud@free.fr"], +category = TOOL_UTILS, +toolclass = 'FindLoop', +optionclass = 'FindLoopOptions', +tool_modes = [TOOL_MODE_GUI, TOOL_MODE_CLI] + ) + From 0650bd86436e4a9e515265ac9ac9d96e2adbf5d4 Mon Sep 17 00:00:00 2001 From: SNoiraud Date: Thu, 25 Feb 2016 11:01:11 +0100 Subject: [PATCH 2/8] 5449: Convert list to set --- gramps/gen/utils/alive.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gramps/gen/utils/alive.py b/gramps/gen/utils/alive.py index ba47375ad..dbbe7b796 100644 --- a/gramps/gen/utils/alive.py +++ b/gramps/gen/utils/alive.py @@ -86,7 +86,7 @@ class ProbablyAlive(object): self.MAX_SIB_AGE_DIFF = max_sib_age_diff self.MAX_AGE_PROB_ALIVE = max_age_prob_alive self.AVG_GENERATION_GAP = avg_generation_gap - self.plist = [] + self.pset = set() def probably_alive_range(self, person, is_spouse=False): # FIXME: some of these computed dates need to be a span. For @@ -96,7 +96,7 @@ class ProbablyAlive(object): # "between 1930 and 1940") if person is None: return (None, None, "", None) - self.plist = [] + self.pset = set() birth_ref = person.get_birth_ref() death_ref = person.get_death_ref() death_date = None @@ -274,9 +274,9 @@ class ProbablyAlive(object): # ago. def descendants_too_old (person, years): - if person in self.plist: + if person.handle in self.pset: return (None, None, "", None) - self.plist.append(person) + self.pset.add(person.handle) for family_handle in person.get_family_handle_list(): family = self.db.get_family_from_handle(family_handle) if not family: @@ -348,9 +348,9 @@ class ProbablyAlive(object): return (date1, date2, explain, other) def ancestors_too_old(person, year): - if person in self.plist: + if person.handle in self.pset: return (None, None, "", None) - self.plist.append(person) + self.pset.add(person.handle) LOG.debug("ancestors_too_old('%s', %s)".format( name_displayer.display(person), year) ) family_handle = person.get_main_parents_family_handle() From 3fe0aaa88acefcc00468d3e436b0f3c0c51ce2ba Mon Sep 17 00:00:00 2001 From: SNoiraud Date: Sun, 10 Apr 2016 09:55:50 +0200 Subject: [PATCH 3/8] 5449: Pedigree view crashes ... : trailing space and replace list() by set() --- gramps/plugins/tool/findloop.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/gramps/plugins/tool/findloop.py b/gramps/plugins/tool/findloop.py index 9a947feae..892d4d518 100644 --- a/gramps/plugins/tool/findloop.py +++ b/gramps/plugins/tool/findloop.py @@ -87,7 +87,7 @@ class FindLoop(ManagedWindow) : # start the progress indicator self.progress = ProgressMeter(self.title,_('Starting'), parent=self.window) - self.progress.set_pass(_('Looking for possible loop for each person'), + self.progress.set_pass(_('Looking for possible loop for each person'), self.db.get_number_of_people()) self.model = Gtk.ListStore( @@ -95,7 +95,7 @@ class FindLoop(ManagedWindow) : GObject.TYPE_STRING, # 1==father GObject.TYPE_STRING, # 2==son id GObject.TYPE_STRING, # 3==son - GObject.TYPE_STRING) # 4==family gid + GObject.TYPE_STRING) # 4==family gid self.treeView = topDialog.get_object("treeview") self.treeView.set_model(self.model) @@ -134,7 +134,7 @@ class FindLoop(ManagedWindow) : count += 1 self.current = person self.parent = None - self.descendants(person_handle, []) + self.descendants(person_handle, set()) self.progress.set_header("%d/%d" % (count, len(people))) self.progress.step() @@ -145,10 +145,10 @@ class FindLoop(ManagedWindow) : def descendants(self, person_handle, new_list): person = self.db.get_person_from_handle(person_handle) - plist = [] + pset = set() for item in new_list: - plist.append(item) - if person in plist: + pset.add(item) + if person.handle in pset: # We found one loop father_id = self.current.get_gramps_id() father = _nd.display(self.current) @@ -156,7 +156,7 @@ class FindLoop(ManagedWindow) : son = _nd.display(self.parent) value = (father_id, father, son_id, son, self.curr_fam) found = False - for pth in range(len(self.model)): + for pth in range(len(self.model)): path = Gtk.TreePath(pth) treeiter = self.model.get_iter(path) find = (self.model.get_value(treeiter, 0), @@ -169,17 +169,17 @@ class FindLoop(ManagedWindow) : if not found: self.model.append(value) return - plist.append(person) + pset.add(person.handle) for family_handle in person.get_family_handle_list(): family = self.db.get_family_from_handle(family_handle) self.curr_fam = family.get_gramps_id() - if not family: + if not family: # can happen with LivingProxyDb(PrivateProxyDb(db)) continue for child_ref in family.get_child_ref_list(): child_handle = child_ref.ref self.parent = person - self.descendants(child_handle, plist) + self.descendants(child_handle, pset) def rowActivated(self, treeView, path, column) : # first we need to check that the row corresponds to a person From 4525b870715f57e4550474cb2be88e47c32e29fc Mon Sep 17 00:00:00 2001 From: SNoiraud Date: Sun, 10 Apr 2016 09:57:39 +0200 Subject: [PATCH 4/8] 5449: Pedigree view crashes ... : remove last empty line. --- gramps/plugins/tool/tools.gpr.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gramps/plugins/tool/tools.gpr.py b/gramps/plugins/tool/tools.gpr.py index 5db7edd31..b9f7aaa9a 100644 --- a/gramps/plugins/tool/tools.gpr.py +++ b/gramps/plugins/tool/tools.gpr.py @@ -496,4 +496,3 @@ toolclass = 'FindLoop', optionclass = 'FindLoopOptions', tool_modes = [TOOL_MODE_GUI, TOOL_MODE_CLI] ) - From 9fe4820f4ca212ac0ce84ca023508eea1ae0400c Mon Sep 17 00:00:00 2001 From: SNoiraud Date: Sun, 10 Apr 2016 20:08:25 +0200 Subject: [PATCH 5/8] 0005449: Pedigree view crashes ... : add help button + suppress CLI tool mode --- gramps/plugins/tool/findloop.py | 5 +++-- gramps/plugins/tool/tools.gpr.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gramps/plugins/tool/findloop.py b/gramps/plugins/tool/findloop.py index 892d4d518..3b6d76e24 100644 --- a/gramps/plugins/tool/findloop.py +++ b/gramps/plugins/tool/findloop.py @@ -55,6 +55,8 @@ from gramps.gui.editors import EditFamily # Constants # #------------------------------------------------------------------------- +WIKI_HELP_PAGE = '%s_-_Tools' % URL_MANUAL_PAGE +WIKI_HELP_SEC = _('manual|Find_possible_loop_in_the_database') #------------------------------------------------------------------------ # @@ -198,8 +200,7 @@ class FindLoop(ManagedWindow) : def on_help_clicked(self, obj): """Display the relevant portion of GRAMPS manual""" - pass - + display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) #------------------------------------------------------------------------ # diff --git a/gramps/plugins/tool/tools.gpr.py b/gramps/plugins/tool/tools.gpr.py index b9f7aaa9a..4262ea458 100644 --- a/gramps/plugins/tool/tools.gpr.py +++ b/gramps/plugins/tool/tools.gpr.py @@ -494,5 +494,5 @@ authors_email = ["serge.noiraud@free.fr"], category = TOOL_UTILS, toolclass = 'FindLoop', optionclass = 'FindLoopOptions', -tool_modes = [TOOL_MODE_GUI, TOOL_MODE_CLI] +tool_modes = [TOOL_MODE_GUI] ) From 02d0e858c0bdbba1ecc580ae6ec433c032de4667 Mon Sep 17 00:00:00 2001 From: SNoiraud Date: Sun, 10 Apr 2016 22:22:28 +0200 Subject: [PATCH 6/8] 0005449: Pedigree view crashes ... : add help to glade file. --- gramps/plugins/tool/findloop.glade | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/gramps/plugins/tool/findloop.glade b/gramps/plugins/tool/findloop.glade index 0bb214452..00a986b52 100644 --- a/gramps/plugins/tool/findloop.glade +++ b/gramps/plugins/tool/findloop.glade @@ -35,6 +35,23 @@ 0 + + + gtk-help + False + True + True + True + False + True + + + + False + False + 0 + + From a472ec4e0810d405b82fc2bceaec2409590b5621 Mon Sep 17 00:00:00 2001 From: SNoiraud Date: Mon, 11 Apr 2016 10:06:20 +0200 Subject: [PATCH 7/8] 5449: Pedigree view crashes : add examples for testing loops. --- example/gramps/child-father-child-loop.gramps | 42 +++++++++++ example/gramps/test_complex_loop.gramps | 72 +++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 example/gramps/child-father-child-loop.gramps create mode 100644 example/gramps/test_complex_loop.gramps diff --git a/example/gramps/child-father-child-loop.gramps b/example/gramps/child-father-child-loop.gramps new file mode 100644 index 000000000..b2e7a69b9 --- /dev/null +++ b/example/gramps/child-father-child-loop.gramps @@ -0,0 +1,42 @@ + + + +
+ + + +
+ + + M + + Child + Child + + + + + + M + + Father + Father + + + + + + + + + + + + + + + + + +
diff --git a/example/gramps/test_complex_loop.gramps b/example/gramps/test_complex_loop.gramps new file mode 100644 index 000000000..d733b88ad --- /dev/null +++ b/example/gramps/test_complex_loop.gramps @@ -0,0 +1,72 @@ + + + +
+ + + +
+ + + M + + Child + Child + + + + + + + M + + Father + Father + + + + + + M + + Child2 + Father + + + + + + M + + Child3 + Father + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
From 9a995b9e30d925bdc83868631586a19e340da629 Mon Sep 17 00:00:00 2001 From: SNoiraud Date: Fri, 15 Apr 2016 13:50:30 +0200 Subject: [PATCH 8/8] 5449: Pedigree view crashes : changed the name and solved the notrelated glade problem. --- gramps/plugins/tool/findloop.glade | 2 +- gramps/plugins/tool/findloop.py | 7 +++++-- gramps/plugins/tool/tools.gpr.py | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/gramps/plugins/tool/findloop.glade b/gramps/plugins/tool/findloop.glade index 00a986b52..3fc52ec70 100644 --- a/gramps/plugins/tool/findloop.glade +++ b/gramps/plugins/tool/findloop.glade @@ -27,7 +27,7 @@ True False True - +
False diff --git a/gramps/plugins/tool/findloop.py b/gramps/plugins/tool/findloop.py index 3b6d76e24..e71ce39f5 100644 --- a/gramps/plugins/tool/findloop.py +++ b/gramps/plugins/tool/findloop.py @@ -56,7 +56,7 @@ from gramps.gui.editors import EditFamily # #------------------------------------------------------------------------- WIKI_HELP_PAGE = '%s_-_Tools' % URL_MANUAL_PAGE -WIKI_HELP_SEC = _('manual|Find_possible_loop_in_the_database') +WIKI_HELP_SEC = _('manual|Find_database_loop') #------------------------------------------------------------------------ # @@ -68,7 +68,7 @@ class FindLoop(ManagedWindow) : def __init__(self, dbstate, user, options_class, name, callback=None): uistate = user.uistate - self.title = _('Find possible loop') + self.title = _('Find database loop') ManagedWindow.__init__(self, uistate, [], self.__class__) self.dbstate = dbstate self.uistate = uistate @@ -202,6 +202,9 @@ class FindLoop(ManagedWindow) : """Display the relevant portion of GRAMPS manual""" display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) + def close(self, *obj): + ManagedWindow.close(self,*obj) + #------------------------------------------------------------------------ # # FindLoopOptions diff --git a/gramps/plugins/tool/tools.gpr.py b/gramps/plugins/tool/tools.gpr.py index 4262ea458..98a86ce10 100644 --- a/gramps/plugins/tool/tools.gpr.py +++ b/gramps/plugins/tool/tools.gpr.py @@ -476,13 +476,13 @@ tool_modes = [TOOL_MODE_GUI] #------------------------------------------------------------------------ # -# Find Possible Loop +# Find database Loop # #------------------------------------------------------------------------ register(TOOL, id = 'loop', -name = _("Find Possible Loop"), +name = _("Find database loop"), description = _("Searches the entire database, looking for " "a possible loop."), version = '1.0',