diff --git a/src/Filters/Rules/_HasTagBase.py b/src/Filters/Rules/_HasTagBase.py
index f7321e401..afac97f9a 100644
--- a/src/Filters/Rules/_HasTagBase.py
+++ b/src/Filters/Rules/_HasTagBase.py
@@ -51,10 +51,17 @@ class HasTagBase(Rule):
description = _("Matches objects with the given tag")
category = _('General filters')
+ def prepare(self, db):
+ """
+ Prepare the rule. Things we want to do just once.
+ """
+ tag = db.get_tag_from_name(self.list[0])
+ self.tag_handle = tag.get_handle()
+
def apply(self, db, obj):
"""
Apply the rule. Return True for a match.
"""
if not self.list[0]:
return False
- return self.list[0] in obj.get_tag_list()
+ return self.tag_handle in obj.get_tag_list()
diff --git a/src/Filters/SideBar/_PersonSidebarFilter.py b/src/Filters/SideBar/_PersonSidebarFilter.py
index 65744291d..4c13997f5 100644
--- a/src/Filters/SideBar/_PersonSidebarFilter.py
+++ b/src/Filters/SideBar/_PersonSidebarFilter.py
@@ -106,8 +106,6 @@ class PersonSidebarFilter(SidebarFilter):
SidebarFilter.__init__(self, dbstate, uistate, "Person")
- self.update_tag_list()
-
def create_widget(self):
cell = gtk.CellRendererText()
cell.set_property('width', self._FILTER_WIDTH)
@@ -274,25 +272,13 @@ class PersonSidebarFilter(SidebarFilter):
self.generic.set_model(build_filter_model('Person', [all_filter]))
self.generic.set_active(0)
- def on_db_changed(self, db):
- """
- Called when the database is changed.
- """
- self.update_tag_list()
-
- def on_tags_changed(self):
- """
- Called when tags are changed.
- """
- self.update_tag_list()
-
- def update_tag_list(self):
+ def on_tags_changed(self, tag_list):
"""
Update the list of tags in the tag filter.
"""
model = gtk.ListStore(str)
model.append(('',))
- for tag in sorted(self.dbstate.db.get_all_tags(), key=locale.strxfrm):
- model.append((tag,))
+ for tag_name in tag_list:
+ model.append((tag_name,))
self.tag.set_model(model)
self.tag.set_active(0)
diff --git a/src/Filters/SideBar/_SidebarFilter.py b/src/Filters/SideBar/_SidebarFilter.py
index b96f6f3c3..5e90498df 100644
--- a/src/Filters/SideBar/_SidebarFilter.py
+++ b/src/Filters/SideBar/_SidebarFilter.py
@@ -21,20 +21,29 @@
# $Id$
from gen.ggettext import gettext as _
+from bisect import insort_left
import gtk
import pango
from gui import widgets
+from gui.dbguielement import DbGUIElement
import config
_RETURN = gtk.gdk.keyval_from_name("Return")
_KP_ENTER = gtk.gdk.keyval_from_name("KP_Enter")
-class SidebarFilter(object):
+class SidebarFilter(DbGUIElement):
_FILTER_WIDTH = 200
_FILTER_ELLIPSIZE = pango.ELLIPSIZE_END
def __init__(self, dbstate, uistate, namespace):
+ self.signal_map = {
+ 'tag-add' : self._tag_add,
+ 'tag-delete' : self._tag_delete,
+ 'tag-rebuild' : self._tag_rebuild
+ }
+ DbGUIElement.__init__(self, dbstate.db)
+
self.position = 1
self.table = gtk.Table(4, 11)
self.table.set_border_width(6)
@@ -47,10 +56,11 @@ class SidebarFilter(object):
self._init_interface()
uistate.connect('filters-changed', self.on_filters_changed)
dbstate.connect('database-changed', self._db_changed)
- dbstate.db.connect('tags-changed', self.on_tags_changed)
self.uistate = uistate
self.dbstate = dbstate
self.namespace = namespace
+ self.__tag_list = []
+ self._tag_rebuild()
def _init_interface(self):
self.table.attach(widgets.MarkupLabel(_('Filter')),
@@ -148,8 +158,9 @@ class SidebarFilter(object):
"""
Called when the database is changed.
"""
- db.connect('tags-changed', self.on_tags_changed)
+ self._change_db(db)
self.on_db_changed(db)
+ self._tag_rebuild()
def on_db_changed(self, db):
"""
@@ -157,7 +168,42 @@ class SidebarFilter(object):
"""
pass
- def on_tags_changed(self):
+ def _connect_db_signals(self):
+ """
+ Connect database signals defined in the signal map.
+ """
+ for sig in self.signal_map:
+ self.callman.add_db_signal(sig, self.signal_map[sig])
+
+ def _tag_add(self, handle_list):
+ """
+ Called when tags are added.
+ """
+ for handle in handle_list:
+ tag = self.dbstate.db.get_tag_from_handle(handle)
+ insort_left(self.__tag_list, tag.get_name())
+ self.on_tags_changed(self.__tag_list)
+
+ def _tag_delete(self, handle_list):
+ """
+ Called when tags are deleted.
+ """
+ for handle in handle_list:
+ tag = self.dbstate.db.get_tag_from_handle(handle)
+ self.__tag_list.remove(tag.get_name())
+ self.on_tags_changed(self.__tag_list)
+
+ def _tag_rebuild(self):
+ """
+ Called when the tag list needs to be rebuilt.
+ """
+ self.__tag_list = []
+ for handle in self.dbstate.db.get_tag_handles():
+ tag = self.dbstate.db.get_tag_from_handle(handle)
+ self.__tag_list.append(tag.get_name())
+ self.on_tags_changed(self.__tag_list)
+
+ def on_tags_changed(self, tag_list):
"""
Called when tags are changed.
"""
@@ -207,4 +253,3 @@ class SidebarFilter(object):
filterdb.save()
reload_custom_filters()
self.on_filters_changed(self.namespace)
-
diff --git a/src/gen/db/backup.py b/src/gen/db/backup.py
index 5368497b0..cc4f2513c 100644
--- a/src/gen/db/backup.py
+++ b/src/gen/db/backup.py
@@ -60,7 +60,7 @@ import cPickle as pickle
#------------------------------------------------------------------------
from gen.db.exceptions import DbException
from gen.db.write import FAMILY_TBL, PLACES_TBL, SOURCES_TBL, MEDIA_TBL, \
- EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, META
+ EVENTS_TBL, PERSON_TBL, REPO_TBL, NOTE_TBL, TAG_TBL, META
#------------------------------------------------------------------------
#
@@ -205,5 +205,6 @@ def __build_tbl_map(database):
( NOTE_TBL, database.note_map.db),
( MEDIA_TBL, database.media_map.db),
( EVENTS_TBL, database.event_map.db),
+ ( TAG_TBL, database.tag_map.db),
( META, database.metadata.db),
]
diff --git a/src/gen/db/base.py b/src/gen/db/base.py
index 9e407f17b..173238ed1 100644
--- a/src/gen/db/base.py
+++ b/src/gen/db/base.py
@@ -452,6 +452,12 @@ class DbReadBase(object):
"""
raise NotImplementedError
+ def get_number_of_tags(self):
+ """
+ Return the number of tags currently in the database.
+ """
+ raise NotImplementedError
+
def get_object_from_gramps_id(self, val):
"""
Find a MediaObject in the database from the passed gramps' ID.
@@ -601,6 +607,12 @@ class DbReadBase(object):
"""
raise NotImplementedError
+ def get_raw_tag_data(self, handle):
+ """
+ Return raw (serialized and pickled) Tag object from handle
+ """
+ raise NotImplementedError
+
def get_reference_map_cursor(self):
"""
Returns a reference to a cursor over the reference map
@@ -726,6 +738,38 @@ class DbReadBase(object):
"""
raise NotImplementedError
+ def get_tag_cursor(self):
+ """
+ Return a reference to a cursor over Tag objects
+ """
+ raise NotImplementedError
+
+ def get_tag_from_handle(self, handle):
+ """
+ Find a Tag in the database from the passed handle.
+
+ If no such Tag exists, None is returned.
+ """
+ raise NotImplementedError
+
+ def get_tag_from_name(self, val):
+ """
+ Find a Tag in the database from the passed Tag name.
+
+ If no such Tag exists, None is returned.
+ Needs to be overridden by the derived class.
+ """
+ raise NotImplementedError
+
+ def get_tag_handles(self, sort_handles=True):
+ """
+ Return a list of database handles, one handle for each Tag in
+ the database.
+
+ If sort_handles is True, the list is sorted by Tag name.
+ """
+ raise NotImplementedError
+
def get_url_types(self):
"""
Return a list of all custom names types associated with Url instances
@@ -765,30 +809,6 @@ class DbReadBase(object):
"""
raise NotImplementedError
- def get_tag(self, tag_name):
- """
- Return the color of the tag.
- """
- raise NotImplementedError
-
- def get_tag_colors(self):
- """
- Return a list of all the tags in the database.
- """
- raise NotImplementedError
-
- def get_all_tags(self):
- """
- Return a dictionary of tags with their associated colors.
- """
- raise NotImplementedError
-
- def has_tag(self, tag_name):
- """
- Return if a tag exists in the tags table.
- """
- raise NotImplementedError
-
def has_note_handle(self, handle):
"""
Return True if the handle exists in the current Note database.
@@ -825,6 +845,12 @@ class DbReadBase(object):
"""
raise NotImplementedError
+ def has_tag_handle(self, handle):
+ """
+ Return True if the handle exists in the current Tag database.
+ """
+ raise NotImplementedError
+
def is_open(self):
"""
Return True if the database has been opened.
@@ -927,6 +953,18 @@ class DbReadBase(object):
"""
raise NotImplementedError
+ def iter_tag_handles(self):
+ """
+ Return an iterator over handles for Tags in the database
+ """
+ raise NotImplementedError
+
+ def iter_tags(self):
+ """
+ Return an iterator over objects for Tags in the database
+ """
+ raise NotImplementedError
+
def load(self, name, callback, mode=None, upgrade=False):
"""
Open the specified database.
@@ -1192,6 +1230,13 @@ class DbWriteBase(object):
"""
raise NotImplementedError
+ def add_tag(self, tag, transaction):
+ """
+ Add a Tag to the database, assigning a handle if it has not already
+ been defined.
+ """
+ raise NotImplementedError
+
def add_to_surname_list(self, person, batch_transaction, name):
"""
Add surname from given person to list of surnames
@@ -1281,6 +1326,13 @@ class DbWriteBase(object):
"""
raise NotImplementedError
+ def commit_tag(self, tag, transaction, change_time=None):
+ """
+ Commit the specified Tag to the database, storing the changes as
+ part of the transaction.
+ """
+ raise NotImplementedError
+
def delete_primary_from_reference_map(self, handle, transaction):
"""
Called each time an object is removed from the database.
@@ -1390,6 +1442,15 @@ class DbWriteBase(object):
"""
raise NotImplementedError
+ def remove_tag(self, handle, transaction):
+ """
+ Remove the Tag specified by the database handle from the
+ database, preserving the change in the passed transaction.
+
+ This method must be overridden in the derived class.
+ """
+ raise NotImplementedError
+
def set_auto_remove(self):
"""
BSDDB change log settings using new method with renamed attributes
@@ -1410,14 +1471,6 @@ class DbWriteBase(object):
"""
raise NotImplementedError
- def set_tag(self, tag_name, color_str):
- """
- Set the color of a tag.
-
- Needs to be overridden in the derived class.
- """
- raise NotImplementedError
-
def sort_surname_list(self):
"""
Sort the list of surnames contained in the database by locale ordering.
diff --git a/src/gen/db/dbconst.py b/src/gen/db/dbconst.py
index 83578248d..8e3635751 100644
--- a/src/gen/db/dbconst.py
+++ b/src/gen/db/dbconst.py
@@ -43,7 +43,7 @@ __all__ = (
('PERSON_KEY', 'FAMILY_KEY', 'SOURCE_KEY', 'EVENT_KEY',
'MEDIA_KEY', 'PLACE_KEY', 'REPOSITORY_KEY', 'NOTE_KEY',
- 'REFERENCE_KEY'
+ 'REFERENCE_KEY', 'TAG_KEY'
) +
('TXNADD', 'TXNUPD', 'TXNDEL')
@@ -53,7 +53,7 @@ DBEXT = ".db" # File extension to be used for database files
DBUNDOFN = "undo.db" # File name of 'undo' database
DBLOCKFN = "lock" # File name of lock file
DBRECOVFN = "need_recover" # File name of recovery file
-DBLOGNAME = ".Db" # Name of logger
+DBLOGNAME = ".Db" # Name of logger
DBMODE_R = "r" # Read-only access
DBMODE_W = "w" # Full Reaw/Write access
DBPAGE = 16384 # Size of the pages used to hold items in the database
@@ -77,5 +77,6 @@ PLACE_KEY = 5
REPOSITORY_KEY = 6
REFERENCE_KEY = 7
NOTE_KEY = 8
+TAG_KEY = 9
TXNADD, TXNUPD, TXNDEL = 0, 1, 2
diff --git a/src/gen/db/read.py b/src/gen/db/read.py
index e9ffb141c..301419d01 100644
--- a/src/gen/db/read.py
+++ b/src/gen/db/read.py
@@ -46,7 +46,7 @@ import logging
#
#-------------------------------------------------------------------------
from gen.lib import (MediaObject, Person, Family, Source, Event, Place,
- Repository, Note, GenderStats, Researcher)
+ Repository, Note, Tag, GenderStats, Researcher)
from gen.db.dbconst import *
from gen.utils.callback import Callback
from gen.db import (BsddbBaseCursor, DbReadBase)
@@ -62,7 +62,7 @@ LOG = logging.getLogger(DBLOGNAME)
from gen.db.dbconst import *
_SIGBASE = ('person', 'family', 'source', 'event',
- 'media', 'place', 'repository', 'reference', 'note')
+ 'media', 'place', 'repository', 'reference', 'note', 'tag')
DBERRS = (db.DBRunRecoveryError, db.DBAccessError,
db.DBPageNotFoundError, db.DBInvalidArgError)
@@ -230,6 +230,13 @@ class DbBsddbRead(DbReadBase, Callback):
"class_func": Note,
"cursor_func": self.get_note_cursor,
},
+ 'Tag':
+ {
+ "handle_func": self.get_tag_from_handle,
+ "gramps_id_func": None,
+ "class_func": Tag,
+ "cursor_func": self.get_tag_cursor,
+ },
}
self.set_person_id_prefix('I%04d')
@@ -280,6 +287,7 @@ class DbBsddbRead(DbReadBase, Callback):
self.rid_trans = {}
self.nid_trans = {}
self.eid_trans = {}
+ self.tag_trans = {}
self.env = None
self.person_map = {}
self.family_map = {}
@@ -291,7 +299,6 @@ class DbBsddbRead(DbReadBase, Callback):
self.event_map = {}
self.metadata = {}
self.name_group = {}
- self.tags = {}
self.undo_callback = None
self.redo_callback = None
self.undo_history_callback = None
@@ -374,6 +381,9 @@ class DbBsddbRead(DbReadBase, Callback):
def get_note_cursor(self, *args, **kwargs):
return self.get_cursor(self.note_map, *args, **kwargs)
+ def get_tag_cursor(self, *args, **kwargs):
+ return self.get_cursor(self.tag_map, *args, **kwargs)
+
def close(self):
"""
Close the specified database.
@@ -401,6 +411,7 @@ class DbBsddbRead(DbReadBase, Callback):
self.emit('event-rebuild')
self.emit('repository-rebuild')
self.emit('note-rebuild')
+ self.emit('tag-rebuild')
@staticmethod
def __find_next_gramps_id(prefix, map_index, trans):
@@ -525,7 +536,7 @@ class DbBsddbRead(DbReadBase, Callback):
def get_person_from_handle(self, handle):
"""
- Find a Person in the database from the passed gramps' ID.
+ Find a Person in the database from the passed handle.
If no such Person exists, None is returned.
"""
@@ -533,7 +544,7 @@ class DbBsddbRead(DbReadBase, Callback):
def get_source_from_handle(self, handle):
"""
- Find a Source in the database from the passed gramps' ID.
+ Find a Source in the database from the passed handle.
If no such Source exists, None is returned.
"""
@@ -541,7 +552,7 @@ class DbBsddbRead(DbReadBase, Callback):
def get_object_from_handle(self, handle):
"""
- Find an Object in the database from the passed gramps' ID.
+ Find an Object in the database from the passed handle.
If no such Object exists, None is returned.
"""
@@ -549,7 +560,7 @@ class DbBsddbRead(DbReadBase, Callback):
def get_place_from_handle(self, handle):
"""
- Find a Place in the database from the passed gramps' ID.
+ Find a Place in the database from the passed handle.
If no such Place exists, None is returned.
"""
@@ -557,7 +568,7 @@ class DbBsddbRead(DbReadBase, Callback):
def get_event_from_handle(self, handle):
"""
- Find a Event in the database from the passed gramps' ID.
+ Find a Event in the database from the passed handle.
If no such Event exists, None is returned.
"""
@@ -565,7 +576,7 @@ class DbBsddbRead(DbReadBase, Callback):
def get_family_from_handle(self, handle):
"""
- Find a Family in the database from the passed gramps' ID.
+ Find a Family in the database from the passed handle.
If no such Family exists, None is returned.
"""
@@ -573,7 +584,7 @@ class DbBsddbRead(DbReadBase, Callback):
def get_repository_from_handle(self, handle):
"""
- Find a Repository in the database from the passed gramps' ID.
+ Find a Repository in the database from the passed handle.
If no such Repository exists, None is returned.
"""
@@ -581,12 +592,20 @@ class DbBsddbRead(DbReadBase, Callback):
def get_note_from_handle(self, handle):
"""
- Find a Note in the database from the passed gramps' ID.
+ Find a Note in the database from the passed handle.
If no such Note exists, None is returned.
"""
return self.get_from_handle(handle, Note, self.note_map)
+ def get_tag_from_handle(self, handle):
+ """
+ Find a Tag in the database from the passed handle.
+
+ If no such Tag exists, None is returned.
+ """
+ return self.get_from_handle(handle, Tag, self.tag_map)
+
def __get_obj_from_gramps_id(self, val, tbl, class_, prim_tbl):
try:
if tbl.has_key(str(val)):
@@ -679,6 +698,15 @@ class DbBsddbRead(DbReadBase, Callback):
"""
return self.__get_obj_from_gramps_id(val, self.nid_trans, Note,
self.note_map)
+
+ def get_tag_from_name(self, val):
+ """
+ Find a Tag in the database from the passed Tag name.
+
+ If no such Tag exists, None is returned.
+ """
+ return self.__get_obj_from_gramps_id(val, self.tag_trans, Tag,
+ self.tag_map)
def get_name_group_mapping(self, name):
"""
@@ -698,30 +726,6 @@ class DbBsddbRead(DbReadBase, Callback):
"""
return self.name_group.has_key(str(name))
- def get_tag(self, tag_name):
- """
- Return the color of the tag.
- """
- return self.tags.get(tag_name)
-
- def get_tag_colors(self):
- """
- Return a list of all the tags in the database.
- """
- return dict([(k, self.tags.get(k)) for k in self.tags.keys()])
-
- def get_all_tags(self):
- """
- Return a dictionary of tags with their associated colors.
- """
- return self.tags.keys()
-
- def has_tag(self, tag_name):
- """
- Return if a tag exists in the tags table.
- """
- return self.tags.has_key(tag_name)
-
def get_number_of_records(self, table):
if not self.db_is_open:
return 0
@@ -778,6 +782,12 @@ class DbBsddbRead(DbReadBase, Callback):
"""
return self.get_number_of_records(self.note_map)
+ def get_number_of_tags(self):
+ """
+ Return the number of tags currently in the database.
+ """
+ return self.get_number_of_records(self.tag_map)
+
def all_handles(self, table):
return table.keys()
@@ -894,6 +904,20 @@ class DbBsddbRead(DbReadBase, Callback):
return self.all_handles(self.note_map)
return []
+ def get_tag_handles(self, sort_handles=True):
+ """
+ Return a list of database handles, one handle for each Tag in
+ the database.
+
+ If sort_handles is True, the list is sorted by Tag name.
+ """
+ if self.db_is_open:
+ handle_list = self.all_handles(self.tag_map)
+ if sort_handles:
+ handle_list.sort(key=self.__sortbytag_key)
+ return handle_list
+ return []
+
def _f(curs_):
"""
Closure that returns an iterator over handles in the database.
@@ -914,6 +938,7 @@ class DbBsddbRead(DbReadBase, Callback):
iter_media_object_handles = _f(get_media_cursor)
iter_repository_handles = _f(get_repository_cursor)
iter_note_handles = _f(get_note_cursor)
+ iter_tag_handles = _f(get_tag_cursor)
del _f
def _f(curs_, obj_):
@@ -938,6 +963,7 @@ class DbBsddbRead(DbReadBase, Callback):
iter_media_objects = _f(get_media_cursor, MediaObject)
iter_repositories = _f(get_repository_cursor, Repository)
iter_notes = _f(get_note_cursor, Note)
+ iter_tags = _f(get_tag_cursor, Tag)
del _f
def get_gramps_ids(self, obj_key):
@@ -1296,7 +1322,10 @@ class DbBsddbRead(DbReadBase, Callback):
def get_raw_note_data(self, handle):
return self.__get_raw_data(self.note_map, handle)
-
+
+ def get_raw_tag_data(self, handle):
+ return self.__get_raw_data(self.tag_map, handle)
+
def __has_handle(self, table, handle):
"""
Helper function for has_