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] + ) +