def update_mru_menu(self, items, update_menu=True):
        """
        Builds the UI and action group for the MRU list.
        """
        menuitem = '''        <item>
              <attribute name="action">win.%s%02d</attribute>
              <attribute name="label">%s</attribute>
            </item>
            '''
        menus = ''
        self.mru_disable()
        nav_type = self.navigation_type()
        hobj = self.get_history()
        menu_len = min(len(items) - 1, MRU_SIZE)

        data = []
        for index in range(menu_len - 1, -1, -1):
            name, _obj = navigation_label(self.dbstate.db, items[index][0],
                                          items[index][1])
            menus += menuitem % (nav_type, index, html.escape(name))
            data.append(('%s%02d' % (nav_type, index),
                         make_callback(hobj.push, items[index]),
                         "%s%d" % (mod_key(), menu_len - 1 - index)))
        self.mru_ui = [MRU_TOP + menus + MRU_BTM]

        self.mru_action = ActionGroup(name=self.title + '/MRU')
        self.mru_action.add_actions(data)
        self.mru_enable(update_menu)
Пример #2
0
    def define_actions(self, view):
        self.event_action = ActionGroup(name='Event')
        self.event_action.add_actions([
            ('AddParticipant', self.add_participant),
            ('ShareParticipant', self.select_participant)
        ])

        view._add_action_group(self.event_action)
    def bookmark_actions(self):
        """
        Define the bookmark menu actions.
        """
        self.book_action = ActionGroup(name=self.title + '/Bookmark')
        self.book_action.add_actions([
            ('AddBook', self.add_bookmark, '<PRIMARY>d'),
            ('EditBook', self.edit_bookmarks, '<shift><PRIMARY>D'),
        ])

        self._add_action_group(self.book_action)
Пример #4
0
    def define_actions(self, view):
        self.order_action = ActionGroup(name='ChangeOrder')
        self.order_action.add_actions([
            ('ChangeOrder', self.reorder)])

        self.family_action = ActionGroup(name='Family')
        self.family_action.add_actions([
            ('AddSpouse', self.add_spouse),
            ('AddParents', self.add_parents),
            ('ShareFamily', self.select_parents)])

        view._add_action_group(self.order_action)
        view._add_action_group(self.family_action)
    def navigation_actions(self):
        """
        Define the navigation menu actions.
        """
        # add the Forward action group to handle the Forward button
        self.fwd_action = ActionGroup(name=self.title + '/Forward')
        self.fwd_action.add_actions([('Forward', self.fwd_clicked,
                                      "%sRight" % mod_key())])

        # add the Backward action group to handle the Forward button
        self.back_action = ActionGroup(name=self.title + '/Backward')
        self.back_action.add_actions([('Back', self.back_clicked,
                                       "%sLeft" % mod_key())])

        self._add_action('HomePerson', self.home, "%sHome" % mod_key())

        self.other_action = ActionGroup(name=self.title + '/PersonOther')
        self.other_action.add_actions([('SetActive', self.set_default_person)])

        self._add_action_group(self.back_action)
        self._add_action_group(self.fwd_action)
        self._add_action_group(self.other_action)
Пример #6
0
class EventPage(BasePage):
    def __init__(self, dbstate, uistate, config):
        BasePage.__init__(self, dbstate, uistate, config)

    def obj_type(self):
        return 'Event'

    def define_actions(self, view):
        self.event_action = ActionGroup(name='Event')
        self.event_action.add_actions([
            ('AddParticipant', self.add_participant),
            ('ShareParticipant', self.select_participant)
        ])

        view._add_action_group(self.event_action)

    def enable_actions(self, uimanager, event):
        uimanager.set_actions_visible(self.event_action, True)

    def disable_actions(self, uimanager):
        uimanager.set_actions_visible(self.event_action, False)

    def write_title(self, event):

        self.handle = event.handle

        grid = Gtk.Grid()
        grid.set_column_spacing(12)
        grid.set_row_spacing(0)

        # event title and edit button
        etype = str(event.get_type())
        desc = event.get_description()
        if desc:
            title = '%s (%s)' % (etype, desc)
        else:
            title = etype
        fmt = '<span size="larger" weight="bold">%s</span>'
        text = fmt % escape(title)
        label = widgets.MarkupLabel(text, halign=Gtk.Align.END)
        if self._config.get('preferences.releditbtn'):
            button = widgets.IconButton(self.edit_event_button, event.handle)
            button.set_tooltip_text(_('Edit %s') % title)
        else:
            button = None

        hbox = widgets.LinkBox(label, button)
        if self.show_tags:
            tag_list = TagList(self.get_tag_list(event))
            hbox.pack_start(tag_list, False, False, 0)
        eventbox = self.make_dragbox(hbox, 'Event', event.get_handle())
        grid.attach(eventbox, 0, 0, 2, 1)

        subgrid = Gtk.Grid()
        subgrid.set_column_spacing(12)
        subgrid.set_row_spacing(0)
        eventbox = self.make_dragbox(subgrid, 'Event', event.get_handle())
        grid.attach(eventbox, 1, 1, 1, 1)

        # Gramps ID
        subgrid.attach(widgets.BasicLabel("%s:" % _('ID')), 1, 0, 1, 1)
        label = widgets.BasicLabel(event.gramps_id)
        label.set_hexpand(True)
        subgrid.attach(label, 2, 0, 1, 1)

        # Date
        subgrid.attach(widgets.BasicLabel("%s:" % 'Date'), 1, 1, 1, 1)
        subgrid.attach(widgets.BasicLabel(get_date(event)), 2, 1, 1, 1)

        # Place
        place = place_displayer.display_event(self.dbstate.db, event)
        subgrid.attach(widgets.BasicLabel("%s:" % 'Place'), 1, 2, 1, 1)
        subgrid.attach(widgets.BasicLabel(place), 2, 2, 1, 1)

        grid.show_all()
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        vbox.pack_start(grid, False, True, 0)

        # Attributes
        attrs = event.get_attribute_list()
        if len(attrs):
            ex = Gtk.Expander(label='%s:' % _('Attributes'))
            attr_grid = self.get_attribute_grid(attrs)
            ex.set_margin_start(24)
            ex.add(attr_grid)
            ex.show()
            vbox.pack_start(ex, False, True, 0)

        vbox.show_all()
        return vbox

    def write_stack(self, event, stack):
        self.write_participants(event, stack)
        self.write_citations(event, stack)

##############################################################################
#
# Participants list
#
##############################################################################

    def write_participants(self, event, stack):

        self.vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        scroll = Gtk.ScrolledWindow()
        scroll.add(self.vbox2)
        scroll.show_all()
        stack.add_titled(scroll, 'participant', _('Participants'))

        roles = {}
        for item in self.dbstate.db.find_backlink_handles(
                event.handle, include_classes=['Person', 'Family']):

            handle = item[1]
            if item[0] == 'Person':
                obj = self.dbstate.db.get_person_from_handle(handle)
            elif item[0] == 'Family':
                obj = self.dbstate.db.get_family_from_handle(handle)

            for eventref in obj.get_event_ref_list():
                if eventref.ref == event.handle:
                    participant = (item[0], obj, eventref)
                    if str(eventref.role) not in roles:
                        roles[str(eventref.role)] = [participant]
                    else:
                        roles[str(eventref.role)].append(participant)

        for role in roles.keys():
            self.write_role(role, roles[role])

    def write_role(self, role, participant_list):

        title = '<span weight="bold">%s: </span>' % role
        label = widgets.MarkupLabel(title)
        self.vbox2.pack_start(label, False, False, 2)

        participants = []
        for participant in participant_list:
            obj_type, obj, eventref = participant
            order = 0
            attrs = eventref.get_attribute_list()
            for attr in attrs:
                if str(attr.get_type()) == _('Order'):
                    order = int(attr.get_value())
            if obj_type == 'Person':
                participants.append((order, obj, attrs))
            elif obj_type == 'Family':
                father_handle = obj.get_father_handle()
                if father_handle:
                    father = self.dbstate.db.get_person_from_handle(
                        father_handle)
                    participants.append((order, father, []))
                mother_handle = obj.get_mother_handle()
                if mother_handle:
                    mother = self.dbstate.db.get_person_from_handle(
                        mother_handle)
                    participants.append((order, mother, []))

        for person in sorted(participants, key=lambda x: x[0]):
            self.write_participant(person[1], person[2])

    def write_participant(self, person, attrs):

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        handle = person.handle
        name = self.get_name(handle, True)
        if self.has_children(person):
            emph = True
        else:
            emph = False
        link_func = self._person_link
        link_label = widgets.LinkLabel(name,
                                       link_func,
                                       handle,
                                       emph,
                                       theme=self.theme)
        link_label.set_padding(3, 0)
        if self._config.get('preferences.releditbtn'):
            button = widgets.IconButton(self.edit_person_button, handle)
            button.set_tooltip_text(_('Edit %s') % name[0])
        else:
            button = None

        hbox = Gtk.Box()
        hbox.set_spacing(6)
        hbox.pack_start(link_label, False, False, 0)
        if self.show_details:
            box = self.info_box(handle)
            if box:
                hbox.pack_start(box, False, False, 0)
        if button is not None:
            hbox.pack_start(button, False, False, 0)
        if self.show_tags:
            tag_list = TagList(self.get_tag_list(person))
            hbox.pack_start(tag_list, False, False, 0)
        vbox.pack_start(hbox, False, False, 0)

        # Write attributes
        attr_grid = self.get_attribute_grid(attrs)
        attr_grid.set_margin_start(24)
        vbox.pack_start(attr_grid, False, False, 0)

        eventbox = self.make_dragbox(vbox, 'Person', handle)
        eventbox.show_all()

        self.vbox2.pack_start(eventbox, False, False, 1)

    def get_attribute_grid(self, attrs):
        grid = Gtk.Grid()
        row = 0
        for attr in attrs:
            if str(attr.get_type()) != _('Order'):
                label = widgets.BasicLabel('%s: ' % str(attr.get_type()))
                grid.attach(label, 0, row, 1, 1)
                label = widgets.BasicLabel(attr.get_value())
                grid.attach(label, 1, row, 1, 1)
                row += 1
        grid.show_all()
        return grid

##############################################################################
#
# Citations list
#
##############################################################################

    def write_citations(self, event, stack):

        self.vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        scroll = Gtk.ScrolledWindow()
        scroll.add(self.vbox2)
        scroll.show_all()
        stack.add_titled(scroll, 'citation', _('Citations'))

        for handle in event.get_citation_list():
            self.write_full_citation(handle)

    def write_full_citation(self, chandle):

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        citation = self.dbstate.db.get_citation_from_handle(chandle)
        shandle = citation.get_reference_handle()
        source = self.dbstate.db.get_source_from_handle(shandle)
        heading = source.get_title() + ' ' + citation.get_page()

        vbox.pack_start(widgets.BasicLabel(heading), False, False, 0)

        hbox = self.load_images(citation)
        vbox.pack_start(hbox, False, False, 0)

        eventbox = self.make_dragbox(vbox, 'Citation', chandle)
        eventbox.show_all()

        self.vbox2.pack_start(eventbox, False, False, 1)

##############################################################################
#
# Toolbar actions
#
##############################################################################

    def edit_active(self, *obj):
        self.edit_event(self.handle)

    def select_participant(self, *obj):
        SelectPerson = SelectorFactory('Person')
        dialog = SelectPerson(self.dbstate, self.uistate)
        person = dialog.run()
        if person is None:
            return

        ref = EventRef()
        ref.ref = self.handle
        person.add_event_ref(ref)

        try:
            EditPerson(self.dbstate, self.uistate, [], person)
        except WindowActiveError:
            pass

    def add_participant(self, *obj):
        person = Person()
        ref = EventRef()
        ref.ref = self.handle
        person.add_event_ref(ref)

        try:
            EditPerson(self.dbstate, self.uistate, [], person)
        except WindowActiveError:
            pass

    def add_tag(self, trans, object_handle, tag_handle):
        """
        Add the given tag to the active object.
        """
        event = self.dbstate.db.get_event_from_handle(object_handle)
        event.add_tag(tag_handle)
        self.dbstate.db.commit_event(event, trans)
class NavigationView(PageView):
    """
    The NavigationView class is the base class for all Data Views that require
    navigation functionalilty. Views that need bookmarks and forward/backward
    should derive from this class.
    """
    def __init__(self, title, pdata, state, uistate, bm_type, nav_group):
        PageView.__init__(self, title, pdata, state, uistate)
        self.bookmarks = bm_type(self.dbstate, self.uistate, self.bm_change)

        self.fwd_action = None
        self.back_action = None
        self.book_action = None
        self.other_action = None
        self.active_signal = None
        self.mru_signal = None
        self.nav_group = nav_group
        self.mru_active = DISABLED
        self.uimanager = uistate.uimanager

        self.history = History(self.dbstate)

        self.hist = {}
        for hist_type in ('Person', 'Event'):
            self.hist[hist_type] = self.uistate.get_history(hist_type)
            self.hist[hist_type].connect('active-changed',
                                         self.sync(hist_type))

    # A partial would be neater here but it doesn't work.
    def sync(self, hist_type):
        def sync(handle):
            self.change_active((hist_type, handle))

        return sync

    def bm_change(self, handle):
        self.change_active(('Person', handle))

    def navigation_type(self):
        """
        Indictates the navigation type. Navigation type can be the string
        name of any of the primary Objects. A History object will be
        created for it, see DisplayState.History
        """
        return None

    def define_actions(self):
        """
        Define menu actions.
        """
        PageView.define_actions(self)
        self.bookmark_actions()
        self.navigation_actions()

    def disable_action_group(self):
        """
        Normally, this would not be overridden from the base class. However,
        in this case, we have additional action groups that need to be
        handled correctly.
        """
        PageView.disable_action_group(self)

        self.uimanager.set_actions_visible(self.fwd_action, False)
        self.uimanager.set_actions_visible(self.back_action, False)

    def enable_action_group(self, obj):
        """
        Normally, this would not be overridden from the base class. However,
        in this case, we have additional action groups that need to be
        handled correctly.
        """
        PageView.enable_action_group(self, obj)

        self.uimanager.set_actions_visible(self.fwd_action, True)
        self.uimanager.set_actions_visible(self.back_action, True)
        hobj = self.get_history()
        self.uimanager.set_actions_sensitive(self.fwd_action,
                                             not hobj.at_end())
        self.uimanager.set_actions_sensitive(self.back_action,
                                             not hobj.at_front())

    def change_page(self):
        """
        Called when the page changes.
        """
        hobj = self.get_history()
        self.uimanager.set_actions_sensitive(self.fwd_action,
                                             not hobj.at_end())
        self.uimanager.set_actions_sensitive(self.back_action,
                                             not hobj.at_front())
        self.uimanager.set_actions_sensitive(self.other_action,
                                             not self.dbstate.db.readonly)
        self.uistate.modify_statusbar(self.dbstate)

    def set_active(self):
        """
        Called when the page becomes active (displayed).
        """
        PageView.set_active(self)
        self.bookmarks.display()

        hobj = self.get_history()
        self.active_signal = hobj.connect('active-changed', self.goto_active)
        self.mru_signal = hobj.connect('mru-changed', self.update_mru_menu)
        self.update_mru_menu(hobj.mru, update_menu=False)

        self.goto_active(None)

    def set_inactive(self):
        """
        Called when the page becomes inactive (not displayed).
        """
        if self.active:
            PageView.set_inactive(self)
            self.bookmarks.undisplay()
            hobj = self.get_history()
            hobj.disconnect(self.active_signal)
            hobj.disconnect(self.mru_signal)
            self.mru_disable()

    def navigation_group(self):
        """
        Return the navigation group.
        """
        return self.nav_group

    def get_history(self):
        """
        Return the history object.
        """
        return self.history

    def goto_active(self, active_handle):
        """
        Callback (and usable function) that selects the active person
        in the display tree.
        """
        hobj = self.get_history()
        active_handle = hobj.present()
        if active_handle:
            self.goto_handle(active_handle)

        self.uimanager.set_actions_sensitive(self.fwd_action,
                                             not hobj.at_end())
        self.uimanager.set_actions_sensitive(self.back_action,
                                             not hobj.at_front())

    def get_active(self):
        """
        Return the handle of the active object.
        """
        hobj = self.get_history()
        return hobj.present()

    def change_active(self, handle):
        """
        Changes the active object.
        """
        hobj = self.get_history()
        if handle and not hobj.lock and not (handle == hobj.present()):
            hobj.push(handle)

            sync_hist = self.hist[handle[0]]
            if sync_hist.present() != handle[1]:
                sync_hist.push(handle[1])

    @abstractmethod
    def goto_handle(self, handle):
        """
        Needs to be implemented by classes derived from this.
        Used to move to the given handle.
        """

    def selected_handles(self):
        """
        Return the active person's handle in a list. Used for
        compatibility with those list views that can return multiply
        selected items.
        """
        active_handle = self.uistate.get_active(self.navigation_type(),
                                                self.navigation_group())
        return [active_handle] if active_handle else []

    ####################################################################
    # BOOKMARKS
    ####################################################################
    def add_bookmark(self, *obj):
        """
        Add a bookmark to the list.
        """
        from gramps.gen.display.name import displayer as name_displayer

        active_handle = self.uistate.get_active('Person')
        active_person = self.dbstate.db.get_person_from_handle(active_handle)
        if active_person:
            self.bookmarks.add(active_handle)
            name = name_displayer.display(active_person)
            self.uistate.push_message(self.dbstate,
                                      _("%s has been bookmarked") % name)
        else:
            from ..dialog import WarningDialog
            WarningDialog(_("Could Not Set a Bookmark"),
                          _("A bookmark could not be set because "
                            "no one was selected."),
                          parent=self.uistate.window)

    def edit_bookmarks(self, *obj):
        """
        Call the bookmark editor.
        """
        self.bookmarks.edit()

    def bookmark_actions(self):
        """
        Define the bookmark menu actions.
        """
        self.book_action = ActionGroup(name=self.title + '/Bookmark')
        self.book_action.add_actions([
            ('AddBook', self.add_bookmark, '<PRIMARY>d'),
            ('EditBook', self.edit_bookmarks, '<shift><PRIMARY>D'),
        ])

        self._add_action_group(self.book_action)

    ####################################################################
    # NAVIGATION
    ####################################################################
    def navigation_actions(self):
        """
        Define the navigation menu actions.
        """
        # add the Forward action group to handle the Forward button
        self.fwd_action = ActionGroup(name=self.title + '/Forward')
        self.fwd_action.add_actions([('Forward', self.fwd_clicked,
                                      "%sRight" % mod_key())])

        # add the Backward action group to handle the Forward button
        self.back_action = ActionGroup(name=self.title + '/Backward')
        self.back_action.add_actions([('Back', self.back_clicked,
                                       "%sLeft" % mod_key())])

        self._add_action('HomePerson', self.home, "%sHome" % mod_key())

        self.other_action = ActionGroup(name=self.title + '/PersonOther')
        self.other_action.add_actions([('SetActive', self.set_default_person)])

        self._add_action_group(self.back_action)
        self._add_action_group(self.fwd_action)
        self._add_action_group(self.other_action)

    def set_default_person(self, *obj):
        """
        Set the default person.
        """
        active = self.uistate.get_active('Person')
        if active:
            self.dbstate.db.set_default_person_handle(active)

    def home(self, *obj):
        """
        Move to the default person.
        """
        defperson = self.dbstate.db.get_default_person()
        if defperson:
            self.change_active(('Person', defperson.get_handle()))
        else:
            from ..dialog import WarningDialog
            WarningDialog(
                _("No Home Person"),
                _("You need to set a 'default person' to go to. "
                  "Select the People View, select the person you want as "
                  "'Home Person', then confirm your choice "
                  "via the menu Edit -> Set Home Person."),
                parent=self.uistate.window)

    def jump(self, *obj):
        """
        A dialog to move to a Gramps ID entered by the user.
        """
        dialog = Gtk.Dialog(_('Jump to by Gramps ID'), self.uistate.window)
        dialog.set_border_width(12)
        label = Gtk.Label(label='<span weight="bold" size="larger">%s</span>' %
                          _('Jump to by Gramps ID'))
        label.set_use_markup(True)
        dialog.vbox.add(label)
        dialog.vbox.set_spacing(10)
        dialog.vbox.set_border_width(12)
        hbox = Gtk.Box()
        hbox.pack_start(Gtk.Label(label=_("%s: ") % _('ID')), True, True, 0)
        text = Gtk.Entry()
        text.set_activates_default(True)
        hbox.pack_start(text, False, True, 0)
        dialog.vbox.pack_start(hbox, False, True, 0)
        dialog.add_buttons(_('_Cancel'), Gtk.ResponseType.CANCEL,
                           _('_Jump to'), Gtk.ResponseType.OK)
        dialog.set_default_response(Gtk.ResponseType.OK)
        dialog.vbox.show_all()

        if dialog.run() == Gtk.ResponseType.OK:
            gid = text.get_text()
            handle = self.get_handle_from_gramps_id(gid)
            if handle is not None:
                self.change_active(handle)
            else:
                self.uistate.push_message(
                    self.dbstate,
                    _("Error: %s is not a valid Gramps ID") % gid)
        dialog.destroy()

    def get_handle_from_gramps_id(self, gid):
        """
        Get an object handle from its Gramps ID.
        Needs to be implemented by the inheriting class.
        """
        pass

    def fwd_clicked(self, *obj):
        """
        Move forward one object in the history.
        """
        hobj = self.get_history()
        hobj.lock = True
        if not hobj.at_end():
            hobj.forward()
            self.uistate.modify_statusbar(self.dbstate)
        self.uimanager.set_actions_sensitive(self.fwd_action,
                                             not hobj.at_end())
        self.uimanager.set_actions_sensitive(self.back_action, True)
        hobj.lock = False

    def back_clicked(self, *obj):
        """
        Move backward one object in the history.
        """
        hobj = self.get_history()
        hobj.lock = True
        if not hobj.at_front():
            hobj.back()
            self.uistate.modify_statusbar(self.dbstate)
        self.uimanager.set_actions_sensitive(self.back_action,
                                             not hobj.at_front())
        self.uimanager.set_actions_sensitive(self.fwd_action, True)
        hobj.lock = False

    ####################################################################
    # MRU functions
    ####################################################################

    def mru_disable(self):
        """
        Remove the UI and action groups for the MRU list.
        """
        if self.mru_active != DISABLED:
            self.uimanager.remove_ui(self.mru_active)
            self.uimanager.remove_action_group(self.mru_action)
            self.mru_active = DISABLED

    def mru_enable(self, update_menu=False):
        """
        Enables the UI and action groups for the MRU list.
        """
        if self.mru_active == DISABLED:
            self.uimanager.insert_action_group(self.mru_action)
            self.mru_active = self.uimanager.add_ui_from_string(self.mru_ui)
            if update_menu:
                self.uimanager.update_menu()

    def update_mru_menu(self, items, update_menu=True):
        """
        Builds the UI and action group for the MRU list.
        """
        menuitem = '''        <item>
              <attribute name="action">win.%s%02d</attribute>
              <attribute name="label">%s</attribute>
            </item>
            '''
        menus = ''
        self.mru_disable()
        nav_type = self.navigation_type()
        hobj = self.get_history()
        menu_len = min(len(items) - 1, MRU_SIZE)

        data = []
        for index in range(menu_len - 1, -1, -1):
            name, _obj = navigation_label(self.dbstate.db, items[index][0],
                                          items[index][1])
            menus += menuitem % (nav_type, index, html.escape(name))
            data.append(('%s%02d' % (nav_type, index),
                         make_callback(hobj.push, items[index]),
                         "%s%d" % (mod_key(), menu_len - 1 - index)))
        self.mru_ui = [MRU_TOP + menus + MRU_BTM]

        self.mru_action = ActionGroup(name=self.title + '/MRU')
        self.mru_action.add_actions(data)
        self.mru_enable(update_menu)

    ####################################################################
    # Template functions
    ####################################################################
    @abstractmethod
    def build_tree(self):
        """
        Rebuilds the current display. This must be overridden by the derived
        class.
        """

    @abstractmethod
    def build_widget(self):
        """
        Builds the container widget for the interface. Must be overridden by the
        the base class. Returns a gtk container widget.
        """

    def key_press_handler(self, widget, event):
        """
        Handle the control+c (copy) and control+v (paste), or pass it on.
        """
        if self.active:
            if event.type == Gdk.EventType.KEY_PRESS:
                if (event.keyval == Gdk.KEY_c
                        and match_primary_mask(event.get_state())):
                    self.call_copy()
                    return True
        return super(NavigationView, self).key_press_handler(widget, event)

    def call_copy(self):
        """
        Navigation specific copy (control+c) hander. If the
        copy can be handled, it returns true, otherwise false.

        The code brings up the Clipboard (if already exists) or
        creates it. The copy is handled through the drag and drop
        system.
        """
        nav_type = self.navigation_type()
        handles = self.selected_handles()
        return self.copy_to_clipboard(nav_type, handles)
Пример #8
0
class PersonPage(BasePage):

    def __init__(self, dbstate, uistate, config):
        BasePage.__init__(self, dbstate, uistate, config)
        self.reorder_sensitive = True

    def obj_type(self):
        return 'Person'

    def define_actions(self, view):
        self.order_action = ActionGroup(name='ChangeOrder')
        self.order_action.add_actions([
            ('ChangeOrder', self.reorder)])

        self.family_action = ActionGroup(name='Family')
        self.family_action.add_actions([
            ('AddSpouse', self.add_spouse),
            ('AddParents', self.add_parents),
            ('ShareFamily', self.select_parents)])

        view._add_action_group(self.order_action)
        view._add_action_group(self.family_action)

    def enable_actions(self, uimanager, person):
        uimanager.set_actions_visible(self.family_action, True)
        uimanager.set_actions_visible(self.order_action, True)

    def disable_actions(self, uimanager):
        uimanager.set_actions_visible(self.family_action, False)
        uimanager.set_actions_visible(self.order_action, False)

    def write_title(self, person):

        self.handle = person.handle

        grid = Gtk.Grid()
        grid.set_column_spacing(12)
        grid.set_row_spacing(0)

        # name and edit button
        name = name_displayer.display(person)
        fmt = '<span size="larger" weight="bold">%s</span>'
        text = fmt % escape(name)
        label = widgets.DualMarkupLabel(text, _GenderCode[person.gender],
                                        halign=Gtk.Align.END)
        if self._config.get('preferences.releditbtn'):
            button = widgets.IconButton(self.edit_person_button,
                                        person.handle)
            button.set_tooltip_text(_('Edit %s') % name)
        else:
            button = None


        hbox = widgets.LinkBox(label, button)
        if self.show_tags:
            tag_list = TagList(self.get_tag_list(person))
            hbox.pack_start(tag_list, False, False, 0)
        eventbox = self.make_dragbox(hbox, 'Person', person.get_handle())
        grid.attach(eventbox, 0, 0, 2, 1)

        subgrid = Gtk.Grid()
        subgrid.set_column_spacing(12)
        subgrid.set_row_spacing(0)
        eventbox = self.make_dragbox(subgrid, 'Person', person.get_handle())
        grid.attach(eventbox, 1, 1, 1, 1)

        # GRAMPS ID
        subgrid.attach(widgets.BasicLabel("%s:" % _('ID')), 1, 0, 1, 1)
        label = widgets.BasicLabel(person.gramps_id)
        label.set_hexpand(True)
        subgrid.attach(label, 2, 0, 1, 1)

        # Birth event.
        birth = get_birth_or_fallback(self.dbstate.db, person)
        if birth:
            birth_title = birth.get_type()
        else:
            birth_title = _("Birth")

        subgrid.attach(widgets.BasicLabel("%s:" % birth_title), 1, 1, 1, 1)
        subgrid.attach(widgets.BasicLabel(self.format_event(birth)), 2, 1, 1, 1)

        death = get_death_or_fallback(self.dbstate.db, person)
        if death:
            death_title = death.get_type()
        else:
            death_title = _("Death")

        showed_death = False
        if birth:
            birth_date = birth.get_date_object()
            if (birth_date and birth_date.get_valid()):
                if death:
                    death_date = death.get_date_object()
                    if (death_date and death_date.get_valid()):
                        age = death_date - birth_date
                        subgrid.attach(widgets.BasicLabel("%s:" % death_title),
                                      1, 2, 1, 1)
                        subgrid.attach(widgets.BasicLabel("%s (%s)" %
                                                         (self.format_event(death), age),
                                                         Pango.EllipsizeMode.END),
                                      2, 2, 1, 1)
                        showed_death = True
                if not showed_death:
                    age = Today() - birth_date
                    if probably_alive(person, self.dbstate.db):
                        subgrid.attach(widgets.BasicLabel("%s:" % _("Alive")),
                                      1, 2, 1, 1)
                        subgrid.attach(widgets.BasicLabel("(%s)" % age, Pango.EllipsizeMode.END),
                                      2, 2, 1, 1)
                    else:
                        subgrid.attach(widgets.BasicLabel("%s:" % _("Death")),
                                      1, 2, 1, 1)
                        subgrid.attach(widgets.BasicLabel("%s (%s)" % (_("unknown"), age),
                                                         Pango.EllipsizeMode.END),
                                      2, 2, 1, 1)
                    showed_death = True

        if not showed_death:
            subgrid.attach(widgets.BasicLabel("%s:" % death_title),
                          1, 2, 1, 1)
            subgrid.attach(widgets.BasicLabel(self.format_event(death)),
                          2, 2, 1, 1)

        mbox = Gtk.Box()
        mbox.add(grid)

        # image
        image_list = person.get_media_list()
        if image_list:
            button = self.get_thumbnail(image_list[0], size=SIZE_NORMAL)
            if button:
                mbox.pack_end(button, False, True, 0)
        mbox.show_all()
        return mbox

    def write_stack(self, person, stack):
        self.write_families(person, stack)
        self.write_events(person, stack)
        self.write_album(person, stack)
        self.write_timeline(person, stack)
        self.write_associations(person, stack)


##############################################################################
#
# Families list
#
##############################################################################

    def write_families(self, person, stack):

        self.vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        scroll = Gtk.ScrolledWindow()
        scroll.add(self.vbox2)
        scroll.show_all()
        stack.add_titled(scroll, 'relationship', _('Relationships'))

        family_handle_list = person.get_parent_family_handle_list()

        self.reorder_sensitive = len(family_handle_list)> 1

        if family_handle_list:
            for family_handle in family_handle_list:
                if family_handle:
                    self.write_parents(family_handle, person)
        else:
            heading = self.write_label(_('Parents'), None, True)
            self.vbox2.pack_start(heading, False, True, 0)

        family_handle_list = person.get_family_handle_list()

        if not self.reorder_sensitive:
            self.reorder_sensitive = len(family_handle_list)> 1

        if family_handle_list:
            for family_handle in family_handle_list:
                if family_handle:
                    self.write_family(family_handle, person)

        self.vbox2.show_all()

    def write_parents(self, family_handle, person = None):
        family = self.dbstate.db.get_family_from_handle(family_handle)
        if not family:
            return

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        heading = self.write_label(_('Parents'), family, True)
        vbox.pack_start(heading, False, False, 1)
        f_handle = family.get_father_handle()
        box = self.write_person(_('Father'), f_handle)
        ebox = self.make_dragbox(box, 'Person', f_handle)
        vbox.pack_start(ebox, False, False, 1)
        m_handle = family.get_mother_handle()
        box = self.write_person(_('Mother'), m_handle)
        ebox = self.make_dragbox(box, 'Person', m_handle)
        vbox.pack_start(ebox, False, False, 1)

        if self.show_siblings:
            active = self.get_handle()

            count = len(family.get_child_ref_list())
            ex2 = Gtk.Expander(label='%s (%s):' % (_('Siblings'), count))
            ex2.set_margin_start(24)
            ex2.set_expanded(True)
            vbox.pack_start(ex2, False, False, 6)

            vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            hbox = Gtk.Box()
            addchild = widgets.IconButton(self.add_child_to_fam,
                                          family.handle,
                                          'list-add')
            addchild.set_tooltip_text(_('Add new child to family'))
            selchild = widgets.IconButton(self.sel_child_to_fam,
                                          family.handle,
                                          'gtk-index')
            selchild.set_tooltip_text(_('Add existing child to family'))
            hbox.pack_start(addchild, False, True, 0)
            hbox.pack_start(selchild, False, True, 0)

            vbox2.pack_start(hbox, False, False, 0)
            i = 1
            child_list = [ref.ref for ref in family.get_child_ref_list()]
            for child_handle in child_list:
                child_should_be_linked = (child_handle != active)
                widget = self.write_child(child_handle, i, child_should_be_linked)
                vbox2.pack_start(widget, True, True, 1)
                i += 1

            ex2.add(vbox2)

        self.vbox2.pack_start(vbox, False, True, 0)

    def write_family(self, family_handle, person = None):
        family = self.dbstate.db.get_family_from_handle(family_handle)
        if family is None:
            from gramps.gui.dialog import WarningDialog
            WarningDialog(
                _('Broken family detected'),
                _('Please run the Check and Repair Database tool'),
                parent=self.uistate.window)
            return

        father_handle = family.get_father_handle()
        mother_handle = family.get_mother_handle()
        if self.get_handle() == father_handle:
            handle = mother_handle
        else:
            handle = father_handle

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        heading = self.write_label(_('Family'), family, False)
        vbox.pack_start(heading, False, False, 1)

        if handle or family.get_relationship() != FamilyRelType.UNKNOWN:
            box = self.write_person(_('Spouse'), handle)
            if not self.write_relationship_events(box, family):
                self.write_relationship(box, family)
            ebox = self.make_dragbox(box, 'Person', handle)
            vbox.pack_start(ebox, False, False, 1)

        count = len(family.get_child_ref_list())
        ex2 = Gtk.Expander(label='%s (%s):' % (_('Children'), count))
        ex2.set_expanded(True)
        ex2.set_margin_start(24)
        vbox.pack_start(ex2, False, False, 6)

        vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        hbox = Gtk.Box()
        addchild = widgets.IconButton(self.add_child_to_fam,
                                      family.handle,
                                      'list-add')
        addchild.set_tooltip_text(_('Add new child to family'))
        selchild = widgets.IconButton(self.sel_child_to_fam,
                                      family.handle,
                                      'gtk-index')
        selchild.set_tooltip_text(_('Add existing child to family'))
        hbox.pack_start(addchild, False, True, 0)
        hbox.pack_start(selchild, False, True, 0)

        vbox2.pack_start(hbox, False, False, 0)

        i = 1
        child_list = family.get_child_ref_list()
        for child_ref in child_list:
            widget = self.write_child(child_ref.ref, i, True)
            vbox2.pack_start(widget, True, True, 1)
            i += 1

        ex2.add(vbox2)

        self.vbox2.pack_start(vbox, False, True, 0)

    def write_label(self, title, family, is_parent):
        """
        Write a Family header row
        Shows following elements:
        (Parents/Family title label, Family gramps_id, and add-choose-edit-delete buttons)
        """
        hbox = Gtk.Box()
        if family:
            msg = '<b>%s (%s):</b>' % (escape(title), escape(family.gramps_id))
        else:
            msg = '<b>%s:</b>' % escape(title)
        label = widgets.MarkupLabel(msg, halign=Gtk.Align.START)
        hbox.pack_start(label, False, True, 0)

        bbox = Gtk.Box()
        if is_parent:
            call_fcn = self.add_parent_family
            del_fcn = self.delete_parent_family
            add_msg = _('Add a new set of parents')
            sel_msg = _('Add person as child to an existing family')
            edit_msg = _('Edit parents')
            ord_msg = _('Reorder parents')
            del_msg = _('Remove person as child of these parents')
        else:
            add_msg = _('Add a new family with person as parent')
            sel_msg = None
            edit_msg = _('Edit family')
            ord_msg = _('Reorder families')
            del_msg = _('Remove person as parent in this family')
            call_fcn = self.add_family
            del_fcn = self.delete_family

        if not self.toolbar_visible and not self.dbstate.db.readonly:
            # Show edit-Buttons if toolbar is not visible
            if self.reorder_sensitive:
                add = widgets.IconButton(self.reorder_button_press, None,
                                         'view-sort-ascending')
                add.set_tooltip_text(ord_msg)
                bbox.pack_start(add, False, True, 0)

            add = widgets.IconButton(call_fcn, None, 'list-add')
            add.set_tooltip_text(add_msg)
            bbox.pack_start(add, False, True, 0)

            if is_parent:
                add = widgets.IconButton(self.select_family, None,
                                         'gtk-index')
                add.set_tooltip_text(sel_msg)
                bbox.pack_start(add, False, True, 0)

        if family:
            edit = widgets.IconButton(self.edit_family_button,
                                      family.handle, 'gtk-edit')
            edit.set_tooltip_text(edit_msg)
            bbox.pack_start(edit, False, True, 0)
            if not self.dbstate.db.readonly:
                delete = widgets.IconButton(del_fcn, family.handle,
                                            'list-remove')
                delete.set_tooltip_text(del_msg)
                bbox.pack_start(delete, False, True, 0)

        hbox.pack_start(bbox, False, True, 6)

        if family:
            if self.show_tags:
                tag_list = TagList(self.get_tag_list(family))
                hbox.pack_start(tag_list, False, False, 3)
            eventbox = self.make_dragbox(hbox, 'Family', family.handle)
            return eventbox
        else:
            return hbox

    def write_person(self, title, handle):
        """
        Create and show a person cell.
        """
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        if handle:
            name = self.get_name(handle, True)
            person = self.dbstate.db.get_person_from_handle(handle)
            parent = len(person.get_parent_family_handle_list()) > 0
            if parent:
                emph = True
            else:
                emph = False
            link_label = widgets.LinkLabel(name, self._person_link,
                                           handle, emph, theme=self.theme)
            if self._config.get('preferences.releditbtn'):
                button = widgets.IconButton(self.edit_person_button, handle)
                button.set_tooltip_text(_('Edit %s') % name[0])
            else:
                button = None
            hbox = Gtk.Box()
            hbox.set_spacing(6)
            hbox.pack_start(link_label, False, False, 0)
            if self.show_details:
                box = self.info_box(handle)
                if box:
                    hbox.pack_start(box, False, False, 0)
            if button is not None:
                hbox.pack_start(button, False, False, 0)
            if self.show_tags:
                tag_list = TagList(self.get_tag_list(person))
                hbox.pack_start(tag_list, False, False, 0)
            vbox.pack_start(hbox, True, True, 0)
        else:
            link_label = Gtk.Label(label=_('Unknown'))
            link_label.set_halign(Gtk.Align.START)
            link_label.show()
            vbox.pack_start(link_label, True, True, 0)

        return vbox

    def write_child(self, handle, index, child_should_be_linked):
        """
        Write a child cell (used for children and siblings of active person)
        """
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        parent = self.has_children(
                    self.dbstate.db.get_person_from_handle(handle))
        emph = False
        if child_should_be_linked and parent:
            emph = True
        elif child_should_be_linked and not parent:
            emph = False
        elif parent and not child_should_be_linked:
            emph = None

        if child_should_be_linked:
            link_func = self._person_link
        else:
            link_func = None

        name = self.get_name(handle, True)
        link_label = widgets.LinkLabel(name, link_func, handle, emph,
                                       theme=self.theme)
        link_label.set_padding(3, 0)
        if self._config.get('preferences.releditbtn'):
            button = widgets.IconButton(self.edit_person_button, handle)
            button.set_tooltip_text(_('Edit %s') % name[0])
        else:
            button = None

        hbox = Gtk.Box()
        hbox.set_spacing(6)
        l = widgets.BasicLabel("%d." % index)
        l.set_width_chars(3)
        l.set_halign(Gtk.Align.END)
        hbox.pack_start(l, False, False, 0)
        person = self.dbstate.db.get_person_from_handle(handle)
        hbox.pack_start(link_label, False, False, 0)
        if self.show_details:
            box = self.info_box(handle)
            if box:
                hbox.pack_start(box, False, False, 0)
        if button is not None:
            hbox.pack_start(button, False, False, 0)
        if self.show_tags:
            tag_list = TagList(self.get_tag_list(person))
            hbox.pack_start(tag_list, False, False, 0)
        hbox.show()
        vbox.pack_start(hbox, True, True, 0)

        ev = self.make_dragbox(vbox, 'Person', handle)

        if not child_should_be_linked:
            frame = Gtk.Frame()
            frame.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
            frame.add(ev)
            return frame
        else:
            return ev

    def write_relationship(self, box, family):
        msg = _('Relationship type: %s') % escape(str(family.get_relationship()))
        box.add(widgets.MarkupLabel(msg))

    def write_relationship_events(self, vbox, family):
        value = False
        for event_ref in family.get_event_ref_list():
            handle = event_ref.ref
            event = self.dbstate.db.get_event_from_handle(handle)
            if (event and event.get_type().is_relationship_event() and
                (event_ref.get_role() == EventRoleType.FAMILY or
                 event_ref.get_role() == EventRoleType.PRIMARY)):
                self.write_event_ref(vbox, event.get_type().string, event)
                value = True
        return value

    def write_event_ref(self, vbox, ename, event):
        if event:
            dobj = event.get_date_object()
            phandle = event.get_place_handle()
            if phandle:
                pname = place_displayer.display_event(self.dbstate.db, event)
            else:
                pname = None

            value = {
                'date' : displayer.display(dobj),
                'place' : pname,
                'event_type' : ename,
                }
        else:
            pname = None
            dobj = None
            value = { 'event_type' : ename, }

        if dobj:
            if pname:
                self.write_data(
                    vbox, _('%(event_type)s: %(date)s in %(place)s') %
                    value)
            else:
                self.write_data(
                    vbox, _('%(event_type)s: %(date)s') % value)
        elif pname:
            self.write_data(
                vbox, _('%(event_type)s: %(place)s') % value)
        else:
            self.write_data(
                vbox, '%(event_type)s:' % value)

    def write_data(self, box, title):
        box.add(widgets.BasicLabel(title))

##############################################################################
#
# Events list
#
##############################################################################

    def write_events(self, person, stack):

        self.vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        scroll = Gtk.ScrolledWindow()
        scroll.add(self.vbox2)
        scroll.show_all()
        stack.add_titled(scroll, 'event', _('Events'))

        events = []
        # Personal events
        for index, event_ref in enumerate(person.get_event_ref_list()):
            event = self.dbstate.db.get_event_from_handle(event_ref.ref)
            sortval = event.get_date_object().get_sort_value()
            events.append(((sortval, index), event_ref, None))

        # Family events
        for family_handle in person.get_family_handle_list():
            family = self.dbstate.db.get_family_from_handle(family_handle)
            father_handle = family.get_father_handle()
            mother_handle = family.get_mother_handle()
            spouse = None
            if father_handle == person.handle:
                if mother_handle:
                    spouse = self.dbstate.db.get_person_from_handle(mother_handle)
            else:
                if father_handle:
                    spouse = self.dbstate.db.get_person_from_handle(father_handle)
            for event_ref in family.get_event_ref_list():
                event = self.dbstate.db.get_event_from_handle(event_ref.ref)
                sortval = event.get_date_object().get_sort_value()
                events.append(((sortval, 0), event_ref, spouse))

        # Write all events sorted by date
        for index, event in enumerate(sorted(events, key=itemgetter(0))):
            self.write_event(event[1], event[2], index+1)

    def write_event(self, event_ref, spouse, index):
        handle = event_ref.ref
        event = self.dbstate.db.get_event_from_handle(handle)
        etype = str(event.get_type())
        desc = event.get_description()
        who = get_participant_from_event(self.dbstate.db, handle)

        title = etype
        if desc:
            title = '%s (%s)' % (title, desc)
        if spouse:
            spouse_name = name_displayer.display(spouse)
            title = '%s - %s' % (title, spouse_name)

        role = event_ref.get_role()
        if role in (EventRoleType.PRIMARY, EventRoleType.FAMILY):
            emph = True
        else:
            emph = False
            title = '%s of %s' % (title, who)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        link_func = self._event_link
        name = (title, None)
        handle = event_ref.ref
        link_label = widgets.LinkLabel(name, link_func, handle, emph,
                                       theme=self.theme)
        link_label.set_padding(3, 0)
        link_label.set_tooltip_text(_('Click to make this event active'))
        if self._config.get('preferences.releditbtn'):
            button = widgets.IconButton(self.edit_event_button, handle)
            button.set_tooltip_text(_('Edit %s') % name[0])
        else:
            button = None

        hbox = widgets.LinkBox(link_label, button)
        if self.show_tags:
            tag_list = TagList(self.get_tag_list(event))
            hbox.pack_start(tag_list, False, False, 0)
        vbox.pack_start(hbox, False, False, 0)

        line2 = self.format_event(event)
        vbox.pack_start(widgets.BasicLabel(line2), False, False, 0)

        for handle in event.get_citation_list():
            self.write_citation(vbox, handle)

        eventbox = self.make_dragbox(vbox, 'Event', handle)
        eventbox.show_all()
        self.vbox2.pack_start(eventbox, False, False, 1)

    def write_citation(self, vbox, chandle):
        citation = self.dbstate.db.get_citation_from_handle(chandle)
        shandle = citation.get_reference_handle()
        source = self.dbstate.db.get_source_from_handle(shandle)
        heading = source.get_title()
        page = citation.get_page()
        if page:
            heading += ' \u2022 ' + page

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        url_label = self.get_url(citation)
        if url_label:
            box.pack_start(url_label, False, False, 0)
        hbox = self.load_images(citation)
        box.pack_start(hbox, False, False, 0)

        if len(hbox.get_children()) > 0 or url_label:
            exp = Gtk.Expander(label=heading)
            exp.add(box)
            vbox.pack_start(exp, False, False, 0)
        else:
            label = widgets.BasicLabel(heading)
            vbox.pack_start(label, False, False, 0)

    def get_url(self, citation):
        for handle in citation.get_note_list():
            note = self.dbstate.db.get_note_from_handle(handle)
            text = note.get()
            url_match = re.compile(r'https?://[^\s]+')
            result = URL_MATCH.search(text)
            if result:
                url = result.group(0)
                link_func = lambda x,y,z: display_url(url)
                name = (url, None)
                link_label = widgets.LinkLabel(name, link_func, None, False,
                                       theme=self.theme)
                link_label.set_tooltip_text(_('Click to visit this link'))
                return link_label
        return None


##############################################################################
#
# Album
#
##############################################################################

    def write_album(self, person, stack):

        self.vbox2 = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL)

        scroll = Gtk.ScrolledWindow()
        scroll.add(self.vbox2)
        scroll.show_all()
        stack.add_titled(scroll, 'album', _('Album'))

        self.write_media(person.get_media_list(), None)

        for event_ref in person.get_event_ref_list():
            event = self.dbstate.db.get_event_from_handle(event_ref.ref)

            self.write_media(event.get_media_list(), event)

        for family_handle in person.get_family_handle_list():
            family = self.dbstate.db.get_family_from_handle(family_handle)

            self.write_media(family.get_media_list(), None)

            for event_ref in family.get_event_ref_list():
                event = self.dbstate.db.get_event_from_handle(event_ref.ref)

                self.write_media(event.get_media_list(), event)

    def write_media(self, media_list, event):
        for media_ref in media_list:

            mobj = self.dbstate.db.get_media_from_handle(media_ref.ref)
            button = self.get_thumbnail(media_ref, size=SIZE_LARGE)
            if button:

                self.vbox2.add(button)

                vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

                if event:
                    etype = str(event.get_type())
                    label = Gtk.Label(etype)
                    vbox.pack_start(label, False, False, 0)
                    who = get_participant_from_event(self.dbstate.db, event.handle)
                    label = Gtk.Label(who)
                    vbox.pack_start(label, False, False, 0)
                    date_place = self.format_event(event)
                    label = Gtk.Label(date_place)
                    vbox.pack_start(label, False, False, 0)

                notes = mobj.get_note_list()
                if len(notes) > 0:
                    note = self.dbstate.db.get_note_from_handle(notes[0])
                    texteditor = StyledTextEditor()
                    texteditor.set_editable(False)
                    texteditor.set_wrap_mode(Gtk.WrapMode.WORD)
                    texteditor.set_text(note.get_styledtext())
                    texteditor.set_hexpand(True)
                    texteditor.show()
                    vbox.pack_start(texteditor, True, True, 0)
                    vbox.show_all()

                self.vbox2.attach_next_to(vbox, button,
                                          Gtk.PositionType.RIGHT, 1, 1)

##############################################################################
#
# Timeline
#
##############################################################################

    def write_timeline(self, person, stack):

        grid = Gtk.Grid(orientation=Gtk.Orientation.VERTICAL)

        scroll = Gtk.ScrolledWindow()
        scroll.add(grid)
        scroll.show_all()
        stack.add_titled(scroll, 'timeline', _('Timeline'))

        events = []
        start_date = None
        # Personal events
        for index, event_ref in enumerate(person.get_event_ref_list()):
            event = self.dbstate.db.get_event_from_handle(event_ref.ref)
            date = event.get_date_object()
            if (start_date is None and event_ref.role.is_primary() and
                (event.type.is_birth_fallback() or
                 event.type == EventType.BIRTH)):
                start_date = date
            sortval = date.get_sort_value()
            events.append(((sortval, index), event_ref, None))

        # Family events
        for family_handle in person.get_family_handle_list():
            family = self.dbstate.db.get_family_from_handle(family_handle)
            father_handle = family.get_father_handle()
            mother_handle = family.get_mother_handle()
            spouse = None
            if father_handle == person.handle:
                if mother_handle:
                    spouse = self.dbstate.db.get_person_from_handle(mother_handle)
            else:
                if father_handle:
                    spouse = self.dbstate.db.get_person_from_handle(father_handle)
            for event_ref in family.get_event_ref_list():
                event = self.dbstate.db.get_event_from_handle(event_ref.ref)
                sortval = event.get_date_object().get_sort_value()
                events.append(((sortval, 0), event_ref, spouse))

        # Write all events sorted by date
        for index, event in enumerate(sorted(events, key=itemgetter(0))):
            self.write_node(grid, event[1], event[2], index+1, start_date)

        grid.show_all()

    def write_node(self, grid, event_ref, spouse, index, start_date):
        handle = event_ref.ref
        event = self.dbstate.db.get_event_from_handle(handle)
        etype = str(event.get_type())
        desc = event.get_description()
        who = get_participant_from_event(self.dbstate.db, handle)

        title = etype
        if desc:
            title = '%s (%s)' % (title, desc)
        if spouse:
            spouse_name = name_displayer.display(spouse)
            title = '%s - %s' % (title, spouse_name)

        role = event_ref.get_role()
        if role in (EventRoleType.PRIMARY, EventRoleType.FAMILY):
            emph = True
        else:
            emph = False
            title = '%s of %s' % (title, who)

        vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        link_func = self._event_link
        name = (title, None)
        handle = event_ref.ref
        link_label = widgets.LinkLabel(name, link_func, handle, emph,
                                       theme=self.theme)
        link_label.set_padding(3, 0)
        link_label.set_tooltip_text(_('Click to make this event active'))
        if self._config.get('preferences.releditbtn'):
            button = widgets.IconButton(self.edit_event_button, handle)
            button.set_tooltip_text(_('Edit %s') % name[0])
        else:
            button = None

        hbox = widgets.LinkBox(link_label, button)
        if self.show_tags:
            tag_list = TagList(self.get_tag_list(event))
            hbox.pack_start(tag_list, False, False, 0)
        vbox1.pack_start(hbox, False, False, 0)

        pname = place_displayer.display_event(self.dbstate.db, event)
        vbox1.pack_start(widgets.BasicLabel(pname), False, False, 0)
        vbox1.set_vexpand(False)
        vbox1.set_valign(Gtk.Align.CENTER)
        vbox1.show_all()

        eventbox = self.make_dragbox(vbox1, 'Event', handle)
        eventbox.set_hexpand(True)
        eventbox.set_vexpand(False)
        eventbox.set_valign(Gtk.Align.CENTER)
        eventbox.set_margin_top(1)
        eventbox.set_margin_bottom(1)
        eventbox.show_all()

        vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        dobj = event.get_date_object()
        date = widgets.BasicLabel(displayer.display(dobj))
        vbox2.pack_start(date, False, False, 0)
        if start_date is not None:
            age_precision = config.get('preferences.age-display-precision')
            diff = (dobj - start_date).format(precision=age_precision)
            age = widgets.BasicLabel(diff)
            vbox2.pack_start(age, False, False, 0)
        vbox2.set_valign(Gtk.Align.CENTER)
        grid.add(vbox2)

        tl = Timeline()
        grid.attach_next_to(tl, vbox2, Gtk.PositionType.RIGHT, 1, 1)

        grid.attach_next_to(eventbox, tl, Gtk.PositionType.RIGHT, 1, 1)


##############################################################################
#
# Associations
#
##############################################################################

    def write_associations(self, person, stack):

        self.vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)

        scroll = Gtk.ScrolledWindow()
        scroll.add(self.vbox2)
        scroll.show_all()
        stack.add_titled(scroll, 'associations', _('Associations'))

        for person_ref in person.get_person_ref_list():
            self.write_association(person, person_ref)


    def write_association(self, person1, person_ref):

        vbox = self.write_person('assoc', person_ref.ref)

        assoc = Gtk.Label(_('Association') + _(': ') + person_ref.rel)
        assoc.set_halign(Gtk.Align.START)
        vbox.pack_start(assoc, False, False, 0)

        calc = get_relationship_calculator()
        person2 = self.dbstate.db.get_person_from_handle(person_ref.ref)
        rel_txt = calc.get_one_relationship(self.dbstate.db, person1, person2)
        rel = Gtk.Label(_('Relationship') + _(': ') + rel_txt)
        rel.set_halign(Gtk.Align.START)
        vbox.pack_start(rel, False, False, 0)

        eventbox = self.make_dragbox(vbox, 'Person', person_ref.ref)
        eventbox.show_all()
        self.vbox2.pack_start(eventbox, False, False, 1)

##############################################################################
#
# Toolbar actions
#
##############################################################################

    def edit_active(self, *obj):
        self.edit_person(self.handle)

    def add_spouse(self, *obj):
        family = Family()
        person = self.dbstate.db.get_person_from_handle(self.handle)

        if not person:
            return

        if person.gender == Person.MALE:
            family.set_father_handle(person.handle)
        else:
            family.set_mother_handle(person.handle)

        try:
            EditFamily(self.dbstate, self.uistate, [], family)
        except WindowActiveError:
            pass

    def select_parents(self, *obj):
        SelectFamily = SelectorFactory('Family')

        phandle = self.handle
        person = self.dbstate.db.get_person_from_handle(phandle)
        skip = set(person.get_family_handle_list()+
                   person.get_parent_family_handle_list())

        dialog = SelectFamily(self.dbstate, self.uistate, skip=skip)
        family = dialog.run()

        if family:
            child = self.dbstate.db.get_person_from_handle(self.handle)

            self.dbstate.db.add_child_to_family(family, child)

    def add_parents(self, *obj):
        family = Family()
        person = self.dbstate.db.get_person_from_handle(self.handle)

        if not person:
            return

        ref = ChildRef()
        ref.ref = person.handle
        family.add_child_ref(ref)

        try:
            EditFamily(self.dbstate, self.uistate, [], family)
        except WindowActiveError:
            pass

    def add_tag(self, trans, object_handle, tag_handle):
        """
        Add the given tag to the active object.
        """
        person = self.dbstate.db.get_person_from_handle(object_handle)
        person.add_tag(tag_handle)
        self.dbstate.db.commit_person(person, trans)