From c86f3831227e60a277f5557e38b4acdf403ee666 Mon Sep 17 00:00:00 2001 From: Benny Malengier Date: Sat, 1 Sep 2012 15:13:44 +0000 Subject: [PATCH] Refractor so common base fan chart gramplet and view are shared Fan chart view allows now configuration: max generation to set, 4 color schemes svn: r20307 --- po/POTFILES.skip | 2 +- .../lib => gen/utils}/libformatting.py | 4 +- src/gui/configure.py | 2 +- src/gui/widgets/fanchart.py | 428 +++++++++++++++++- src/plugins/gramplet/fanchartgramplet.py | 359 +-------------- src/plugins/gramplet/quickviewgramplet.py | 1 - src/plugins/lib/Makefile.am | 1 - src/plugins/lib/libplugins.gpr.py | 18 - src/plugins/lib/maps/geography.py | 2 +- src/plugins/view/fanchartview.py | 425 +++-------------- src/plugins/view/pedigreeview.py | 2 +- 11 files changed, 498 insertions(+), 746 deletions(-) rename src/{plugins/lib => gen/utils}/libformatting.py (98%) diff --git a/po/POTFILES.skip b/po/POTFILES.skip index 633ca30b1..9044897f4 100644 --- a/po/POTFILES.skip +++ b/po/POTFILES.skip @@ -218,6 +218,7 @@ src/gen/utils/debug.py src/gen/utils/file.py src/gen/utils/id.py src/gen/utils/image.py +src/gen/utils/libformatting.py # gen.utils.docgen src/gen/utils/docgen/__init__.py @@ -316,7 +317,6 @@ src/plugins/sidebar/sidebar.gpr.py src/plugins/docgen/pdfdoc.py # plugins/lib directory -src/plugins/lib/libformatting.py src/plugins/lib/libgrampsxml.py src/plugins/lib/libhtml.py src/plugins/lib/libhtmlbackend.py diff --git a/src/plugins/lib/libformatting.py b/src/gen/utils/libformatting.py similarity index 98% rename from src/plugins/lib/libformatting.py rename to src/gen/utils/libformatting.py index 0534b726c..426f1be42 100644 --- a/src/plugins/lib/libformatting.py +++ b/src/gen/utils/libformatting.py @@ -40,7 +40,8 @@ from cgi import escape import gen.lib import gen.datehandler from gen.display.name import displayer as name_displayer -from gen.utils.db import get_birth_or_fallback, get_death_or_fallback, get_marriage_or_fallback +from gen.utils.db import (get_birth_or_fallback, get_death_or_fallback, + get_marriage_or_fallback) #------------------------------------------------------------------------- # @@ -198,3 +199,4 @@ class FormattingHelper(object): """ self._text_cache = {} self._markup_cache = {} + diff --git a/src/gui/configure.py b/src/gui/configure.py index 644772e50..33cfda6ee 100644 --- a/src/gui/configure.py +++ b/src/gui/configure.py @@ -1110,7 +1110,7 @@ class GrampsPreferences(ConfigureDialog): self.add_spinner(table, _('Date before range'), 2, 'behavior.date-before-range', (1, 80)) - self.add_spinner(table, + self.add_spinner(table, _('Maximum age probably alive'), 3, 'behavior.max-age-prob-alive', (80, 140)) self.add_spinner(table, diff --git a/src/gui/widgets/fanchart.py b/src/gui/widgets/fanchart.py index 76afe5c1d..369a21856 100644 --- a/src/gui/widgets/fanchart.py +++ b/src/gui/widgets/fanchart.py @@ -42,6 +42,7 @@ from gi.repository import Gtk from gi.repository import PangoCairo import math import cPickle as pickle +from cgi import escape #------------------------------------------------------------------------- # @@ -53,6 +54,8 @@ import gen.lib import gui.utils from gui.ddtargets import DdTargets from gen.utils.alive import probably_alive +from gen.utils.libformatting import FormattingHelper +from gen.utils.db import (find_children, find_parents, find_witnessed_people) #------------------------------------------------------------------------- # @@ -78,17 +81,37 @@ class FanChartWidget(Gtk.DrawingArea): Interactive Fan Chart Widget. """ BORDER_EDGE_WIDTH = 10 - GENCOLOR = ((229,191,252), - (191,191,252), - (191,222,252), - (183,219,197), - (206,246,209)) TRANSLATE_PX = 10 + + BACKGROUND_SCHEME1 = 0 + BACKGROUND_SCHEME2 = 1 + BACKGROUND_GENDER = 2 + BACKGROUND_WHITE = 3 + GENCOLOR = { + BACKGROUND_SCHEME1: ((255, 63, 0), + (255,175, 15), + (255,223, 87), + (255,255,111), + (159,255,159), + (111,215,255), + ( 79,151,255), + (231, 23,255), + (231, 23,121), + (210,170,124), + (189,153,112)), + BACKGROUND_SCHEME2: ((229,191,252), + (191,191,252), + (191,222,252), + (183,219,197), + (206,246,209)), + BACKGROUND_WHITE: ((255,255,255),), + } + COLLAPSED = 0 NORMAL = 1 EXPANDED = 2 - def __init__(self, generations, dbstate, context_popup_callback=None): + def __init__(self, dbstate, callback_popup=None): """ Fan Chart Widget. Handles visualization of data in self.data. See main() of FanChartGramplet for example of model format. @@ -96,6 +119,7 @@ class FanChartWidget(Gtk.DrawingArea): GObject.GObject.__init__(self) self.dbstate = dbstate self.translating = False + self.on_popup = callback_popup self.last_x, self.last_y = None, None self.connect("button_release_event", self.on_mouse_up) self.connect("motion_notify_event", self.on_mouse_move) @@ -104,7 +128,6 @@ class FanChartWidget(Gtk.DrawingArea): self.connect("drag_data_get", self.on_drag_data_get) self.connect("drag_begin", self.on_drag_begin) self.connect("drag_end", self.on_drag_end) - self.context_popup_callback = context_popup_callback self.add_events(Gdk.EventMask.BUTTON_PRESS_MASK | Gdk.EventMask.BUTTON_RELEASE_MASK | Gdk.EventMask.POINTER_MOTION_MASK) @@ -122,22 +145,26 @@ class FanChartWidget(Gtk.DrawingArea): self.pixels_per_generation = 50 # size of radius for generation ## gotten from experiments with "sans serif 8": self.degrees_per_radius = .80 - self._mouse_click = False ## Other fonts will have different settings. Can you compute that ## from the font size? I have no idea. - self.generations = generations + self._mouse_click = False self.rotate_value = 90 # degrees, initially, 1st gen male on right half self.center_xy = [0, 0] # distance from center (x, y) - self.set_generations(self.generations) self.center = 50 # pixel radius of center - self.gen_color = True + #default values + self.reset(9, self.BACKGROUND_SCHEME1) self.set_size_request(120, 120) - def reset_generations(self): + def reset(self, maxgen, background): """ - Reset all of the data on where slices appear, and if they are expanded. + Reset all of the data on where/how slices appear, and if they are expanded. """ - self.set_generations(self.generations) + self.background = background + if self.background == self.BACKGROUND_GENDER: + self.colors = None + else: + self.colors = self.GENCOLOR[self.background] + self.set_generations(maxgen) def set_generations(self, generations): """ @@ -269,19 +296,19 @@ class FanChartWidget(Gtk.DrawingArea): x, y, w, h = alloc.x, alloc.y, alloc.width, alloc.height start_rad = start * math.pi/180 stop_rad = stop * math.pi/180 - if self.gen_color: - r,g,b = self.GENCOLOR[generation % len(self.GENCOLOR)] - if gender == gen.lib.Person.MALE: - r *= .9 - g *= .9 - b *= .9 - else: + if self.background == self.BACKGROUND_GENDER: try: alive = probably_alive(person, self.dbstate.db) except RuntimeError: alive = False backgr, border = gui.utils.color_graph_box(alive, person.gender) r, g, b = gui.utils.hex_to_rgb(backgr) + else: + r,g,b = self.colors[generation % len(self.colors)] + if gender == gen.lib.Person.MALE: + r *= .9 + g *= .9 + b *= .9 radius = generation * self.pixels_per_generation + self.center # If max generation, and they have parents: if generation == self.generations - 1 and parents: @@ -572,8 +599,8 @@ class FanChartWidget(Gtk.DrawingArea): # Do things based on state, event.get_state(), or button, event.button if gui.utils.is_right_click(event): text, person, parents, child = self.data[generation][selected] - if person and self.context_popup_callback: - self.context_popup_callback(widget, event, person.handle) + if person and self.on_popup: + self.on_popup(widget, event, person.handle) return True return False @@ -619,3 +646,358 @@ class FanChartWidget(Gtk.DrawingArea): sel_data.set(sel_data.get_target(), 8, pickle.dumps(data)) elif ('TEXT' in tgs or 'text/plain' in tgs) and info == 0L: sel_data.set_text(self.format_helper.format_person(person, 11), -1) + +class FanChartGrampsGUI(object): + """ class for functions fanchart GUI elements will need in Gramps + """ + def __init__(self, maxgen, background, on_childmenu_changed): + """ + Common part of GUI that shows Fan Chart, needs to know what to do if + one moves via Fan Chart to a new person + on_childmenu_changed: in popup, function called on moving to a new person + """ + self.fan = None + self.on_childmenu_changed = on_childmenu_changed + self.format_helper = FormattingHelper(self.dbstate) + + self.maxgen = maxgen + self.background = background + + def have_parents(self, person): + """ + Returns True if a person has parents. + """ + if person: + m = self.get_parent(person, False) + f = self.get_parent(person, True) + return not m is f is None + return False + + def have_children(self, person): + """ + Returns True if a person has children. + """ + if person: + for family_handle in person.get_family_handle_list(): + family = self.dbstate.db.get_family_from_handle(family_handle) + if family and len(family.get_child_ref_list()) > 0: + return True + return False + + def get_parent(self, person, father): + """ + Get the father of the family if father == True, otherwise mother + """ + if person: + parent_handle_list = person.get_parent_family_handle_list() + if parent_handle_list: + family_id = parent_handle_list[0] + family = self.dbstate.db.get_family_from_handle(family_id) + if family: + if father: + person_handle = gen.lib.Family.get_father_handle(family) + else: + person_handle = gen.lib.Family.get_mother_handle(family) + if person_handle: + return self.dbstate.db.get_person_from_handle(person_handle) + return None + + def set_fan(self, fan): + """ + Set the fanchartwidget to work on + """ + self.fan = fan + self.fan.format_helper = self.format_helper + + def main(self): + """ + Fill the data structures with the active data. This initializes all + data. + """ + self.fan.reset(self.maxgen, self.background) + person = self.dbstate.db.get_person_from_handle(self.get_active('Person')) + if not person: + name = None + else: + name = name_displayer.display(person) + parents = self.have_parents(person) + child = self.have_children(person) + self.fan.data[0][0] = (name, person, parents, child) + for current in range(1, self.maxgen): + parent = 0 + # name, person, parents, children + for (n,p,q,c) in self.fan.data[current - 1]: + # Get father's details: + person = self.get_parent(p, True) + if person: + name = name_displayer.display(person) + else: + name = None + if current == self.maxgen - 1: + parents = self.have_parents(person) + else: + parents = None + self.fan.data[current][parent] = (name, person, parents, None) + if person is None: + # start,stop,male/right,state + self.fan.angle[current][parent][3] = self.fan.COLLAPSED + parent += 1 + # Get mother's details: + person = self.get_parent(p, False) + if person: + name = name_displayer.display(person) + else: + name = None + if current == self.maxgen - 1: + parents = self.have_parents(person) + else: + parents = None + self.fan.data[current][parent] = (name, person, parents, None) + if person is None: + # start,stop,male/right,state + self.fan.angle[current][parent][3] = self.fan.COLLAPSED + parent += 1 + self.fan.queue_draw() + + def on_popup(self, obj, event, person_handle): + """ + Builds the full menu (including Siblings, Spouses, Children, + and Parents) with navigation. Copied from PedigreeView. + """ + #store menu for GTK3 to avoid it being destroyed before showing + self.menu = Gtk.Menu() + menu = self.menu + menu.set_title(_('People Menu')) + + person = self.dbstate.db.get_person_from_handle(person_handle) + if not person: + return 0 + + go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) + go_image.show() + go_item = Gtk.ImageMenuItem(name_displayer.display(person)) + go_item.set_image(go_image) + go_item.connect("activate",self.on_childmenu_changed,person_handle) + go_item.show() + menu.append(go_item) + + edit_item = Gtk.ImageMenuItem.new_from_stock(stock_id=Gtk.STOCK_EDIT, accel_group=None) + edit_item.connect("activate", self.edit_person_cb, person_handle) + edit_item.show() + menu.append(edit_item) + + clipboard_item = Gtk.ImageMenuItem.new_from_stock(stock_id=Gtk.STOCK_COPY, accel_group=None) + clipboard_item.connect("activate",self.copy_person_to_clipboard_cb,person_handle) + clipboard_item.show() + menu.append(clipboard_item) + + # collect all spouses, parents and children + linked_persons = [] + + # Go over spouses and build their menu + item = Gtk.MenuItem(label=_("Spouses")) + fam_list = person.get_family_handle_list() + no_spouses = 1 + for fam_id in fam_list: + family = self.dbstate.db.get_family_from_handle(fam_id) + if family.get_father_handle() == person.get_handle(): + sp_id = family.get_mother_handle() + else: + sp_id = family.get_father_handle() + spouse = self.dbstate.db.get_person_from_handle(sp_id) + if not spouse: + continue + + if no_spouses: + no_spouses = 0 + item.set_submenu(Gtk.Menu()) + sp_menu = item.get_submenu() + + go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) + go_image.show() + sp_item = Gtk.ImageMenuItem(name_displayer.display(spouse)) + sp_item.set_image(go_image) + linked_persons.append(sp_id) + sp_item.connect("activate",self.on_childmenu_changed, sp_id) + sp_item.show() + sp_menu.append(sp_item) + + if no_spouses: + item.set_sensitive(0) + + item.show() + menu.append(item) + + # Go over siblings and build their menu + item = Gtk.MenuItem(label=_("Siblings")) + pfam_list = person.get_parent_family_handle_list() + no_siblings = 1 + for f in pfam_list: + fam = self.dbstate.db.get_family_from_handle(f) + sib_list = fam.get_child_ref_list() + for sib_ref in sib_list: + sib_id = sib_ref.ref + if sib_id == person.get_handle(): + continue + sib = self.dbstate.db.get_person_from_handle(sib_id) + if not sib: + continue + + if no_siblings: + no_siblings = 0 + item.set_submenu(Gtk.Menu()) + sib_menu = item.get_submenu() + + if find_children(self.dbstate.db,sib): + label = Gtk.Label(label='%s' % escape(name_displayer.display(sib))) + else: + label = Gtk.Label(label=escape(name_displayer.display(sib))) + + go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) + go_image.show() + sib_item = Gtk.ImageMenuItem(None) + sib_item.set_image(go_image) + label.set_use_markup(True) + label.show() + label.set_alignment(0,0) + sib_item.add(label) + linked_persons.append(sib_id) + sib_item.connect("activate", self.on_childmenu_changed, sib_id) + sib_item.show() + sib_menu.append(sib_item) + + if no_siblings: + item.set_sensitive(0) + item.show() + menu.append(item) + + # Go over children and build their menu + item = Gtk.MenuItem(label=_("Children")) + no_children = 1 + childlist = find_children(self.dbstate.db, person) + for child_handle in childlist: + child = self.dbstate.db.get_person_from_handle(child_handle) + if not child: + continue + + if no_children: + no_children = 0 + item.set_submenu(Gtk.Menu()) + child_menu = item.get_submenu() + + if find_children(self.dbstate.db,child): + label = Gtk.Label(label='%s' % escape(name_displayer.display(child))) + else: + label = Gtk.Label(label=escape(name_displayer.display(child))) + + go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) + go_image.show() + child_item = Gtk.ImageMenuItem(None) + child_item.set_image(go_image) + label.set_use_markup(True) + label.show() + label.set_alignment(0,0) + child_item.add(label) + linked_persons.append(child_handle) + child_item.connect("activate", self.on_childmenu_changed, child_handle) + child_item.show() + child_menu.append(child_item) + + if no_children: + item.set_sensitive(0) + item.show() + menu.append(item) + + # Go over parents and build their menu + item = Gtk.MenuItem(label=_("Parents")) + no_parents = 1 + par_list = find_parents(self.dbstate.db,person) + for par_id in par_list: + par = self.dbstate.db.get_person_from_handle(par_id) + if not par: + continue + + if no_parents: + no_parents = 0 + item.set_submenu(Gtk.Menu()) + par_menu = item.get_submenu() + + if find_parents(self.dbstate.db,par): + label = Gtk.Label(label='%s' % escape(name_displayer.display(par))) + else: + label = Gtk.Label(label=escape(name_displayer.display(par))) + + go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) + go_image.show() + par_item = Gtk.ImageMenuItem(None) + par_item.set_image(go_image) + label.set_use_markup(True) + label.show() + label.set_alignment(0,0) + par_item.add(label) + linked_persons.append(par_id) + par_item.connect("activate",self.on_childmenu_changed, par_id) + par_item.show() + par_menu.append(par_item) + + if no_parents: + item.set_sensitive(0) + item.show() + menu.append(item) + + # Go over parents and build their menu + item = Gtk.MenuItem(label=_("Related")) + no_related = 1 + for p_id in find_witnessed_people(self.dbstate.db,person): + #if p_id in linked_persons: + # continue # skip already listed family members + + per = self.dbstate.db.get_person_from_handle(p_id) + if not per: + continue + + if no_related: + no_related = 0 + item.set_submenu(Gtk.Menu()) + per_menu = item.get_submenu() + + label = Gtk.Label(label=escape(name_displayer.display(per))) + + go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) + go_image.show() + per_item = Gtk.ImageMenuItem(None) + per_item.set_image(go_image) + label.set_use_markup(True) + label.show() + label.set_alignment(0, 0) + per_item.add(label) + per_item.connect("activate", self.on_childmenu_changed, p_id) + per_item.show() + per_menu.append(per_item) + + if no_related: + item.set_sensitive(0) + item.show() + menu.append(item) + menu.popup(None, None, None, None, event.button, event.time) + return 1 + + def edit_person_cb(self, obj,person_handle): + person = self.dbstate.db.get_person_from_handle(person_handle) + if person: + try: + EditPerson(self.dbstate, self.uistate, [], person) + except WindowActiveError: + pass + return True + return False + + def copy_person_to_clipboard_cb(self, obj,person_handle): + """Renders the person data into some lines of text and puts that into the clipboard""" + person = self.dbstate.db.get_person_from_handle(person_handle) + if person: + cb = Gtk.Clipboard.get_for_display(Gdk.Display.get_default(), + Gdk.SELECTION_CLIPBOARD) + cb.set_text( self.format_helper.format_person(person,11)) + return True + return False diff --git a/src/plugins/gramplet/fanchartgramplet.py b/src/plugins/gramplet/fanchartgramplet.py index fa90ba694..8bdad64f5 100644 --- a/src/plugins/gramplet/fanchartgramplet.py +++ b/src/plugins/gramplet/fanchartgramplet.py @@ -38,7 +38,6 @@ from gi.repository import Pango from gi.repository import Gtk import math from gi.repository import Gdk -from cgi import escape try: import cairo except ImportError: @@ -49,33 +48,31 @@ except ImportError: # GRAMPS modules # #------------------------------------------------------------------------- -from gen.display.name import displayer as name_displayer from gen.ggettext import gettext as _ from gen.plug import Gramplet -from gen.utils.db import (find_children, find_parents, find_witnessed_people) -from libformatting import FormattingHelper import gen.lib from gen.errors import WindowActiveError from gui.editors import EditPerson import gui.utils -from gui.widgets.fanchart import FanChartWidget +from gui.widgets.fanchart import FanChartWidget, FanChartGrampsGUI -class FanChartGramplet(Gramplet): +class FanChartGramplet(FanChartGrampsGUI, Gramplet): """ The Gramplet code that realizes the FanChartWidget. """ - def init(self): - self.set_tooltip(_("Click to expand/contract person\nRight-click for options\nClick and drag in open area to rotate")) - self.generations = 6 - self.format_helper = FormattingHelper(self.dbstate) - self.gui.fan = FanChartWidget(self.generations, self.dbstate, - context_popup_callback=self.on_popup) - self.gui.fan.format_helper = self.format_helper + + def __init__(self, gui, nav_group=0): + Gramplet.__init__(self, gui, nav_group) + FanChartGrampsGUI.__init__(self, 6, 0, self.on_childmenu_changed) + self.set_fan(FanChartWidget(self.dbstate, self.on_popup)) # Replace the standard textview with the fan chart widget: self.gui.get_container_widget().remove(self.gui.textview) - self.gui.get_container_widget().add_with_viewport(self.gui.fan) + self.gui.get_container_widget().add_with_viewport(self.fan) # Make sure it is visible: - self.gui.fan.show() + self.fan.show() + + def init(self): + self.set_tooltip(_("Click to expand/contract person\nRight-click for options\nClick and drag in open area to rotate")) def active_changed(self, handle): """ @@ -84,340 +81,8 @@ class FanChartGramplet(Gramplet): # Reset everything but rotation angle (leave it as is) self.update() - def have_parents(self, person): - """ - Returns True if a person has parents. - """ - if person: - m = self.get_parent(person, "female") - f = self.get_parent(person, "male") - return not m is f is None - return False - - def have_children(self, person): - """ - Returns True if a person has children. - """ - if person: - for family_handle in person.get_family_handle_list(): - family = self.dbstate.db.get_family_from_handle(family_handle) - if family and len(family.get_child_ref_list()) > 0: - return True - return False - - def get_parent(self, person, gender): - """ - Get the father if gender == "male", or get mother otherwise. - """ - if person: - parent_handle_list = person.get_parent_family_handle_list() - if parent_handle_list: - family_id = parent_handle_list[0] - family = self.dbstate.db.get_family_from_handle(family_id) - if family: - if gender == "male": - person_handle = gen.lib.Family.get_father_handle(family) - else: - person_handle = gen.lib.Family.get_mother_handle(family) - if person_handle: - return self.dbstate.db.get_person_from_handle(person_handle) - return None - - def main(self): - """ - Fill the data structures with the active data. This initializes all - data. - """ - self.gui.fan.reset_generations() - active_handle = self.get_active('Person') - person = self.dbstate.db.get_person_from_handle(active_handle) - if not person: - name = None - else: - name = name_displayer.display(person) - parents = self.have_parents(person) - child = self.have_children(person) - self.gui.fan.data[0][0] = (name, person, parents, child) - for current in range(1, self.generations): - parent = 0 - # name, person, parents, children - for (n,p,q,c) in self.gui.fan.data[current - 1]: - # Get father's details: - person = self.get_parent(p, "male") - if person: - name = name_displayer.display(person) - else: - name = None - if current == self.generations - 1: - parents = self.have_parents(person) - else: - parents = None - self.gui.fan.data[current][parent] = (name, person, parents, None) - if person is None: - # start,stop,male/right,state - self.gui.fan.angle[current][parent][3] = self.gui.fan.COLLAPSED - parent += 1 - # Get mother's details: - person = self.get_parent(p, "female") - if person: - name = name_displayer.display(person) - else: - name = None - if current == self.generations - 1: - parents = self.have_parents(person) - else: - parents = None - self.gui.fan.data[current][parent] = (name, person, parents, None) - if person is None: - # start,stop,male/right,state - self.gui.fan.angle[current][parent][3] = self.gui.fan.COLLAPSED - parent += 1 - self.gui.fan.queue_draw() - def on_childmenu_changed(self, obj, person_handle): """Callback for the pulldown menu selection, changing to the person attached with menu item.""" self.set_active('Person', person_handle) return True - - def edit_person_cb(self, obj,person_handle): - person = self.dbstate.db.get_person_from_handle(person_handle) - if person: - try: - EditPerson(self.dbstate, self.uistate, [], person) - except WindowActiveError: - pass - return True - return False - - def copy_person_to_clipboard_cb(self, obj,person_handle): - """Renders the person data into some lines of text and puts that into the clipboard""" - person = self.dbstate.db.get_person_from_handle(person_handle) - if person: - cb = Gtk.Clipboard.get_for_display(Gdk.Display.get_default(), - Gdk.SELECTION_CLIPBOARD) - cb.set_text( self.format_helper.format_person(person,11)) - return True - return False - - def on_popup(self, obj, event, person_handle): - """ - Builds the full menu (including Siblings, Spouses, Children, - and Parents) with navigation. Copied from PedigreeView. - """ - - menu = Gtk.Menu() - menu.set_title(_('People Menu')) - - person = self.dbstate.db.get_person_from_handle(person_handle) - if not person: - return 0 - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) - go_image.show() - go_item = Gtk.ImageMenuItem(name_displayer.display(person)) - go_item.set_image(go_image) - go_item.connect("activate",self.on_childmenu_changed,person_handle) - go_item.show() - menu.append(go_item) - - edit_item = Gtk.ImageMenuItem(Gtk.STOCK_EDIT) - edit_item.connect("activate",self.edit_person_cb,person_handle) - edit_item.show() - menu.append(edit_item) - - clipboard_item = Gtk.ImageMenuItem(Gtk.STOCK_COPY) - clipboard_item.connect("activate",self.copy_person_to_clipboard_cb,person_handle) - clipboard_item.show() - menu.append(clipboard_item) - - # collect all spouses, parents and children - linked_persons = [] - - # Go over spouses and build their menu - item = Gtk.MenuItem(label=_("Spouses")) - fam_list = person.get_family_handle_list() - no_spouses = 1 - for fam_id in fam_list: - family = self.dbstate.db.get_family_from_handle(fam_id) - if family.get_father_handle() == person.get_handle(): - sp_id = family.get_mother_handle() - else: - sp_id = family.get_father_handle() - spouse = self.dbstate.db.get_person_from_handle(sp_id) - if not spouse: - continue - - if no_spouses: - no_spouses = 0 - item.set_submenu(Gtk.Menu()) - sp_menu = item.get_submenu() - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) - go_image.show() - sp_item = Gtk.ImageMenuItem(name_displayer.display(spouse)) - sp_item.set_image(go_image) - linked_persons.append(sp_id) - sp_item.connect("activate",self.on_childmenu_changed,sp_id) - sp_item.show() - sp_menu.append(sp_item) - - if no_spouses: - item.set_sensitive(0) - - item.show() - menu.append(item) - - # Go over siblings and build their menu - item = Gtk.MenuItem(label=_("Siblings")) - pfam_list = person.get_parent_family_handle_list() - no_siblings = 1 - for f in pfam_list: - fam = self.dbstate.db.get_family_from_handle(f) - sib_list = fam.get_child_ref_list() - for sib_ref in sib_list: - sib_id = sib_ref.ref - if sib_id == person.get_handle(): - continue - sib = self.dbstate.db.get_person_from_handle(sib_id) - if not sib: - continue - - if no_siblings: - no_siblings = 0 - item.set_submenu(Gtk.Menu()) - sib_menu = item.get_submenu() - - if find_children(self.dbstate.db,sib): - label = Gtk.Label(label='%s' % escape(name_displayer.display(sib))) - else: - label = Gtk.Label(label=escape(name_displayer.display(sib))) - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) - go_image.show() - sib_item = Gtk.ImageMenuItem(None) - sib_item.set_image(go_image) - label.set_use_markup(True) - label.show() - label.set_alignment(0,0) - sib_item.add(label) - linked_persons.append(sib_id) - sib_item.connect("activate",self.on_childmenu_changed,sib_id) - sib_item.show() - sib_menu.append(sib_item) - - if no_siblings: - item.set_sensitive(0) - item.show() - menu.append(item) - - # Go over children and build their menu - item = Gtk.MenuItem(label=_("Children")) - no_children = 1 - childlist = find_children(self.dbstate.db,person) - for child_handle in childlist: - child = self.dbstate.db.get_person_from_handle(child_handle) - if not child: - continue - - if no_children: - no_children = 0 - item.set_submenu(Gtk.Menu()) - child_menu = item.get_submenu() - - if find_children(self.dbstate.db,child): - label = Gtk.Label(label='%s' % escape(name_displayer.display(child))) - else: - label = Gtk.Label(label=escape(name_displayer.display(child))) - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) - go_image.show() - child_item = Gtk.ImageMenuItem(None) - child_item.set_image(go_image) - label.set_use_markup(True) - label.show() - label.set_alignment(0,0) - child_item.add(label) - linked_persons.append(child_handle) - child_item.connect("activate",self.on_childmenu_changed,child_handle) - child_item.show() - child_menu.append(child_item) - - if no_children: - item.set_sensitive(0) - item.show() - menu.append(item) - - # Go over parents and build their menu - item = Gtk.MenuItem(label=_("Parents")) - no_parents = 1 - par_list = find_parents(self.dbstate.db,person) - for par_id in par_list: - par = self.dbstate.db.get_person_from_handle(par_id) - if not par: - continue - - if no_parents: - no_parents = 0 - item.set_submenu(Gtk.Menu()) - par_menu = item.get_submenu() - - if find_parents(self.dbstate.db,par): - label = Gtk.Label(label='%s' % escape(name_displayer.display(par))) - else: - label = Gtk.Label(label=escape(name_displayer.display(par))) - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) - go_image.show() - par_item = Gtk.ImageMenuItem(None) - par_item.set_image(go_image) - label.set_use_markup(True) - label.show() - label.set_alignment(0,0) - par_item.add(label) - linked_persons.append(par_id) - par_item.connect("activate",self.on_childmenu_changed,par_id) - par_item.show() - par_menu.append(par_item) - - if no_parents: - item.set_sensitive(0) - item.show() - menu.append(item) - - # Go over parents and build their menu - item = Gtk.MenuItem(label=_("Related")) - no_related = 1 - for p_id in find_witnessed_people(self.dbstate.db,person): - #if p_id in linked_persons: - # continue # skip already listed family members - - per = self.dbstate.db.get_person_from_handle(p_id) - if not per: - continue - - if no_related: - no_related = 0 - item.set_submenu(Gtk.Menu()) - per_menu = item.get_submenu() - - label = Gtk.Label(label=escape(name_displayer.display(per))) - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) - go_image.show() - per_item = Gtk.ImageMenuItem(None) - per_item.set_image(go_image) - label.set_use_markup(True) - label.show() - label.set_alignment(0,0) - per_item.add(label) - per_item.connect("activate",self.on_childmenu_changed,p_id) - per_item.show() - per_menu.append(per_item) - - if no_related: - item.set_sensitive(0) - item.show() - menu.append(item) - menu.popup(None, None, None, None, event.button, event.time) - return 1 diff --git a/src/plugins/gramplet/quickviewgramplet.py b/src/plugins/gramplet/quickviewgramplet.py index 02e570d07..e5a87a6f4 100644 --- a/src/plugins/gramplet/quickviewgramplet.py +++ b/src/plugins/gramplet/quickviewgramplet.py @@ -146,4 +146,3 @@ class QuickViewGramplet(Gramplet): qv_list = get_quick_report_list(code_map[qv_option.get_value()]) for pdata in qv_list: list_option.add_item(pdata.id, pdata.name) - diff --git a/src/plugins/lib/Makefile.am b/src/plugins/lib/Makefile.am index 0672226c4..5557529b5 100644 --- a/src/plugins/lib/Makefile.am +++ b/src/plugins/lib/Makefile.am @@ -17,7 +17,6 @@ SUBDIRS = maps pkgpython_PYTHON = \ libcairodoc.py\ - libformatting.py\ libgedcom.py\ libgrampsxml.py\ libgrdb.py\ diff --git a/src/plugins/lib/libplugins.gpr.py b/src/plugins/lib/libplugins.gpr.py index 22e4190ec..e3fa0b556 100644 --- a/src/plugins/lib/libplugins.gpr.py +++ b/src/plugins/lib/libplugins.gpr.py @@ -40,24 +40,6 @@ authors_email = ["http://gramps-project.org"], #load_on_reg = True ) -#------------------------------------------------------------------------ -# -# libformatting -# -#------------------------------------------------------------------------ -register(GENERAL, -id = 'libformatting', -name = "FormattingHelper lib", -description = _("Provides a FormattingHelper class for common strings"), -version = '1.0', -gramps_target_version = '4.0', -status = STABLE, -fname = 'libformatting.py', -authors = ["The Gramps project"], -authors_email = ["http://gramps-project.org"], -#load_on_reg = True - ) - #------------------------------------------------------------------------ # # libgedcom diff --git a/src/plugins/lib/maps/geography.py b/src/plugins/lib/maps/geography.py index c9f9e290d..964c85979 100644 --- a/src/plugins/lib/maps/geography.py +++ b/src/plugins/lib/maps/geography.py @@ -50,7 +50,7 @@ from gi.repository import GdkPixbuf import gen.lib from gen.display.name import displayer as _nd from gui.views.navigationview import NavigationView -from libformatting import FormattingHelper +from gen.utils.libformatting import FormattingHelper from gen.errors import WindowActiveError from gen.const import HOME_DIR, ROOT_DIR from gui.managedwindow import ManagedWindow diff --git a/src/plugins/view/fanchartview.py b/src/plugins/view/fanchartview.py index 890f54f23..3b3f86e90 100644 --- a/src/plugins/view/fanchartview.py +++ b/src/plugins/view/fanchartview.py @@ -35,7 +35,6 @@ from gi.repository import Gdk from gi.repository import Gtk import cairo -from cgi import escape from gen.ggettext import gettext as _ #------------------------------------------------------------------------- @@ -43,37 +42,40 @@ from gen.ggettext import gettext as _ # GRAMPS modules # #------------------------------------------------------------------------- -from gen.display.name import displayer as name_displayer -from gen.utils.db import (find_children, find_parents, find_witnessed_people) -from libformatting import FormattingHelper import gen.lib -from gui.widgets.fanchart import FanChartWidget +from gui.widgets.fanchart import FanChartWidget, FanChartGrampsGUI from gui.views.navigationview import NavigationView from gen.errors import WindowActiveError from gui.views.bookmarks import PersonBookmarks from gui.editors import EditPerson - # the print settings to remember between print sessions PRINT_SETTINGS = None -class FanChartView(NavigationView): +class FanChartView(FanChartGrampsGUI, NavigationView): """ The Gramplet code that realizes the FanChartWidget. """ + #settings in the config file + CONFIGSETTINGS = ( + ('interface.fanview-maxgen', 9), + ('interface.fanview-background', 0), + ) def __init__(self, pdata, dbstate, uistate, nav_group=0): + self.dbstate = dbstate + self.uistate = uistate NavigationView.__init__(self, _('Fan Chart'), pdata, dbstate, uistate, dbstate.db.get_bookmarks(), PersonBookmarks, - nav_group) + nav_group) + FanChartGrampsGUI.__init__(self, + self._config.get('interface.fanview-maxgen'), + self._config.get('interface.fanview-background'), + self.on_childmenu_changed) dbstate.connect('active-changed', self.active_changed) dbstate.connect('database-changed', self.change_db) - self.dbstate = dbstate - self.uistate = uistate - self.generations = 9 - self.format_helper = FormattingHelper(self.dbstate) self.additional_uis.append(self.additional_ui()) @@ -81,9 +83,7 @@ class FanChartView(NavigationView): return 'Person' def build_widget(self): - self.fan = FanChartWidget(self.generations, self.dbstate, - context_popup_callback=self.on_popup) - self.fan.format_helper = self.format_helper + self.set_fan(FanChartWidget(self.dbstate, self.on_popup)) self.scrolledwindow = Gtk.ScrolledWindow(None, None) self.scrolledwindow.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC) @@ -188,110 +188,10 @@ class FanChartView(NavigationView): self.change_active(handle) self.main() - def have_parents(self, person): + def get_active(self, object): + """overrule get_active, to support call as in Gramplets """ - Returns True if a person has parents. - """ - if person: - m = self.get_parent(person, False) - f = self.get_parent(person, True) - return not m is f is None - return False - - def have_children(self, person): - """ - Returns True if a person has children. - """ - if person: - for family_handle in person.get_family_handle_list(): - family = self.dbstate.db.get_family_from_handle(family_handle) - if family and len(family.get_child_ref_list()) > 0: - return True - return False - - def get_parent(self, person, father): - """ - Get the father of the family if father == True, otherwise mother - """ - if person: - parent_handle_list = person.get_parent_family_handle_list() - if parent_handle_list: - family_id = parent_handle_list[0] - family = self.dbstate.db.get_family_from_handle(family_id) - if family: - if father: - person_handle = gen.lib.Family.get_father_handle(family) - else: - person_handle = gen.lib.Family.get_mother_handle(family) - if person_handle: - return self.dbstate.db.get_person_from_handle(person_handle) - return None - - def main(self): - """ - Fill the data structures with the active data. This initializes all - data. - """ - self.fan.reset_generations() - person = self.dbstate.db.get_person_from_handle(self.get_active()) - if not person: - name = None - else: - name = name_displayer.display(person) - parents = self.have_parents(person) - child = self.have_children(person) - self.fan.data[0][0] = (name, person, parents, child) - for current in range(1, self.generations): - parent = 0 - # name, person, parents, children - for (n,p,q,c) in self.fan.data[current - 1]: - # Get father's details: - person = self.get_parent(p, True) - if person: - name = name_displayer.display(person) - else: - name = None - if current == self.generations - 1: - parents = self.have_parents(person) - else: - parents = None - self.fan.data[current][parent] = (name, person, parents, None) - if person is None: - # start,stop,male/right,state - self.fan.angle[current][parent][3] = self.fan.COLLAPSED - parent += 1 - # Get mother's details: - person = self.get_parent(p, False) - if person: - name = name_displayer.display(person) - else: - name = None - if current == self.generations - 1: - parents = self.have_parents(person) - else: - parents = None - self.fan.data[current][parent] = (name, person, parents, None) - if person is None: - # start,stop,male/right,state - self.fan.angle[current][parent][3] = self.fan.COLLAPSED - parent += 1 - self.fan.queue_draw() - - def on_childmenu_changed(self, obj, person_handle): - """Callback for the pulldown menu selection, changing to the person - attached with menu item.""" - self.change_active(person_handle) - return True - - def edit_person_cb(self, obj,person_handle): - person = self.dbstate.db.get_person_from_handle(person_handle) - if person: - try: - EditPerson(self.dbstate, self.uistate, [], person) - except WindowActiveError: - pass - return True - return False + return NavigationView.get_active(self) def person_rebuild(self, *args): self.update() @@ -301,239 +201,6 @@ class FanChartView(NavigationView): self.person_rebuild() if self.active: self.bookmarks.redraw() - - def copy_person_to_clipboard_cb(self, obj, person_handle): - """Renders the person data into some lines of text and puts that into the clipboard""" - person = self.dbstate.db.get_person_from_handle(person_handle) - if person: - cb = Gtk.Clipboard.get_for_display(Gdk.Display.get_default(), - Gdk.SELECTION_CLIPBOARD) - cb.set_text( self.format_helper.format_person(person,11), -1) - return True - return False - - def on_popup(self, obj, event, person_handle): - """ - Builds the full menu (including Siblings, Spouses, Children, - and Parents) with navigation. Copied from PedigreeView. - """ - #store menu for GTK3 to avoid it being destroyed before showing - self.menu = Gtk.Menu() - menu = self.menu - menu.set_title(_('People Menu')) - - person = self.dbstate.db.get_person_from_handle(person_handle) - if not person: - return 0 - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO,Gtk.IconSize.MENU) - go_image.show() - go_item = Gtk.ImageMenuItem(name_displayer.display(person)) - go_item.set_image(go_image) - go_item.connect("activate",self.on_childmenu_changed,person_handle) - go_item.show() - menu.append(go_item) - - edit_item = Gtk.ImageMenuItem.new_from_stock(stock_id=Gtk.STOCK_EDIT, accel_group=None) - edit_item.connect("activate", self.edit_person_cb, person_handle) - edit_item.show() - menu.append(edit_item) - - clipboard_item = Gtk.ImageMenuItem.new_from_stock(stock_id=Gtk.STOCK_COPY, accel_group=None) - clipboard_item.connect("activate",self.copy_person_to_clipboard_cb,person_handle) - clipboard_item.show() - menu.append(clipboard_item) - - # collect all spouses, parents and children - linked_persons = [] - - # Go over spouses and build their menu - item = Gtk.MenuItem(label=_("Spouses")) - fam_list = person.get_family_handle_list() - no_spouses = 1 - for fam_id in fam_list: - family = self.dbstate.db.get_family_from_handle(fam_id) - if family.get_father_handle() == person.get_handle(): - sp_id = family.get_mother_handle() - else: - sp_id = family.get_father_handle() - spouse = self.dbstate.db.get_person_from_handle(sp_id) - if not spouse: - continue - - if no_spouses: - no_spouses = 0 - item.set_submenu(Gtk.Menu()) - sp_menu = item.get_submenu() - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) - go_image.show() - sp_item = Gtk.ImageMenuItem(name_displayer.display(spouse)) - sp_item.set_image(go_image) - linked_persons.append(sp_id) - sp_item.connect("activate",self.on_childmenu_changed,sp_id) - sp_item.show() - sp_menu.append(sp_item) - - if no_spouses: - item.set_sensitive(0) - - item.show() - menu.append(item) - - # Go over siblings and build their menu - item = Gtk.MenuItem(label=_("Siblings")) - pfam_list = person.get_parent_family_handle_list() - no_siblings = 1 - for f in pfam_list: - fam = self.dbstate.db.get_family_from_handle(f) - sib_list = fam.get_child_ref_list() - for sib_ref in sib_list: - sib_id = sib_ref.ref - if sib_id == person.get_handle(): - continue - sib = self.dbstate.db.get_person_from_handle(sib_id) - if not sib: - continue - - if no_siblings: - no_siblings = 0 - item.set_submenu(Gtk.Menu()) - sib_menu = item.get_submenu() - - if find_children(self.dbstate.db,sib): - label = Gtk.Label(label='%s' % escape(name_displayer.display(sib))) - else: - label = Gtk.Label(label=escape(name_displayer.display(sib))) - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) - go_image.show() - sib_item = Gtk.ImageMenuItem(None) - sib_item.set_image(go_image) - label.set_use_markup(True) - label.show() - label.set_alignment(0,0) - sib_item.add(label) - linked_persons.append(sib_id) - sib_item.connect("activate", self.on_childmenu_changed, sib_id) - sib_item.show() - sib_menu.append(sib_item) - - if no_siblings: - item.set_sensitive(0) - item.show() - menu.append(item) - - # Go over children and build their menu - item = Gtk.MenuItem(label=_("Children")) - no_children = 1 - childlist = find_children(self.dbstate.db, person) - for child_handle in childlist: - child = self.dbstate.db.get_person_from_handle(child_handle) - if not child: - continue - - if no_children: - no_children = 0 - item.set_submenu(Gtk.Menu()) - child_menu = item.get_submenu() - - if find_children(self.dbstate.db,child): - label = Gtk.Label(label='%s' % escape(name_displayer.display(child))) - else: - label = Gtk.Label(label=escape(name_displayer.display(child))) - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) - go_image.show() - child_item = Gtk.ImageMenuItem(None) - child_item.set_image(go_image) - label.set_use_markup(True) - label.show() - label.set_alignment(0,0) - child_item.add(label) - linked_persons.append(child_handle) - child_item.connect("activate", self.on_childmenu_changed, child_handle) - child_item.show() - child_menu.append(child_item) - - if no_children: - item.set_sensitive(0) - item.show() - menu.append(item) - - # Go over parents and build their menu - item = Gtk.MenuItem(label=_("Parents")) - no_parents = 1 - par_list = find_parents(self.dbstate.db,person) - for par_id in par_list: - par = self.dbstate.db.get_person_from_handle(par_id) - if not par: - continue - - if no_parents: - no_parents = 0 - item.set_submenu(Gtk.Menu()) - par_menu = item.get_submenu() - - if find_parents(self.dbstate.db,par): - label = Gtk.Label(label='%s' % escape(name_displayer.display(par))) - else: - label = Gtk.Label(label=escape(name_displayer.display(par))) - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) - go_image.show() - par_item = Gtk.ImageMenuItem(None) - par_item.set_image(go_image) - label.set_use_markup(True) - label.show() - label.set_alignment(0,0) - par_item.add(label) - linked_persons.append(par_id) - par_item.connect("activate",self.on_childmenu_changed,par_id) - par_item.show() - par_menu.append(par_item) - - if no_parents: - item.set_sensitive(0) - item.show() - menu.append(item) - - # Go over parents and build their menu - item = Gtk.MenuItem(label=_("Related")) - no_related = 1 - for p_id in find_witnessed_people(self.dbstate.db,person): - #if p_id in linked_persons: - # continue # skip already listed family members - - per = self.dbstate.db.get_person_from_handle(p_id) - if not per: - continue - - if no_related: - no_related = 0 - item.set_submenu(Gtk.Menu()) - per_menu = item.get_submenu() - - label = Gtk.Label(label=escape(name_displayer.display(per))) - - go_image = Gtk.Image.new_from_stock(Gtk.STOCK_JUMP_TO, Gtk.IconSize.MENU) - go_image.show() - per_item = Gtk.ImageMenuItem(None) - per_item.set_image(go_image) - label.set_use_markup(True) - label.show() - label.set_alignment(0, 0) - per_item.add(label) - per_item.connect("activate", self.on_childmenu_changed, p_id) - per_item.show() - per_menu.append(per_item) - - if no_related: - item.set_sensitive(0) - item.show() - menu.append(item) - menu.popup(None, None, None, None, event.button, event.time) - return 1 def printview(self, obj): """ @@ -544,6 +211,62 @@ class FanChartView(NavigationView): prt = CairoPrintSave(widthpx, self.fan.on_draw, self.uistate.window) prt.run() + def on_childmenu_changed(self, obj, person_handle): + """Callback for the pulldown menu selection, changing to the person + attached with menu item.""" + self.change_active(person_handle) + return True + + def can_configure(self): + """ + See :class:`~gui.views.pageview.PageView + :return: bool + """ + return True + + def _get_configure_page_funcs(self): + """ + Return a list of functions that create gtk elements to use in the + notebook pages of the Configure dialog + + :return: list of functions + """ + return [self.config_panel] + + def config_panel(self, configdialog): + """ + Function that builds the widget in the configuration dialog + """ + table = Gtk.Table(3, 2) + table.set_border_width(12) + table.set_col_spacings(6) + table.set_row_spacings(6) + + configdialog.add_spinner(table, _("Max generations"), 0, + 'interface.fanview-maxgen', (1, 11), + callback=self.cb_update_maxgen) + configdialog.add_combo(table, + _('Background'), + 4, 'interface.fanview-background', + ((0, _('Color Scheme 1')), + (1, _('Color Scheme 2')), + (2, _('Gender Colors')), + (3, _('White'))), + callback=self.cb_update_background) + + return _('Layout'), table + + def cb_update_maxgen(self, spinbtn, constant): + self.maxgen = spinbtn.get_value_as_int() + self._config.set(constant, self.maxgen) + self.update() + + def cb_update_background(self, obj, constant): + entry = obj.get_active() + self._config.set(constant, entry) + self.background = int(entry) + self.update() + #------------------------------------------------------------------------ # # CairoPrintSave class diff --git a/src/plugins/view/pedigreeview.py b/src/plugins/view/pedigreeview.py index 2b8b17187..fda780e9b 100644 --- a/src/plugins/view/pedigreeview.py +++ b/src/plugins/view/pedigreeview.py @@ -60,7 +60,7 @@ from gen.display.name import displayer as name_displayer from gen.utils.alive import probably_alive from gen.utils.file import media_path_full from gen.utils.db import find_children, find_parents, find_witnessed_people -from libformatting import FormattingHelper +from gen.utils.libformatting import FormattingHelper from gui.thumbnails import get_thumbnail_path from gen.errors import WindowActiveError from gui.editors import EditPerson, EditFamily