Fix for: 1277: database corroption on delete outside of DisplayTabs while tab open

Introduces the concept of callman.py as one single way to follow handles an
interface is interested in. 
dbguielement.py contains a small base class using that, usable for all windows/
guielements that need to track database changes to handles


svn: r12881
This commit is contained in:
Benny Malengier 2009-08-05 10:32:05 +00:00
parent f34d4656a7
commit ee69317b62
30 changed files with 1048 additions and 107 deletions

View File

@ -56,11 +56,10 @@ src/cli/grampscli.py
src/gen/__init__.py
# gen utils API
src/gen/utils/dbutils.py
src/gen/utils/progressmon.py
src/gen/utils/__init__.py
src/gen/utils/dbutils.py
src/gen/utils/callback.py
src/gen/utils/callman.py
src/gen/utils/dbutils.py
src/gen/utils/longop.py
src/gen/utils/progressmon.py
@ -182,6 +181,7 @@ src/gen/plug/docbackend/docbackend.py
# gui - GUI code
src/gui/__init__.py
src/gui/dbguielement.py
src/gui/dbloader.py
src/gui/dbman.py
src/gui/grampsgui.py

View File

@ -495,7 +495,8 @@ class EmbeddedList(ButtonTab):
"""
The view must be remade when data changes outside this tab.
Use this method to connect to after a db change. It makes sure the
data is obtained again from db and the view rebuild
data is obtained again from the present object and the db what is not
present in the obj, and the view rebuild
"""
self.changed = True
self.rebuild()

View File

@ -39,13 +39,14 @@ import Errors
from DdTargets import DdTargets
from _GroupEmbeddedList import GroupEmbeddedList
from _EventRefModel import EventRefModel
from gui.dbguielement import DbGUIElement
#-------------------------------------------------------------------------
#
# EventEmbedList
#
#-------------------------------------------------------------------------
class EventEmbedList(GroupEmbeddedList):
class EventEmbedList(DbGUIElement, GroupEmbeddedList):
_HANDLE_COL = 7
_DND_TYPE = DdTargets.EVENTREF
@ -86,9 +87,55 @@ class EventEmbedList(GroupEmbeddedList):
self.obj = obj
self._groups = []
self._data = []
DbGUIElement.__init__(self, dbstate.db)
GroupEmbeddedList.__init__(self, dbstate, uistate, track, _('_Events'),
build_model, share_button=True,
move_buttons=True)
def _connect_db_signals(self):
"""
called on init of DbGUIElement, connect to db as required.
"""
#note: event-rebuild closes the editors, so no need to connect to it
self.callman.register_callbacks(
{'event-update': self.event_change, #change to an event we track
'event-delete': self.event_delete, #delete of event we track
})
self.callman.connect_all(keys=['event'])
def event_change(self, *obj):
"""
Callback method called when a tracked event changes (description
changes, source added, ...)
Note that adding an event
"""
self.rebuild_callback()
def event_delete(self, obj):
"""
Callback method called when a tracked event is deleted.
There are two possibilities:
* a tracked non-workgroup event is deleted, just rebuilding the view
will correct this.
* a workgroup event is deleted. The event must be removed from the obj
so that no inconsistent data is shown.
"""
for handle in obj:
refs = self.get_data()[self._WORKGROUP]
ref_list = [eref.ref for eref in refs]
indexlist = []
last = 0
while True:
try:
last = ref_list.index(handle)
indexlist.append(last)
except ValueError:
break
#remove the deleted workgroup events from the object
for index in indexlist.reverse():
del refs[index]
#now rebuild the display tab
self.rebuild_callback()
def get_ref_editor(self):
from Editors import EditFamilyEventRef
@ -118,6 +165,10 @@ class EventEmbedList(GroupEmbeddedList):
if mdata:
self._groups.append((mhandle, self._MOTHNAME))
self._data.append(mdata)
#we register all events that need to be tracked
for group in self._data:
self.callman.register_handles(
{'event': [eref.ref for eref in group]})
self.changed = False
return self._data
@ -195,10 +246,17 @@ class EventEmbedList(GroupEmbeddedList):
def object_added(self, reference, primary):
reference.ref = primary.handle
self.get_data()[self._WORKGROUP].append(reference)
self.callman.register_handles({'event': [primary.handle]})
self.changed = True
self.rebuild()
def object_edited(self, ref, event):
"""
Called as callback after eventref has been edited.
Note that if the event changes too (so not only the ref data), then
an event-update signal from the database will also be raised, and the
rebuild done here will not be needed. There is no way to avoid this ...
"""
self.changed = True
self.rebuild()

View File

@ -46,6 +46,7 @@ import gobject
#
#-------------------------------------------------------------------------
from gui.utils import open_file_with_default_application
from gui.dbguielement import DbGUIElement
import gen.lib
import Utils
import ThumbNails
@ -67,7 +68,7 @@ def make_launcher(path):
# GalleryTab
#
#-------------------------------------------------------------------------
class GalleryTab(ButtonTab):
class GalleryTab(ButtonTab, DbGUIElement):
_DND_TYPE = DdTargets.MEDIAREF
_DND_EXTRA = DdTargets.URI_LIST
@ -75,8 +76,11 @@ class GalleryTab(ButtonTab):
def __init__(self, dbstate, uistate, track, media_list, update=None):
self.iconlist = gtk.IconView()
ButtonTab.__init__(self, dbstate, uistate, track, _('_Gallery'), True)
DbGUIElement.__init__(self, dbstate.db)
self.track_ref_for_deletion("iconlist")
self.media_list = media_list
self.callman.register_handles({'media': [mref.ref for mref
in self.media_list]})
self.update = update
self._set_dnd()
@ -84,11 +88,16 @@ class GalleryTab(ButtonTab):
self.rebuild()
self.show_all()
def connect_db_signals(self):
#connect external remove/change of object to rebuild of grampstab
self._add_db_signal('media-delete', self.media_delete)
self._add_db_signal('media-rebuild', self.rebuild)
self._add_db_signal('media-update', self.media_update)
def _connect_db_signals(self):
"""
Implement base class DbGUIElement method
"""
#note: media-rebuild closes the editors, so no need to connect to it
self.callman.register_callbacks(
{'media-delete': self.media_delete, # delete a mediaobj we track
'media-update': self.media_update, # change a mediaobj we track
})
self.callman.connect_all(keys=['media'])
def double_click(self, obj, event):
"""
@ -259,12 +268,13 @@ class GalleryTab(ButtonTab):
def add_callback(self, media_ref, media):
media_ref.ref = media.handle
self.get_data().append(media_ref)
self.callman.register_handles({'media': [media.handle]})
self.changed = True
self.rebuild()
def share_button_clicked(self, obj):
"""
Function called when the Add button is clicked.
Function called when the Share button is clicked.
This function should be overridden by the derived class.

View File

@ -73,8 +73,6 @@ class GrampsTab(gtk.VBox):
self.changed = False
self.__refs_for_deletion = []
self._add_db_signal = None
# save name used for notebook label, and build the widget used
# for the label
@ -168,19 +166,6 @@ class GrampsTab(gtk.VBox):
return
return True
def add_db_signal_callback(self, add_db_signal):
"""
The grampstab must be able to react to database signals, however
on destroy of the editor to which the tab is attached, these signals
must be disconnected.
This method sets the method with which to add database signals on tabs,
typically EditPrimary and EditSecondary add tabs, and have methods to
connect signals and register them so they are correctly disconnected
on close
"""
self._add_db_signal = add_db_signal
self.connect_db_signals()
def _set_label(self, show_image=True):
"""
Updates the label based of if the tab contains information. Tabs
@ -208,14 +193,6 @@ class GrampsTab(gtk.VBox):
can be used to add widgets to the interface.
"""
pass
def connect_db_signals(self):
"""
Function to connect db signals to GrampsTab methods. This function
should be overridden in the derived class.
It is called after the interface is build.
"""
pass
def set_parent_notebook(self, book):
self.parent_notebook = book

View File

@ -40,6 +40,7 @@ from gettext import gettext as _
#-------------------------------------------------------------------------
import Errors
import gen.lib
from gui.dbguielement import DbGUIElement
from _NoteModel import NoteModel
from _EmbeddedList import EmbeddedList
from DdTargets import DdTargets
@ -49,7 +50,7 @@ from DdTargets import DdTargets
# NoteTab
#
#-------------------------------------------------------------------------
class NoteTab(EmbeddedList):
class NoteTab(EmbeddedList, DbGUIElement):
"""
Note List display tab for edit dialogs.
@ -83,12 +84,19 @@ class NoteTab(EmbeddedList):
EmbeddedList.__init__(self, dbstate, uistate, track,
_("_Notes"), NoteModel, share_button=True,
move_buttons=True)
DbGUIElement.__init__(self, dbstate.db)
self.callman.register_handles({'note': self.data})
def connect_db_signals(self):
#connect external remove/change of object to rebuild of grampstab
self._add_db_signal('note-delete', self.note_delete)
self._add_db_signal('note-rebuild', self.rebuild)
self._add_db_signal('note-update',self.note_update)
def _connect_db_signals(self):
"""
Implement base class DbGUIElement method
"""
#note: note-rebuild closes the editors, so no need to connect to it
self.callman.register_callbacks(
{'note-delete': self.note_delete, # delete a note we track
'note-update': self.note_update, # change a note we track
})
self.callman.connect_all(keys=['note'])
def get_editor(self):
pass
@ -133,6 +141,7 @@ class NoteTab(EmbeddedList):
Called to update the screen when a new note is added
"""
self.get_data().append(name)
self.callman.register_handles({'note': [name]})
self.changed = True
self.rebuild()

View File

@ -33,6 +33,7 @@ from gettext import gettext as _
#
#-------------------------------------------------------------------------
import gen.lib
from gui.dbguielement import DbGUIElement
import Errors
from DdTargets import DdTargets
from _RepoRefModel import RepoRefModel
@ -43,7 +44,7 @@ from _EmbeddedList import EmbeddedList
# RepoEmbedList
#
#-------------------------------------------------------------------------
class RepoEmbedList(EmbeddedList):
class RepoEmbedList(EmbeddedList, DbGUIElement):
_HANDLE_COL = 4
_DND_TYPE = DdTargets.REPOREF
@ -72,6 +73,20 @@ class RepoEmbedList(EmbeddedList):
EmbeddedList.__init__(self, dbstate, uistate, track,
_('_Repositories'), RepoRefModel,
share_button=True, move_buttons=True)
DbGUIElement.__init__(self, dbstate.db)
self.callman.register_handles({'repository': [rref.ref for rref
in self.obj]})
def _connect_db_signals(self):
"""
Implement base class DbGUIElement method
"""
#note: repository-rebuild closes the editors, so no need to connect
self.callman.register_callbacks(
{'repository-delete': self.repo_delete, # delete a repo we track
'repository-update': self.repo_update, # change a repo we track
})
self.callman.connect_all(keys=['repository'])
def get_icon_name(self):
return 'gramps-repository'
@ -135,6 +150,7 @@ class RepoEmbedList(EmbeddedList):
def add_callback(self, value):
value[0].ref = value[1].handle
self.get_data().append(value[0])
self.callman.register_handles({'repository': [value[1].handle]})
self.changed = True
self.rebuild()
@ -164,3 +180,39 @@ class RepoEmbedList(EmbeddedList):
def edit_callback(self, name):
self.changed = True
self.rebuild()
def repo_delete(self, del_repo_handle_list):
"""
Outside of this tab repo objects have been deleted. Check if tab
and object must be changed.
Note: delete of object will cause reference on database to be removed,
so this method need not do this
"""
rebuild = False
ref_handles = [rref.ref for rref in self.obj]
for handle in del_repo_handle_list :
while 1:
pos = None
try :
pos = ref_handles.index(handle)
except ValueError :
break
if pos is not None:
#oeps, we need to remove this reference, and rebuild tab
del self.obj[pos]
del ref_handles[pos]
rebuild = True
if rebuild:
self.rebuild()
def repo_update(self, upd_repo_handle_list):
"""
Outside of this tab repo objects have been changed. Check if tab
and object must be changed.
"""
ref_handles = [rref.ref for rref in self.obj]
for handle in upd_repo_handle_list :
if handle in ref_handles:
self.rebuild()
break

View File

@ -33,6 +33,7 @@ from gettext import gettext as _
#
#-------------------------------------------------------------------------
import gen.lib
from gui.dbguielement import DbGUIElement
import Errors
from DdTargets import DdTargets
from _SourceRefModel import SourceRefModel
@ -43,7 +44,7 @@ from _EmbeddedList import EmbeddedList
# SourceEmbedList
#
#-------------------------------------------------------------------------
class SourceEmbedList(EmbeddedList):
class SourceEmbedList(EmbeddedList, DbGUIElement):
_HANDLE_COL = 4
_DND_TYPE = DdTargets.SOURCEREF
@ -72,6 +73,20 @@ class SourceEmbedList(EmbeddedList):
EmbeddedList.__init__(self, dbstate, uistate, track, _('_Sources'),
SourceRefModel, share_button=True,
move_buttons=True)
DbGUIElement.__init__(self, dbstate.db)
self.callman.register_handles({'source': [sref.ref for sref
in self.obj.get_source_references()]})
def _connect_db_signals(self):
"""
Implement base class DbGUIElement method
"""
#note: source-rebuild closes the editors, so no need to connect to it
self.callman.register_callbacks(
{'source-delete': self.source_delete, # delete a source we track
'source-update': self.source_update, # change a source we track
})
self.callman.connect_all(keys=['source'])
def get_icon_name(self):
return 'gramps-source'
@ -141,12 +156,26 @@ class SourceEmbedList(EmbeddedList):
)
def object_added(self, reference, primary):
"""
Callback from sourceref editor after adding a new reference (to a new
or an existing source).
Note that if it was to an existing source already present in the
sourcelist, then the source-update signal will also cause a rebuild
at that time.
"""
reference.ref = primary.handle
self.get_data().append(reference)
self.callman.register_handles({'source': [primary.handle]})
self.changed = True
self.rebuild()
def object_edited(self, refererence, primary):
"""
Callback from sourceref editor. If the source changes itself, also
the source-change signal will cause a rebuild.
This could be solved in the source editor if it only calls this
method in the case the sourceref part only changes.
"""
self.changed = True
self.rebuild()
@ -160,3 +189,40 @@ class SourceEmbedList(EmbeddedList):
src, sref, self.object_added)
except Errors.WindowActiveError:
pass
def source_delete(self, del_src_handle_list):
"""
Outside of this tab source objects have been deleted. Check if tab
and object must be changed.
Note: delete of object will cause reference on database to be removed,
so this method need not do this
"""
rebuild = False
sourceref_list = self.get_data()
ref_handles = [sref.ref for sref in sourceref_list]
for handle in del_src_handle_list :
while 1:
pos = None
try :
pos = ref_handles.index(handle)
except ValueError :
break
if pos is not None:
#oeps, we need to remove this reference, and rebuild tab
del sourceref_list[pos]
del ref_handles[pos]
rebuild = True
if rebuild:
self.rebuild()
def source_update(self, upd_src_handle_list):
"""
Outside of this tab media objects have been changed. Check if tab
and object must be changed.
"""
ref_handles = [sref.ref for sref in self.get_data()]
for handle in upd_src_handle_list :
if handle in ref_handles:
self.rebuild()
break

View File

@ -129,7 +129,15 @@ class EditChildRef(EditSecondary):
self.define_ok_button(self.ok_button, self.save)
self.edit_button.connect('button-press-event', self.edit_child)
self.edit_button.connect('key-press-event', self.edit_child)
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('person-update', self.person_change)
self._add_db_signal('person-rebuild', self.close)
self._add_db_signal('person-delete', self.check_for_close)
def _create_tabbed_pages(self):
"""
@ -185,6 +193,15 @@ class EditChildRef(EditSecondary):
self.callback(self.obj)
self.close()
def check_for_close(self, handles):
"""
Callback method for delete signals.
If there is a delete signal of the primary object we are editing, the
editor (and all child windows spawned) should be closed
"""
if self.obj.ref in handles:
self.close()
def button_activated(event, mouse_button):
if (event.type == gtk.gdk.BUTTON_PRESS and \
event.button == mouse_button) or \

View File

@ -116,6 +116,14 @@ class EditEvent(EditPrimary):
self.ok_button.set_sensitive(not self.db.readonly)
self.ok_button.connect('clicked', self.save)
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('event-rebuild', self._do_close)
self._add_db_signal('event-delete', self.check_for_close)
def _setup_fields(self):
# place, select_place, add_del_place

View File

@ -96,6 +96,14 @@ class EditEventRef(EditReference):
# FIXME: activate when help page is available
#self.define_help_button(self.top.get_object('help'))
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('event-rebuild', self.close)
self._add_db_signal('event-delete', self.check_for_close)
def _setup_fields(self):
self.ref_privacy = PrivacyButton(

View File

@ -445,47 +445,92 @@ class EditFamily(EditPrimary):
def _local_init(self):
self.build_interface()
self._add_db_signal('family-update', self.check_for_family_change)
self._add_db_signal('family-delete', self.check_for_close)
# Add a signal pick up changes to events, bug #1329
self._add_db_signal('event-update', self.event_updated)
self.added = self.obj.handle is None
if self.added:
self.obj.handle = Utils.create_id()
self.load_data()
def check_for_close(self, handles):
if self.obj.get_handle() in handles:
self._do_close()
def _connect_db_signals(self):
"""
implement from base class DbGUIElement
Register the callbacks we need.
Note:
* we do not connect to person-delete, as a delete of a person in
the family outside of this editor will cause a family-update
signal of this family
"""
self.callman.register_handles({'family': [self.obj.get_handle()]})
self.callman.register_callbacks(
{'family-update': self.check_for_family_change,
'family-delete': self.check_for_close,
'family-rebuild': self._do_close,
'event-update': self.topdata_updated, # change eg birth event fath
'event-rebuild': self.topdata_updated,
'event-delete': self.topdata_updated, # delete eg birth event fath
'person-update': self.topdata_updated, # change eg name of father
'person-rebuild': self._do_close,
})
self.callman.connect_all(keys=['family', 'event', 'person'])
def check_for_family_change(self, handles):
# check to see if the handle matches the current object
"""
Callback for family-update signal
1. This method checks to see if the family shown has been changed. This
is possible eg in the relationship view. If the family was changed,
the view is refreshed and a warning dialog shown to indicate all
changes have been lost.
If a source/note/event is deleted, this method is called too. This
is unfortunate as the displaytabs can track themself a delete and
correct the view for this. Therefore, these tabs are not rebuild.
Conclusion: this method updates so that remove/change of parent or
remove/change of children in relationship view reloads the family
from db.
2. Changes in other families are of no consequence to the family shown
"""
if self.obj.get_handle() in handles:
#rebuild data
## Todo: Gallery and note tab are not rebuild ??
objreal = self.dbstate.db.get_family_from_handle(
self.obj.get_handle())
#update selection of data that we obtain from database change:
maindatachanged = (self.obj.gramps_id != objreal.gramps_id or
self.obj.father_handle != objreal.father_handle or
self.obj.mother_handle != objreal.mother_handle or
self.obj.private != objreal.private or
self.obj.type != objreal.type or
self.obj.marker != objreal.marker or
self.obj.child_ref_list != objreal.child_ref_list)
if maindatachanged:
self.obj.gramps_id = objreal.gramps_id
self.obj.father_handle = objreal.father_handle
self.obj.mother_handle = objreal.mother_handle
self.obj.private = objreal.private
self.obj.type = objreal.type
self.obj.marker = objreal.marker
self.obj.child_ref_list = objreal.child_ref_list
self.reload_people()
self.obj = self.dbstate.db.get_family_from_handle(self.obj.get_handle())
self.reload_people()
self.event_list.rebuild()
self.source_list.rebuild()
self.attr_list.data = self.obj.get_attribute_list()
self.attr_list.rebuild()
self.lds_embed.data = self.obj.get_lds_ord_list()
self.lds_embed.rebuild()
# No matter why the family changed (eg delete of a source), we notify
# the user
WarningDialog(
_("Family has changed"),
_("The family you are editing has changed. To make sure that the "
"database is not corrupted, GRAMPS has updated the family to "
"reflect these changes. Any edits you have made may have been lost."))
_("Family has changed"),
_("The %(object)s you are editing has changed outside this editor."
" This can be due to a change in one of the main views, for "
"example a source used here is deleted in the source view.\n"
"To make sure the information shown is still correct, the "
"data shown has been updated. Some edits you have made may have"
" been lost.") % {'object': _('family')}, parent=self.window)
def event_updated(self, obj):
def topdata_updated(self, *obj):
"""
Callback method called if data shown in top part of family editor
(a parent, birth/death event of parent) changes
Note: person events shown in the event list are not tracked, the
tabpage itself tracks it
"""
self.load_data()
#place in event might have changed, or person event shown in the list
self.event_list.rebuild_callback()
def show_buttons(self):
"""
@ -617,6 +662,10 @@ class EditFamily(EditPrimary):
)
def load_data(self):
"""
Show top data of family editor: father and mother info
and set self.phandles with all person handles in the family
"""
fhandle = self.obj.get_father_handle()
self.update_father(fhandle)
@ -839,7 +888,8 @@ class EditFamily(EditPrimary):
'in the database. If you save, you will create '
'a duplicate family. It is recommended that '
'you cancel the editing of this window, and '
'select the existing family'))
'select the existing family'),
parent=self.window)
def edit_father(self, obj, event):
handle = self.obj.get_father_handle()
@ -871,10 +921,17 @@ class EditFamily(EditPrimary):
name = "%s [%s]" % (name_displayer.display(person),
person.gramps_id)
birth = ReportUtils.get_birth_or_fallback(db, person)
self.callman.register_handles({'person': [handle]})
if birth:
#if event changes it view needs to update
self.callman.register_handles({'event': [birth.get_handle()]})
if birth and birth.get_type() == gen.lib.EventType.BAPTISM:
birth_label.set_label(_("Baptism:"))
death = ReportUtils.get_death_or_fallback(db, person)
if death:
#if event changes it view needs to update
self.callman.register_handles({'event': [death.get_handle()]})
if death and death.get_type() == gen.lib.EventType.BURIAL:
death_label.set_label(_("Burial:"))
@ -985,9 +1042,7 @@ class EditFamily(EditPrimary):
# We disconnect the callbacks to all signals we connected earlier.
# This prevents the signals originating in any of the following
# commits from being caught by us again.
for key in self.signal_keys:
self.db.disconnect(key)
self.signal_keys = []
self._cleanup_callbacks()
if not original and not self.object_is_empty():
trans = self.db.transaction_begin()

View File

@ -103,6 +103,14 @@ class EditMedia(EditPrimary):
self.define_ok_button(self.glade.get_object('ok'), self.save)
self.define_help_button(self.glade.get_object('button102'))
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('media-rebuild', self._do_close)
self._add_db_signal('media-delete', self.check_for_close)
def _setup_fields(self):
self.date_field = MonitoredDate(self.glade.get_object("date_entry"),
self.glade.get_object("date_edit"),

View File

@ -475,6 +475,14 @@ class EditMediaRef(EditReference):
self.define_cancel_button(self.top.get_object('button84'))
self.define_ok_button(self.top.get_object('button82'),self.save)
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('media-rebuild', self.close)
self._add_db_signal('media-delete', self.check_for_close)
def _create_tabbed_pages(self):
"""
Create the notebook tabs and inserts them into the main

View File

@ -226,7 +226,15 @@ class EditNote(EditPrimary):
self.define_ok_button(self.top.get_object('ok'), self.save)
self.define_cancel_button(self.top.get_object('cancel'))
self.define_help_button(self.top.get_object('help'))
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('note-rebuild', self._do_close)
self._add_db_signal('note-delete', self.check_for_close)
def _create_tabbed_pages(self):
"""Create the notebook tabs and inserts them into the main window."""
notebook = self.top.get_object("note_notebook")

View File

@ -167,9 +167,7 @@ class EditPerson(EditPrimary):
def _connect_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self.define_cancel_button(self.top.get_object("button15"))
self.define_ok_button(self.top.get_object("ok"), self.save)
@ -182,6 +180,13 @@ class EditPerson(EditPrimary):
self.eventbox.connect('button-press-event',
self._image_button_press)
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('person-rebuild', self._do_close)
self._add_db_signal('person-delete', self.check_for_close)
self._add_db_signal('family-rebuild', self.family_change)
self._add_db_signal('family-delete', self.family_change)
self._add_db_signal('family-update', self.family_change)
@ -409,7 +414,6 @@ class EditPerson(EditPrimary):
notebook.show_all()
self.top.get_object('vbox').pack_start(notebook, True)
def _changed_name(self, obj):
"""
callback to changes typed by user to the person name.

View File

@ -107,6 +107,23 @@ class EditPersonRef(EditSecondary):
self.define_ok_button(self.top.get_object('ok'),self.save)
self.top.get_object('select').connect('clicked',self._select_person)
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('person-rebuild', self.close)
self._add_db_signal('person-delete', self.check_for_close)
def check_for_close(self, handles):
"""
Callback method for delete signals.
If there is a delete signal of the primary object we are editing, the
editor (and all child windows spawned) should be closed
"""
if self.obj.ref in handles:
self.close()
def _select_person(self, obj):
from Selectors import selector_factory
SelectPerson = selector_factory('Person')

View File

@ -142,6 +142,14 @@ class EditPlace(EditPrimary):
self.define_cancel_button(self.top.get_object('cancel'))
self.define_help_button(self.top.get_object('help'))
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('place-rebuild', self._do_close)
self._add_db_signal('place-delete', self.check_for_close)
def _setup_fields(self):
mloc = self.obj.get_main_location()

View File

@ -21,9 +21,25 @@
# $Id$
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
from gettext import gettext as _
#-------------------------------------------------------------------------
#
# GTK modules
#
#-------------------------------------------------------------------------
import gtk
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
import ManagedWindow
import DateHandler
from BasicUtils import name_displayer
@ -31,8 +47,9 @@ import Config
import GrampsDisplay
from QuestionDialog import SaveDialog
import gen.lib
from gui.dbguielement import DbGUIElement
class EditPrimary(ManagedWindow.ManagedWindow):
class EditPrimary(ManagedWindow.ManagedWindow, DbGUIElement):
QR_CATEGORY = -1
@ -52,19 +69,23 @@ class EditPrimary(ManagedWindow.ManagedWindow):
self.uistate = uistate
self.db = state.db
self.callback = callback
self.signal_keys = []
self.ok_button = None
self.get_from_handle = get_from_handle
self.get_from_gramps_id = get_from_gramps_id
self.contexteventbox = None
self.__tabs = []
ManagedWindow.ManagedWindow.__init__(self, uistate, track, obj)
DbGUIElement.__init__(self, self.db)
self._local_init()
self._set_size()
self._create_tabbed_pages()
self._setup_fields()
self._connect_signals()
#if the database is changed, all info shown is invalid and the window
# should close
self.dbstate.connect('database-changed', self._do_close)
self.show()
self._post_init()
@ -80,18 +101,15 @@ class EditPrimary(ManagedWindow.ManagedWindow):
"""
pass
def _add_db_signal(self, name, callback):
self.signal_keys.append(self.db.connect(name, callback))
def _connect_signals(self):
pass
def _setup_fields(self):
pass
def _create_tabbed_pages(self):
pass
def _connect_signals(self):
pass
def build_window_key(self, obj):
if obj and obj.get_handle():
return obj.get_handle()
@ -126,8 +144,8 @@ class EditPrimary(ManagedWindow.ManagedWindow):
notebook.set_current_page(page_no)
def _add_tab(self, notebook, page):
self.__tabs.append(page)
notebook.insert_page(page, page.get_tab_widget())
page.add_db_signal_callback(self._add_db_signal)
page.label.set_use_underline(True)
return page
@ -151,11 +169,31 @@ class EditPrimary(ManagedWindow.ManagedWindow):
section))
def _do_close(self, *obj):
for key in self.signal_keys:
self.db.disconnect(key)
self._cleanup_db_connects()
self._cleanup_on_exit()
ManagedWindow.ManagedWindow.close(self)
def _cleanup_db_connects(self):
"""
All connects that happened to signals of the db must be removed on
closed. This implies two things:
1. The connects on the main view must be disconnected
2. Connects done in subelements must be disconnected
"""
#cleanup callbackmanager of this editor
self._cleanup_callbacks()
for tab in [tab for tab in self.__tabs if hasattr(tab, 'callman')]:
tab._cleanup_callbacks()
def check_for_close(self, handles):
"""
Callback method for delete signals.
If there is a delete signal of the primary object we are editing, the
editor (and all child windows spawned) should be closed
"""
if self.obj.get_handle() in handles:
self._do_close()
def close(self, *obj):
"""If the data has changed, give the user a chance to cancel
the close window"""

View File

@ -36,6 +36,7 @@ import gtk
import ManagedWindow
from DisplayTabs import GrampsTab
import Config
from gui.dbguielement import DbGUIElement
#-------------------------------------------------------------------------
#
@ -85,7 +86,7 @@ class RefTab(GrampsTab):
# EditReference class
#
#-------------------------------------------------------------------------
class EditReference(ManagedWindow.ManagedWindow):
class EditReference(ManagedWindow.ManagedWindow, DbGUIElement):
def __init__(self, state, uistate, track, source, source_ref, update):
self.db = state.db
@ -95,10 +96,11 @@ class EditReference(ManagedWindow.ManagedWindow):
self.source = source
self.source_added = False
self.update = update
self.signal_keys = []
self.warn_box = None
self.__tabs = []
ManagedWindow.ManagedWindow.__init__(self, uistate, track, source_ref)
DbGUIElement.__init__(self, self.db)
self._local_init()
self._set_size()
@ -155,14 +157,11 @@ class EditReference(ManagedWindow.ManagedWindow):
notebook.set_current_page(page_no)
def _add_tab(self, notebook,page):
self.__tabs.append(page)
notebook.insert_page(page, page.get_tab_widget())
page.add_db_signal_callback(self._add_db_signal)
page.label.set_use_underline(True)
return page
def _add_db_signal(self, name, callback):
self.signal_keys.append(self.db.connect(name,callback))
def _connect_signals(self):
pass
@ -190,6 +189,15 @@ class EditReference(ManagedWindow.ManagedWindow):
self._cleanup_on_exit()
self.close(obj)
def check_for_close(self, handles):
"""
Callback method for delete signals.
If there is a delete signal of the primary object we are editing, the
editor (and all child windows spawned) should be closed
"""
if self.source.get_handle() in handles:
self.close()
def define_help_button(self, button, webpage='', section=''):
import GrampsDisplay
button.connect('clicked', lambda x: GrampsDisplay.help(webpage,
@ -200,6 +208,17 @@ class EditReference(ManagedWindow.ManagedWindow):
pass
def close(self,*obj):
for key in self.signal_keys:
self.db.disconnect(key)
self._cleanup_db_connects()
ManagedWindow.ManagedWindow.close(self)
def _cleanup_db_connects(self):
"""
All connects that happened to signals of the db must be removed on
closed. This implies two things:
1. The connects on the main view must be disconnected
2. Connects done in subelements must be disconnected
"""
#cleanup callbackmanager of this editor
self._cleanup_callbacks()
for tab in [tab for tab in self.__tabs if hasattr(tab, 'callman')]:
tab._cleanup_callbacks()

View File

@ -83,7 +83,15 @@ class EditRepoRef(EditReference):
def _connect_signals(self):
self.define_ok_button(self.top.get_object('ok'),self.ok_clicked)
self.define_cancel_button(self.top.get_object('cancel'))
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('repository-rebuild', self.close)
self._add_db_signal('repository-delete', self.check_for_close)
def _setup_fields(self):
self.callno = MonitoredEntry(
self.top.get_object("call_number"),

View File

@ -148,6 +148,14 @@ class EditRepository(EditPrimary):
self.define_cancel_button(self.glade.get_object('cancel'))
self.define_ok_button(self.glade.get_object('ok'), self.save)
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('repository-rebuild', self._do_close)
self._add_db_signal('repository-delete', self.check_for_close)
def save(self, *obj):
self.ok_button.set_sensitive(False)
if self.object_is_empty():

View File

@ -24,8 +24,9 @@
import ManagedWindow
import GrampsDisplay
import Config
from gui.dbguielement import DbGUIElement
class EditSecondary(ManagedWindow.ManagedWindow):
class EditSecondary(ManagedWindow.ManagedWindow, DbGUIElement):
def __init__(self, state, uistate, track, obj, callback=None):
"""Create an edit window. Associates a person with the window."""
@ -35,9 +36,10 @@ class EditSecondary(ManagedWindow.ManagedWindow):
self.uistate = uistate
self.db = state.db
self.callback = callback
self.signal_keys = []
self.__tabs = []
ManagedWindow.ManagedWindow.__init__(self, uistate, track, obj)
DbGUIElement.__init__(self, self.db)
self._local_init()
self._set_size()
@ -60,9 +62,6 @@ class EditSecondary(ManagedWindow.ManagedWindow):
"""
pass
def _add_db_signal(self, name, callback):
self.signal_keys.append(self.db.connect(name,callback))
def _connect_signals(self):
pass
@ -101,8 +100,8 @@ class EditSecondary(ManagedWindow.ManagedWindow):
notebook.set_current_page(page_no)
def _add_tab(self, notebook,page):
self.__tabs.append(page)
notebook.insert_page(page, page.get_tab_widget())
page.add_db_signal_callback(self._add_db_signal)
page.label.set_use_underline(True)
return page
@ -121,7 +120,18 @@ class EditSecondary(ManagedWindow.ManagedWindow):
section))
def close(self,*obj):
for key in self.signal_keys:
self.db.disconnect(key)
self._cleanup_db_connects()
self._cleanup_on_exit()
ManagedWindow.ManagedWindow.close(self)
def _cleanup_db_connects(self):
"""
All connects that happened to signals of the db must be removed on
closed. This implies two things:
1. The connects on the main view must be disconnected
2. Connects done in subelements must be disconnected
"""
#cleanup callbackmanager of this editor
self._cleanup_callbacks()
for tab in [tab for tab in self.__tabs if hasattr(tab, 'callman')]:
tab._cleanup_callbacks()

View File

@ -92,6 +92,14 @@ class EditSource(EditPrimary):
self.define_cancel_button(self.glade.get_object('cancel'))
self.define_help_button(self.glade.get_object('help'))
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('source-rebuild', self._do_close)
self._add_db_signal('source-delete', self.check_for_close)
def _setup_fields(self):
self.author = MonitoredEntry(self.glade.get_object("author"),
self.obj.set_author, self.obj.get_author,

View File

@ -86,6 +86,16 @@ class EditSourceRef(EditReference):
self.define_cancel_button(self.top.get_object('cancel'))
self.define_help_button(self.top.get_object("help"))
def _connect_db_signals(self):
"""
Connect any signals that need to be connected.
Called by the init routine of the base class (_EditPrimary).
"""
self._add_db_signal('source-rebuild', self.close)
self._add_db_signal('source-delete', self.check_for_close)
#note: at the moment, a source cannot be updated while an editor with
# that source shown is open. So no need to connect to source-update
def _setup_fields(self):
self.ref_privacy = PrivacyButton(
self.top.get_object('privacy'), self.source_ref, self.db.readonly)

View File

@ -417,6 +417,10 @@ class GrampsDbBase(Callback):
"""
Notify clients that the data has changed significantly, and that all
internal data dependent on the database should be rebuilt.
Note that all rebuild signals on all objects are emitted at the same
time. It is correct to assume that this is always the case.
TODO: it might be better to replace these rebuild signals by one single
database-rebuild signal.
"""
self.emit('person-rebuild')
self.emit('family-rebuild')

View File

@ -9,6 +9,7 @@ pkgdata_PYTHON = \
__init__.py \
dbutils.py \
callback.py \
callman.py \
progressmon.py \
longop.py

431
src/gen/utils/callman.py Normal file
View File

@ -0,0 +1,431 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009 Benny Malengier
#
# 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$
"""
Module providing support for callback handling in the GUI
* track object handles
* register new handles
* manage callback functions
"""
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
#-------------------------------------------------------------------------
#
# Constants
#
#-------------------------------------------------------------------------
PERSONKEY = 'person'
FAMILYKEY = 'family'
EVENTKEY = 'event'
PLACEKEY = 'place'
MEDIAKEY = 'media'
SOURCEKEY = 'source'
REPOKEY = 'repository'
NOTEKEY = 'note'
ADD = '-add'
UPDATE = '-update'
DELETE = '-delete'
REBUILD = '-rebuild'
KEYS = [PERSONKEY, FAMILYKEY, EVENTKEY, PLACEKEY, MEDIAKEY, SOURCEKEY,
REPOKEY, NOTEKEY]
METHODS = [ADD, UPDATE, DELETE, REBUILD]
METHODS_LIST = [ADD, UPDATE, DELETE]
METHODS_NONE = [REBUILD]
PERSONCLASS = 'Person'
FAMILYCLASS = 'Family'
EVENTCLASS = 'Event'
PLACECLASS = 'Place'
MEDIACLASS = 'MediaObject'
SOURCECLASS = 'Source'
REPOCLASS = 'Repository'
NOTECLASS = 'Note'
CLASS2KEY = {
PERSONCLASS: PERSONKEY,
FAMILYCLASS: FAMILYKEY,
EVENTCLASS: EVENTKEY,
PLACECLASS: PLACEKEY,
MEDIACLASS: MEDIAKEY,
SOURCECLASS: SOURCEKEY,
REPOCLASS: REPOKEY,
NOTECLASS: NOTEKEY
}
def _return(*args):
"""
Function that does nothing with the arguments
"""
return True
#-------------------------------------------------------------------------
#
# CallbackManager class
#
#-------------------------------------------------------------------------
class CallbackManager(object):
"""
Manage callback handling from GUI to the db.
It is unique to a db and some GUI element. When a db is changed, one should
destroy the CallbackManager and set up a new one (or delete the GUI element
as it shows info from a previous db).
Track changes to your relevant objects, calling callback functions as
needed.
"""
def __init__(self, database):
"""
:param database: database to which to connect the callbacks of this
CallbackManager object
:type database: a class:`~gen.db.base.GrampsDbBase` object
"""
#no handles to track
self.database = database
self.__handles = {
PERSONKEY: [],
FAMILYKEY: [],
EVENTKEY: [],
PLACEKEY: [],
MEDIAKEY: [],
SOURCEKEY: [],
REPOKEY: [],
NOTEKEY: [],
}
#no custom callbacks to do
self.custom_signal_keys = []
#set up callbacks to do nothing
self.__callbacks = {}
self.__init_callbacks()
def __init_callbacks(self):
"""
set up callbacks to do nothing
"""
self.__callbacks = {}
for key in KEYS:
for method in METHODS:
self.__callbacks[key+method] = [_return, None]
def disconnect_all(self):
"""
Disconnect from all signals from the database
This method should always be called before a the callback methods
become invalid.
"""
for key in self.custom_signal_keys:
self.database.disconnect(key)
self.custom_signal_keys = []
for key, value in self.__callbacks.iteritems():
if not value[1] is None:
self.database.disconnect(value[1])
self.__init_callbacks()
def register_obj(self, baseobj, directonly=False):
"""
Convenience method, will register all directly and not directly
referenced prim objects connected to baseobj with the CallbackManager
If directonly is True, only directly registered objects will be
registered.
Note that baseobj is not registered itself as it can be a sec obj.
"""
if directonly:
self.register_handles(directhandledict(baseobj))
else:
self.register_handles(handledict(baseobj))
def register_handles(self, ahandledict):
"""
Register handles that need to be tracked by the manager.
This function can be called several times, adding to existing
registered handles.
:param ahandledict: a dictionary with key one of the KEYS,
and value a list of handles to track
"""
for key in KEYS:
handles = ahandledict.get(key)
if handles:
self.__handles[key] = list(
set(self.__handles[key]).union(handles))
def unregister_handles(self, ahandledict):
"""
All handles in handledict are no longer tracked
:param handledict: a dictionary with key one of the KEYS,
and value a list of handles to track
"""
for key in KEYS:
handles = ahandledict.get(key)
if handles:
for handle in handles:
self.__handles[key].remove(handle)
def unregister_all(self):
"""
Unregister all handles that are registered
"""
self.__handles = {
PERSONKEY: [],
FAMILYKEY: [],
EVENTKEY: [],
PLACEKEY: [],
MEDIAKEY: [],
SOURCEKEY: [],
REPOKEY: [],
NOTEKEY: [],
}
def register_callbacks(self, callbackdict):
"""
register callback functions that need to be called for a specific
db action. This function can be called several times, adding to and if
needed overwriting, existing callbacks.
No db connects are done. If a signal already is connected to the db,
it is removed from the connect list of the db.
:param callbackdict: a dictionary with key one of KEYS+METHODS, or one
of KEYS, and value a function to be called when signal is raised.
"""
for key in KEYS:
function = callbackdict.get(key)
if function:
for method in METHODS:
self.__add_callback(key+method, function)
for method in METHODS:
function = callbackdict.get(key+method)
if function:
self.__add_callback(key+method, function)
def connect_all(self, keys=None):
"""
Convenience function, connects all database signals related to the
primary objects given in keys to the callbacks attached to self.
Note that only those callbacks registered with register_callbacks will
effectively result in an action, so one can connect to all keys
even if not all keys have a registered callback.
:param keys: list of keys of primary objects for which to connect the
signals, default is no connects being done. One can enable signal
activity to needed objects by passing a list, eg
keys=[callman.SOURCEKEY, callman.PLACEKEY], or to all with
keys=callman.KEYS
"""
if keys is None:
return
for key in keys:
for method in METHODS:
signal = key + method
self.__do_unconnect(signal)
self.__callbacks[signal][1] = self.database.connect(
signal,
self.__callbackcreator(key, signal))
def __do_callback(self, signal, *arg):
"""
Execute a specific callback. This is only actually done if one of the
registered handles is involved.
Arg must conform to the requirements of the signal emitter.
For a GrampsDbBase that is that arg must be not given (rebuild
methods), or arg[0] must be the list of handles affected.
"""
key = signal.split('-')[0]
if arg:
handles = arg[0]
affected = list(set(self.__handles[key]).intersection(handles))
if affected:
self.__callbacks[signal][0](affected)
else:
affected = self.__handles[key]
if affected:
self.__callbacks[signal][0]()
def __add_callback(self, signal, callback):
"""
Add a callback to a signal. There can be only one callback per signal
that is managed, so if there is a previous one, it is removed
"""
self.__do_unconnect(signal)
self.__callbacks[signal] = [callback, None]
def __do_unconnect(self, signal):
"""
unconnect a signal from the database if it is already connected
"""
oldcall, oldconnectkey = self.__callbacks[signal]
if not oldconnectkey is None:
self.database.disconnect(oldconnectkey)
def add_db_signal(self, name, callback):
"""
Do a custom db connect signal outside of the primary object ones
managed automatically.
"""
self.custom_signal_keys.append(self.database.connect(name, callback))
def __callbackcreator(self, key, signal):
"""
helper function, a lambda function needs a string to be defined
explicitly. This function creates the correct lambda function to use
as callback based on the key/signal one needs to connect to.
AttributeError is raised for unknown key or signal.
"""
if key == PERSONKEY:
if signal == 'person-update':
return lambda arg: self.__do_callback('person-update', *(arg,))
elif signal == 'person-add':
return lambda arg: self.__do_callback('person-add', *(arg,))
elif signal == 'person-delete':
return lambda arg: self.__do_callback('person-delete', *(arg,))
elif signal == 'person-rebuild':
return lambda *arg: self.__do_callback('person-rebuild')
else:
raise AttributeError, 'Signal ' + signal + 'not supported.'
elif key == FAMILYKEY:
if signal == 'family-update':
return lambda arg: self.__do_callback('family-update', *(arg,))
elif signal == 'family-add':
return lambda arg: self.__do_callback('family-add', *(arg,))
elif signal == 'family-delete':
return lambda arg: self.__do_callback('family-delete', *(arg,))
elif signal == 'family-rebuild':
return lambda *arg: self.__do_callback('family-rebuild')
else:
raise AttributeError, 'Signal ' + signal + 'not supported.'
elif key == EVENTKEY:
if signal == 'event-update':
return lambda arg: self.__do_callback('event-update', *(arg,))
elif signal == 'event-add':
return lambda arg: self.__do_callback('event-add', *(arg,))
elif signal == 'event-delete':
return lambda arg: self.__do_callback('event-delete', *(arg,))
elif signal == 'event-rebuild':
return lambda *arg: self.__do_callback('event-rebuild')
else:
raise AttributeError, 'Signal ' + signal + 'not supported.'
elif key == PLACEKEY:
if signal == 'place-update':
return lambda arg: self.__do_callback('place-update', *(arg,))
elif signal == 'place-add':
return lambda arg: self.__do_callback('place-add', *(arg,))
elif signal == 'place-delete':
return lambda arg: self.__do_callback('place-delete', *(arg,))
elif signal == 'place-rebuild':
return lambda *arg: self.__do_callback('place-rebuild')
else:
raise AttributeError, 'Signal ' + signal + 'not supported.'
elif key == SOURCEKEY:
if signal == 'source-update':
return lambda arg: self.__do_callback('source-update', *(arg,))
elif signal == 'source-add':
return lambda arg: self.__do_callback('source-add', *(arg,))
elif signal == 'source-delete':
return lambda arg: self.__do_callback('source-delete', *(arg,))
elif signal == 'source-rebuild':
return lambda *arg: self.__do_callback('source-rebuild')
else:
raise AttributeError, 'Signal ' + signal + 'not supported.'
elif key == REPOKEY:
if signal == 'repository-update':
return lambda arg: self.__do_callback('repository-update',
*(arg,))
elif signal == 'repository-add':
return lambda arg: self.__do_callback('repository-add',
*(arg,))
elif signal == 'repository-delete':
return lambda arg: self.__do_callback('repository-delete',
*(arg,))
elif signal == 'repository-rebuild':
return lambda *arg: self.__do_callback('repository-rebuild')
else:
raise AttributeError, 'Signal ' + signal + 'not supported.'
elif key == MEDIAKEY:
if signal == 'media-update':
return lambda arg: self.__do_callback('media-update', *(arg,))
elif signal == 'media-add':
return lambda arg: self.__do_callback('media-add', *(arg,))
elif signal == 'media-delete':
return lambda arg: self.__do_callback('media-delete', *(arg,))
elif signal == 'media-rebuild':
return lambda *arg: self.__do_callback('media-rebuild')
else:
raise AttributeError, 'Signal ' + signal + 'not supported.'
elif key == NOTEKEY:
if signal == 'note-update':
return lambda arg: self.__do_callback('note-update', *(arg,))
elif signal == 'note-add':
return lambda arg: self.__do_callback('note-add', *(arg,))
elif signal == 'note-delete':
return lambda arg: self.__do_callback('note-delete', *(arg,))
elif signal == 'note-rebuild':
return lambda *arg: self.__do_callback('note-rebuild')
else:
raise AttributeError, 'Signal ' + signal + 'not supported.'
else:
raise AttributeError, 'Signal ' + signal + 'not supported.'
def directhandledict(baseobj):
"""
Build a handledict from baseobj with all directly referenced objects
"""
handles = {
PERSONKEY: [],
FAMILYKEY: [],
EVENTKEY: [],
PLACEKEY: [],
MEDIAKEY: [],
SOURCEKEY: [],
REPOKEY: [],
NOTEKEY: [],
}
for classn, handle in baseobj.get_referenced_handles():
handles[CLASS2KEY[classn]].append(handle)
return handles
def handledict(baseobj):
"""
Build a handledict from baseobj with all directly and not directly
referenced base obj that are present
"""
handles = {
PERSONKEY: [],
FAMILYKEY: [],
EVENTKEY: [],
PLACEKEY: [],
MEDIAKEY: [],
SOURCEKEY: [],
REPOKEY: [],
NOTEKEY: [],
}
for classn, handle in baseobj.get_referenced_handles_recursively():
handles[CLASS2KEY[classn]].append(handle)
return handles

View File

@ -10,6 +10,7 @@ pkgdatadir = $(datadir)/@PACKAGE@/gui
pkgdata_PYTHON = \
__init__.py \
dbguielement.py \
dbloader.py \
dbman.py \
grampsgui.py \

89
src/gui/dbguielement.py Normal file
View File

@ -0,0 +1,89 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2009 Benny Malengier
#
# 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$
"""
Group common stuff GRAMPS GUI elements must be able to do when tracking a DB:
* connect to db signals
* listen to db changes to update themself on relevant changes
* determine if the GUI has become out of sync with the db
"""
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from gen.utils.callman import CallbackManager
#-------------------------------------------------------------------------
#
# GUIElement class
#
#-------------------------------------------------------------------------
class DbGUIElement(object):
"""
Group common stuff GRAMPS GUI elements must be able to do when tracking
a DB:
* connect to db signals
* listen to db changes to update themself on relevant changes
* determine if the GUI has become out of sync with the db
Most interaction with the DB should be done via the callman attribute.
On initialization, the method _connect_db_signals is called. Inheriting
objects are advised to group the setup of the callman attribute here.
.. attribute callman : a `~gen.utils.callman.CallbackManager` object, to
be used to track specific changes in the db and set up callbacks
"""
def __init__(self, database):
self.callman = CallbackManager(database)
self._connect_db_signals()
def _add_db_signal(self, name, callback):
"""
Convenience function to add a custom db signal. The attributes are just
passed to the callman object.
For primary objects, use the register method of the callman attribute.
:param name: name of the signal to connect to
:type name: string
:param callback: function to call when signal is emitted
:type callback: a funtion or method with the correct signature for the
signal
"""
self.callman.add_db_signal(name, callback)
def _connect_db_signals(self):
"""
Convenience method that is called on initialization of DbGUIElement.
Use this to group setup of the callman attribute
"""
pass
def _cleanup_callbacks(self):
"""
Remove all db callbacks.
This is done automatically on destruction of the object, but is
normally needed earlier, calling this method does so.
"""
database = self.callman.database
self.callman.disconnect_all()
self.callman = CallbackManager(database)