class FindLoop(ManagedWindow): """ Find loops in the family tree. """ def __init__(self, dbstate, user, options_class, name, callback=None): uistate = user.uistate self.title = _('Find database loop') ManagedWindow.__init__(self, uistate, [], self.__class__) self.dbstate = dbstate self.uistate = uistate #self.db = CacheProxyDb(dbstate.db) self.db = dbstate.db top_dialog = Glade() top_dialog.connect_signals({ "destroy_passed_object" : self.close, "on_help_clicked" : self.on_help_clicked, "on_delete_event" : self.close, }) window = top_dialog.toplevel title = top_dialog.get_object("title") self.set_window(window, title, self.title) # start the progress indicator self.progress = ProgressMeter(self.title, _('Starting'), parent=uistate.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 GObject.TYPE_STRING) # 5==loop number self.model.set_sort_column_id( Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 0) self.treeview = top_dialog.get_object("treeview") self.treeview.set_model(self.model) col0 = Gtk.TreeViewColumn('', Gtk.CellRendererText(), text=5) col1 = Gtk.TreeViewColumn(_('Gramps ID'), Gtk.CellRendererText(), text=0) col2 = Gtk.TreeViewColumn(_('Parent'), Gtk.CellRendererText(), text=1) col3 = Gtk.TreeViewColumn(_('Gramps ID'), Gtk.CellRendererText(), text=2) col4 = Gtk.TreeViewColumn(_('Child'), 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) self.treeview.append_column(col0) 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_cb) self.curr_fam = None people = self.db.get_person_handles() self.total = len(people) # total number of people to process. self.count = 0 # current number of people completely processed self.loop = 0 # Number of loops found for GUI pset = OrderedDict() # pset is the handle list of persons from the current start of # exploration path to the current limit. The use of OrderedDict # allows us to use it as a LIFO during recursion, as well as makes for # quick lookup. If we find a loop, pset provides a nice way to get # the loop path. self.done = set() # self.done is the handle set of people that have been fully explored # and do NOT have loops in the decendent tree. We use this to avoid # repeating work when we encounter one of these during the search. for person_handle in people: person = self.db.get_person_from_handle(person_handle) self.current = person self.parent = None self.descendants(person_handle, pset) # close the progress bar self.progress.close() self.show() def descendants(self, person_handle, pset): """ Find the descendants of a given person. Returns False if a loop for the person is NOT found, True if loop found We use the return value to ensure a person is not put on done list if part of a loop """ if person_handle in self.done: return False # We have already verified no loops for this one if person_handle in pset: # We found one loop. # person_handle is child, self.parent, self.curr_fam valid # see if it has already been put into display person = self.db.get_person_from_handle(person_handle) pers_id = person.get_gramps_id() pers_name = _nd.display(person) parent_id = self.parent.get_gramps_id() parent_name = _nd.display(self.parent) value = (parent_id, parent_name, pers_id, pers_name, 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 # This loop is in display model break if not found: # Need to put loop in display model. self.loop += 1 # place first node self.model.append(value + (str(self.loop),)) state = 0 # Now search for loop beginning. for hndl in pset.keys(): if hndl != person_handle and state == 0: continue # beginning not found if state == 0: state = 1 # found beginning, get first item to display continue # we have a good handle, now put item on display list self.parent = person person = self.db.get_person_from_handle(hndl) # Get the family that is both parent/person for fam_h in person.get_parent_family_handle_list(): if fam_h in self.parent.get_family_handle_list(): break family = self.db.get_family_from_handle(fam_h) fam_id = family.get_gramps_id() pers_id = person.get_gramps_id() pers_name = _nd.display(person) parent_id = self.parent.get_gramps_id() parent_name = _nd.display(self.parent) value = (parent_id, parent_name, pers_id, pers_name, fam_id, str(self.loop)) self.model.append(value) return True # We are not part of loop (yet) so search descendents person = self.db.get_person_from_handle(person_handle) # put in the pset path list for recursive calls to find pset[person_handle] = None loop = False for family_handle in person.get_family_handle_list(): family = self.db.get_family_from_handle(family_handle) 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.curr_fam = family.get_gramps_id() self.parent = person # if any descendants are part of loop, so is search person loop |= self.descendants(child_handle, pset) # we have completed search, we can pop the person off pset list person_handle, dummy = pset.popitem(last=True) if not loop: # person was not in loop, so add to done list and update progress self.done.add(person_handle) self.count += 1 self.progress.set_header("%d/%d" % (self.count, self.total)) self.progress.step() return False # person was in loop... return True def rowactivated_cb(self, treeview, path, column): """ Called when a row is activated. """ # first we need to check that the row corresponds to a person iter_ = self.model.get_iter(path) 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. """ display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) def close(self, *obj): ManagedWindow.close(self, *obj)
class FindLoop(ManagedWindow) : def __init__(self, dbstate, user, options_class, name, callback=None): uistate = user.uistate self.title = _('Find database 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, set()) 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) pset = set() for item in new_list: pset.add(item) if person.handle in pset: # 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 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: # 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, pset) 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""" display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) def close(self, *obj): ManagedWindow.close(self,*obj)
class FindLoop(ManagedWindow): """ Find loops in the family tree. """ def __init__(self, dbstate, user, options_class, name, callback=None): uistate = user.uistate self.title = _('Find database loop') ManagedWindow.__init__(self, uistate, [], self.__class__) self.dbstate = dbstate self.uistate = uistate #self.db = CacheProxyDb(dbstate.db) self.db = dbstate.db top_dialog = Glade() top_dialog.connect_signals({ "destroy_passed_object": self.close, "on_help_clicked": self.on_help_clicked, "on_delete_event": self.close, }) window = top_dialog.toplevel title = top_dialog.get_object("title") self.set_window(window, title, self.title) # start the progress indicator self.progress = ProgressMeter(self.title, _('Starting'), parent=uistate.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 GObject.TYPE_STRING) # 5==loop number self.model.set_sort_column_id( Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, 0) self.treeview = top_dialog.get_object("treeview") self.treeview.set_model(self.model) col0 = Gtk.TreeViewColumn('', Gtk.CellRendererText(), text=5) col1 = Gtk.TreeViewColumn(_('Gramps ID'), Gtk.CellRendererText(), text=0) col2 = Gtk.TreeViewColumn(_('Parent'), Gtk.CellRendererText(), text=1) col3 = Gtk.TreeViewColumn(_('Gramps ID'), Gtk.CellRendererText(), text=2) col4 = Gtk.TreeViewColumn(_('Child'), 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) self.treeview.append_column(col0) 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_cb) self.curr_fam = None people = self.db.get_person_handles() self.total = len(people) # total number of people to process. self.count = 0 # current number of people completely processed self.loop = 0 # Number of loops found for GUI pset = OrderedDict() # pset is the handle list of persons from the current start of # exploration path to the current limit. The use of OrderedDict # allows us to use it as a LIFO during recursion, as well as makes for # quick lookup. If we find a loop, pset provides a nice way to get # the loop path. self.done = set() # self.done is the handle set of people that have been fully explored # and do NOT have loops in the decendent tree. We use this to avoid # repeating work when we encounter one of these during the search. for person_handle in people: person = self.db.get_person_from_handle(person_handle) self.current = person self.parent = None self.descendants(person_handle, pset) # close the progress bar self.progress.close() self.show() def descendants(self, person_handle, pset): """ Find the descendants of a given person. Returns False if a loop for the person is NOT found, True if loop found We use the return value to ensure a person is not put on done list if part of a loop """ if person_handle in self.done: return False # We have already verified no loops for this one if person_handle in pset: # We found one loop. # person_handle is child, self.parent, self.curr_fam valid # see if it has already been put into display person = self.db.get_person_from_handle(person_handle) pers_id = person.get_gramps_id() pers_name = _nd.display(person) parent_id = self.parent.get_gramps_id() parent_name = _nd.display(self.parent) value = (parent_id, parent_name, pers_id, pers_name, 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 # This loop is in display model break if not found: # Need to put loop in display model. self.loop += 1 # place first node self.model.append(value + (str(self.loop), )) state = 0 # Now search for loop beginning. for hndl in pset.keys(): if hndl != person_handle and state == 0: continue # beginning not found if state == 0: state = 1 # found beginning, get first item to display continue # we have a good handle, now put item on display list self.parent = person person = self.db.get_person_from_handle(hndl) # Get the family that is both parent/person for fam_h in person.get_parent_family_handle_list(): if fam_h in self.parent.get_family_handle_list(): break family = self.db.get_family_from_handle(fam_h) fam_id = family.get_gramps_id() pers_id = person.get_gramps_id() pers_name = _nd.display(person) parent_id = self.parent.get_gramps_id() parent_name = _nd.display(self.parent) value = (parent_id, parent_name, pers_id, pers_name, fam_id, str(self.loop)) self.model.append(value) return True # We are not part of loop (yet) so search descendents person = self.db.get_person_from_handle(person_handle) # put in the pset path list for recursive calls to find pset[person_handle] = None loop = False for family_handle in person.get_family_handle_list(): family = self.db.get_family_from_handle(family_handle) 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.curr_fam = family.get_gramps_id() self.parent = person # if any descendants are part of loop, so is search person loop |= self.descendants(child_handle, pset) # we have completed search, we can pop the person off pset list person_handle, dummy = pset.popitem(last=True) if not loop: # person was not in loop, so add to done list and update progress self.done.add(person_handle) self.count += 1 self.progress.set_header("%d/%d" % (self.count, self.total)) self.progress.step() return False # person was in loop... return True def rowactivated_cb(self, treeview, path, column): """ Called when a row is activated. """ # first we need to check that the row corresponds to a person iter_ = self.model.get_iter(path) 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. """ display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) def close(self, *obj): ManagedWindow.close(self, *obj)
class RelationTab(tool.Tool, ManagedWindow): def __init__(self, dbstate, user, options_class, name, callback=None): uistate = user.uistate self.label = _("Relation and distances with root") self.dbstate = dbstate FilterClass = GenericFilterFactory('Person') self.path = '.' filter = FilterClass() tool.Tool.__init__(self, dbstate, options_class, name) if uistate: window = Gtk.Window() window.set_default_size(880, 600) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) window.add(box) # dirty work-around for Gtk.HeaderBar() and FolderChooser chooser = Gtk.FileChooserDialog( _("Folder Chooser"), parent=uistate.window, action=Gtk.FileChooserAction.SELECT_FOLDER, buttons=(_('_Cancel'), Gtk.ResponseType.CANCEL, _('_Select'), Gtk.ResponseType.OK)) chooser.set_tooltip_text(_("Please, select a folder")) status = chooser.run() if status == Gtk.ResponseType.OK: # work-around 'IsADirectoryError' with self() # TypeError: invalid file: gi.FunctionInfo() self.path = chooser.get_current_folder() chooser.destroy() ManagedWindow.__init__(self, uistate, [], self.__class__) self.titles = [ (_('Rel_id'), 0, 40, INTEGER), # would be INTEGER (_('Relation'), 1, 300, str), (_('Name'), 2, 200, str), (_('up'), 3, 35, INTEGER), (_('down'), 4, 35, INTEGER), (_('Common MRA'), 5, 40, INTEGER), (_('Rank'), 6, 40, INTEGER), (_('Period'), 7, 40, str), ] treeview = Gtk.TreeView() model = ListModel(treeview, self.titles) s = Gtk.ScrolledWindow() s.add(treeview) box.pack_start(s, True, True, 0) button = Gtk.Button(label=_("Save")) button.connect("clicked", self.button_clicked) box.pack_end(button, False, True, 0) self.stats_list = [] # behavior can be different according to CPU and generation depth max_level = config.get('behavior.generation-depth') # compact and interlinked tree # single core 2.80 Ghz needs +/- 0.1 second per person if max_level >= 15: var = max_level * 0.01 elif 10 <= max_level < 15: var = max_level * 0.02 else: var = max_level * 0.025 plist = self.dbstate.db.iter_person_handles() length = self.dbstate.db.get_number_of_people() default_person = self.dbstate.db.get_default_person() if uistate: self.progress = ProgressMeter(self.label, can_cancel=True, parent=window) else: self.progress = ProgressMeter(self.label) if default_person: # rather designed for run via GUI... root_id = default_person.get_gramps_id() ancestors = rules.person.IsAncestorOf([str(root_id), True]) descendants = rules.person.IsDescendantOf([str(root_id), True]) related = rules.person.IsRelatedWith([str(root_id)]) # filtering people can be useful on some large data set # counter on filtering pass was not efficient # Not the proper solution, but a lazy one providing expected message filter.add_rule(related) self.progress.set_pass(_('Please wait, filtering...')) filtered_list = filter.apply(self.dbstate.db, plist) relationship = get_relationship_calculator() else: # TODO: provide selection widget for CLI and GUI WarningDialog(_("No default_person")) return count = 0 filtered_people = len(filtered_list) self.progress.set_pass(_('Generating relation map...'), filtered_people) if self.progress.get_cancelled(): self.progress.close() return step_one = time.clock() # init for counters for handle in filtered_list: nb = len(self.stats_list) count += 1 self.progress.step() step_two = time.clock() start = 99 if count > start: # provide a basic interface for counters need = (step_two - step_one) / count wait = need * filtered_people remain = int(wait) - int(step_two - step_one) # sorry, lazy header = _("%d/%d \n %d/%d seconds \n %d/%d \n%f|\t%f" % (count, filtered_people, remain, int(wait), nb, length, float(need), float(var))) self.progress.set_header(header) if self.progress.get_cancelled(): self.progress.close() return person = dbstate.db.get_person_from_handle(handle) timeout_one = time.clock() # for delta and timeout estimations dist = relationship.get_relationship_distance_new(dbstate.db, default_person, person, only_birth=True) timeout_two = time.clock() rank = dist[0][0] if rank == -1 or rank > max_level: # not related and ignored people continue limit = timeout_two - timeout_one expect = (limit - var) / max_level if limit > var: n = name_displayer.display(person) _LOG.debug("Sorry! '{0}' needs {1} second, \ variation = '{2}'".format(n, limit, expect)) continue else: _LOG.debug("variation = '{}'".format( limit)) # delta, see above max_level 'wall' section rel = relationship.get_one_relationship( dbstate.db, default_person, person) rel_a = dist[0][2] Ga = len(rel_a) rel_b = dist[0][4] Gb = len(rel_b) mra = 1 # m: mother; f: father if Ga > 0: for letter in rel_a: if letter == 'm': mra = mra * 2 + 1 if letter == 'f': mra = mra * 2 # design: mra gender will be often female (m: mother) if rel_a[-1] == "f" and Gb != 0: # male gender, look at spouse mra = mra + 1 name = name_displayer.display(person) # pseudo privacy; sample for DNA stuff and mapping import hashlib no_name = hashlib.sha384(name.encode() + handle.encode()).hexdigest() _LOG.info(no_name) # own internal password via handle kekule = number.get_number(Ga, Gb, rel_a, rel_b) # workaround - possible unique ID and common numbers uuid = str(uuid4()) _LOG.info("Random UUID: {}".format(uuid)) if kekule == "u": # TODO: cousin(e)s need a key kekule = 0 if kekule == "nb": # non-birth kekule = -1 try: test = int(kekule) except: # 1: related to mother; 0.x : no more girls lineage kekule = 1 period = get_timeperiod(self.dbstate.db, handle) # sometimes 'iterator' (generator) is more faster #handle_list = map(handle, filtered_list) iterator = (handle for handle in filtered_list) # experimentations; not used yet new_list = [int(kekule), int(Ga), int(Gb), int(mra), int(rank)] line = (iterator, array('b', new_list)) self.stats_list.append( (int(kekule), rel, name, int(Ga), int(Gb), int(mra), int(rank), str(period))) self.progress.close() from itertools import groupby for key, items in groupby(self.stats_list, lambda x: x[0]): for subitem in items: _LOG.info(subitem) _LOG.debug("total: {}".format(nb)) for entry in self.stats_list: if uistate: model.add(entry, entry[0]) else: print(entry) if uistate: window.show() self.set_window(window, None, self.label) self.show() def save(self): """ save action """ doc = ODSTab(len(self.stats_list)) doc.creator(self.db.get_researcher().get_name()) name = self.dbstate.db.get_default_person().get_handle() + '.ods' if self.path != '.': name = os.path.join(self.path, name) try: import io io.open(name, "w", encoding='utf8') except PermissionError or IsADirectoryError: WarningDialog(_("You do not have write rights on this folder")) return spreadsheet = TableReport(name, doc) new_titles = [] skip_columns = [] index = 0 for title in self.titles: if title == 'sort': skip_columns.append(index) else: new_titles.append(title) index += 1 spreadsheet.initialize(len(new_titles)) spreadsheet.write_table_head(new_titles) index = 0 for top in self.stats_list: spreadsheet.set_row(index % 2) index += 1 spreadsheet.write_table_data(top, skip_columns) spreadsheet.finalize() def build_menu_names(self, obj): return (self.label, None) def button_clicked(self, button): self.save()
class RelationTab(tool.Tool, ManagedWindow): def __init__(self, dbstate, user, options_class, name, callback=None): uistate = user.uistate self.label = _("Relation and distances with root") self.dbstate = dbstate FilterClass = GenericFilterFactory('Person') self.path = '.' filter = FilterClass() tool.Tool.__init__(self, dbstate, options_class, name) if uistate: window = Gtk.Window() window.set_default_size(880, 600) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) window.add(box) # dirty work-around for Gtk.HeaderBar() and FolderChooser chooser = Gtk.FileChooserDialog(_("Folder Chooser"), parent=uistate.window, action=Gtk.FileChooserAction.SELECT_FOLDER, buttons=(_('_Cancel'), Gtk.ResponseType.CANCEL, _('_Select'), Gtk.ResponseType.OK)) chooser.set_tooltip_text(_("Please, select a folder")) status = chooser.run() if status == Gtk.ResponseType.OK: # work-around 'IsADirectoryError' with self() # TypeError: invalid file: gi.FunctionInfo() self.path = chooser.get_current_folder() chooser.destroy() ManagedWindow.__init__(self, uistate, [], self.__class__) self.titles = [ (_('Rel_id'), 0, 40, INTEGER), # would be INTEGER (_('Relation'), 1, 300, str), (_('Name'), 2, 200, str), (_('up'), 3, 35, INTEGER), (_('down'), 4, 35, INTEGER), (_('Common MRA'), 5, 40, INTEGER), (_('Rank'), 6, 40, INTEGER), (_('Period'), 7, 40, str), ] treeview = Gtk.TreeView() model = ListModel(treeview, self.titles) s = Gtk.ScrolledWindow() s.add(treeview) box.pack_start(s, True, True, 0) button = Gtk.Button(label=_("Save")) button.connect("clicked", self.button_clicked) box.pack_end(button, False, True, 0) self.stats_list = [] # behavior can be different according to CPU and generation depth max_level = config.get('behavior.generation-depth') # compact and interlinked tree # single core 2.80 Ghz needs +/- 0.1 second per person if max_level >= 15: var = max_level * 0.01 elif 10 <= max_level < 15: var = max_level * 0.02 else: var = max_level * 0.025 plist = self.dbstate.db.iter_person_handles() length = self.dbstate.db.get_number_of_people() default_person = self.dbstate.db.get_default_person() if uistate: self.progress = ProgressMeter(self.label, can_cancel=True, parent=window) else: self.progress = ProgressMeter(self.label) if default_person: # rather designed for run via GUI... root_id = default_person.get_gramps_id() ancestors = rules.person.IsAncestorOf([str(root_id), True]) descendants = rules.person.IsDescendantOf([str(root_id), True]) related = rules.person.IsRelatedWith([str(root_id)]) # filtering people can be useful on some large data set # counter on filtering pass was not efficient # Not the proper solution, but a lazy one providing expected message filter.add_rule(related) self.progress.set_pass(_('Please wait, filtering...')) filtered_list = filter.apply(self.dbstate.db, plist) relationship = get_relationship_calculator() else: # TODO: provide selection widget for CLI and GUI WarningDialog(_("No default_person")) return count = 0 filtered_people = len(filtered_list) self.progress.set_pass(_('Generating relation map...'), filtered_people) if self.progress.get_cancelled(): self.progress.close() return step_one = time.clock() # init for counters for handle in filtered_list: nb = len(self.stats_list) count += 1 self.progress.step() step_two = time.clock() start = 99 if count > start: # provide a basic interface for counters need = (step_two - step_one) / count wait = need * filtered_people remain = int(wait) - int(step_two - step_one) # sorry, lazy header = _("%d/%d \n %d/%d seconds \n %d/%d \n%f|\t%f" % (count, filtered_people, remain, int(wait), nb, length, float(need), float(var)) ) self.progress.set_header(header) if self.progress.get_cancelled(): self.progress.close() return person = dbstate.db.get_person_from_handle(handle) timeout_one = time.clock() # for delta and timeout estimations dist = relationship.get_relationship_distance_new( dbstate.db, default_person, person, only_birth=True) timeout_two = time.clock() rank = dist[0][0] if rank == -1 or rank > max_level: # not related and ignored people continue limit = timeout_two - timeout_one expect = (limit - var) / max_level if limit > var: n = name_displayer.display(person) _LOG.debug("Sorry! '{0}' needs {1} second, \ variation = '{2}'".format(n, limit, expect ) ) continue else: _LOG.debug("variation = '{}'".format(limit)) # delta, see above max_level 'wall' section rel = relationship.get_one_relationship( dbstate.db, default_person, person) rel_a = dist[0][2] Ga = len(rel_a) rel_b = dist[0][4] Gb = len(rel_b) mra = 1 # m: mother; f: father if Ga > 0: for letter in rel_a: if letter == 'm': mra = mra * 2 + 1 if letter == 'f': mra = mra * 2 # design: mra gender will be often female (m: mother) if rel_a[-1] == "f" and Gb != 0: # male gender, look at spouse mra = mra + 1 name = name_displayer.display(person) # pseudo privacy; sample for DNA stuff and mapping import hashlib no_name = hashlib.sha384(name.encode() + handle.encode()).hexdigest() _LOG.info(no_name) # own internal password via handle kekule = number.get_number(Ga, Gb, rel_a, rel_b) # workaround - possible unique ID and common numbers uuid = str(uuid4()) _LOG.info("Random UUID: {}".format(uuid)) if kekule == "u": # TODO: cousin(e)s need a key kekule = 0 if kekule == "nb": # non-birth kekule = -1 try: test = int(kekule) except: # 1: related to mother; 0.x : no more girls lineage kekule = 1 period = get_timeperiod(self.dbstate.db, handle) # sometimes 'iterator' (generator) is more faster #handle_list = map(handle, filtered_list) iterator = (handle for handle in filtered_list) # experimentations; not used yet new_list=[int(kekule), int(Ga), int(Gb), int(mra), int(rank)] if max_level > 7: line = (iterator, array('l', new_list)) else: line = (iterator, array('b', new_list)) self.stats_list.append((int(kekule), rel, name, int(Ga), int(Gb), int(mra), int(rank), str(period))) self.progress.close() from itertools import groupby for key, items in groupby(self.stats_list, lambda x: x[0]): for subitem in items: _LOG.info(subitem) _LOG.debug("total: {}".format(nb)) for entry in self.stats_list: if uistate: model.add(entry, entry[0]) else: print(entry) if uistate: window.show() self.set_window(window, None, self.label) self.show() def save(self): """ save action """ doc = ODSTab(len(self.stats_list)) doc.creator(self.db.get_researcher().get_name()) name = self.dbstate.db.get_default_person().get_handle() + '.ods' if self.path != '.': name = os.path.join(self.path, name) try: import io io.open(name, "w", encoding='utf8') except PermissionError or IsADirectoryError: WarningDialog(_("You do not have write rights on this folder")) return spreadsheet = TableReport(name, doc) new_titles = [] skip_columns = [] index = 0 for title in self.titles: if title == 'sort': skip_columns.append(index) else: new_titles.append(title) index += 1 spreadsheet.initialize(len(new_titles)) spreadsheet.write_table_head(new_titles) index = 0 for top in self.stats_list: spreadsheet.set_row(index%2) index += 1 spreadsheet.write_table_data(top, skip_columns) spreadsheet.finalize() def build_menu_names(self, obj): return (self.label, None) def button_clicked(self, button): self.save()
class FindLoop(ManagedWindow): """ Find loops in the family tree. """ def __init__(self, dbstate, user, options_class, name, callback=None): uistate = user.uistate self.title = _('Find database loop') ManagedWindow.__init__(self, uistate, [], self.__class__) self.dbstate = dbstate self.uistate = uistate self.db = dbstate.db top_dialog = Glade() top_dialog.connect_signals({ "destroy_passed_object" : self.close, "on_help_clicked" : self.on_help_clicked, "on_delete_event" : self.close, }) window = top_dialog.toplevel title = top_dialog.get_object("title") self.set_window(window, title, self.title) # start the progress indicator self.progress = ProgressMeter(self.title, _('Starting'), # parent-OK 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 = top_dialog.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_cb) self.curr_fam = None 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, set()) 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): """ Find the descendants of a given person. """ person = self.db.get_person_from_handle(person_handle) pset = set() for item in new_list: pset.add(item) if person.handle in pset: # 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 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: # 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, pset) def rowactivated_cb(self, treeview, path, column): """ Called when a row is activated. """ # first we need to check that the row corresponds to a person iter_ = self.model.get_iter(path) 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. """ display_help(webpage=WIKI_HELP_PAGE, section=WIKI_HELP_SEC) def close(self, *obj): ManagedWindow.close(self, *obj)