From 56e78691370e1b2702f10d890dbcadf5ee06bf4b Mon Sep 17 00:00:00 2001 From: Nick Hall Date: Mon, 8 Nov 2010 15:50:51 +0000 Subject: [PATCH] Introduce workspaces with a shared filter/gramplet sidebar svn: r16160 --- src/DisplayState.py | 2 +- src/Filters/SideBar/_SidebarFilter.py | 18 -- src/Filters/_SearchBar.py | 6 + src/gui/navigator.py | 171 ++++++++++++++++ src/gui/sidebar.py | 133 +++++++------ src/gui/viewmanager.py | 69 +++---- src/gui/views/listview.py | 49 +---- src/gui/workspace.py | 274 ++++++++++++++++++++++++++ src/plugins/lib/libpersonview.py | 1 - src/plugins/lib/libplaceview.py | 3 - src/plugins/view/eventview.py | 3 +- src/plugins/view/familyview.py | 3 +- src/plugins/view/fanchartview.py | 2 +- src/plugins/view/geoview.py | 93 ++------- src/plugins/view/grampletview.py | 2 +- src/plugins/view/htmlrenderer.py | 2 +- src/plugins/view/mediaview.py | 5 +- src/plugins/view/noteview.py | 5 +- src/plugins/view/pedigreeview.py | 2 +- src/plugins/view/personlistview.py | 2 +- src/plugins/view/persontreeview.py | 2 +- src/plugins/view/placelistview.py | 2 +- src/plugins/view/placetreeview.py | 2 +- src/plugins/view/relview.py | 2 +- src/plugins/view/repoview.py | 5 +- src/plugins/view/sourceview.py | 5 +- 26 files changed, 590 insertions(+), 273 deletions(-) create mode 100644 src/gui/navigator.py create mode 100644 src/gui/workspace.py diff --git a/src/DisplayState.py b/src/DisplayState.py index 9b9d65356..35566bba1 100644 --- a/src/DisplayState.py +++ b/src/DisplayState.py @@ -516,7 +516,7 @@ class DisplayState(gen.utils.Callback): def show_filter_results(self, dbstate, matched, total): #nav_type = self.viewmanager.active_page.navigation_type() #text = ((_("%(nav_type)s View") % {"nav_type": _(nav_type)}) + - text = (self.viewmanager.active_page.title + + text = (self.viewmanager.active_page.get_title() + (": %d/%d" % (matched, total))) self.status.pop(1, self.last_bar) self.status.push(1, text, self.last_bar) diff --git a/src/Filters/SideBar/_SidebarFilter.py b/src/Filters/SideBar/_SidebarFilter.py index 7c2011085..b44235db5 100644 --- a/src/Filters/SideBar/_SidebarFilter.py +++ b/src/Filters/SideBar/_SidebarFilter.py @@ -65,20 +65,6 @@ class SidebarFilter(DbGUIElement): self._tag_rebuild() def _init_interface(self): - self.table.attach(widgets.MarkupLabel(_('Filter')), - 0, 2, 0, 1, xoptions=gtk.FILL|gtk.EXPAND, yoptions=0) - btn = gtk.Button() - img = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU) - box = gtk.HBox() - btn.set_image(img) - btn.set_relief(gtk.RELIEF_NONE) - btn.set_alignment(1.0, 0.5) - box.pack_start(gtk.Label(''), expand=True, fill=True) - box.pack_end(btn, fill=False, expand=False) - box.show_all() - self.table.attach(box, 2, 4, 0, 1, yoptions=0) - btn.connect('clicked', self.btn_clicked) - self.create_widget() self.apply_btn.connect('clicked', self.clicked) @@ -105,10 +91,6 @@ class SidebarFilter(DbGUIElement): self.table.attach(hbox, 2, 4, self.position, self.position+1, xoptions=gtk.FILL, yoptions=0) - def btn_clicked(self, obj): - config.set('interface.filter', False) - config.save() - def get_widget(self): return self.table diff --git a/src/Filters/_SearchBar.py b/src/Filters/_SearchBar.py index 9af644877..3b1c2a441 100644 --- a/src/Filters/_SearchBar.py +++ b/src/Filters/_SearchBar.py @@ -48,6 +48,7 @@ class SearchBar(object): self.dbstate = dbstate self.uistate = uistate self.apply_text = '' + self.visible = False self.filterbar = gtk.HBox() self.filter_text = gtk.Entry() @@ -167,6 +168,11 @@ class SearchBar(object): def show(self): self.filterbar.show() + self.visible = True def hide(self): self.filterbar.hide() + self.visible = False + + def is_visible(self): + return self.visible diff --git a/src/gui/navigator.py b/src/gui/navigator.py new file mode 100644 index 000000000..bbd2f4f75 --- /dev/null +++ b/src/gui/navigator.py @@ -0,0 +1,171 @@ +# +# 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 + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gen.plug import (START, END) + +#------------------------------------------------------------------------- +# +# Navigator class +# +#------------------------------------------------------------------------- +class Navigator(object): + """ + A class which defines the graphical representation of the Gramps navigator. + """ + 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('') + 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_end(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, order): + """ + Add a page to the sidebar for a plugin. + """ + self.pages.append((title, sidebar)) + index = self.notebook.append_page(sidebar.get_top(), gtk.Label(title)) + + menu_item = gtk.MenuItem(title) + if order == START: + self.menu.prepend(menu_item) + self.notebook.set_current_page(index) + else: + self.menu.append(menu_item) + menu_item.connect('activate', self.cb_menu_activate, index) + menu_item.show() + + def view_changed(self, cat_num, view_num): + """ + Called when a Gramps view is changed. + """ + for page in self.pages: + page[1].view_changed(cat_num, view_num) + + 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. + """ + pass + #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/Navigator').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) diff --git a/src/gui/sidebar.py b/src/gui/sidebar.py index 31c944ec6..248feed1e 100644 --- a/src/gui/sidebar.py +++ b/src/gui/sidebar.py @@ -19,10 +19,13 @@ # # $Id$ -""" -A module that provides pluggable sidebars. These provide an interface to -manage pages in the main Gramps window. -""" +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ + #------------------------------------------------------------------------- # # GNOME modules @@ -35,7 +38,7 @@ import gtk # Gramps modules # #------------------------------------------------------------------------- -from gen.plug import (START, END) +import config #------------------------------------------------------------------------- # @@ -46,9 +49,9 @@ class Sidebar(object): """ A class which defines the graphical representation of the Gramps sidebar. """ - def __init__(self, viewmanager): + def __init__(self, callback): - self.viewmanager = viewmanager + self.callback = callback self.pages = [] self.top = gtk.VBox() @@ -59,7 +62,7 @@ class Sidebar(object): select_button = gtk.ToggleButton() select_button.set_relief(gtk.RELIEF_NONE) select_hbox = gtk.HBox() - self.title_label = gtk.Label('') + self.title_label = gtk.Label() arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_NONE) select_hbox.pack_start(self.title_label, False) select_hbox.pack_end(arrow, False) @@ -71,51 +74,66 @@ class Sidebar(object): 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) + close_button.connect('clicked', self.__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.menu.connect('deactivate', self.__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.notebook.connect('switch_page', self.__switch_page) self.top.pack_start(self.notebook, True) + self.top.show_all() - def get_top(self): + def get_display(self): """ Return the top container widget for the GUI. """ return self.top + + def get_page_type(self): + """ + Return the type of the active page. + """ + return self.pages[self.notebook.get_current_page()][1] - def add(self, title, sidebar, order): + def add(self, title, container, page_type): """ - Add a page to the sidebar for a plugin. + Add a page to the sidebar. """ - self.pages.append((title, sidebar)) - index = self.notebook.append_page(sidebar.get_top(), gtk.Label(title)) - menu_item = gtk.MenuItem(title) - if order == START: - self.menu.prepend(menu_item) - self.notebook.set_current_page(index) - else: - self.menu.append(menu_item) - menu_item.connect('activate', self.cb_menu_activate, index) + self.pages.append([title, page_type, menu_item]) + index = self.notebook.append_page(container, gtk.Label(title)) + menu_item.connect('activate', self.__menu_activate, index) menu_item.show() + self.menu.append(menu_item) + self.notebook.set_current_page(index) - def view_changed(self, cat_num, view_num): + def remove(self, page_type): """ - Called when a Gramps view is changed. + Replace a page in the sidebar. """ - for page in self.pages: - page[1].view_changed(cat_num, view_num) + position = self.__get_page(page_type) + if position is not None: + self.notebook.remove_page(position) + self.menu.remove(self.pages[position][2]) + self.pages = self.pages[:position] + self.pages[position+1:] + + def __get_page(self, page_type): + """ + Return the page number of the page with the given type. + """ + for page_num, page in enumerate(self.pages): + if page[1] == page_type: + return page_num + return None def __menu_button_pressed(self, button, event): """ @@ -125,46 +143,43 @@ class Sidebar(object): button.grab_focus() button.set_active(True) - self.menu.popup(None, None, cb_menu_position, event.button, + self.menu.popup(None, None, self.__menu_position, event.button, event.time, button) - - def cb_menu_activate(self, menu, index): + + def __menu_position(self, menu, button): + """ + Determine the position of the popup menu. + """ + x, y = button.window.get_origin() + x += button.allocation.x + y += button.allocation.y + button.allocation.height + + return (x, y, False) + + def __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): + def __menu_deactivate(self, menu, button): """ - Called when the user has switched to a new sidebar plugin page. + Called when the popup menu disappears. + """ + button.set_active(False) + + def __switch_page(self, notebook, unused, index): + """ + Called when the user has switched to a new sidebar page. """ if self.pages: - self.title_label.set_text(self.pages[index][0]) + self.title_label.set_markup('%s' % self.pages[index][0]) + active = self.top.get_property('visible') + self.callback(self.pages[index][1], active) - def cb_close_clicked(self, button): + def __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) + config.set('interface.filter', False) + config.save() diff --git a/src/gui/viewmanager.py b/src/gui/viewmanager.py index c255d7e73..31b5d412d 100644 --- a/src/gui/viewmanager.py +++ b/src/gui/viewmanager.py @@ -89,7 +89,8 @@ 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 +from workspace import Workspace +from gui.navigator import Navigator from gui.views.tags import Tags from gen.utils.configmanager import safe_eval @@ -130,9 +131,9 @@ UIDEFAULT = ''' - + - + @@ -278,9 +279,9 @@ class ViewManager(CLIManager): self.views = None self.current_views = [] # The current view in each category - self.show_sidebar = config.get('interface.view') + self.show_navigator = config.get('interface.view') self.show_toolbar = config.get('interface.toolbar-on') - self.show_filter = config.get('interface.filter') + self.show_sidebar = config.get('interface.filter') self.fullscreen = config.get('interface.fullscreen') self.__build_main_window() @@ -515,8 +516,8 @@ class ViewManager(CLIManager): hpane = gtk.HPaned() self.ebox = gtk.EventBox() - self.sidebar = Sidebar(self) - self.ebox.add(self.sidebar.get_top()) + self.navigator = Navigator(self) + self.ebox.add(self.navigator.get_top()) hpane.add1(self.ebox) hpane.show_all() @@ -547,8 +548,8 @@ class ViewManager(CLIManager): self.tags = Tags(self.uistate, self.dbstate) - self.filter_menu = self.uimanager.get_widget( - '/MenuBar/ViewMenu/Filter/') + self.sidebar_menu = self.uimanager.get_widget( + '/MenuBar/ViewMenu/Sidebar/') # handle OPEN button, insert it into the toolbar. Unfortunately, # UIManager has no built in support for and Open Recent button @@ -563,7 +564,7 @@ class ViewManager(CLIManager): self.db_loader = DbLoader(self.dbstate, self.uistate) - self.__setup_sidebar() + self.__setup_navigator() if self.show_toolbar: self.toolbar.show() @@ -591,7 +592,7 @@ class ViewManager(CLIManager): sidebar_class = getattr(module, pdata.sidebarclass) sidebar_page = sidebar_class(self.dbstate, self.uistate) - self.sidebar.add(pdata.menu_label, sidebar_page, pdata.order) + self.navigator.add(pdata.menu_label, sidebar_page, pdata.order) def __setup_statusbar(self): """ @@ -615,12 +616,12 @@ class ViewManager(CLIManager): hbox2.show() return hbox2 - def __setup_sidebar(self): + def __setup_navigator(self): """ If we have enabled te sidebar, show it, and turn off the tabs. If disabled, hide the sidebar and turn on the tabs. """ - if self.show_sidebar: + if self.show_navigator: self.ebox.show() else: self.ebox.hide() @@ -733,12 +734,12 @@ class ViewManager(CLIManager): ] self._file_toggle_action_list = [ - ('Sidebar', None, _('_Sidebar'), None, None, self.sidebar_toggle, - self.show_sidebar ), + ('Navigator', None, _('_Navigator'), None, None, + self.navigator_toggle, self.show_navigator ), ('Toolbar', None, _('_Toolbar'), None, None, self.toolbar_toggle, self.show_toolbar ), - ('Filter', None, _('_Filter Sidebar'), None, None, - filter_toggle, self.show_filter), + ('Sidebar', None, _('_Sidebar'), None, None, + sidebar_toggle, self.show_sidebar), ('Fullscreen', None, _('F_ull Screen'), "F11", None, self.fullscreen_toggle, self.fullscreen), ] @@ -814,10 +815,8 @@ class ViewManager(CLIManager): self.__rebuild_report_and_tool_menus) self.fileactions.set_sensitive(True) self.uistate.widget.set_sensitive(True) - config.connect("interface.statusbar", - self.__statusbar_key_update) - config.connect("interface.filter", - self.__filter_signal) + config.connect("interface.statusbar", self.__statusbar_key_update) + config.connect("interface.filter", self.__sidebar_signal) def __statusbar_key_update(self, client, cnxn_id, entry, data): """ @@ -825,12 +824,12 @@ class ViewManager(CLIManager): """ self.uistate.modify_statusbar(self.dbstate) - def __filter_signal(self, client, cnxn_id, entry, data): + def __sidebar_signal(self, client, cnxn_id, entry, data): """ - Callback function for statusbar key update + Callback function for sidebar key update """ - if self.filter_menu.get_active() != config.get('interface.filter'): - self.filter_menu.set_active(config.get('interface.filter')) + if self.sidebar_menu.get_active() != config.get('interface.filter'): + self.sidebar_menu.set_active(config.get('interface.filter')) def post_init_interface(self, show_manager=True): """ @@ -995,7 +994,7 @@ class ViewManager(CLIManager): except Errors.WindowActiveError: pass - def sidebar_toggle(self, obj): + def navigator_toggle(self, obj): """ Set the sidebar based on the value of the toggle button. Save the results in the configuration settings @@ -1003,11 +1002,11 @@ class ViewManager(CLIManager): if obj.get_active(): self.ebox.show() config.set('interface.view', True) - self.show_sidebar = True + self.show_navigator = True else: self.ebox.hide() config.set('interface.view', False) - self.show_sidebar = False + self.show_navigator = False config.save() def toolbar_toggle(self, obj): @@ -1084,8 +1083,9 @@ class ViewManager(CLIManager): """ Create a new page and set it as the current page. """ + wspace = Workspace(self.uistate, self.dbstate) try: - page = page_def(self.dbstate, self.uistate) + page = page_def(self.dbstate, self.uistate, wspace) except: import traceback LOG.warn("View '%s' failed to load." % pdata.id) @@ -1109,7 +1109,9 @@ class ViewManager(CLIManager): return page_display.show_all() page.post() - self.pages.append(page) + + wspace.add_view(page) + self.pages.append(wspace) # create icon/label for workspace notebook hbox = gtk.HBox() @@ -1118,8 +1120,7 @@ class ViewManager(CLIManager): hbox.pack_start(image, False) hbox.add(gtk.Label(pdata.name)) hbox.show_all() - page_num = self.notebook.append_page(page_display, - hbox) + page_num = self.notebook.append_page(wspace.get_display(), hbox) def view_changed(self, notebook, page, page_num): """ @@ -1142,7 +1143,7 @@ class ViewManager(CLIManager): config.set('preferences.last-views', last_views) config.save() - self.sidebar.view_changed(cat_num, view_num) + self.navigator.view_changed(cat_num, view_num) self.__change_page(page_num) def __change_page(self, page_num): @@ -1663,7 +1664,7 @@ def display_about_box(obj): about.run() about.destroy() -def filter_toggle(obj): +def sidebar_toggle(obj): """ Save the filter state to the config settings on change """ diff --git a/src/gui/views/listview.py b/src/gui/views/listview.py index d321db8aa..555650ec4 100644 --- a/src/gui/views/listview.py +++ b/src/gui/views/listview.py @@ -175,11 +175,7 @@ class ListView(NavigationView): self.selection.connect('changed', self.row_changed) self.setup_filter() - - if self.filter_class: - return self.build_filter_container(self.vbox, self.filter_class) - else: - return self.vbox + return self.vbox def define_actions(self): """ @@ -252,7 +248,7 @@ class ListView(NavigationView): def build_tree(self, force_sidebar=False): if self.active: cput0 = time.clock() - if config.get('interface.filter') or force_sidebar: + if not self.search_bar.is_visible(): filter_info = (True, self.generic_filter, False) else: value = self.search_bar.get_value() @@ -308,50 +304,11 @@ class ListView(NavigationView): """ return () - #################################################################### - # Filter - #################################################################### - def build_filter_container(self, box, filter_class): - self.filter_sidebar = filter_class(self.dbstate, self.uistate, - self.filter_clicked) - self.filter_pane = self.filter_sidebar.get_widget() - - hpaned = gtk.HBox() - hpaned.pack_start(self.vbox, True, True) - hpaned.pack_end(self.filter_pane, False, False) - self.filter_toggle(None, None, None, None) - return hpaned - - def filter_toggle(self, client, cnxn_id, entry, data): - """ - Callback on change interface.filter, inheriting methods connect to - change in ini file - """ - if config.get('interface.filter'): - self.search_bar.hide() - self.filter_pane.show() - else: - self.search_bar.show() - self.filter_pane.hide() - - def post(self): - if self.filter_class: - if config.get('interface.filter'): - self.search_bar.hide() - self.filter_pane.show() - else: - self.search_bar.show() - self.filter_pane.hide() - def get_viewtype_stock(self): """Type of view in category, default listview is a flat list """ return 'gramps-tree-list' - def filter_clicked(self): - self.generic_filter = self.filter_sidebar.get_filter() - self.build_tree() - def filter_editor(self, obj): try: FilterEditor(self.FILTER_TYPE , const.CUSTOM_FILTERS, @@ -588,7 +545,7 @@ class ListView(NavigationView): self.sort_order = order handle = self.first_selected() - if config.get('interface.filter'): + if not self.search_bar.is_visible(): filter_info = (True, self.generic_filter, False) else: value = self.search_bar.get_value() diff --git a/src/gui/workspace.py b/src/gui/workspace.py new file mode 100644 index 000000000..db1c36e76 --- /dev/null +++ b/src/gui/workspace.py @@ -0,0 +1,274 @@ +# +# 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$ + +""" +Workspace +""" +#------------------------------------------------------------------------- +# +# Python modules +# +#------------------------------------------------------------------------- +from gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# GNOME modules +# +#------------------------------------------------------------------------- +import gtk + +#------------------------------------------------------------------------- +# +# Gramps modules +# +#------------------------------------------------------------------------- +from gui.sidebar import Sidebar +from gui.widgets.grampletpane import GrampletPane +from gui.views.listview import ListView +import config + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +GRAMPLET_PAGE = 0 +FILTER_PAGE = 1 + +#------------------------------------------------------------------------- +# +# Workspace class +# +#------------------------------------------------------------------------- +class Workspace(object): + """ + A Workspace contains panes to contain a view and associated objects such as + a filter and gramplet pane. + """ + def __init__(self, uistate, dbstate): + self.uistate = uistate + self.dbstate = dbstate + self.active = False + self.view = None + self.sidebar = Sidebar(self.sidebar_changed) + self.hpane = gtk.HPaned() + self.vpane = gtk.VPaned() + self.gramplet_pane = self.__create_gramplet_pane() + self.gramplet_pane.show_all() + self.hpane.pack1(self.vpane, resize=True, shrink=True) + self.hpane.pack2(self.sidebar.get_display(), resize=False, shrink=False) + self.hpane.show() + self.vpane.show() + if config.get('interface.filter'): + self.sidebar.get_display().show() + else: + self.sidebar.get_display().hide() + self.define_actions() + + def get_display(self): + """ + Return the top container widget for the GUI. + """ + return self.hpane + + def add_view(self, view): + """ + Add a view to the workspace. + """ + self.view = view + self.vpane.add1(view.get_display()) + + if isinstance(view, ListView): + self.add_filter(view.filter_class) + + def add_aux(self, aux): + """ + Add an auxilliary object to the workspace. + """ + self.aux = aux + self.vpane.add2(aux.get_display()) + + def add_filter(self, filter_class): + """ + Add a filter to the workspace sidebar. + """ + self.filter_sidebar = filter_class(self.dbstate, self.uistate, + self.__filter_clicked) + top = self.filter_sidebar.get_widget() + top.show_all() + self.sidebar.add(_('Filter'), top, FILTER_PAGE) + + def remove_filter(self,): + """ + Remove the filter from the workspace sidebar. + """ + self.filter_sidebar = None + self.sidebar.remove(FILTER_PAGE) + + def __create_gramplet_pane(self): + """ + Create a gramplet pane. + """ + self.uidef = ''' + + + + + ''' + + eb = gtk.EventBox() + eb.connect('button-press-event', self._gramplet_button_press) + + gramplet_pane = GrampletPane("grampletsidebar", + self, self.dbstate, self.uistate, + column_count=1) + eb.add(gramplet_pane) + eb.show() + self.sidebar.add(_('Gramplets'), eb, GRAMPLET_PAGE) + return gramplet_pane + + def _gramplet_button_press(self, obj, event): + """ + Called to display the context menu in the gramplet pane. + """ + if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: + menu = self.uistate.uimanager.get_widget('/Popup') + if menu: + menu.popup(None, None, None, event.button, event.time) + return True + + def __filter_clicked(self): + """ + Called when the filter 'Find' button is clicked. + """ + self.view.generic_filter = self.filter_sidebar.get_filter() + self.view.build_tree() + + def __sidebar_toggled(self, action): + """ + Called when the sidebar is toggled. + """ + active = action.get_active() + if active: + self.sidebar.get_display().show() + self.sidebar_changed(self.sidebar.get_page_type(), True) + else: + self.sidebar.get_display().hide() + self.sidebar_changed(None, False) + config.set('interface.filter', active) + config.save() + + def sidebar_changed(self, page_type, active): + """ + Called when the sidebar page is changed. + """ + if isinstance(self.view, ListView): + if active and page_type == FILTER_PAGE: + self.view.search_bar.hide() + else: + self.view.search_bar.show() + + def get_title(self): + """ + Return the title of the view. + """ + if self.view: + return self.view.title + return '' + + def define_actions(self): + """ + Defines the UIManager actions. + """ + self.action_group = gtk.ActionGroup('Workspace') + self.action_group.add_toggle_actions([ + ('Sidebar', None, _('_Sidebar'), + None, None, self.__sidebar_toggled, config.get('interface.filter')) + ]) + self.action_group.add_actions([ + ("AddGramplet", None, _("Add a gramplet")), + ("RestoreGramplet", None, _("Restore a gramplet") + )]) + + def set_active(self): + """ + Called when the view is set as active. + """ + self.active = True + self.view.set_active() + self.gramplet_pane.set_active() + + def set_inactive(self): + """ + Called when the view is set as inactive. + """ + self.active = False + self.view.set_inactive() + self.gramplet_pane.set_inactive() + + def get_actions(self): + """ + Return the actions that should be used for the view. + """ + action_list = self.view.get_actions() + action_list.append(self.action_group) + return action_list + + def ui_definition(self): + """ + Returns the XML UI definition for the UIManager. + """ + return self.view.ui_definition() + + def additional_ui_definitions(self): + """ + Return any additional interfaces for the UIManager that the view + needs to define. + """ + defs = self.view.additional_ui_definitions() + defs.append(self.uidef) + return defs + + def change_page(self): + """ + Called when the view changes. + """ + self.view.change_page() + + def on_delete(self): + """ + Method called on shutdown. + """ + self.view.on_delete() + self.gramplet_pane.on_delete() + + def can_configure(self): + """ + Returns True if the view has a configure window. + """ + return self.view.can_configure() + + def configure(self): + """ + Open the configure dialog for the view. + """ + self.view.configure() diff --git a/src/plugins/lib/libpersonview.py b/src/plugins/lib/libpersonview.py index d34a24fa6..c045cd4d1 100644 --- a/src/plugins/lib/libpersonview.py +++ b/src/plugins/lib/libpersonview.py @@ -141,7 +141,6 @@ class BasePersonView(ListView): 'BackSpace' : self.key_delete, }) - config.connect("interface.filter", self.filter_toggle) uistate.connect('nameformat-changed', self.build_tree) def navigation_type(self): diff --git a/src/plugins/lib/libplaceview.py b/src/plugins/lib/libplaceview.py index b205f812f..5b2743fd7 100644 --- a/src/plugins/lib/libplaceview.py +++ b/src/plugins/lib/libplaceview.py @@ -147,9 +147,6 @@ class PlaceBaseView(ListView): 'BackSpace' : self.key_delete, }) - config.connect("interface.filter", - self.filter_toggle) - def navigation_type(self): return 'Place' diff --git a/src/plugins/view/eventview.py b/src/plugins/view/eventview.py index 64b6eea1e..b9b60d3a3 100644 --- a/src/plugins/view/eventview.py +++ b/src/plugins/view/eventview.py @@ -100,7 +100,7 @@ class EventView(ListView): FILTER_TYPE = "Event" QR_CATEGORY = CATEGORY_QR_EVENT - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): """ Create the Event View """ @@ -126,7 +126,6 @@ class EventView(ListView): 'BackSpace' : self.key_delete, }) - config.connect("interface.filter", self.filter_toggle) uistate.connect('nameformat-changed', self.build_tree) def navigation_type(self): diff --git a/src/plugins/view/familyview.py b/src/plugins/view/familyview.py index 3ab20ab60..c6629267d 100644 --- a/src/plugins/view/familyview.py +++ b/src/plugins/view/familyview.py @@ -97,7 +97,7 @@ class FamilyView(ListView): FILTER_TYPE = "Family" QR_CATEGORY = CATEGORY_QR_FAMILY - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): signal_map = { 'family-add' : self.row_add, @@ -121,7 +121,6 @@ class FamilyView(ListView): 'BackSpace' : self.key_delete, }) - config.connect("interface.filter", self.filter_toggle) uistate.connect('nameformat-changed', self.build_tree) def navigation_type(self): diff --git a/src/plugins/view/fanchartview.py b/src/plugins/view/fanchartview.py index 1332cf306..218276072 100644 --- a/src/plugins/view/fanchartview.py +++ b/src/plugins/view/fanchartview.py @@ -566,7 +566,7 @@ class FanChartView(NavigationView): """ The Gramplet code that realizes the FanChartWidget. """ - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): NavigationView.__init__(self, _('Fan Chart'), dbstate, uistate, dbstate.db.get_bookmarks(), diff --git a/src/plugins/view/geoview.py b/src/plugins/view/geoview.py index b47a44195..d71cf6c9d 100644 --- a/src/plugins/view/geoview.py +++ b/src/plugins/view/geoview.py @@ -158,7 +158,6 @@ _UI_DEF = '''\ - @@ -344,8 +343,9 @@ class GeoView(HtmlView): ('preferences.webkit', True), ) - def __init__(self, dbstate, uistate): - HtmlView.__init__(self, dbstate, uistate, title=_("GeoView")) + def __init__(self, dbstate, uistate, wspace): + HtmlView.__init__(self, dbstate, uistate, wspace, title=_("GeoView")) + self.wspace = wspace self.dbstate = dbstate self.uistate = uistate self.dbstate.connect('database-changed', self._new_database) @@ -704,9 +704,9 @@ class GeoView(HtmlView): self.box1.pack_start(self.title, False, False, padding=2) self.box1.show_all() if self.displaytype == "places": - self.build_filters_container(self.filter, PlaceSidebarFilter) + self.wspace.add_filter(PlaceSidebarFilter) elif self.displaytype == "event": - self.build_filters_container(self.filter, EventSidebarFilter) + self.wspace.add_filter(EventSidebarFilter) return self.box1 def _entry_key_event(self, widget, event): @@ -1235,9 +1235,6 @@ class GeoView(HtmlView): self._add_action('EventMapsMenu', 'geo-show-event', _('_Event'), callback=self._event_places, tip=_("Attempt to view places connected to all events.")) - self._add_toggle_action('FilterEdit', None, _('_Filter Sidebar'), - callback=self.filter_toggle_action) - config.connect('interface.filter', self.filter_toggle) #################################################################### # BOOKMARKS @@ -1481,7 +1478,6 @@ class GeoView(HtmlView): self._savezoomandposition(500) # every 500 millisecondes self.endinit = True self.uistate.clear_filter_results() - self.filter_toggle(None, None, None, None) self._set_provider_icon() self._geo_places() @@ -1491,7 +1487,6 @@ class GeoView(HtmlView): """ if not self.uistate.get_active('Person'): return - self.filter_toggle(None, None, None, None) self._geo_places() def _all_places(self, hanle=None): # pylint: disable-msg=W0613 @@ -1499,7 +1494,8 @@ class GeoView(HtmlView): Specifies the place for the home person to display with mapstraction. """ self.displaytype = "places" - self.build_filters_container(self.filter, PlaceSidebarFilter) + self.wspace.remove_filter() + self.wspace.add_filter(PlaceSidebarFilter) self._geo_places() def _person_places(self, handle=None): # pylint: disable-msg=W0613 @@ -1507,7 +1503,7 @@ class GeoView(HtmlView): Specifies the person places. """ self.displaytype = "person" - self.no_filter() + self.wspace.remove_filter() if not self.uistate.get_active('Person'): return self._geo_places() @@ -1517,7 +1513,7 @@ class GeoView(HtmlView): Specifies the family places to display with mapstraction. """ self.displaytype = "family" - self.no_filter() + self.wspace.remove_filter() if not self.uistate.get_active('Person'): return self._geo_places() @@ -1527,7 +1523,8 @@ class GeoView(HtmlView): Specifies all event places to display with mapstraction. """ self.displaytype = "event" - self.build_filters_container(self.filter, EventSidebarFilter) + self.wspace.remove_filter() + self.wspace.add_filter(EventSidebarFilter) self._geo_places() def _new_database(self, database): @@ -2576,74 +2573,6 @@ class GeoView(HtmlView): except Errors.WindowActiveError: # pylint: disable-msg=W0704 pass # pylint: disable-msg=W0702 - #################################################################### - # Filters - #################################################################### - def build_filters_container(self, box, filter_class): - """ - Used to create the filters on Geoview. - Depending on the events view or places, view we must generate the - good filter. - We need to remove the old filter if it exists then add the new one. - """ - try: - self.vbox.destroy() - except: # pylint: disable-msg=W0704 - pass # pylint: disable-msg=W0702 - map(self.hpaned.remove, self.hpaned.get_children()) - self.vbox = gtk.VBox() - self.hpaned.pack_start(self.vbox, True, True) - self.filter_sidebar = filter_class(self.dbstate, self.uistate, - self.filter_clicked) - self.filter_pane = self.filter_sidebar.get_widget() - self.hpaned.pack_end(self.filter_pane, False, False) - box.show_all() - self.filter_toggle(None, None, None, None) - - def no_filter(self): - """ - We don't need a filter for the current view. - """ - try: - self.filter_pane.hide() - except: # pylint: disable-msg=W0704 - pass # pylint: disable-msg=W0702 - - def filter_toggle(self, client, cnxn_id, entry, data): - # pylint: disable-msg=W0613 - """ - We must show or hide the filter depending on the filter toggle button. - """ - if not self.endinit: - return - - if self.displaytype == "places" or self.displaytype == "event": - if config.get('interface.filter'): - self.filter.show() - else: - self.filter.hide() - - def filter_toggle_action(self, obj): - """ - Depending on the filter toggle button action, we must show or hile - the filter then save the state in the config file. - """ - if self.displaytype == "places" or self.displaytype == "event": - if obj.get_active(): - self.filter.show() - active = True - else: - self.filter.hide() - active = False - config.set('interface.filter', active) - - def filter_clicked(self): - """ - We have clicked on the Find button into the filter box. - """ - self.generic_filter = self.filter_sidebar.get_filter() - self.build_tree() - def build_tree(self): """ Builds the new view depending on the filter. diff --git a/src/plugins/view/grampletview.py b/src/plugins/view/grampletview.py index beb676325..90dcdc017 100644 --- a/src/plugins/view/grampletview.py +++ b/src/plugins/view/grampletview.py @@ -45,7 +45,7 @@ class GrampletView(PageView): GrampletView interface """ - def __init__(self, dbstate, uistate): + def __init__(self, dbstate, uistate, wspace): """ Create a GrampletView, with the current dbstate and uistate """ diff --git a/src/plugins/view/htmlrenderer.py b/src/plugins/view/htmlrenderer.py index c024a7061..90c39c9c7 100644 --- a/src/plugins/view/htmlrenderer.py +++ b/src/plugins/view/htmlrenderer.py @@ -441,7 +441,7 @@ class HtmlView(PageView): with an embedded webbrowser showing a given URL """ - def __init__(self, dbstate, uistate, title=_('HtmlView')): + def __init__(self, dbstate, uistate, wspace, title=_('HtmlView')): PageView.__init__(self, title, dbstate, uistate) self.dbstate = dbstate self.back_action = None diff --git a/src/plugins/view/mediaview.py b/src/plugins/view/mediaview.py index b5b40088b..dd8395be5 100644 --- a/src/plugins/view/mediaview.py +++ b/src/plugins/view/mediaview.py @@ -113,7 +113,7 @@ class MediaView(ListView): _DND_TYPE = DdTargets.URI_LIST - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): signal_map = { 'media-add' : self.row_add, @@ -137,9 +137,6 @@ class MediaView(ListView): 'BackSpace' : self.key_delete, }) - config.connect("interface.filter", - self.filter_toggle) - def navigation_type(self): return 'Media' diff --git a/src/plugins/view/noteview.py b/src/plugins/view/noteview.py index 9df48a173..afebe7e09 100644 --- a/src/plugins/view/noteview.py +++ b/src/plugins/view/noteview.py @@ -92,7 +92,7 @@ class NoteView(ListView): FILTER_TYPE = "Note" QR_CATEGORY = CATEGORY_QR_NOTE - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): signal_map = { 'note-add' : self.row_add, @@ -115,9 +115,6 @@ class NoteView(ListView): 'BackSpace' : self.key_delete, }) - config.connect("interface.filter", - self.filter_toggle) - def navigation_type(self): return 'Note' diff --git a/src/plugins/view/pedigreeview.py b/src/plugins/view/pedigreeview.py index 5ed5d4077..3430b57b9 100644 --- a/src/plugins/view/pedigreeview.py +++ b/src/plugins/view/pedigreeview.py @@ -667,7 +667,7 @@ class PedigreeView(NavigationView): ('interface.pedview-show-unknown-people', True), ) - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): NavigationView.__init__(self, _('Pedigree'), dbstate, uistate, dbstate.db.get_bookmarks(), Bookmarks.PersonBookmarks, diff --git a/src/plugins/view/personlistview.py b/src/plugins/view/personlistview.py index ef19b6a3a..ca5d9da7b 100644 --- a/src/plugins/view/personlistview.py +++ b/src/plugins/view/personlistview.py @@ -53,7 +53,7 @@ class PersonListView(BasePersonView): """ A hierarchical view of the top three levels of places. """ - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): BasePersonView.__init__(self, dbstate, uistate, _('Person View'), PersonListModel, nav_group=nav_group) diff --git a/src/plugins/view/persontreeview.py b/src/plugins/view/persontreeview.py index 04ec5091a..139f506d3 100644 --- a/src/plugins/view/persontreeview.py +++ b/src/plugins/view/persontreeview.py @@ -55,7 +55,7 @@ class PersonTreeView(BasePersonView): """ A hierarchical view of the top three levels of places. """ - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): BasePersonView.__init__(self, dbstate, uistate, _('People Tree View'), PersonTreeModel, nav_group=nav_group) diff --git a/src/plugins/view/placelistview.py b/src/plugins/view/placelistview.py index 6ea9e0478..a9027037b 100644 --- a/src/plugins/view/placelistview.py +++ b/src/plugins/view/placelistview.py @@ -47,7 +47,7 @@ class PlaceListView(PlaceBaseView): """ Flat place view. (Original code in PlaceBaseView). """ - def __init__(self, dbstate, uistate): + def __init__(self, dbstate, uistate, wspace): PlaceBaseView.__init__(self, dbstate, uistate, _('Place View'), PlaceListModel, nav_group=0) diff --git a/src/plugins/view/placetreeview.py b/src/plugins/view/placetreeview.py index add640de9..09c5edd26 100644 --- a/src/plugins/view/placetreeview.py +++ b/src/plugins/view/placetreeview.py @@ -93,7 +93,7 @@ class PlaceTreeView(PlaceBaseView): 100, 150, 150, 100, 150]) ) - def __init__(self, dbstate, uistate): + def __init__(self, dbstate, uistate, wspace): PlaceBaseView.__init__(self, dbstate, uistate, _('Place Tree View'), PlaceTreeModel, nav_group=0, markup=PlaceBaseView.MARKUP_COLS) diff --git a/src/plugins/view/relview.py b/src/plugins/view/relview.py index 22d88a562..4db471069 100644 --- a/src/plugins/view/relview.py +++ b/src/plugins/view/relview.py @@ -129,7 +129,7 @@ class RelationshipView(NavigationView): ('preferences.releditbtn', True), ) - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): NavigationView.__init__(self, _('Relationships'), dbstate, uistate, dbstate.db.get_bookmarks(), diff --git a/src/plugins/view/repoview.py b/src/plugins/view/repoview.py index 3b545e4a8..c408b1ce1 100644 --- a/src/plugins/view/repoview.py +++ b/src/plugins/view/repoview.py @@ -109,7 +109,7 @@ class RepositoryView(ListView): FILTER_TYPE = "Repository" QR_CATEGORY = CATEGORY_QR_REPOSITORY - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): signal_map = { 'repository-add' : self.row_add, @@ -132,9 +132,6 @@ class RepositoryView(ListView): 'BackSpace' : self.key_delete, }) - config.connect("interface.filter", - self.filter_toggle) - def navigation_type(self): return 'Repository' diff --git a/src/plugins/view/sourceview.py b/src/plugins/view/sourceview.py index 5ecd46e34..af38ed8f2 100644 --- a/src/plugins/view/sourceview.py +++ b/src/plugins/view/sourceview.py @@ -93,7 +93,7 @@ class SourceView(ListView): FILTER_TYPE = "Source" QR_CATEGORY = CATEGORY_QR_SOURCE - def __init__(self, dbstate, uistate, nav_group=0): + def __init__(self, dbstate, uistate, wspace, nav_group=0): signal_map = { 'source-add' : self.row_add, @@ -116,9 +116,6 @@ class SourceView(ListView): 'BackSpace' : self.key_delete, }) - config.connect("interface.filter", - self.filter_toggle) - def navigation_type(self): return 'Source'