GEPS 019: Improved sidebar which allows sidebar plugins

svn: r15029
This commit is contained in:
Nick Hall 2010-04-04 18:16:03 +00:00
parent 5726d688ca
commit 6ae2f1c81e
14 changed files with 793 additions and 445 deletions

View File

@ -140,6 +140,7 @@ src/plugins/lib/Makefile
src/plugins/mapservices/Makefile
src/plugins/quickview/Makefile
src/plugins/rel/Makefile
src/plugins/sidebar/Makefile
src/plugins/textreport/Makefile
src/plugins/tool/Makefile
src/plugins/view/Makefile

View File

@ -288,6 +288,11 @@ class BasePluginManager(object):
"""
return self.__pgr.gramplet_plugins()
def get_reg_sidebars(self):
""" Return list of registered sidebars.
"""
return self.__pgr.sidebar_plugins()
def get_external_opt_dict(self):
""" Return the dictionary of external options. """
return self.__external_opt_dict

View File

@ -68,8 +68,9 @@ MAPSERVICE = 7
VIEW = 8
RELCALC = 9
GRAMPLET = 10
PTYPE = [ REPORT , QUICKREPORT, TOOL, IMPORT,
EXPORT, DOCGEN, GENERAL, MAPSERVICE, VIEW, RELCALC, GRAMPLET]
SIDEBAR = 11
PTYPE = [REPORT , QUICKREPORT, TOOL, IMPORT, EXPORT, DOCGEN, GENERAL,
MAPSERVICE, VIEW, RELCALC, GRAMPLET, SIDEBAR]
PTYPE_STR = {
REPORT: _('Report') ,
QUICKREPORT: _('Quickreport'),
@ -82,6 +83,7 @@ PTYPE_STR = {
VIEW: _('Gramps View'),
RELCALC: _('Relationships'),
GRAMPLET: _('Gramplet'),
SIDEBAR: _('Sidebar'),
}
#possible report categories
@ -301,6 +303,14 @@ class PluginData(object):
the view is appended to the list of views. If START, then the view is
prepended. Only set START if you want a view to be the first in the
order of views
.. attribute:: stock_icon
The icon in the toolbar or sidebar used to select the view
Attributes for SIDEBAR plugins
.. attribute:: sidebarclass
The class that defines the sidebar.
.. attribute:: menu_label
A label to use on the seltion menu.
"""
def __init__(self):
@ -364,6 +374,10 @@ class PluginData(object):
#VIEW attr
self._viewclass = None
self._order = END
self._stock_icon = None
#SIDEBAR attr
self._sidebarclass = None
self._menu_label = ''
def _set_id(self, id):
self._id = id
@ -811,10 +825,38 @@ class PluginData(object):
def _get_order(self):
return self._order
viewclass = property(_get_viewclass, _set_viewclass)
order = property(_get_order, _set_order)
def _set_stock_icon(self, stock_icon):
if not self._ptype == VIEW:
raise ValueError, 'stock_icon may only be set for VIEW plugins'
self._stock_icon = stock_icon
def _get_stock_icon(self):
return self._stock_icon
viewclass = property(_get_viewclass, _set_viewclass)
order = property(_get_order, _set_order)
stock_icon = property(_get_stock_icon, _set_stock_icon)
#SIDEBAR attributes
def _set_sidebarclass(self, sidebarclass):
if not self._ptype == SIDEBAR:
raise ValueError, 'sidebarclass may only be set for SIDEBAR plugins'
self._sidebarclass = sidebarclass
def _get_sidebarclass(self):
return self._sidebarclass
def _set_menu_label(self, menu_label):
if not self._ptype == SIDEBAR:
raise ValueError, 'menu_label may only be set for SIDEBAR plugins'
self._menu_label = menu_label
def _get_menu_label(self):
return self._menu_label
sidebarclass = property(_get_sidebarclass, _set_sidebarclass)
menu_label = property(_get_menu_label, _set_menu_label)
def newplugin():
"""
Function to create a new plugindata object, add it to list of
@ -865,6 +907,7 @@ def make_environment(**kwargs):
'VIEW': VIEW,
'RELCALC': RELCALC,
'GRAMPLET': GRAMPLET,
'SIDEBAR': SIDEBAR,
'CATEGORY_TEXT': CATEGORY_TEXT,
'CATEGORY_DRAW': CATEGORY_DRAW,
'CATEGORY_CODE': CATEGORY_CODE,
@ -1106,6 +1149,11 @@ class PluginRegister(object):
"""Return a list of PluginData that are of type GRAMPLET
"""
return self.type_plugins(GRAMPLET)
def sidebar_plugins(self):
"""Return a list of PluginData that are of type SIDEBAR
"""
return self.type_plugins(SIDEBAR)
def filter_load_on_reg(self):
"""Return a list of PluginData that have load_on_reg == True

View File

@ -21,6 +21,7 @@ pkgdata_PYTHON = \
filtereditor.py \
grampsgui.py \
pluginmanager.py \
sidebar.py \
utils.py \
viewmanager.py

View File

@ -62,8 +62,7 @@ from QuestionDialog import ErrorDialog
import config
import Utils
from constfunc import win
from gui.pluginmanager import GuiPluginManager, base_reg_stock_icons
from gen.plug import (START, END)
from gui.pluginmanager import base_reg_stock_icons
#-------------------------------------------------------------------------
#
@ -71,7 +70,6 @@ from gen.plug import (START, END)
#
#-------------------------------------------------------------------------
def register_stock_icons ():
"""
Add the gramps names for its icons (eg gramps-person) to the GTK icon
@ -185,53 +183,6 @@ def _display_welcome_message():
config.set('behavior.autoload', False)
# config.set('behavior.betawarn', True)
config.set('behavior.betawarn', config.get('behavior.betawarn'))
def construct_view_order():
"""
Query the views and determine what views to show and in which order
:Returns: a list of lists containing tuples (view_id, viewclass)
"""
pmgr = GuiPluginManager.get_instance()
view_list = pmgr.get_reg_views()
viewstoshow = {}
for pdata in view_list:
mod = pmgr.load_plugin(pdata)
if not mod or not hasattr(mod, pdata.viewclass):
#import of plugin failed
ErrorDialog(
_('Failed Loading View'),
_('The view %(name)s did not load. See Help Menu, Plugin Manager'
' for more info.\nUse http://bugs.gramps-project.org to'
' submit bugs of official views, contact the view '
'author (%(firstauthoremail)s) otherwise. ') % {
'name': pdata.name,
'firstauthoremail': pdata.authors_email[0] if
pdata.authors_email else '...'})
continue
viewclass = getattr(mod, pdata.viewclass)
# pdata.category is (string, trans-string):
if pdata.category[0] in viewstoshow:
if pdata.order == START:
viewstoshow[pdata.category[0]].insert(0, ((pdata, viewclass)))
else:
viewstoshow[pdata.category[0]].append((pdata, viewclass))
else:
viewstoshow[pdata.category[0]] = [(pdata, viewclass)]
resultorder = []
# First, get those in order defined, if exists:
for item in config.get("interface.view-categories"):
if item in viewstoshow:
resultorder.append(viewstoshow[item])
# Next, get the rest in some order:
viewstoshow_names = viewstoshow.keys()
viewstoshow_names.sort()
for item in viewstoshow_names:
if viewstoshow[item] in resultorder:
continue
resultorder.append(viewstoshow[item])
return resultorder
#-------------------------------------------------------------------------
#
@ -256,11 +207,7 @@ class Gramps(object):
dbstate = DbState.DbState()
self.vm = ViewManager(dbstate, config.get("interface.view-categories"))
#now we determine which views are present, which to show, and we
#instruct the viewmanager to show them
vieworder = construct_view_order()
self.vm.init_interface(vieworder)
self.vm.init_interface()
#act based on the given arguments
ah = ArgHandler(dbstate, argparser, self.vm, self.argerrorfunc,
@ -289,7 +236,6 @@ class Gramps(object):
def argerrorfunc(self, string):
""" Show basic errors in argument handling in GUI fashion"""
ErrorDialog(_("Error parsing arguments"), string)
#-------------------------------------------------------------------------
#

View File

@ -289,6 +289,12 @@ class GuiPluginManager(gen.utils.Callback):
return [plg for plg in self.basemgr.get_reg_gramplets()
if plg.id not in self.__hidden_plugins]
def get_reg_sidebars(self):
""" Return list of non hidden registered sidebars
"""
return [plg for plg in self.basemgr.get_reg_sidebars()
if plg.id not in self.__hidden_plugins]
def get_reg_importers(self):
""" Return list of registered importers
"""

173
src/gui/sidebar.py Normal file
View File

@ -0,0 +1,173 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"""
A module that provides pluggable sidebars. These provide an interface to
manage pages in the main Gramps window.
"""
#-------------------------------------------------------------------------
#
# GNOME modules
#
#-------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------
#
# Sidebar class
#
#-------------------------------------------------------------------------
class Sidebar(object):
"""
A class which defines the graphical representation of the Gramps sidebar.
"""
def __init__(self, viewmanager):
self.viewmanager = viewmanager
self.pages = []
self.top = gtk.VBox()
frame = gtk.Frame()
hbox = gtk.HBox()
frame.add(hbox)
select_button = gtk.ToggleButton()
select_button.set_relief(gtk.RELIEF_NONE)
select_hbox = gtk.HBox()
self.title_label = gtk.Label('Category')
arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE)
select_hbox.pack_start(self.title_label, False)
select_hbox.pack_end(arrow, False)
select_button.add(select_hbox)
select_button.connect('button_press_event', self.__menu_button_pressed)
close_button = gtk.Button()
img = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
close_button.set_image(img)
close_button.set_relief(gtk.RELIEF_NONE)
close_button.connect('clicked', self.cb_close_clicked)
hbox.pack_start(select_button, False)
hbox.pack_end(close_button, False)
self.top.pack_start(frame, False)
self.menu = gtk.Menu()
self.menu.show()
self.menu.connect('deactivate', cb_menu_deactivate, select_button)
self.notebook = gtk.Notebook()
self.notebook.show()
self.notebook.set_show_tabs(False)
self.notebook.set_show_border(False)
self.notebook.connect('switch_page', self.cb_switch_page)
self.top.pack_start(self.notebook, True)
def get_top(self):
"""
Return the top container widget for the GUI.
"""
return self.top
def add(self, title, sidebar):
"""
Add a page to the sidebar for a plugin.
"""
index = self.notebook.append_page(sidebar.get_top(), gtk.Label(title))
self.pages.append((title, sidebar))
menu_item = gtk.MenuItem(title)
menu_item.connect('activate', self.cb_menu_activate, index)
menu_item.show()
self.menu.append(menu_item)
def view_changed(self, page_num):
"""
Called when a Gramps view is changed.
"""
for page in self.pages:
page[1].view_changed(page_num)
def handlers_block(self):
"""
Block signals to the buttons to prevent spurious events.
"""
for page in self.pages:
page[1].handlers_block()
def handlers_unblock(self):
"""
Unblock signals to the buttons.
"""
for page in self.pages:
page[1].handlers_unblock()
def __menu_button_pressed(self, button, event):
"""
Called when the button to select a sidebar page is pressed.
"""
if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS:
button.grab_focus()
button.set_active(True)
self.menu.popup(None, None, cb_menu_position, event.button,
event.time, button)
def cb_menu_activate(self, menu, index):
"""
Called when an item in the popup menu is selected.
"""
self.notebook.set_current_page(index)
def cb_switch_page(self, notebook, unused, index):
"""
Called when the user has switched to a new sidebar plugin page.
"""
if self.pages:
self.title_label.set_text(self.pages[index][0])
def cb_close_clicked(self, button):
"""
Called when the sidebar is closed.
"""
uimanager = self.viewmanager.uimanager
uimanager.get_action('/MenuBar/ViewMenu/Sidebar').activate()
#-------------------------------------------------------------------------
#
# Functions
#
#-------------------------------------------------------------------------
def cb_menu_position(menu, button):
"""
Determine the position of the popup menu.
"""
x_pos, y_pos = button.window.get_origin()
x_pos += button.allocation.x
y_pos += button.allocation.y + button.allocation.height
return (x_pos, y_pos, False)
def cb_menu_deactivate(menu, button):
"""
Called when the popup menu disappears.
"""
button.set_active(False)

View File

@ -4,6 +4,7 @@
# Copyright (C) 2005-2007 Donald N. Allingham
# Copyright (C) 2008 Brian G. Matherly
# Copyright (C) 2009 Benny Malengier
# Copyright (C) 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
@ -61,6 +62,7 @@ from PluginUtils import Tool, PluginWindows, \
ReportPluginDialog, ToolPluginDialog, gui_tool
from gen.plug import REPORT
from gui.pluginmanager import GuiPluginManager
from gen.plug import (START, END)
import Relationship
import ReportBase
import DisplayState
@ -78,6 +80,7 @@ from gui.configure import GrampsPreferences
from gen.db.backup import backup
from gen.db.exceptions import DbException
from GrampsAboutDialog import GrampsAboutDialog
from gui.sidebar import Sidebar
#-------------------------------------------------------------------------
#
@ -183,20 +186,6 @@ UIDEFAULT = '''<ui>
</ui>
'''
UICATEGORY = '''<ui>
<menubar name="MenuBar">
<menu action="ViewMenu">
<placeholder name="ViewsInCategory">%s
</placeholder>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="ViewsInCategory">%s
</placeholder>
</toolbar>
</ui>
'''
WIKI_HELP_PAGE_FAQ = '%s_-_FAQ' % const.URL_MANUAL_PAGE
WIKI_HELP_PAGE_KEY = '%s_-_Keybindings' % const.URL_MANUAL_PAGE
WIKI_HELP_PAGE_MAN = '%s' % const.URL_MANUAL_PAGE
@ -234,13 +223,14 @@ class ViewManager(CLIManager):
The View Manager does not have to know the number of views, the type of
views, or any other details about the views. It simply provides the
method of containing each view, and switching between the views.
method of containing each view, and has methods for creating, deleting and
switching between the views.
"""
def __init__(self, dbstate, view_category_order):
"""
The viewmanager is initialiste with a dbstate on which GRAMPS is
The viewmanager is initialised with a dbstate on which GRAMPS is
working, and a fixed view_category_order, which is the order in which
the view categories are accessible in the sidebar.
"""
@ -248,12 +238,8 @@ class ViewManager(CLIManager):
self.view_category_order = view_category_order
#set pluginmanager to GUI one
self._pmgr = GuiPluginManager.get_instance()
self.page_is_changing = False
self.active_page = None
self.views = []
self.pages = []
self.button_handlers = []
self.buttons = []
self.merge_ids = []
self.toolactions = None
self.tool_menu_ui_id = None
@ -294,12 +280,13 @@ class ViewManager(CLIManager):
vbox = gtk.VBox()
self.window.add(vbox)
hbox = gtk.HBox()
hpane = gtk.HPaned()
self.ebox = gtk.EventBox()
self.bbox = gtk.VBox()
self.ebox.add(self.bbox)
hbox.pack_start(self.ebox, False)
hbox.show_all()
self.sidebar = Sidebar(self)
self.ebox.add(self.sidebar.get_top())
hpane.add1(self.ebox)
hpane.show_all()
self.notebook = gtk.Notebook()
self.notebook.set_scrollable(True)
@ -309,12 +296,12 @@ class ViewManager(CLIManager):
self.__init_lists()
self.__build_ui_manager()
hbox.pack_start(self.notebook, True)
hpane.add2(self.notebook)
self.menubar = self.uimanager.get_widget('/MenuBar')
self.toolbar = self.uimanager.get_widget('/ToolBar')
vbox.pack_start(self.menubar, False)
vbox.pack_start(self.toolbar, False)
vbox.add(hbox)
vbox.add(hpane)
vbox.pack_end(self.__setup_statusbar(), False)
vbox.show()
@ -358,6 +345,21 @@ class ViewManager(CLIManager):
# But we need to realize it here to have gtk.gdk.window handy
self.window.realize()
def __load_sidebar_plugins(self):
"""
Load the sidebar plugins.
"""
for pdata in self._pmgr.get_reg_sidebars():
module = self._pmgr.load_plugin(pdata)
if not module:
print "Error loading sidebar '%s': skipping content" \
% pdata.name
continue
sidebar_class = getattr(module, pdata.sidebarclass)
sidebar_page = sidebar_class(self.dbstate, self.uistate)
self.sidebar.add(pdata.menu_label, sidebar_page)
def __setup_statusbar(self):
"""
Create the statusbar that sits at the bottom of the window
@ -387,10 +389,8 @@ class ViewManager(CLIManager):
"""
if self.show_sidebar:
self.ebox.show()
self.notebook.set_show_tabs(False)
else:
self.ebox.hide()
self.notebook.set_show_tabs(True)
def __build_open_button(self):
"""
@ -406,10 +406,17 @@ class ViewManager(CLIManager):
def __connect_signals(self):
"""
connects the signals needed
Connects the signals needed
"""
self.window.connect('delete-event', self.quit)
self.notebook.connect('switch-page', self.change_category)
self.notebook.connect('switch-page', self.view_changed)
def view_changed(self, notebook, page, page_num):
"""
Called when the notebook page is changed.
"""
self.sidebar.view_changed(page_num)
self.__change_page(page_num)
def __init_lists(self):
"""
@ -547,11 +554,9 @@ class ViewManager(CLIManager):
new_page = 0
else:
new_page = current_page + 1
if self.show_sidebar:
#cause a click signal
self.buttons[new_page].set_active(True)
else:
self.notebook.set_current_page(new_page)
self.sidebar.handlers_block()
self.notebook.set_current_page(new_page)
self.sidebar.handlers_unblock()
def __prev_view(self, action):
"""
@ -564,19 +569,16 @@ class ViewManager(CLIManager):
new_page = len(self.pages)-1
else:
new_page = current_page - 1
if self.show_sidebar:
#cause a click signal
self.buttons[new_page].set_active(True)
else:
self.notebook.set_current_page(new_page)
self.sidebar.handlers_block()
self.notebook.set_current_page(new_page)
self.sidebar.handlers_unblock()
def init_interface(self, vieworder):
def init_interface(self):
"""
Initialize the interface, creating the pages as given in vieworder
"""
self.views = vieworder
self.__init_lists()
self.__create_pages()
self.__load_sidebar_plugins()
if not self.file_loaded:
self.actiongroup.set_visible(False)
@ -787,12 +789,10 @@ class ViewManager(CLIManager):
"""
if obj.get_active():
self.ebox.show()
self.notebook.set_show_tabs(False)
config.set('interface.view', True)
self.show_sidebar = True
else:
self.ebox.hide()
self.notebook.set_show_tabs(True)
config.set('interface.view', False)
self.show_sidebar = False
config.save()
@ -823,267 +823,71 @@ class ViewManager(CLIManager):
config.set('interface.fullscreen', False)
config.save()
def view_toggle(self, radioaction, current, category_page):
"""
Go to the views in category_page, with in category: view_page
The view has id id_page
This is the only method that can call change of views in a category
"""
self.__vb_handlers_block()
if self.notebook.get_current_page() != category_page:
raise Error, 'Error changing view, category is not active'
cat_notebook = self.notebook_cat[category_page]
view_page = radioaction.get_current_value()
if self.notebook_cat[category_page].get_current_page() != view_page:
self.notebook_cat[category_page].set_current_page(view_page)
self.__change_view(category_page, view_page)
self.__vb_handlers_unblock()
def create_page(self, pdata, page_def):
try:
page = page_def(self.dbstate, self.uistate)
except:
import traceback
LOG.warn("View '%s' failed to load." % pdata.id)
traceback.print_exc()
return
# Category is (string, trans):
page.set_category(pdata.category)
page.set_ident(page.get_category() + '_' + pdata.id)
page_title = page.get_title()
page_category = page.get_category()
page_translated_category = page.get_translated_category()
page_stock = page.get_stock()
page.define_actions()
try:
page_display = page.get_display()
except:
import traceback
print "ERROR: '%s' failed to create view" % pdata.name
traceback.print_exc()
return
page_display.show_all()
page.post()
self.pages.append(page)
# create icon/label for workspace notebook
hbox = gtk.HBox()
image = gtk.Image()
image.set_from_stock(page_stock, gtk.ICON_SIZE_MENU)
hbox.pack_start(image, False)
hbox.add(gtk.Label(pdata.name))
hbox.show_all()
def __switch_page_on_dnd(self, widget, context, xpos, ypos, time, page_no):
"""
Switches the page based on drag and drop
"""
self.__vb_handlers_block()
if self.notebook.get_current_page() != page_no:
self.notebook.set_current_page(page_no)
self.__vb_handlers_unblock()
page_num = self.notebook.append_page(page_display, hbox)
return page_num
def goto_page(self, page_num):
self.sidebar.handlers_block()
self.notebook.set_current_page(page_num)
self.sidebar.handlers_unblock()
self.__change_page(page_num)
def __change_page(self, page_num):
self.__disconnect_previous_page()
self.active_page = self.pages[page_num]
self.active_page.set_active()
self.__connect_active_page(page_num)
self.uimanager.ensure_update()
while gtk.events_pending():
gtk.main_iteration()
self.active_page.change_page()
def __delete_pages(self):
"""
Calls on_delete() for each view
"""
for pages in self.pages:
for page in pages:
page.on_delete()
def __create_pages(self):
"""
Create the Views
"""
self.pages = []
self.ui_category = {}
self.view_toggle_actions = {}
self.cat_view_group = None
use_text = config.get('interface.sidebar-text')
#obtain which views should be the active ones
current_cat, current_cat_view, default_cat_views = \
self.__views_to_show(config.get('preferences.use-last-view'))
for indexcat, cat_views in enumerate(self.views):
#for every category, we create a button in the sidebar and a main
#workspace in which to show the view
nr_views = len(cat_views)
uimenuitems = ''
uitoolitems = ''
self.view_toggle_actions[indexcat] = []
self.pages.append([])
nrpage = 0
for pdata, page_def in cat_views:
try:
page = page_def(self.dbstate, self.uistate)
except:
import traceback
LOG.warn("View '%s' failed to load." % pdata.id)
traceback.print_exc()
continue
# Category is (string, trans):
page.set_category(pdata.category)
page.set_ident(page.get_category() + '_' + pdata.id)
page_title = page.get_title()
page_category = page.get_category()
page_translated_category = page.get_translated_category()
page_stock = page.get_stock()
if nrpage == 0:
#the first page of this category, used to obtain
#category workspace notebook
notebook = gtk.Notebook()
notebook.set_scrollable(False)
notebook.set_show_tabs(False)
notebook.show()
self.notebook_cat.append(notebook)
# create icon/label for workspace notebook
hbox = gtk.HBox()
image = gtk.Image()
image.set_from_stock(page_stock, gtk.ICON_SIZE_MENU)
hbox.pack_start(image, False)
hbox.add(gtk.Label(page_translated_category))
hbox.show_all()
page_cat = self.notebook.append_page(notebook, hbox)
# Enable view switching during DnD
hbox.drag_dest_set(0, [], 0)
hbox.connect('drag_motion', self.__switch_page_on_dnd,
page_cat)
# create the button and add it to the sidebar
button = self.__make_sidebar_button(use_text, indexcat,
page_translated_category,
page_stock)
self.bbox.pack_start(button, False)
self.buttons.append(button)
# Enable view switching during DnD
button.drag_dest_set(0, [], 0)
button.connect('drag_motion', self.__switch_page_on_dnd,
page_cat)
# create view page and add to category notebook
page.define_actions()
try:
page_display = page.get_display()
except:
import traceback
print "ERROR: '%s' failed to create view" % pdata.name
traceback.print_exc()
continue
page_display.show_all()
page.post()
page_no = self.notebook_cat[-1].append_page(page_display,
gtk.Label(page_title))
self.pages[-1].append(page)
pageid = (pdata.id + '_%i' % nrpage)
uimenuitems += '\n<menuitem action="%s"/>' % pageid
uitoolitems += '\n<toolitem action="%s"/>' % pageid
# id, stock, button text, UI, tooltip, page
if nrpage < 9:
modifier = "<CONTROL>%d" % ((nrpage % 9) + 1)
else:
modifier = ""
self.view_toggle_actions[indexcat].append((pageid,
page.get_viewtype_stock(),
pdata.name, modifier, page_title, nrpage))
nrpage += 1
if nr_views > 1:
#allow for switching views in a category
self.ui_category[indexcat] = UICATEGORY % (uimenuitems,
uitoolitems)
#set view cat to last used in this category
self.notebook_cat[-1].set_current_page(default_cat_views[indexcat])
if self.views:
self.active_page = self.pages[current_cat][current_cat_view]
self.buttons[current_cat].set_active(True)
self.active_page.set_active()
self.notebook.set_current_page(current_cat)
self.notebook_cat[current_cat].set_current_page(current_cat_view)
else:
#not one single view loaded
WarningDialog(
_("No views loaded"),
_("No view plugins are loaded. Go to Help->Plugin "
"Manager, and make sure some plugins of type 'View' are "
"enabled. Then restart Gramps"))
def __views_to_show(self, use_last = True):
"""
Determine based on preference setting which views should be shown
"""
current_cat = 0
current_cat_view = 0
default_cat_views = [0] * len(self.views)
if use_last:
current_page_id = config.get('preferences.last-view')
default_page_ids = config.get('preferences.last-views')
found = False
for indexcat, cat_views in enumerate(self.views):
cat_view = 0
for pdata, page_def in cat_views:
if not found:
if pdata.id == current_page_id:
current_cat = indexcat
current_cat_view = cat_view
default_cat_views[indexcat] = cat_view
found = True
break
if pdata.id in default_page_ids:
default_cat_views[indexcat] = cat_view
cat_view += 1
if not found:
current_cat = 0
current_cat_view = 0
return current_cat, current_cat_view, default_cat_views
def __make_sidebar_button(self, use_text, index, page_title, page_stock):
"""
Create the sidebar button. The page_title is the text associated with
the button.
"""
# create the button
button = gtk.ToggleButton()
button.set_relief(gtk.RELIEF_NONE)
button.set_alignment(0, 0.5)
# add the tooltip
button.set_tooltip_text(page_title)
#self.tips.set_tip(button, page_title)
# connect the signal, along with the index as user data
handler_id = button.connect('clicked', self.__vb_clicked, index)
self.button_handlers.append(handler_id)
button.show()
# add the image. If we are using text, use the BUTTON (larger) size.
# otherwise, use the smaller size
hbox = gtk.HBox()
hbox.show()
image = gtk.Image()
if use_text:
image.set_from_stock(page_stock, gtk.ICON_SIZE_BUTTON)
else:
image.set_from_stock(page_stock, gtk.ICON_SIZE_DND)
image.show()
hbox.pack_start(image, False, False)
hbox.set_spacing(4)
# add text if requested
if use_text:
label = gtk.Label(page_title)
label.show()
hbox.pack_start(label, False, True)
button.add(hbox)
return button
def __vb_clicked(self, button, index):
"""
Called when the button causes a page change
"""
if config.get('interface.view'):
self.__vb_handlers_block()
self.notebook.set_current_page(index)
# If the click is on the same view we're in,
# restore the button state to active
if not button.get_active():
button.set_active(True)
self.__vb_handlers_unblock()
def __vb_handlers_block(self):
"""
Block signals to the buttons to prevent spurious events
"""
for idx in range(len(self.buttons)):
self.buttons[idx].handler_block(self.button_handlers[idx])
def __vb_handlers_unblock(self):
"""
Unblock signals to the buttons
"""
for idx in range(len(self.buttons)):
self.buttons[idx].handler_unblock(self.button_handlers[idx])
def __set_active_button(self, num):
"""
Set the corresponding button active, while setting the others
inactive
"""
for idx in range(len(self.buttons)):
self.buttons[idx].set_active(idx==num)
for page in self.pages:
page.on_delete()
def __disconnect_previous_page(self):
"""
@ -1098,11 +902,8 @@ class ViewManager(CLIManager):
for grp in groups:
if grp in self.uimanager.get_action_groups():
self.uimanager.remove_action_group(grp)
if self.cat_view_group:
if self.cat_view_group in self.uimanager.get_action_groups():
self.uimanager.remove_action_group(self.cat_view_group)
def __connect_active_page(self, category_page, view_page):
def __connect_active_page(self, page_num):
"""
Inserts the action groups associated with the current page
into the UIManager
@ -1117,90 +918,12 @@ class ViewManager(CLIManager):
mergeid = self.uimanager.add_ui_from_string(uidef)
self.merge_ids.append(mergeid)
if category_page in self.ui_category:
#add entries for the different views in the category
self.cat_view_group = gtk.ActionGroup('categoryviews')
self.cat_view_group.add_radio_actions(
self.view_toggle_actions[category_page], value=view_page,
on_change=self.view_toggle, user_data=category_page)
self.cat_view_group.set_sensitive(True)
self.uimanager.insert_action_group(self.cat_view_group, 1)
mergeid = self.uimanager.add_ui_from_string(self.ui_category[
category_page])
self.merge_ids.append(mergeid)
configaction = self.actiongroup.get_action('ConfigView')
if self.active_page.can_configure():
configaction.set_sensitive(True)
else:
configaction.set_sensitive(False)
def change_category(self, obj, page, num=-1):
"""
Wrapper for the __do_change_category, to prevent entering into the
routine while already in it.
"""
if not self.page_is_changing:
self.page_is_changing = True
self.__do_change_category(num)
self.page_is_changing = False
def __do_change_category(self, num):
"""
Change the category to the new category
"""
if num == -1:
num = self.notebook.get_current_page()
# set button of current page active
self.__set_active_button(num)
# now do view specific change
self.__change_view(num)
def __change_view(self, category_page, view_page=-1):
"""
Change a view in a category.
:Param category_page: the category number the view is in
:Type category_page: integer >= 0
:Param view_page: the view page number to switch to. If -1 is passed
the currently already active view in the category is switched to.
Use this when a category changes.
:Type view_page: integer >=0 to switch to a specific page, or -1 to
switch to the active view in the category
"""
if self.notebook_cat:
if view_page == -1:
#just show active one
view_page = self.notebook_cat[category_page].get_current_page()
if self.dbstate.open:
self.__disconnect_previous_page()
if len(self.pages) > 0:
self.active_page = self.pages[category_page][view_page]
self.active_page.set_active()
newcurpageid = self.views[category_page][view_page][0].id
config.set('preferences.last-view', newcurpageid)
olddefaults = config.get('preferences.last-views')
if len(olddefaults) != len(self.pages):
#number views changed, we cannot trust the old
olddefaults = [''] * len(self.pages)
olddefaults[category_page] = newcurpageid
config.set('preferences.last-views', olddefaults)
config.save()
self.__connect_active_page(category_page, view_page)
self.uimanager.ensure_update()
while gtk.events_pending():
gtk.main_iteration()
self.active_page.change_page()
else:
#no views loaded
pass
def import_data(self, obj):
"""
Imports a file
@ -1267,7 +990,6 @@ class ViewManager(CLIManager):
self.uistate.window.set_title(msg)
self.actiongroup.set_sensitive(True)
self.change_category(None, None)
self.actiongroup.set_visible(True)
self.readonlygroup.set_visible(True)
@ -1597,3 +1319,79 @@ def make_plugin_callback(pdata, dbstate, uistate):
Makes a callback for a report/tool menu item
"""
return lambda x: run_plugin(pdata, dbstate, uistate)
def get_available_views():
"""
Query the views and determine what views to show and in which order
:Returns: a list of lists containing tuples (view_id, viewclass)
"""
pmgr = GuiPluginManager.get_instance()
view_list = pmgr.get_reg_views()
viewstoshow = {}
for pdata in view_list:
mod = pmgr.load_plugin(pdata)
if not mod or not hasattr(mod, pdata.viewclass):
#import of plugin failed
ErrorDialog(
_('Failed Loading View'),
_('The view %(name)s did not load. See Help Menu, Plugin Manager'
' for more info.\nUse http://bugs.gramps-project.org to'
' submit bugs of official views, contact the view '
'author (%(firstauthoremail)s) otherwise. ') % {
'name': pdata.name,
'firstauthoremail': pdata.authors_email[0] if
pdata.authors_email else '...'})
continue
viewclass = getattr(mod, pdata.viewclass)
# pdata.category is (string, trans-string):
if pdata.category[0] in viewstoshow:
if pdata.order == START:
viewstoshow[pdata.category[0]].insert(0, ((pdata, viewclass)))
else:
viewstoshow[pdata.category[0]].append((pdata, viewclass))
else:
viewstoshow[pdata.category[0]] = [(pdata, viewclass)]
resultorder = []
# First, get those in order defined, if exists:
for item in config.get("interface.view-categories"):
if item in viewstoshow:
resultorder.append(viewstoshow[item])
# Next, get the rest in some order:
viewstoshow_names = viewstoshow.keys()
viewstoshow_names.sort()
for item in viewstoshow_names:
if viewstoshow[item] in resultorder:
continue
resultorder.append(viewstoshow[item])
return resultorder
def views_to_show(views, use_last=True):
"""
Determine based on preference setting which views should be shown
"""
current_cat = 0
current_cat_view = 0
default_cat_views = [0] * len(views)
if use_last:
current_page_id = config.get('preferences.last-view')
default_page_ids = config.get('preferences.last-views')
found = False
for indexcat, cat_views in enumerate(views):
cat_view = 0
for pdata, page_def in cat_views:
if not found:
if pdata.id == current_page_id:
current_cat = indexcat
current_cat_view = cat_view
default_cat_views[indexcat] = cat_view
found = True
break
if pdata.id in default_page_ids:
default_cat_views[indexcat] = cat_view
cat_view += 1
if not found:
current_cat = 0
current_cat_view = 0
return current_cat, current_cat_view, default_cat_views

View File

@ -0,0 +1,22 @@
# This is the src/plugins/sidebar level Makefile for Gramps
# We could use GNU make's ':=' syntax for nice wildcard use,
# but that is not necessarily portable.
# If not using GNU make, then list all .py files individually
pkgdatadir = $(datadir)/@PACKAGE@/plugins/sidebar
pkgdata_PYTHON = \
categorysidebar.py\
sidebar.gpr.py
pkgpyexecdir = @pkgpyexecdir@/plugins/sidebar
pkgpythondir = @pkgpythondir@/plugins/sidebar
# Clean up all the byte-compiled files
MOSTLYCLEANFILES = *pyc *pyo
GRAMPS_PY_MODPATH = "../../"
pycheck:
(export PYTHONPATH=$(GRAMPS_PY_MODPATH); \
pychecker $(pkgdata_PYTHON));

View File

@ -0,0 +1,301 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2005-2007 Donald N. Allingham
# Copyright (C) 2008 Brian G. Matherly
# Copyright (C) 2009 Benny Malengier
# Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
#-------------------------------------------------------------------------
#
# GNOME modules
#
#-------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
import config
from gui.viewmanager import get_available_views, views_to_show
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
UICATEGORY = '''<ui>
<menubar name="MenuBar">
<menu action="ViewMenu">
<placeholder name="ViewsInCategory">%s
</placeholder>
</menu>
</menubar>
<toolbar name="ToolBar">
<placeholder name="ViewsInCategory">%s
</placeholder>
</toolbar>
</ui>
'''
CATEGORY_ICON = {
'Gramplets': 'gramps-gramplet',
'People': 'gramps-person',
'Relationships': 'gramps-relation',
'Families': 'gramps-family',
'Events': 'gramps-event',
'Ancestry': 'gramps-pedigree',
'Places': 'gramps-place',
'Geography': 'gramps-geo',
'Sources': 'gramps-source',
'Repositories': 'gramps-repository',
'Media': 'gramps-media',
'Notes': 'gramps-notes'}
#-------------------------------------------------------------------------
#
# CategorySidebar class
#
#-------------------------------------------------------------------------
class CategorySidebar(object):
"""
A sidebar displaying a column of toggle buttons that allows the user to
change the current view.
"""
def __init__(self, dbstate, uistate):
self.viewmanager = uistate.viewmanager
self.buttons = []
self.button_handlers = []
self.vbox = gtk.VBox()
self.pages = {}
self.page_defs = {}
self.ui_category = {}
self.view_toggle_actions = {}
self.cat_view_group = None
self.merge_ids = []
self.views = get_available_views()
defaults = views_to_show(self.views,
config.get('preferences.use-last-view'))
self.current_views = defaults[2]
use_text = config.get('interface.sidebar-text')
for cat_num, cat_views in enumerate(self.views):
uimenuitems = ''
uitoolitems = ''
self.view_toggle_actions[cat_num] = []
for view_num, page in enumerate(cat_views):
if view_num == 0:
category = page[0].category[1]
cat_icon = CATEGORY_ICON.get(page[0].category[0])
if cat_icon is None:
cat_icon = 'gramps-view'
# create the button and add it to the sidebar
button = self.__make_sidebar_button(use_text, cat_num,
category, cat_icon)
self.vbox.pack_start(button, False)
# Enable view switching during DnD
button.drag_dest_set(0, [], 0)
button.connect('drag_motion', self.cb_switch_page_on_dnd,
cat_num)
self.vbox.show_all()
self.page_defs[(cat_num, view_num)] = page
pageid = (page[0].id + '_%i' % view_num)
uimenuitems += '\n<menuitem action="%s"/>' % pageid
uitoolitems += '\n<toolitem action="%s"/>' % pageid
# id, stock, button text, UI, tooltip, page
if view_num < 9:
modifier = "<CONTROL>%d" % ((view_num % 9) + 1)
else:
modifier = ""
stock_icon = page[0].stock_icon
if stock_icon is None:
stock_icon = cat_icon
self.view_toggle_actions[cat_num].append((pageid,
stock_icon,
page[0].name, modifier, page[0].name, view_num))
if len(cat_views) > 1:
#allow for switching views in a category
self.ui_category[cat_num] = UICATEGORY % (uimenuitems,
uitoolitems)
# Open the default view
self.__category_clicked(self.buttons[defaults[0]], defaults[0])
def get_top(self):
"""
Return the top container widget for the GUI.
"""
return self.vbox
def view_changed(self, page_num):
"""
Called when the active view is changed.
"""
cat_num = view_num = None
for key in self.pages:
if self.pages[key] == page_num:
cat_num, view_num = key
break
# Save last view in configuration
view_id = self.views[cat_num][view_num][0].id
config.set('preferences.last-view', view_id)
last_views = config.get('preferences.last-views')
last_views[cat_num] = view_id
config.set('preferences.last-views', last_views)
config.save()
# Add buttons to the toolbar for the different view in the category
uimanager = self.viewmanager.uimanager
if self.cat_view_group:
if self.cat_view_group in uimanager.get_action_groups():
uimanager.remove_action_group(self.cat_view_group)
map(uimanager.remove_ui, self.merge_ids)
if cat_num in self.ui_category:
self.cat_view_group = gtk.ActionGroup('categoryviews')
self.cat_view_group.add_radio_actions(
self.view_toggle_actions[cat_num], value=view_num,
on_change=self.cb_view_clicked, user_data=cat_num)
self.cat_view_group.set_sensitive(True)
uimanager.insert_action_group(self.cat_view_group, 1)
mergeid = uimanager.add_ui_from_string(self.ui_category[cat_num])
self.merge_ids.append(mergeid)
# Set new button as selected
self.handlers_block()
for index, button in enumerate(self.buttons):
if index == cat_num:
button.set_active(True)
else:
button.set_active(False)
self.handlers_unblock()
def handlers_block(self):
"""
Block signals to the buttons to prevent spurious events.
"""
for idx in range(len(self.buttons)):
self.buttons[idx].handler_block(self.button_handlers[idx])
def handlers_unblock(self):
"""
Unblock signals to the buttons.
"""
for idx in range(len(self.buttons)):
self.buttons[idx].handler_unblock(self.button_handlers[idx])
def cb_view_clicked(self, radioaction, current, cat_num):
"""
Called when a button causes a view change.
"""
view_num = radioaction.get_current_value()
self.__goto_page(cat_num, view_num)
def __category_clicked(self, button, cat_num):
"""
Called when a button causes a category change.
"""
view_num = self.current_views[cat_num]
self.__goto_page(cat_num, view_num)
# If the click is on the same view we're in,
# restore the button state to active
if not button.get_active():
button.set_active(True)
def __goto_page(self, cat_num, view_num):
"""
Create the page if it doesn't exist and make it the current page.
"""
self.current_views[cat_num] = view_num
page_num = self.pages.get((cat_num, view_num))
if page_num is None:
page = self.page_defs[(cat_num, view_num)]
page_num = self.viewmanager.create_page(page[0], page[1])
self.pages[(cat_num, view_num)] = page_num
self.current_views[cat_num] = view_num
self.viewmanager.goto_page(page_num)
def __make_sidebar_button(self, use_text, index, page_title, page_stock):
"""
Create the sidebar button. The page_title is the text associated with
the button.
"""
# create the button
button = gtk.ToggleButton()
button.set_relief(gtk.RELIEF_NONE)
button.set_alignment(0, 0.5)
self.buttons.append(button)
# add the tooltip
button.set_tooltip_text(page_title)
# connect the signal, along with the index as user data
handler_id = button.connect('clicked', self.__category_clicked, index)
self.button_handlers.append(handler_id)
button.show()
# add the image. If we are using text, use the BUTTON (larger) size.
# otherwise, use the smaller size
hbox = gtk.HBox()
hbox.show()
image = gtk.Image()
if use_text:
image.set_from_stock(page_stock, gtk.ICON_SIZE_BUTTON)
else:
image.set_from_stock(page_stock, gtk.ICON_SIZE_DND)
image.show()
hbox.pack_start(image, False, False)
hbox.set_spacing(4)
# add text if requested
if use_text:
label = gtk.Label(page_title)
label.show()
hbox.pack_start(label, False, True)
button.add(hbox)
return button
def cb_switch_page_on_dnd(self, widget, context, xpos, ypos, time, page_no):
"""
Switches the page based on drag and drop.
"""
self.handlers_block()
if self.viewmanager.notebook.get_current_page() != page_no:
self.viewmanager.notebook.set_current_page(page_no)
self.handlers_unblock()

View File

@ -0,0 +1,40 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
#------------------------------------------------------------------------
#
# Register default sidebars
#
#------------------------------------------------------------------------
register(SIDEBAR,
id = 'categorysidebar',
name = _("Category Sidebar"),
description = _("A sidebar to allow the selection of view categories"),
version = '1.0',
gramps_target_version = '3.3',
status = STABLE,
fname = 'categorysidebar.py',
authors = [u"Nick Hall"],
authors_email = ["nick__hall@hotmail.com"],
sidebarclass = 'CategorySidebar',
menu_label = 'Category'
)

View File

@ -10,4 +10,5 @@ register(VIEW,
authors = [u"Douglas S. Blank"],
authors_email = ["doug.blank@gmail.com"],
viewclass = 'FanChartView',
stock_icon = 'gramps-fanchart',
)

View File

@ -10,4 +10,5 @@ register(VIEW,
authors_email = [""],
category = ("Places", _("Places")),
viewclass = 'PlaceTreeView',
stock_icon = 'gramps-tree-group',
)

View File

@ -130,6 +130,7 @@ authors_email = ["http://gramps-project.org"],
category = ("Ancestry", _("Ancestry")),
viewclass = 'PedigreeView',
order = START,
stock_icon = 'gramps-pedigree',
)
register(VIEW,
@ -145,6 +146,7 @@ authors_email = ["http://gramps-project.org"],
category = ("People", _("People")),
viewclass = 'PersonTreeView',
order = START,
stock_icon = 'gramps-tree-group',
)
register(VIEW,
@ -161,7 +163,9 @@ authors_email = ["http://gramps-project.org"],
category = ("People", _("People")),
viewclass = 'PersonListView',
order = START,
stock_icon = 'gramps-tree-list',
)
register(VIEW,
id = 'placelistview',
name = _("Place View"),
@ -175,6 +179,7 @@ authors_email = ["http://gramps-project.org"],
category = ("Places", _("Places")),
viewclass = 'PlaceListView',
order = START,
stock_icon = 'gramps-tree-list',
)
register(VIEW,