536 lines
18 KiB
Python
536 lines
18 KiB
Python
#
|
|
# Gramps - a GTK+/GNOME based genealogy program
|
|
#
|
|
# Copyright (C) 2001-2007 Donald N. Allingham
|
|
# Copyright (C) 2009-2010 Nick Hall
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
#
|
|
|
|
"""
|
|
Provide the base classes for GRAMPS' DataView classes
|
|
"""
|
|
|
|
#----------------------------------------------------------------
|
|
#
|
|
# python modules
|
|
#
|
|
#----------------------------------------------------------------
|
|
import logging
|
|
|
|
_LOG = logging.getLogger('.navigationview')
|
|
|
|
#----------------------------------------------------------------
|
|
#
|
|
# gtk
|
|
#
|
|
#----------------------------------------------------------------
|
|
from gi.repository import Gdk
|
|
from gi.repository import Gtk
|
|
|
|
#----------------------------------------------------------------
|
|
#
|
|
# GRAMPS
|
|
#
|
|
#----------------------------------------------------------------
|
|
from .pageview import PageView
|
|
from ..actiongroup import ActionGroup
|
|
from gramps.gen.const import GRAMPS_LOCALE as glocale
|
|
_ = glocale.translation.sgettext
|
|
from gramps.gen.utils.db import navigation_label
|
|
from gramps.gen.constfunc import mod_key
|
|
|
|
DISABLED = -1
|
|
MRU_SIZE = 10
|
|
|
|
MRU_TOP = [
|
|
'<ui>'
|
|
'<menubar name="MenuBar">'
|
|
'<menu action="GoMenu">'
|
|
'<placeholder name="CommonHistory">'
|
|
]
|
|
|
|
MRU_BTM = [
|
|
'</placeholder>'
|
|
'</menu>'
|
|
'</menubar>'
|
|
'</ui>'
|
|
]
|
|
#------------------------------------------------------------------------------
|
|
#
|
|
# NavigationView
|
|
#
|
|
#------------------------------------------------------------------------------
|
|
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.change_active)
|
|
|
|
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.uistate.register(state, self.navigation_type(), self.nav_group)
|
|
|
|
|
|
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.fwd_action.set_visible(False)
|
|
self.back_action.set_visible(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.fwd_action.set_visible(True)
|
|
self.back_action.set_visible(True)
|
|
hobj = self.get_history()
|
|
self.fwd_action.set_sensitive(not hobj.at_end())
|
|
self.back_action.set_sensitive(not hobj.at_front())
|
|
|
|
def change_page(self):
|
|
"""
|
|
Called when the page changes.
|
|
"""
|
|
hobj = self.get_history()
|
|
self.fwd_action.set_sensitive(not hobj.at_end())
|
|
self.back_action.set_sensitive(not hobj.at_front())
|
|
self.other_action.set_sensitive(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)
|
|
|
|
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.uistate.get_history(self.navigation_type(),
|
|
self.navigation_group())
|
|
|
|
def goto_active(self, active_handle):
|
|
"""
|
|
Callback (and usable function) that selects the active person
|
|
in the display tree.
|
|
"""
|
|
active_handle = self.uistate.get_active(self.navigation_type(),
|
|
self.navigation_group())
|
|
if active_handle:
|
|
self.goto_handle(active_handle)
|
|
|
|
hobj = self.get_history()
|
|
self.fwd_action.set_sensitive(not hobj.at_end())
|
|
self.back_action.set_sensitive(not hobj.at_front())
|
|
|
|
def get_active(self):
|
|
"""
|
|
Return the handle of the active object.
|
|
"""
|
|
hobj = self.uistate.get_history(self.navigation_type(),
|
|
self.navigation_group())
|
|
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)
|
|
|
|
def goto_handle(self, handle):
|
|
"""
|
|
Needs to be implemented by classes derived from this.
|
|
Used to move to the given handle.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
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', 'gramps-bookmark-new', _('_Add Bookmark'),
|
|
'<PRIMARY>d', None, self.add_bookmark),
|
|
('EditBook', 'gramps-bookmark-edit',
|
|
_("%(title)s...") % {'title': _("Organize Bookmarks")},
|
|
'<shift><PRIMARY>D', None,
|
|
self.edit_bookmarks),
|
|
])
|
|
|
|
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', 'go-next', _("_Forward"),
|
|
"%sRight" % mod_key(), _("Go to the next object in the history"),
|
|
self.fwd_clicked)
|
|
])
|
|
|
|
# add the Backward action group to handle the Forward button
|
|
self.back_action = ActionGroup(name=self.title + '/Backward')
|
|
self.back_action.add_actions([
|
|
('Back', 'go-previous', _("_Back"),
|
|
"%sLeft" % mod_key(), _("Go to the previous object in the history"),
|
|
self.back_clicked)
|
|
])
|
|
|
|
self._add_action('HomePerson', 'go-home', _("_Home"),
|
|
accel="%sHome" % mod_key(),
|
|
tip=_("Go to the default person"), callback=self.home)
|
|
|
|
self.other_action = ActionGroup(name=self.title + '/PersonOther')
|
|
self.other_action.add_actions([
|
|
('SetActive', 'go-home', _("Set _Home Person"), None,
|
|
None, 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(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):
|
|
"""
|
|
A dialog to move to a Gramps ID entered by the user.
|
|
"""
|
|
dialog = Gtk.Dialog(_('Jump to by Gramps ID'), parent=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("%s: " % _('ID', True, True, 0)), False)
|
|
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.fwd_action.set_sensitive(not hobj.at_end())
|
|
self.back_action.set_sensitive(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.back_action.set_sensitive(not hobj.at_front())
|
|
self.fwd_action.set_sensitive(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.uistate.uimanager.remove_ui(self.mru_active)
|
|
self.uistate.uimanager.remove_action_group(self.mru_action)
|
|
self.mru_active = DISABLED
|
|
|
|
def mru_enable(self):
|
|
"""
|
|
Enables the UI and action groups for the MRU list.
|
|
"""
|
|
if self.mru_active == DISABLED:
|
|
self.uistate.uimanager.insert_action_group(self.mru_action, 1)
|
|
self.mru_active = self.uistate.uimanager.add_ui_from_string(self.mru_ui)
|
|
self.uistate.uimanager.ensure_update()
|
|
|
|
def update_mru_menu(self, items):
|
|
"""
|
|
Builds the UI and action group for the MRU list.
|
|
"""
|
|
self.mru_disable()
|
|
nav_type = self.navigation_type()
|
|
hobj = self.get_history()
|
|
menu_len = min(len(items) - 1, MRU_SIZE)
|
|
|
|
entry = '<menuitem action="%s%02d"/>'
|
|
data = [entry % (nav_type, index) for index in range(0, menu_len)]
|
|
self.mru_ui = "".join(MRU_TOP) + "".join(data) + "".join(MRU_BTM)
|
|
|
|
mitems = items[-MRU_SIZE - 1:-1] # Ignore current handle
|
|
mitems.reverse()
|
|
data = []
|
|
for index, handle in enumerate(mitems):
|
|
name, obj = navigation_label(self.dbstate.db, nav_type, handle)
|
|
data.append(('%s%02d'%(nav_type, index), None, name,
|
|
"%s%d" % (mod_key(), index), None,
|
|
make_callback(hobj.push, handle)))
|
|
|
|
self.mru_action = ActionGroup(name=self.title + '/MRU')
|
|
self.mru_action.add_actions(data)
|
|
self.mru_enable()
|
|
|
|
####################################################################
|
|
# Template functions
|
|
####################################################################
|
|
def get_bookmarks(self):
|
|
"""
|
|
Template function to get bookmarks.
|
|
We could implement this here based on navigation_type()
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def edit(self, obj):
|
|
"""
|
|
Template function to allow the editing of the selected object
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def remove(self, handle):
|
|
"""
|
|
Template function to allow the removal of an object by its handle
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def add(self, obj):
|
|
"""
|
|
Template function to allow the adding of a new object
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def remove_object_from_handle(self, handle):
|
|
"""
|
|
Template function to allow the removal of an object by its handle
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def build_tree(self):
|
|
"""
|
|
Rebuilds the current display. This must be overridden by the derived
|
|
class.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def build_widget(self):
|
|
"""
|
|
Builds the container widget for the interface. Must be overridden by the
|
|
the base class. Returns a gtk container widget.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
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
|
|
(event.get_state() & Gdk.ModifierType.CONTROL_MASK)):
|
|
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)
|
|
|
|
def make_callback(func, handle):
|
|
"""
|
|
Generates a callback function based off the passed arguments
|
|
"""
|
|
return lambda x: func(handle)
|