1# 2# Gramps - a GTK+/GNOME based genealogy program 3# 4# Copyright (C) 2010 Nick Hall 5# 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 19# 20# $Id: navigator.py 20492 2012-10-02 21:08:19Z nick-h $ 21 22""" 23A module that provides pluggable sidebars. These provide an interface to 24manage pages in the main Gramps window. 25""" 26#------------------------------------------------------------------------- 27# 28# GNOME modules 29# 30#------------------------------------------------------------------------- 31from gi.repository import Gtk 32from gi.repository import Gdk 33 34#------------------------------------------------------------------------- 35# 36# Gramps modules 37# 38#------------------------------------------------------------------------- 39from gramps.gen.plug import (START, END) 40from .pluginmanager import GuiPluginManager 41from .uimanager import ActionGroup 42 43#------------------------------------------------------------------------- 44# 45# Constants 46# 47#------------------------------------------------------------------------- 48UICATEGORY = ''' <section id="ViewsInCatagory"> 49 %s 50 </section> 51 ''' 52UICATAGORYBAR = ''' <placeholder id='ViewsInCategoryBar'> 53 %s 54 </placeholder> 55 ''' 56 57CATEGORY_ICON = { 58 'Dashboard': 'gramps-gramplet', 59 'People': 'gramps-person', 60 'Relationships': 'gramps-relation', 61 'Families': 'gramps-family', 62 'Events': 'gramps-event', 63 'Ancestry': 'gramps-pedigree', 64 'Places': 'gramps-place', 65 'Geography': 'gramps-geo', 66 'Sources': 'gramps-source', 67 'Repositories': 'gramps-repository', 68 'Media': 'gramps-media', 69 'Notes': 'gramps-notes', 70 'Citations': 'gramps-citation', 71} 72 73#------------------------------------------------------------------------- 74# 75# Navigator class 76# 77#------------------------------------------------------------------------- 78class Navigator: 79 """ 80 A class which defines the graphical representation of the Gramps navigator. 81 """ 82 def __init__(self, viewmanager): 83 84 self.viewmanager = viewmanager 85 self.pages = [] 86 self.active_cat = None 87 self.active_view = None 88 89 self.ui_category = {} 90 self.cat_view_group = None 91 self.merge_ids = [] 92 93 self.top = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) 94 95 frame = Gtk.Frame() 96 hbox = Gtk.Box() 97 hbox.show() 98 frame.add(hbox) 99 frame.show() 100 101 self.select_button = Gtk.ToggleButton() 102 self.select_button.set_relief(Gtk.ReliefStyle.NONE) 103 select_hbox = Gtk.Box() 104 self.title_label = Gtk.Label(label='') 105 arrow = Gtk.Arrow(arrow_type=Gtk.ArrowType.DOWN, 106 shadow_type=Gtk.ShadowType.NONE) 107 select_hbox.pack_start(self.title_label, False, True, 0) 108 select_hbox.pack_end(arrow, False, True, 0) 109 self.select_button.add(select_hbox) 110 111 self.select_button.connect('button_press_event', 112 self.__menu_button_pressed) 113 114 #close_button = Gtk.Button() 115 #img = Gtk.Image.new_from_icon_name('window-close', Gtk.IconSize.MENU) 116 #close_button.set_image(img) 117 #close_button.set_relief(Gtk.ReliefStyle.NONE) 118 #close_button.connect('clicked', self.cb_close_clicked) 119 hbox.pack_start(self.select_button, False, True, 0) 120 #hbox.pack_end(close_button, False, True, 0) 121 122 self.top.pack_end(frame, False, True, 0) 123 124 self.menu = Gtk.Menu() 125 self.menu.show() 126 self.menu.connect('deactivate', cb_menu_deactivate, self.select_button) 127 128 self.notebook = Gtk.Notebook() 129 self.notebook.show() 130 self.notebook.set_show_tabs(False) 131 self.notebook.set_show_border(False) 132 self.notebook.connect('switch_page', self.cb_switch_page) 133 self.top.show() 134 self.top.pack_start(self.notebook, True, True, 0) 135 136 def load_plugins(self, dbstate, uistate): 137 """ 138 Load the sidebar plugins. 139 """ 140 menuitem = ''' 141 <item> 142 <attribute name="action">win.ViewInCatagory</attribute> 143 <attribute name="label" translatable="yes">%s</attribute> 144 <attribute name="target">%d %d</attribute> 145 </item> 146 ''' 147 baritem = ''' 148 <child> 149 <object class="GtkToggleToolButton" id="bar%d"> 150 <property name="action-name">win.ViewInCatagory</property> 151 <property name="action-target">'%d %d'</property> 152 <property name="icon-name">%s</property> 153 <property name="tooltip_text" translatable="yes">%s</property> 154 <property name="label" translatable="yes">%s</property> 155 </object> 156 <packing> 157 <property name="homogeneous">False</property> 158 </packing> 159 </child> 160 ''' 161 162 plugman = GuiPluginManager.get_instance() 163 164 categories = [] 165 views = {} 166 for cat_num, cat_views in enumerate(self.viewmanager.get_views()): 167 uimenuitems = '' 168 uibaritems = '' 169 for view_num, page in enumerate(cat_views): 170 171 if view_num == 0: 172 views[cat_num] = [] 173 cat_name = page[0].category[1] 174 cat_icon = CATEGORY_ICON.get(page[0].category[0]) 175 if cat_icon is None: 176 cat_icon = 'gramps-view' 177 categories.append([cat_num, cat_name, cat_icon]) 178 179 if view_num < 9: 180 accel = "<PRIMARY><ALT>%d" % ((view_num % 9) + 1) 181 self.viewmanager.uimanager.app.set_accels_for_action( 182 "win.ViewInCatagory('%d %d')" % (cat_num, view_num), 183 [accel]) 184 uimenuitems += menuitem % (page[0].name, cat_num, view_num) 185 186 stock_icon = page[0].stock_icon 187 if stock_icon is None: 188 stock_icon = cat_icon 189 uibaritems += baritem % (view_num, cat_num, view_num, 190 stock_icon, page[0].name, 191 page[0].name) 192 193 views[cat_num].append((view_num, page[0].name, stock_icon)) 194 195 if len(cat_views) > 1: 196 #allow for switching views in a category 197 self.ui_category[cat_num] = [UICATEGORY % uimenuitems, 198 UICATAGORYBAR % uibaritems] 199 200 for pdata in plugman.get_reg_sidebars(): 201 module = plugman.load_plugin(pdata) 202 if not module: 203 print("Error loading sidebar '%s': skipping content" 204 % pdata.name) 205 continue 206 207 sidebar_class = getattr(module, pdata.sidebarclass) 208 sidebar_page = sidebar_class(dbstate, uistate, categories, views) 209 self.add(pdata.menu_label, sidebar_page, pdata.order) 210 211 def get_top(self): 212 """ 213 Return the top container widget for the GUI. 214 """ 215 return self.top 216 217 def add(self, title, sidebar, order): 218 """ 219 Add a page to the sidebar for a plugin. 220 """ 221 self.pages.append((title, sidebar)) 222 page = sidebar.get_top() 223 # hide for now so notebook is only size of active page 224 page.get_child().hide() 225 index = self.notebook.append_page(page, Gtk.Label(label=title)) 226 227 menu_item = Gtk.MenuItem(label=title) 228 if order == START: 229 self.menu.prepend(menu_item) 230 self.notebook.set_current_page(index) 231 else: 232 self.menu.append(menu_item) 233 menu_item.connect('activate', self.cb_menu_activate, index) 234 menu_item.show() 235 236 if self.notebook.get_n_pages() == 2: 237 self.select_button.show_all() 238 239 def view_changed(self, cat_num, view_num): 240 """ 241 Called when a Gramps view is changed. 242 """ 243 self.active_cat = cat_num 244 self.active_view = view_num 245 246 # Add buttons to the menu for the different view in the category 247 uimanager = self.viewmanager.uimanager 248 if self.cat_view_group: 249 if self.cat_view_group in uimanager.get_action_groups(): 250 uimanager.remove_action_group(self.cat_view_group) 251 252 list(map(uimanager.remove_ui, self.merge_ids)) 253 254 if cat_num in self.ui_category: 255 action = ('ViewInCatagory', self.cb_view_clicked, '', 256 str(cat_num) + ' ' + str(view_num)) 257 self.cat_view_group = ActionGroup('viewmenu', [action]) 258 uimanager.insert_action_group(self.cat_view_group) 259 mergeid = uimanager.add_ui_from_string(self.ui_category[cat_num]) 260 self.merge_ids.append(mergeid) 261 262 # Call the view_changed method for the active sidebar 263 try: 264 sidebar = self.pages[self.notebook.get_current_page()][1] 265 except IndexError: 266 return 267 sidebar.view_changed(cat_num, view_num) 268 269 def cb_view_clicked(self, radioaction, value): 270 """ 271 Called when a view is selected from the menu. 272 """ 273 cat_num, view_num = value.get_string().split() 274 self.viewmanager.goto_page(int(cat_num), int(view_num)) 275 276 def __menu_button_pressed(self, button, event): 277 """ 278 Called when the button to select a sidebar page is pressed. 279 """ 280 if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS: 281 button.grab_focus() 282 button.set_active(True) 283 284 self.menu.popup(None, None, cb_menu_position, button, event.button, 285 event.time) 286 287 def cb_menu_activate(self, menu, index): 288 """ 289 Called when an item in the popup menu is selected. 290 """ 291 self.notebook.set_current_page(index) 292 293 def cb_switch_page(self, notebook, unused, index): 294 """ 295 Called when the user has switched to a new sidebar plugin page. 296 """ 297 old_page = notebook.get_current_page() 298 if old_page != -1: 299 self.pages[old_page][1].inactive() 300 # hide so notebook is only size of active page 301 notebook.get_nth_page(old_page).get_child().hide() 302 303 self.pages[index][1].active(self.active_cat, self.active_view) 304 notebook.get_nth_page(index).get_child().show() 305 notebook.queue_resize() 306 if self.active_view is not None: 307 self.pages[index][1].view_changed(self.active_cat, 308 self.active_view) 309 self.title_label.set_text(self.pages[index][0]) 310 311 def cb_close_clicked(self, button): 312 """ 313 Called when the sidebar is closed. 314 """ 315 uimanager = self.viewmanager.uimanager 316 uimanager.get_action('/MenuBar/ViewMenu/Navigator').activate() 317 318#------------------------------------------------------------------------- 319# 320# Functions 321# 322#------------------------------------------------------------------------- 323def cb_menu_position(*args): 324 """ 325 Determine the position of the popup menu. 326 """ 327 # takes two argument: menu, button 328 if len(args) == 2: 329 menu = args[0] 330 button = args[1] 331 # broken introspection can't handle MenuPositionFunc annotations corectly 332 else: 333 menu = args[0] 334 button = args[3] 335 ret_val, x_pos, y_pos = button.get_window().get_origin() 336 x_pos += button.get_allocation().x 337 y_pos += button.get_allocation().y + button.get_allocation().height 338 339 return (x_pos, y_pos, False) 340 341def cb_menu_deactivate(menu, button): 342 """ 343 Called when the popup menu disappears. 344 """ 345 button.set_active(False) 346