ManagedWindow

svn: r6249
This commit is contained in:
Don Allingham 2006-04-01 03:59:42 +00:00
parent e45cf453ff
commit 0db23b014a
8 changed files with 363 additions and 330 deletions

View File

@ -1,4 +1,11 @@
2006-03-31 Don Allingham <don@gramps-project.org>
* src/ManagedWindow.py: added
* src/DisplayState.py: break out managed window stuff
* src/Editors/_EditPrimary.py: use ManagedWindow
* src/Editors/_EditSecondary.py: use ManagedWindow
* src/Editors/_EditReference.py: use ManagedWindow
* src/ObjectSelector/_ObjectSelectorWindow.py: use ManagedWindow
* src/PluginUtils/_Plugins.py: use ManagedWindow
* src/GrampsDb/_GrampsDbBase.py: handle saving of custom
attributes and event types for use in menus
* src/Editors/_EditAttribute.py: support for family vs. person

View File

@ -51,9 +51,10 @@ import gtk
import GrampsDb
import Config
import NameDisplay
import Mime
import const
import Errors
import ManagedWindow
DISABLED = -1
#-------------------------------------------------------------------------
#
@ -135,221 +136,6 @@ class History(GrampsDb.GrampsDBCallback):
if not self.at_end():
self.history = self.history[0:self.index+1]
#-------------------------------------------------------------------------
#
# Window manager
#
#-------------------------------------------------------------------------
_win_top = '<ui><menubar name="MenuBar"><menu action="WindowsMenu">'
_win_btm = '</menu></menubar></ui>'
DISABLED = -1
class GrampsWindowManager:
"""
Manage hierarchy of open GRAMPS windows.
This class's purpose is to manage the hierarchy of open windows.
The idea is to maintain the tree of branches and leaves.
A leaf does not have children and corresponds to a single open window.
A branch has children and corresponds to a group of windows.
We will follow the convention of having first leaf in any given
branch represent a parent window of the group, and the rest of the
children leaves/branches represent windows spawned from the parent.
The tree structure is maintained as a list of items.
Items which are lists are branches.
Items which are not lists are leaves.
Lookup of an item is done via track sequence. The elements of
the track sequence specify the lookup order: [2,3,1] means
'take the second item of the tree, take its third child, and
then the first child of that child'.
Lookup can be also done by ID for windows that are identifiable.
"""
def __init__(self,uimanager):
# initialize empty tree and lookup dictionary
self.uimanager = uimanager
self.window_tree = []
self.id2item = {}
self.action_group = gtk.ActionGroup('WindowManger')
self.active = DISABLED
self.ui = _win_top + _win_btm
def disable(self):
"""
Removes the UI and action groups if the navigation is enabled
"""
if self.active != DISABLED:
self.uimanager.remove_ui(self.active)
self.uimanager.remove_action_group(self.action_group)
self.active = DISABLED
def enable(self):
"""
Enables the UI and action groups
"""
self.uimanager.insert_action_group(self.action_group, 1)
self.active = self.uimanager.add_ui_from_string(self.ui)
def get_item_from_track(self,track):
# Recursively find an item given track sequence
item = self.window_tree
for index in track:
item = item[index]
return item
def get_item_from_id(self,item_id):
# Find an item given its ID
# Return None if the ID is not found
return self.id2item.get(item_id,None)
def close_track(self,track):
# This is called when item needs to be closed
# Closes all its children and then removes the item from the tree.
item = self.get_item_from_track(track)
self.recursive_action(item,self.close_item)
# This only needs to be run once for the highest level point
# to remove.
self.remove_item(track)
def recursive_action(self,item,func,*args):
# This function recursively calls itself over the child items
# starting with the given item.
# Eventualy, every non-list item (leaf) will be reached
# and the func(item,*args) will be called on that item.
if type(item) == list:
# If this item is a branch
# close the children except for the first one
for sub_item in item[1:]:
self.recursive_action(sub_item,func,*args)
# return the first child
last_item = item[0]
else:
# This item is a leaf -- no children to close
# return itself
last_item = item
func(last_item,*args)
def close_item(self,item,*args):
# Given an item, close its window and remove it's ID from the dict
if item.window_id:
del self.id2item[item.window_id]
if item.window:
item.window.destroy()
def remove_item(self,track):
# We need the whole gymnastics below because our item
# may actually be a list consisting of a single real
# item and empty lists.
# find the track corresponding to the parent item
parent_track = track[:-1]
# find index of our item in parent
child_in_parent = track[-1:][0]
# obtain parent item and remove our item from it
parent_item = self.get_item_from_track(parent_track)
parent_item.pop(child_in_parent)
# Adjust each item following the removed one
# so that it's track is down by one on this level
for ix in range(child_in_parent,len(parent_item)):
item = parent_item[ix]
self.recursive_action(item,self.move_item_down,len(track)-1)
# Rebuild menu
self.build_windows_menu()
def move_item_down(self,item,*args):
# Given an item and an index, adjust the item's track
# by subtracting 1 from that index's level
index = args[0]
item.track[index] -= 1
def add_item(self,track,item):
# if the item is identifiable then we need to remember
# its id so that in the future we recall this window
# instead of spawning a new one
if item.window_id:
self.id2item[item.window_id] = item
# Make sure we have a track
parent_item = self.get_item_from_track(track)
assert type(parent_item) == list or track == [], \
"Gwm: add_item: Incorrect track."
# Prepare a new item, depending on whether it is branch or leaf
if item.submenu_label:
# This is an item with potential children -- branch
new_item = [item]
else:
# This is an item without children -- leaf
new_item = item
# append new item to the parent
parent_item.append(new_item)
# rebuild the Windows menu based on the new tree
self.build_windows_menu()
# prepare new track corresponding to the added item and return it
new_track = track + [len(parent_item)-1]
return new_track
def call_back_factory(self,item):
if type(item) != list:
def f(obj):
if item.window_id and self.id2item.get(item.window_id):
self.id2item[item.window_id].present()
else:
def f(obj):
pass
return f
def generate_id(self,item):
return str(item.window_id)
def display_menu_list(self,data,action_data,mlist):
i = mlist[0]
idval = self.generate_id(i)
data.write('<menu action="M:%s">' % idval)
data.write('<menuitem action="%s"/>' % idval)
action_data.append(("M:"+idval,None,i.submenu_label,None,None,None))
action_data.append((idval,None,i.menu_label,None,None,
self.call_back_factory(i)))
if len(mlist) > 1:
for i in mlist[1:]:
if type(i) == list:
self.display_menu_list(data,action_data,i)
else:
idval = self.generate_id(i)
data.write('<menuitem action="%s"/>'
% self.generate_id(i))
action_data.append((idval,None,i.menu_label,None,None,
self.call_back_factory(i)))
data.write('</menu>')
def build_windows_menu(self):
if self.active != DISABLED:
self.uimanager.remove_ui(self.active)
self.uimanager.remove_action_group(self.action_group)
self.action_group = gtk.ActionGroup('WindowManger')
action_data = []
data = StringIO()
data.write(_win_top)
for i in self.window_tree:
self.display_menu_list(data,action_data,i)
data.write(_win_btm)
self.ui = data.getvalue()
data.close()
self.action_group.add_actions(action_data)
self.enable()
#-------------------------------------------------------------------------
#
@ -404,7 +190,6 @@ class RecentDocsMenu:
for item in rfiles:
try:
filename = os.path.basename(item.get_path()).replace('_','__')
filetype = Mime.get_type(item.get_path())
action_id = "RecentMenu%d" % count
f.write('<menuitem action="%s"/>' % action_id)
actions.append((action_id,None,filename,None,None,
@ -428,102 +213,6 @@ def make_callback(n,f):
def by_time(a,b):
return cmp(b.get_time(),a.get_time())
#-------------------------------------------------------------------------
#
# Gramps Managed Window class
#
#-------------------------------------------------------------------------
class ManagedWindow:
"""
Managed window base class.
This class provides all the goodies necessary for user-friendly window
management in GRAMPS: registering the menu item under the Windows
menu, keeping track of child windows, closing them on close/delete
event, and presenting itself when selected or attempted to create again.
"""
def __init__(self,uistate,track,obj):
"""
Create child windows and add itself to menu, if not there already.
The usage from derived classes is envisioned as follows:
import DisplayState
class SomeWindowClass(DisplayState.ManagedWindow):
def __init__(self,uistate,dbstate,track):
window_id = self # Or e.g. window_id = person.handle
submenu_label = None # This window cannot have children
menu_label = 'Menu label for this window'
DisplayState.ManagedWindow.__init__(self,
uistate,
track,
window_id,
submenu_label,
menu_label)
# Proceed with the class.
...
"""
window_key = self.build_window_key(obj)
menu_label,submenu_label = self.build_menu_names(obj)
if uistate.gwm.get_item_from_id(window_key):
uistate.gwm.get_item_from_id(window_key).present()
raise Errors.WindowActiveError('This window is already active')
else:
self.window_id = window_key
self.submenu_label = submenu_label
self.menu_label = menu_label
self.uistate = uistate
self.track = self.uistate.gwm.add_item(track,self)
# Work out parent_window
if len(self.track) > 1:
# We don't belong to the lop level
if self.track[-1] > 0:
# If we are not the first in the group,
# then first in that same group is our parent
parent_item_track = self.track[:-1]
parent_item_track.append(0)
else:
# If we're first in the group, then our parent
# is the first in the group one level up
parent_item_track = self.track[:-2]
parent_item_track.append(0)
# Based on the track, get item and then window object
self.parent_window = self.uistate.gwm.get_item_from_track(
parent_item_track).window
else:
# On the top level: we use gramps top window
self.parent_window = self.uistate.window
def build_menu_names(self,obj):
return ('Undefined Menu','Undefined Submenu')
def build_window_key(self,obj):
return id(self)
def show(self):
self.window.set_transient_for(self.parent_window)
self.window.show()
def close(self,obj=None,obj2=None):
"""
Close itself.
Takes care of closing children and removing itself from menu.
"""
self.uistate.gwm.close_track(self.track)
def present(self):
"""
Present window (unroll/unminimize/bring to top).
"""
self.window.present()
from GrampsLogger import RotateHandler
@ -574,7 +263,7 @@ class DisplayState(GrampsDb.GrampsDBCallback):
self.status_id = status.get_context_id('GRAMPS')
self.progress = progress
self.phistory = History()
self.gwm = GrampsWindowManager(uimanager)
self.gwm = ManagedWindow.GrampsWindowManager(uimanager)
self.widget = None
self.warnbtn = warnbtn

View File

@ -22,7 +22,7 @@
from TransUtils import sgettext as _
import DisplayState
import ManagedWindow
import DateHandler
import NameDisplay
import Config
@ -31,7 +31,7 @@ import Utils
from QuestionDialog import SaveDialog
class EditPrimary(DisplayState.ManagedWindow):
class EditPrimary(ManagedWindow.ManagedWindow):
def __init__(self, state, uistate, track, obj, get_from_handle, callback=None):
"""Creates an edit window. Associates a person with the window."""
@ -47,7 +47,7 @@ class EditPrimary(DisplayState.ManagedWindow):
self.signal_keys = []
self.get_from_handle = get_from_handle
DisplayState.ManagedWindow.__init__(self, uistate, track, obj)
ManagedWindow.ManagedWindow.__init__(self, uistate, track, obj)
self._local_init()

View File

@ -48,7 +48,7 @@ import gtk.glade
import const
import Utils
import RelLib
import DisplayState
import ManagedWindow
from QuestionDialog import WarningDialog, ErrorDialog
from DisplayTabs import *
@ -59,7 +59,7 @@ from GrampsWidgets import *
# EditReference class
#
#-------------------------------------------------------------------------
class EditReference(DisplayState.ManagedWindow):
class EditReference(ManagedWindow.ManagedWindow):
def __init__(self, state, uistate, track, source, source_ref, update):
self.db = state.db
self.dbstate = state
@ -71,7 +71,7 @@ class EditReference(DisplayState.ManagedWindow):
self.signal_keys = []
self.warn_box = None
DisplayState.ManagedWindow.__init__(self, uistate, track, source_ref)
ManagedWindow.ManagedWindow.__init__(self, uistate, track, source_ref)
self._local_init()
self._setup_warnbox()

View File

@ -22,12 +22,12 @@
from TransUtils import sgettext as _
import DisplayState
import ManagedWindow
import Config
import GrampsDisplay
import Utils
class EditSecondary(DisplayState.ManagedWindow):
class EditSecondary(ManagedWindow.ManagedWindow):
def __init__(self, state, uistate, track, obj, callback=None):
"""Creates an edit window. Associates a person with the window."""
@ -39,7 +39,7 @@ class EditSecondary(DisplayState.ManagedWindow):
self.callback = callback
self.signal_keys = []
DisplayState.ManagedWindow.__init__(self, uistate, track, obj)
ManagedWindow.ManagedWindow.__init__(self, uistate, track, obj)
self._local_init()

View File

@ -0,0 +1,337 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006 Donald N. Allingham
#
# 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: DisplayState.py 6085 2006-03-05 23:39:20Z dallingham $
import Errors
import gtk
from cStringIO import StringIO
#-------------------------------------------------------------------------
#
# Window manager
#
#-------------------------------------------------------------------------
_win_top = '<ui><menubar name="MenuBar"><menu action="WindowsMenu">'
_win_btm = '</menu></menubar></ui>'
DISABLED = -1
class GrampsWindowManager:
"""
Manage hierarchy of open GRAMPS windows.
This class's purpose is to manage the hierarchy of open windows.
The idea is to maintain the tree of branches and leaves.
A leaf does not have children and corresponds to a single open window.
A branch has children and corresponds to a group of windows.
We will follow the convention of having first leaf in any given
branch represent a parent window of the group, and the rest of the
children leaves/branches represent windows spawned from the parent.
The tree structure is maintained as a list of items.
Items which are lists are branches.
Items which are not lists are leaves.
Lookup of an item is done via track sequence. The elements of
the track sequence specify the lookup order: [2,3,1] means
'take the second item of the tree, take its third child, and
then the first child of that child'.
Lookup can be also done by ID for windows that are identifiable.
"""
def __init__(self,uimanager):
# initialize empty tree and lookup dictionary
self.uimanager = uimanager
self.window_tree = []
self.id2item = {}
self.action_group = gtk.ActionGroup('WindowManger')
self.active = DISABLED
self.ui = _win_top + _win_btm
def disable(self):
"""
Removes the UI and action groups if the navigation is enabled
"""
if self.active != DISABLED:
self.uimanager.remove_ui(self.active)
self.uimanager.remove_action_group(self.action_group)
self.active = DISABLED
def enable(self):
"""
Enables the UI and action groups
"""
self.uimanager.insert_action_group(self.action_group, 1)
self.active = self.uimanager.add_ui_from_string(self.ui)
def get_item_from_track(self,track):
# Recursively find an item given track sequence
item = self.window_tree
for index in track:
item = item[index]
return item
def get_item_from_id(self,item_id):
# Find an item given its ID
# Return None if the ID is not found
return self.id2item.get(item_id,None)
def close_track(self,track):
# This is called when item needs to be closed
# Closes all its children and then removes the item from the tree.
item = self.get_item_from_track(track)
self.recursive_action(item,self.close_item)
# This only needs to be run once for the highest level point
# to remove.
self.remove_item(track)
def recursive_action(self,item,func,*args):
# This function recursively calls itself over the child items
# starting with the given item.
# Eventualy, every non-list item (leaf) will be reached
# and the func(item,*args) will be called on that item.
if type(item) == list:
# If this item is a branch
# close the children except for the first one
for sub_item in item[1:]:
self.recursive_action(sub_item,func,*args)
# return the first child
last_item = item[0]
else:
# This item is a leaf -- no children to close
# return itself
last_item = item
func(last_item,*args)
def close_item(self,item,*args):
# Given an item, close its window and remove it's ID from the dict
if item.window_id:
del self.id2item[item.window_id]
if item.window:
item.window.destroy()
def remove_item(self,track):
# We need the whole gymnastics below because our item
# may actually be a list consisting of a single real
# item and empty lists.
# find the track corresponding to the parent item
parent_track = track[:-1]
# find index of our item in parent
child_in_parent = track[-1:][0]
# obtain parent item and remove our item from it
parent_item = self.get_item_from_track(parent_track)
parent_item.pop(child_in_parent)
# Adjust each item following the removed one
# so that it's track is down by one on this level
for ix in range(child_in_parent,len(parent_item)):
item = parent_item[ix]
self.recursive_action(item,self.move_item_down,len(track)-1)
# Rebuild menu
self.build_windows_menu()
def move_item_down(self,item,*args):
# Given an item and an index, adjust the item's track
# by subtracting 1 from that index's level
index = args[0]
item.track[index] -= 1
def add_item(self,track,item):
# if the item is identifiable then we need to remember
# its id so that in the future we recall this window
# instead of spawning a new one
if item.window_id:
self.id2item[item.window_id] = item
# Make sure we have a track
parent_item = self.get_item_from_track(track)
assert type(parent_item) == list or track == [], \
"Gwm: add_item: Incorrect track."
# Prepare a new item, depending on whether it is branch or leaf
if item.submenu_label:
# This is an item with potential children -- branch
new_item = [item]
else:
# This is an item without children -- leaf
new_item = item
# append new item to the parent
parent_item.append(new_item)
# rebuild the Windows menu based on the new tree
self.build_windows_menu()
# prepare new track corresponding to the added item and return it
new_track = track + [len(parent_item)-1]
return new_track
def call_back_factory(self,item):
if type(item) != list:
def f(obj):
if item.window_id and self.id2item.get(item.window_id):
self.id2item[item.window_id].present()
else:
def f(obj):
pass
return f
def generate_id(self,item):
return str(item.window_id)
def display_menu_list(self,data,action_data,mlist):
i = mlist[0]
idval = self.generate_id(i)
data.write('<menu action="M:%s">' % idval)
data.write('<menuitem action="%s"/>' % idval)
action_data.append(("M:"+idval,None,i.submenu_label,None,None,None))
action_data.append((idval,None,i.menu_label,None,None,
self.call_back_factory(i)))
if len(mlist) > 1:
for i in mlist[1:]:
if type(i) == list:
self.display_menu_list(data,action_data,i)
else:
idval = self.generate_id(i)
data.write('<menuitem action="%s"/>'
% self.generate_id(i))
action_data.append((idval,None,i.menu_label,None,None,
self.call_back_factory(i)))
data.write('</menu>')
def build_windows_menu(self):
if self.active != DISABLED:
self.uimanager.remove_ui(self.active)
self.uimanager.remove_action_group(self.action_group)
self.action_group = gtk.ActionGroup('WindowManger')
action_data = []
data = StringIO()
data.write(_win_top)
for i in self.window_tree:
self.display_menu_list(data,action_data,i)
data.write(_win_btm)
self.ui = data.getvalue()
data.close()
self.action_group.add_actions(action_data)
self.enable()
#-------------------------------------------------------------------------
#
# Gramps Managed Window class
#
#-------------------------------------------------------------------------
class ManagedWindow:
"""
Managed window base class.
This class provides all the goodies necessary for user-friendly window
management in GRAMPS: registering the menu item under the Windows
menu, keeping track of child windows, closing them on close/delete
event, and presenting itself when selected or attempted to create again.
"""
def __init__(self,uistate,track,obj):
"""
Create child windows and add itself to menu, if not there already.
The usage from derived classes is envisioned as follows:
import ManagedWindow
class SomeWindowClass(ManagedWindow.ManagedWindow):
def __init__(self,uistate,dbstate,track):
window_id = self # Or e.g. window_id = person.handle
submenu_label = None # This window cannot have children
menu_label = 'Menu label for this window'
ManagedWindow.ManagedWindow.__init__(self,
uistate,
track,
window_id,
submenu_label,
menu_label)
# Proceed with the class.
...
"""
window_key = self.build_window_key(obj)
menu_label,submenu_label = self.build_menu_names(obj)
if uistate.gwm.get_item_from_id(window_key):
uistate.gwm.get_item_from_id(window_key).present()
raise Errors.WindowActiveError('This window is already active')
else:
self.window_id = window_key
self.submenu_label = submenu_label
self.menu_label = menu_label
self.uistate = uistate
self.track = self.uistate.gwm.add_item(track,self)
# Work out parent_window
if len(self.track) > 1:
# We don't belong to the lop level
if self.track[-1] > 0:
# If we are not the first in the group,
# then first in that same group is our parent
parent_item_track = self.track[:-1]
parent_item_track.append(0)
else:
# If we're first in the group, then our parent
# is the first in the group one level up
parent_item_track = self.track[:-2]
parent_item_track.append(0)
# Based on the track, get item and then window object
self.parent_window = self.uistate.gwm.get_item_from_track(
parent_item_track).window
else:
# On the top level: we use gramps top window
self.parent_window = self.uistate.window
def build_menu_names(self,obj):
return ('Undefined Menu','Undefined Submenu')
def build_window_key(self,obj):
return id(self)
def show(self):
self.window.set_transient_for(self.parent_window)
self.window.show()
def close(self,obj=None,obj2=None):
"""
Close itself.
Takes care of closing children and removing itself from menu.
"""
self.uistate.gwm.close_track(self.track)
def present(self):
"""
Present window (unroll/unminimize/bring to top).
"""
self.window.present()

View File

@ -34,7 +34,7 @@ from TransUtils import sgettext as _
import _Factories
from _Constants import ObjectTypes
from DisplayState import ManagedWindow
from ManagedWindow import ManagedWindow
import const
class _ObjectTypeWidgets(object):

View File

@ -61,7 +61,7 @@ import _Report
import _Tool
import _PluginMgr
import GrampsDisplay
import DisplayState
import ManagedWindow
#-------------------------------------------------------------------------
#
@ -78,7 +78,7 @@ UNSUPPORTED = _("Unsupported")
#
#-------------------------------------------------------------------------
class PluginDialog(DisplayState.ManagedWindow):
class PluginDialog(ManagedWindow.ManagedWindow):
"""Displays the dialog box that allows the user to select the
report that is desired."""
@ -93,7 +93,7 @@ class PluginDialog(DisplayState.ManagedWindow):
self.msg = msg
self.content = content
DisplayState.ManagedWindow.__init__(self, uistate, [], None)
ManagedWindow.ManagedWindow.__init__(self, uistate, [], None)
self.state = state
self.uistate = uistate
@ -303,13 +303,13 @@ class ToolPlugins(PluginDialog):
#
#-------------------------------------------------------------------------
class PluginStatus(DisplayState.ManagedWindow):
class PluginStatus(ManagedWindow.ManagedWindow):
"""Displays a dialog showing the status of loaded plugins"""
def __init__(self,state,uistate,track):
import cStringIO
DisplayState.ManagedWindow.__init__(self, uistate, [], None)
ManagedWindow.ManagedWindow.__init__(self, uistate, [], None)
self.state = state
self.uistate = uistate