GEPS 011: Tagging - Converted to use new database table object

svn: r15921
This commit is contained in:
Nick Hall
2010-09-21 17:52:37 +00:00
parent 72693c94fa
commit f748668c89
23 changed files with 940 additions and 362 deletions

View File

@@ -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()

View File

@@ -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)

View File

@@ -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(_('<b>Filter</b>')),
@@ -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)

View File

@@ -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),
]

View File

@@ -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.

View File

@@ -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

View File

@@ -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)):
@@ -680,6 +699,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):
"""
Return the default grouping name for a surname.
@@ -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):
@@ -1297,6 +1323,9 @@ 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_<object>_handle methods
@@ -1355,6 +1384,12 @@ class DbBsddbRead(DbReadBase, Callback):
"""
return self.__has_handle(self.source_map, handle)
def has_tag_handle(self, handle):
"""
Return True if the handle exists in the current Tag database.
"""
return self.__has_handle(self.tag_map, handle)
def __sortbyperson_key(self, person):
return locale.strxfrm(self.person_map.get(str(person))[3][5])
@@ -1383,6 +1418,15 @@ class DbBsddbRead(DbReadBase, Callback):
media = self.media_map[str(key)][4]
return locale.strxfrm(media)
def __sortbytag(self, first, second):
tag1 = self.tag_map[str(first)][1]
tag2 = self.tag_map[str(second)][1]
return locale.strcoll(tag1, tag2)
def __sortbytag_key(self, key):
tag = self.tag_map[str(key)][1]
return locale.strxfrm(tag)
def set_mediapath(self, path):
"""Set the default media path for database, path should be utf-8."""
if (self.metadata is not None) and (not self.readonly):
@@ -1452,6 +1496,10 @@ class DbBsddbRead(DbReadBase, Callback):
'cursor_func': self.get_note_cursor,
'class_func': Note,
},
'Tag': {
'cursor_func': self.get_tag_cursor,
'class_func': Tag,
},
}
# Find which tables to iterate over

View File

@@ -129,6 +129,7 @@ class DbTxn(defaultdict):
REPOSITORY_KEY: (self.db.repository_map, 'repository'),
#REFERENCE_KEY: (self.db.reference_map, 'reference'),
NOTE_KEY: (self.db.note_map, 'note'),
TAG_KEY: (self.db.tag_map, 'tag'),
}
def get_description(self):

View File

@@ -55,7 +55,7 @@ DBERRS = (db.DBRunRecoveryError, db.DBAccessError,
db.DBPageNotFoundError, db.DBInvalidArgError)
_SIGBASE = ('person', 'family', 'source', 'event', 'media',
'place', 'repository', 'reference', 'note')
'place', 'repository', 'reference', 'note', 'tag')
#-------------------------------------------------------------------------
#
# DbUndo class
@@ -89,6 +89,7 @@ class DbUndo(object):
self.db.repository_map,
self.db.reference_map,
self.db.note_map,
self.db.tag_map,
)
def clear(self):
@@ -459,6 +460,7 @@ def testundo():
self.media_map = {}
self.place_map = {}
self.note_map = {}
self.tag_map = {}
self.repository_map = {}
self.reference_map = {}

View File

@@ -49,7 +49,7 @@ from sys import maxint
#
#-------------------------------------------------------------------------
from gen.lib import (GenderStats, Person, Family, Event, Place, Source,
MediaObject, Repository, Note)
MediaObject, Repository, Note, Tag)
from gen.db import (DbBsddbRead, DbWriteBase, BSDDBTxn,
DbTxn, BsddbBaseCursor, DbVersionError,
DbUpgradeRequiredError,
@@ -71,9 +71,9 @@ EIDTRANS = "event_id"
RIDTRANS = "repo_id"
NIDTRANS = "note_id"
SIDTRANS = "source_id"
TAGTRANS = "tag_name"
SURNAMES = "surnames"
NAME_GROUP = "name_group"
TAGS = "tags"
META = "meta_data"
FAMILY_TBL = "family"
@@ -84,6 +84,7 @@ EVENTS_TBL = "event"
PERSON_TBL = "person"
REPO_TBL = "repo"
NOTE_TBL = "note"
TAG_TBL = "tag"
REF_MAP = "reference_map"
REF_PRI = "primary_map"
@@ -105,7 +106,8 @@ CLASS_TO_KEY_MAP = {Person.__name__: PERSON_KEY,
MediaObject.__name__: MEDIA_KEY,
Place.__name__: PLACE_KEY,
Repository.__name__:REPOSITORY_KEY,
Note.__name__: NOTE_KEY}
Note.__name__: NOTE_KEY,
Tag.__name__: TAG_KEY}
KEY_TO_CLASS_MAP = {PERSON_KEY: Person.__name__,
FAMILY_KEY: Family.__name__,
@@ -114,7 +116,8 @@ KEY_TO_CLASS_MAP = {PERSON_KEY: Person.__name__,
MEDIA_KEY: MediaObject.__name__,
PLACE_KEY: Place.__name__,
REPOSITORY_KEY: Repository.__name__,
NOTE_KEY: Note.__name__}
NOTE_KEY: Note.__name__,
TAG_KEY: Tag.__name__}
#-------------------------------------------------------------------------
#
@@ -179,7 +182,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
__signals__ = dict((obj+'-'+op, signal)
for obj in
['person', 'family', 'event', 'place',
'source', 'media', 'note', 'repository']
'source', 'media', 'note', 'repository', 'tag']
for op, signal in zip(
['add', 'update', 'delete', 'rebuild'],
[(list,), (list,), (list,), None]
@@ -198,10 +201,6 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
# 4. Signal for change in person group name, parameters are
__signals__['person-groupname-rebuild'] = (unicode, unicode)
# 5. Signals for change ins tags
__signals__['tags-changed'] = None
__signals__['tag-update'] = (str, str)
def __init__(self):
"""Create a new GrampsDB."""
@@ -453,6 +452,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
("person_map", PERSON_TBL, db.DB_HASH),
("repository_map", REPO_TBL, db.DB_HASH),
("note_map", NOTE_TBL, db.DB_HASH),
("tag_map", TAG_TBL, db.DB_HASH),
("reference_map", REF_MAP, db.DB_BTREE),
]
@@ -468,9 +468,6 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
self.name_group = self.__open_db(self.full_name, NAME_GROUP,
db.DB_HASH, db.DB_DUP)
# Open tags database
self.tags = self.__open_db(self.full_name, TAGS, db.DB_HASH, db.DB_DUP)
# Here we take care of any changes in the tables related to new code.
# If secondary indices change, then they should removed
# or rebuilt by upgrade as well. In any case, the
@@ -591,6 +588,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
("oid_trans", OIDTRANS, db.DB_HASH, 0),
("rid_trans", RIDTRANS, db.DB_HASH, 0),
("nid_trans", NIDTRANS, db.DB_HASH, 0),
("tag_trans", TAGTRANS, db.DB_HASH, 0),
("reference_map_primary_map", REF_PRI, db.DB_BTREE, 0),
("reference_map_referenced_map", REF_REF, db.DB_BTREE, db.DB_DUPSORT),
]
@@ -612,6 +610,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
(self.media_map, self.oid_trans, find_idmap),
(self.repository_map, self.rid_trans, find_idmap),
(self.note_map, self.nid_trans, find_idmap),
(self.tag_map, self.tag_trans, find_idmap),
(self.reference_map, self.reference_map_primary_map,
find_primary_handle),
(self.reference_map, self.reference_map_referenced_map,
@@ -650,6 +649,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
( self.eid_trans, EIDTRANS ),
( self.rid_trans, RIDTRANS ),
( self.nid_trans, NIDTRANS ),
( self.tag_trans, TAGTRANS ),
( self.reference_map_primary_map, REF_PRI),
( self.reference_map_referenced_map, REF_REF),
]
@@ -924,6 +924,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
(self.get_media_cursor, MediaObject),
(self.get_repository_cursor, Repository),
(self.get_note_cursor, Note),
(self.get_tag_cursor, Tag),
)
# Now we use the functions and classes defined above
@@ -1014,7 +1015,6 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
self.__close_metadata()
self.name_group.close()
self.tags.close()
self.surnames.close()
self.id_trans.close()
self.fid_trans.close()
@@ -1024,6 +1024,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
self.oid_trans.close()
self.sid_trans.close()
self.pid_trans.close()
self.tag_trans.close()
self.reference_map_primary_map.close()
self.reference_map_referenced_map.close()
self.reference_map.close()
@@ -1039,6 +1040,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
self.source_map.close()
self.media_map.close()
self.event_map.close()
self.tag_map.close()
self.env.close()
self.__close_undodb()
@@ -1050,8 +1052,8 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
self.source_map = None
self.media_map = None
self.event_map = None
self.tag_map = None
self.surnames = None
self.tags = None
self.env = None
self.metadata = None
self.db_is_open = False
@@ -1181,6 +1183,13 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
self.find_next_note_gramps_id if set_gid else None,
self.commit_note)
def add_tag(self, obj, transaction):
"""
Add a Tag to the database, assigning a handle if it has not already
been defined.
"""
return self.__add_object(obj, transaction, None, self.commit_tag)
def __do_remove(self, handle, transaction, data_map, key):
if self.readonly or not handle:
return
@@ -1272,6 +1281,14 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
self.__do_remove(handle, transaction, self.note_map,
NOTE_KEY)
def remove_tag(self, handle, transaction):
"""
Remove the Tag specified by the database handle from the
database, preserving the change in the passed transaction.
"""
self.__do_remove(handle, transaction, self.tag_map,
TAG_KEY)
@catch_db_error
def set_name_group_mapping(self, name, group):
if not self.readonly:
@@ -1289,24 +1306,6 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
grouppar = group
self.emit('person-groupname-rebuild', (name, grouppar))
@catch_db_error
def set_tag(self, tag_name, color_str):
"""
Set the color of a tag.
"""
if not self.readonly:
# Start transaction
with BSDDBTxn(self.env, self.tags) as txn:
data = txn.get(tag_name)
if data is not None:
txn.delete(tag_name)
if color_str is not None:
txn.put(tag_name, color_str)
if data is not None and color_str is not None:
self.emit('tag-update', (tag_name, color_str))
else:
self.emit('tags-changed')
def sort_surname_list(self):
self.surname_list.sort(key=locale.strxfrm)
@@ -1560,6 +1559,14 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
if note.type.is_custom():
self.note_types.add(str(note.type))
def commit_tag(self, tag, transaction, change_time=None):
"""
Commit the specified Tag to the database, storing the changes as part
of the transaction.
"""
self.commit_base(tag, self.tag_map, TAG_KEY,
transaction, change_time)
def get_from_handle(self, handle, class_type, data_map):
try:
data = data_map.get(str(handle), txn=self.txn)

View File

@@ -60,6 +60,8 @@ pkgdata_PYTHON = \
styledtext.py \
styledtexttag.py \
styledtexttagtype.py \
tableobj.py \
tag.py \
tagbase.py \
urlbase.py \
url.py \

View File

@@ -52,6 +52,9 @@ from gen.lib.mediaobj import MediaObject
from gen.lib.repo import Repository
from gen.lib.note import Note
# Table objects
from gen.lib.tag import Tag
# These are actually metadata
from gen.lib.genderstats import GenderStats
from gen.lib.researcher import Researcher

View File

@@ -235,6 +235,8 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
elif classname == 'Place':
return any(ordinance.place == handle
for ordinance in self.lds_ord_list)
elif classname == 'Tag':
return handle in self.tag_list
return False
def _remove_handle_references(self, classname, handle_list):
@@ -277,6 +279,9 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
for ordinance in self.lds_ord_list:
if ordinance.place in handle_list:
ordinance.place = None
elif classname == 'Tag':
for handle in handle_list:
self.tag_list.remove(handle)
def _replace_handle_reference(self, classname, old_handle, new_handle):
if classname == 'Event':
@@ -335,7 +340,6 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
while old_handle in self.parent_family_list:
ix = self.parent_family_list.index(old_handle)
self.parent_family_list[ix] = new_handle
elif classname == 'Place':
handle_list = [ordinance.place for ordinance in self.lds_ord_list]
while old_handle in handle_list:
ix = handle_list.index(old_handle)
@@ -403,7 +407,8 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
"""
return [('Family', handle) for handle in
(self.family_list + self.parent_family_list)] \
+ self.get_referenced_note_handles()
+ self.get_referenced_note_handles() \
+ self.get_referenced_tag_handles()
def get_handle_referents(self):
"""

View File

@@ -24,41 +24,23 @@
Basic Primary Object class for GRAMPS.
"""
#-------------------------------------------------------------------------
#
# standard python modules
#
#-------------------------------------------------------------------------
import time
import locale
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from gen.lib.baseobj import BaseObject
from gen.lib.tableobj import TableObject
from gen.lib.privacybase import PrivacyBase
from gen.lib.markertype import MarkerType
from gen.lib.srcbase import SourceBase
from gen.lib.mediabase import MediaBase
#-------------------------------------------------------------------------
#
# Localized constants
#
#-------------------------------------------------------------------------
try:
CODESET = locale.nl_langinfo(locale.CODESET)
except:
CODESET = locale.getpreferredencoding()
#-------------------------------------------------------------------------
#
# Basic Primary Object class
#
#-------------------------------------------------------------------------
class BasicPrimaryObject(BaseObject, PrivacyBase):
class BasicPrimaryObject(TableObject, PrivacyBase):
"""
The BasicPrimaryObject is the base class for Note objects.
@@ -82,73 +64,15 @@ class BasicPrimaryObject(BaseObject, PrivacyBase):
:param source: Object used to initialize the new object
:type source: PrimaryObject
"""
TableObject.__init__(self, source)
PrivacyBase.__init__(self, source)
if source:
self.gramps_id = source.gramps_id
self.handle = source.handle
self.change = source.change
self.marker = source.marker
else:
self.gramps_id = None
self.handle = None
self.change = 0
self.marker = MarkerType()
def get_change_time(self):
"""
Return the time that the data was last changed.
The value in the format returned by the time.time() command.
:returns: Time that the data was last changed. The value in the format
returned by the time.time() command.
:rtype: int
"""
return self.change
def set_change_time(self, change):
"""
Modify the time that the data was last changed.
The value must be in the format returned by the time.time() command.
@param change: new time
@type change: int in format as time.time() command
"""
self.change = change
def get_change_display(self):
"""
Return the string representation of the last change time.
:returns: string representation of the last change time.
:rtype: str
"""
if self.change:
return unicode(time.strftime('%x %X', time.localtime(self.change)),
CODESET)
else:
return u''
def set_handle(self, handle):
"""
Set the database handle for the primary object.
:param handle: object database handle
:type handle: str
"""
self.handle = handle
def get_handle(self):
"""
Return the database handle for the primary object.
:returns: database handle associated with the object
:rtype: str
"""
return self.handle
def set_gramps_id(self, gramps_id):
"""
Set the GRAMPS ID for the primary object.

139
src/gen/lib/tableobj.py Normal file
View File

@@ -0,0 +1,139 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2010 Nick Hall
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"""
Table Object class for Gramps.
"""
#-------------------------------------------------------------------------
#
# standard python modules
#
#-------------------------------------------------------------------------
import time
import locale
#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from gen.lib.baseobj import BaseObject
#-------------------------------------------------------------------------
#
# Localized constants
#
#-------------------------------------------------------------------------
try:
CODESET = locale.nl_langinfo(locale.CODESET)
except:
CODESET = locale.getpreferredencoding()
#-------------------------------------------------------------------------
#
# Table Object class
#
#-------------------------------------------------------------------------
class TableObject(BaseObject):
"""
The TableObject is the base class for all objects that are stored in a
seperate database table. Each object has a database handle and a last
changed time. The database handle is used as the unique key for a record
in the database. This is not the same as the Gramps ID, which is a user
visible identifier for a record.
It is the base class for the BasicPrimaryObject class and Tag class.
"""
def __init__(self, source=None):
"""
Initialize a TableObject.
If source is None, the handle is assigned as an empty string.
If source is not None, then the handle is initialized from the value in
the source object.
:param source: Object used to initialize the new object
:type source: TableObject
"""
if source:
self.handle = source.handle
self.change = source.change
else:
self.handle = None
self.change = 0
def get_change_time(self):
"""
Return the time that the data was last changed.
The value in the format returned by the time.time() command.
:returns: Time that the data was last changed. The value in the format
returned by the time.time() command.
:rtype: int
"""
return self.change
def set_change_time(self, change):
"""
Modify the time that the data was last changed.
The value must be in the format returned by the time.time() command.
@param change: new time
@type change: int in format as time.time() command
"""
self.change = change
def get_change_display(self):
"""
Return the string representation of the last change time.
:returns: string representation of the last change time.
:rtype: str
"""
if self.change:
return unicode(time.strftime('%x %X', time.localtime(self.change)),
CODESET)
else:
return u''
def set_handle(self, handle):
"""
Set the database handle for the primary object.
:param handle: object database handle
:type handle: str
"""
self.handle = handle
def get_handle(self):
"""
Return the database handle for the primary object.
:returns: database handle associated with the object
:rtype: str
"""
return self.handle

202
src/gen/lib/tag.py Normal file
View File

@@ -0,0 +1,202 @@
#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2010 Nick Hall
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# $Id$
"""
Tag object for GRAMPS.
"""
#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from gen.lib.tableobj import TableObject
#-------------------------------------------------------------------------
#
# Tag class
#
#-------------------------------------------------------------------------
class Tag(TableObject):
"""
The Tag record is used to store information about a tag that can be
attached to a primary object.
"""
def __init__(self, source=None):
"""
Create a new Tag instance, copying from the source if present.
:param source: A tag used to initialize the new tag
:type source: Tag
"""
TableObject.__init__(self, source)
if source:
self.__name = source.__name
self.__color = source.__color
self.__priority = source.__priority
else:
self.__name = ""
self.__color = "#000000000000" # Black
self.__priority = 0
def serialize(self):
"""
Convert the data held in the event to a Python tuple that
represents all the data elements.
This method is used to convert the object into a form that can easily
be saved to a database.
These elements may be primitive Python types (string, integers),
complex Python types (lists or tuples, or Python objects. If the
target database cannot handle complex types (such as objects or
lists), the database is responsible for converting the data into
a form that it can use.
:returns: Returns a python tuple containing the data that should
be considered persistent.
:rtype: tuple
"""
return (self.handle,
self.__name,
self.__color,
self.__priority,
self.change)
def unserialize(self, data):
"""
Convert the data held in a tuple created by the serialize method
back into the data in a Tag structure.
:param data: tuple containing the persistent data associated the
Person object
:type data: tuple
"""
(self.handle,
self.__name,
self.__color,
self.__priority,
self.change) = data
def get_text_data_list(self):
"""
Return the list of all textual attributes of the object.
:returns: Returns the list of all textual attributes of the object.
:rtype: list
"""
return [self.__name]
def is_empty(self):
"""
Return True if the Tag is an empty object (no values set).
:returns: True if the Tag is empty
:rtype: bool
"""
return self.__name != ""
def are_equal(self, other):
"""
Return True if the passed Tag is equivalent to the current Tag.
:param other: Tag to compare against
:type other: Tag
:returns: True if the Tags are equal
:rtype: bool
"""
if other is None:
other = Tag()
if self.__name != other.__name or \
self.__color != other.__color or \
self.__priority != other.__priority:
return False
return True
def set_name(self, name):
"""
Set the name of the Tag to the passed string.
:param the_type: Name to assign to the Tag
:type the_type: str
"""
self.__name = name
def get_name(self):
"""
Return the name of the Tag.
:returns: Name of the Tag
:rtype: str
"""
return self.__name
name = property(get_name, set_name, None,
'Returns or sets name of the tag')
def set_color(self, color):
"""
Set the color of the Tag to the passed string.
The string is of the format #rrrrggggbbbb.
:param color: Color to assign to the Tag
:type color: str
"""
self.__color = color
def get_color(self) :
"""
Return the color of the Tag.
:returns: Returns the color of the Tag
:rtype: str
"""
return self.__color
color = property(get_color, set_color, None,
'Returns or sets color of the tag')
def set_priority(self, priority):
"""
Set the priority of the Tag to the passed integer.
The lower the value the higher the priority.
:param priority: Priority to assign to the Tag
:type priority: int
"""
self.__priority = priority
def get_priority(self) :
"""
Return the priority of the Tag.
:returns: Returns the priority of the Tag
:rtype: int
"""
return self.__priority
priority = property(get_priority, set_priority, None,
'Returns or sets priority of the tag')

View File

@@ -105,3 +105,15 @@ class TagBase(object):
:type tag_list: list
"""
self.tag_list = tag_list
def get_referenced_tag_handles(self):
"""
Return the list of (classname, handle) tuples for all referenced tags.
This method should be used to get the :class:`~gen.lib.tag.Tag` portion
of the list by objects that store tag lists.
:returns: List of (classname, handle) tuples for referenced objects.
:rtype: list
"""
return [('Tag', handle) for handle in self.tag_list]

View File

@@ -310,7 +310,7 @@ class EditPerson(EditPrimary):
self.top.get_object("tag_button"),
self.obj.set_tag_list,
self.obj.get_tag_list,
self.db.get_all_tags(),
self.db,
self.uistate, self.track,
self.db.readonly)

View File

@@ -26,7 +26,7 @@ Provide tagging functionality.
# Python modules
#
#-------------------------------------------------------------------------
import locale
from bisect import insort_left
#-------------------------------------------------------------------------
#
@@ -41,7 +41,9 @@ import gtk
#
#-------------------------------------------------------------------------
from gen.ggettext import sgettext as _
from ListModel import ListModel, NOSORT, COLOR
from gen.lib import Tag
from gui.dbguielement import DbGUIElement
from ListModel import ListModel, NOSORT, COLOR, INTEGER
import const
import GrampsDisplay
from QuestionDialog import QuestionDialog2
@@ -85,19 +87,27 @@ WIKI_HELP_SEC = _('manual|Tags')
# Tags
#
#-------------------------------------------------------------------------
class Tags(object):
class Tags(DbGUIElement):
"""
Provide tagging functionality.
"""
def __init__(self, uistate, dbstate):
self.signal_map = {
'tag-add' : self._tag_add,
'tag-delete' : self._tag_delete,
'tag-rebuild' : self._tag_rebuild
}
DbGUIElement.__init__(self, dbstate.db)
self.db = dbstate.db
self.uistate = uistate
self.tag_id = None
self.tag_ui = None
self.tag_action = None
self.__tag_list = []
dbstate.connect('database-changed', self.db_changed)
dbstate.connect('database-changed', self._db_changed)
self._build_tag_menu()
@@ -118,12 +128,47 @@ class Tags(object):
self.uistate.uimanager.ensure_update()
self.tag_id = None
def db_changed(self, db):
def _db_changed(self, db):
"""
When the database chages update the tag list and rebuild the menus.
Called when the database is changed.
"""
self.db = db
self.db.connect('tags-changed', self.update_tag_menu)
self._change_db(db)
self._tag_rebuild()
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.db.get_tag_from_handle(handle)
insort_left(self.__tag_list, (tag.get_name(), handle))
self.update_tag_menu()
def _tag_delete(self, handle_list):
"""
Called when tags are deleted.
"""
for handle in handle_list:
tag = self.db.get_tag_from_handle(handle)
self.__tag_list.remove((tag.get_name(), handle))
self.update_tag_menu()
def _tag_rebuild(self):
"""
Called when the tag list needs to be rebuilt.
"""
self.__tag_list = []
for handle in self.db.get_tag_handles():
tag = self.db.get_tag_from_handle(handle)
self.__tag_list.append((tag.get_name(), tag.get_handle()))
self.update_tag_menu()
def update_tag_menu(self):
@@ -151,10 +196,10 @@ class Tags(object):
tag_menu = '<menuitem action="NewTag"/>'
tag_menu += '<menuitem action="OrganizeTags"/>'
tag_menu += '<separator/>'
for tag_name in sorted(self.db.get_all_tags(), key=locale.strxfrm):
for tag_name, handle in self.__tag_list:
tag_menu += '<menuitem action="TAG_%s"/>' % tag_name
actions.append(('TAG_%s' % tag_name, None, tag_name, None, None,
make_callback(self.tag_selected, tag_name)))
make_callback(self.tag_selected_rows, handle)))
self.tag_ui = TAG_1 + tag_menu + TAG_2 + tag_menu + TAG_3
@@ -190,17 +235,40 @@ class Tags(object):
"""
new_dialog = NewTagDialog(self.uistate.window)
tag_name, color_str = new_dialog.run()
if tag_name and not self.db.has_tag(tag_name):
self.db.set_tag(tag_name, color_str)
self.tag_selected(tag_name)
self.update_tag_menu()
if tag_name and not self.db.get_tag_from_name(tag_name):
trans = self.db.transaction_begin()
tag = Tag()
tag.set_name(tag_name)
tag.set_color(color_str)
tag.set_priority(self.db.get_number_of_tags())
self.db.add_tag(tag, trans)
self.db.transaction_commit(trans, _('Add Tag (%s)') % tag_name)
self.tag_selected_rows(tag.get_handle())
def tag_selected(self, tag_name):
def tag_selected_rows(self, tag_handle):
"""
Tag the selected objects with the given tag.
Tag the selected rows with the given tag.
"""
view = self.uistate.viewmanager.active_page
view.add_tag(tag_name)
selected = view.selected_handles()
pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog,
popup_time=2)
status = progressdlg.LongOpStatus(msg=_("Adding Tags"),
total_steps=len(selected),
interval=len(selected)//20,
can_cancel=True)
pmon.add_op(status)
trans = self.db.transaction_begin()
for object_handle in selected:
status.heartbeat()
if status.should_cancel():
break
view.add_tag(trans, object_handle, tag_handle)
if not status.was_cancelled():
tag = self.db.get_tag_from_handle(tag_handle)
msg = _('Tag Selection (%s)') % tag.get_name()
self.db.transaction_commit(trans, msg)
status.end()
def cb_menu_position(menu, button):
"""
@@ -212,11 +280,11 @@ def cb_menu_position(menu, button):
return (x_pos, y_pos, False)
def make_callback(func, tag_name):
def make_callback(func, tag_handle):
"""
Generates a callback function based off the passed arguments
"""
return lambda x: func(tag_name)
return lambda x: func(tag_handle)
#-------------------------------------------------------------------------
#
@@ -246,15 +314,43 @@ class OrganizeTagsDialog(object):
section=WIKI_HELP_SEC)
else:
break
# Save changed priority values
trans = self.db.transaction_begin()
if self.__change_tag_priority(trans):
self.db.transaction_commit(trans, _('Change Tag Priority'))
self.top.destroy()
def __change_tag_priority(self, trans):
"""
Change the priority of the tags. The order of the list corresponds to
the priority of the tags. The top tag in the list is the highest
priority tag.
"""
changed = False
for new_priority, row in enumerate(self.namemodel.model):
if row[0] != new_priority:
changed = True
tag = self.db.get_tag_from_handle(row[1])
tag.set_priority(new_priority)
self.db.commit_tag(tag, trans)
return changed
def _populate_model(self):
"""
Populate the model.
"""
self.namemodel.clear()
for tag in sorted(self.db.get_all_tags(), key=locale.strxfrm):
self.namemodel.add([tag, self.db.get_tag(tag)])
tags = []
for tag in self.db.iter_tags():
tags.append((tag.get_priority(),
tag.get_handle(),
tag.get_name(),
tag.get_color()))
for row in sorted(tags):
self.namemodel.add(row)
def _create_dialog(self):
"""
@@ -275,7 +371,9 @@ class OrganizeTagsDialog(object):
box = gtk.HBox()
top.vbox.pack_start(box, 1, 1, 5)
name_titles = [(_('Name'), NOSORT, 200),
name_titles = [('', NOSORT, 20, INTEGER), # Priority
('', NOSORT, 100), # Handle
(_('Name'), NOSORT, 200),
(_('Color'), NOSORT, 50, COLOR)]
self.namelist = gtk.TreeView()
self.namemodel = ListModel(self.namelist, name_titles)
@@ -287,14 +385,20 @@ class OrganizeTagsDialog(object):
bbox = gtk.VButtonBox()
bbox.set_layout(gtk.BUTTONBOX_START)
bbox.set_spacing(6)
up = gtk.Button(stock=gtk.STOCK_GO_UP)
down = gtk.Button(stock=gtk.STOCK_GO_DOWN)
add = gtk.Button(stock=gtk.STOCK_ADD)
edit = gtk.Button(stock=gtk.STOCK_EDIT)
remove = gtk.Button(stock=gtk.STOCK_REMOVE)
up.connect('clicked', self.cb_up_clicked)
down.connect('clicked', self.cb_down_clicked)
add.connect('clicked', self.cb_add_clicked, top)
edit.connect('clicked', self.cb_edit_clicked)
remove.connect('clicked', self.cb_remove_clicked, top)
top.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
top.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
bbox.add(up)
bbox.add(down)
bbox.add(add)
bbox.add(edit)
bbox.add(remove)
@@ -302,15 +406,36 @@ class OrganizeTagsDialog(object):
top.show_all()
return top
def cb_up_clicked(self, obj):
"""
Move the current selection up one row.
"""
row = self.namemodel.get_selected_row()
self.namemodel.move_up(row)
def cb_down_clicked(self, obj):
"""
Move the current selection down one row.
"""
row = self.namemodel.get_selected_row()
self.namemodel.move_down(row)
def cb_add_clicked(self, button, top):
"""
Create a new tag.
"""
new_dialog = NewTagDialog(top)
tag_name, color_str = new_dialog.run()
if tag_name and not self.db.has_tag(tag_name):
self.db.set_tag(tag_name, color_str)
self._populate_model()
if tag_name and not self.db.get_tag_from_name(tag_name):
trans = self.db.transaction_begin()
tag = Tag()
tag.set_name(tag_name)
tag.set_color(color_str)
priority = self.db.get_number_of_tags() # Lowest
tag.set_priority(priority)
handle = self.db.add_tag(tag, trans)
self.db.transaction_commit(trans, _('Add Tag (%s)') % tag_name)
self.namemodel.add((priority, handle, tag_name, color_str))
def cb_edit_clicked(self, button):
"""
@@ -320,8 +445,9 @@ class OrganizeTagsDialog(object):
store, iter_ = self.namemodel.get_selected()
if iter_ is None:
return
tag_name = store.get_value(iter_, 0)
old_color = gtk.gdk.Color(store.get_value(iter_, 1))
handle = store.get_value(iter_, 1)
tag_name = store.get_value(iter_, 2)
old_color = gtk.gdk.Color(store.get_value(iter_, 3))
title = _("%(title)s - Gramps") % {'title': _("Pick a Color")}
colorseldlg = gtk.ColorSelectionDialog(title)
@@ -331,8 +457,12 @@ class OrganizeTagsDialog(object):
response = colorseldlg.run()
if response == gtk.RESPONSE_OK:
color_str = colorseldlg.colorsel.get_current_color().to_string()
self.db.set_tag(tag_name, color_str)
store.set_value(iter_, 1, color_str)
trans = self.db.transaction_begin()
tag = self.db.get_tag_from_handle(handle)
tag.set_color(color_str)
self.db.commit_tag(tag, trans)
self.db.transaction_commit(trans, _('Edit Tag (%s)') % tag_name)
store.set_value(iter_, 3, color_str)
colorseldlg.destroy()
def cb_remove_clicked(self, button, top):
@@ -342,7 +472,8 @@ class OrganizeTagsDialog(object):
store, iter_ = self.namemodel.get_selected()
if iter_ is None:
return
tag_name = store.get_value(iter_, 0)
tag_handle = store.get_value(iter_, 1)
tag_name = store.get_value(iter_, 2)
yes_no = QuestionDialog2(
_("Remove tag '%s'?") % tag_name,
@@ -352,36 +483,49 @@ class OrganizeTagsDialog(object):
_("No"))
prompt = yes_no.run()
if prompt:
self.remove_tag(tag_name)
store.remove(iter_)
def remove_tag(self, tag_name):
"""
Remove the tag from all objects and delete the tag.
"""
items = self.db.get_number_of_people()
pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog,
popup_time=2)
status = progressdlg.LongOpStatus(msg=_("Removing Tags"),
total_steps=items,
interval=items//20,
can_cancel=True)
pmon.add_op(status)
trans = self.db.transaction_begin()
for handle in self.db.get_person_handles():
status.heartbeat()
if status.should_cancel():
break
person = self.db.get_person_from_handle(handle)
tags = person.get_tag_list()
if tag_name in tags:
tags.remove(tag_name)
person.set_tag_list(tags)
self.db.commit_person(person, trans)
if not status.was_cancelled():
self.db.set_tag(tag_name, None)
self.db.transaction_commit(trans, _('Remove tag %s') % tag_name)
status.end()
fnc = {'Person': (self.db.get_person_from_handle,
self.db.commit_person),
'Family': (self.db.get_family_from_handle,
self.db.commit_family),
'Event': (self.db.get_event_from_handle,
self.db.commit_event),
'Place': (self.db.get_place_from_handle,
self.db.commit_place),
'Source': (self.db.get_source_from_handle,
self.db.commit_source),
'Repository': (self.db.get_repository_from_handle,
self.db.commit_repository),
'MediaObject': (self.db.get_object_from_handle,
self.db.commit_media_object),
'Note': (self.db.get_note_from_handle,
self.db.commit_note)}
links = [link for link in self.db.find_backlink_handles(tag_handle)]
pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog,
popup_time=2)
status = progressdlg.LongOpStatus(msg=_("Removing Tags"),
total_steps=len(links),
interval=len(links)//20,
can_cancel=True)
pmon.add_op(status)
trans = self.db.transaction_begin()
for classname, handle in links:
status.heartbeat()
if status.should_cancel():
break
obj = fnc[classname][0](handle) # get from handle
obj.remove_tag(tag_handle)
fnc[classname][1](obj, trans) # commit
self.db.remove_tag(tag_handle, trans)
self.__change_tag_priority(trans)
if not status.was_cancelled():
msg = _('Delete Tag (%s)') % tag_name
self.db.transaction_commit(trans, msg)
store.remove(iter_)
status.end()
#-------------------------------------------------------------------------
#

View File

@@ -34,6 +34,7 @@ TreeModel for the GRAMPS Person tree.
#-------------------------------------------------------------------------
from gen.ggettext import gettext as _
import cgi
import locale
#-------------------------------------------------------------------------
#
@@ -149,21 +150,6 @@ class PeopleBaseModel(object):
self.lru_bdate = LRU(PeopleBaseModel._CACHE_SIZE)
self.lru_ddate = LRU(PeopleBaseModel._CACHE_SIZE)
db.connect('tags-changed', self._tags_changed)
self._tags_changed()
def _tags_changed(self):
"""
Refresh the tag colors when a tag is added or deleted.
"""
self.tag_colors = self.db.get_tag_colors()
def update_tag(self, tag_name, color_str):
"""
Update the tag color and signal that affected rows have been updated.
"""
self.tag_colors[tag_name] = color_str
def marker_column(self):
"""
Return the column for marker colour.
@@ -450,13 +436,32 @@ class PeopleBaseModel(object):
def column_int_id(self, data):
return data[0]
def get_tag_name(self, tag_handle):
"""
Return the tag name from the given tag handle.
"""
return self.db.get_tag_from_handle(tag_handle).get_name()
def column_tag_color(self, data):
if len(data[COLUMN_TAGS]) > 0:
return self.tag_colors.get(data[COLUMN_TAGS][0])
return None
"""
Return the tag color.
"""
tag_color = None
tag_priority = None
for handle in data[COLUMN_TAGS]:
tag = self.db.get_tag_from_handle(handle)
this_priority = tag.get_priority()
if tag_priority is None or this_priority < tag_priority:
tag_color = tag.get_color()
tag_priority = this_priority
return tag_color
def column_tags(self, data):
return ','.join(data[COLUMN_TAGS])
"""
Return the sorted list of tags.
"""
tag_list = map(self.get_tag_name, data[COLUMN_TAGS])
return ','.join(sorted(tag_list, key=locale.strxfrm))
class PersonListModel(PeopleBaseModel, FlatBaseModel):
"""

View File

@@ -50,7 +50,7 @@ import gtk
from gen.ggettext import gettext as _
import AutoComp
import DateEdit
from tageditor import TagEditor
from gui.widgets.tageditor import TagEditor
#-------------------------------------------------------------------------
#
@@ -59,7 +59,8 @@ from tageditor import TagEditor
#-------------------------------------------------------------------------
class MonitoredCheckbox(object):
def __init__(self, obj, button, set_val, get_val, on_toggle=None, readonly = False):
def __init__(self, obj, button, set_val, get_val, on_toggle=None,
readonly = False):
self.button = button
self.button.connect('toggled', self._on_toggle)
self.on_toggle = on_toggle
@@ -610,15 +611,23 @@ class MonitoredTagList(object):
A MonitoredTagList consists of a label to display a list of tags and a
button to invoke the tag editor.
"""
def __init__(self, label, button, set_list, get_list, full_list,
def __init__(self, label, button, set_list, get_list, db,
uistate, track, readonly=False):
self.uistate = uistate
self.track = track
self.db = db
self.set_list = set_list
self.tag_list = get_list()
self.all_tags = full_list
self.tag_list = []
for handle in get_list():
tag = self.db.get_tag_from_handle(handle)
self.tag_list.append((handle, tag.get_name()))
self.all_tags = []
for tag in self.db.iter_tags():
self.all_tags.append((tag.get_handle(), tag.get_name()))
self.label = label
self.label.set_alignment(0, 0.5)
image = gtk.Image()
@@ -636,7 +645,7 @@ class MonitoredTagList(object):
"""
Display the tag list.
"""
tag_text = ','.join(self.tag_list)
tag_text = ','.join(item[1] for item in self.tag_list)
self.label.set_text(tag_text)
self.label.set_tooltip_text(tag_text)
@@ -649,5 +658,4 @@ class MonitoredTagList(object):
if editor.return_list is not None:
self.tag_list = editor.return_list
self._display()
self.set_list(self.tag_list)
self.set_list([item[0] for item in self.tag_list])

View File

@@ -21,13 +21,6 @@
"""
Tag editing module for Gramps.
"""
#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import locale
#-------------------------------------------------------------------------
#
# GNOME modules
@@ -75,8 +68,8 @@ class TagEditor(ManagedWindow.ManagedWindow):
top = self._create_dialog()
self.set_window(top, None, _('Tag selection'))
for tag_name in sorted(full_list, key=locale.strxfrm):
self.namemodel.add([tag_name, tag_name in tag_list])
for tag in full_list:
self.namemodel.add([tag[0], tag[1], tag in tag_list])
self.namemodel.connect_model()
# The dialog is modal. We don't want to have several open dialogs of
@@ -93,8 +86,9 @@ class TagEditor(ManagedWindow.ManagedWindow):
break
else:
if response == gtk.RESPONSE_OK:
self.return_list = [row[0] for row in self.namemodel.model
if row[1]]
self.return_list = [(row[0], row[1])
for row in self.namemodel.model
if row[2]]
self.close()
break
@@ -110,8 +104,9 @@ class TagEditor(ManagedWindow.ManagedWindow):
top.set_has_separator(False)
top.vbox.set_spacing(5)
columns = [(_('Tag'), -1, 300),
(_(' '), -1, 25, TOGGLE, True, None)]
columns = [('', -1, 300),
(_('Tag'), -1, 300),
(' ', -1, 25, TOGGLE, True, None)]
view = gtk.TreeView()
self.namemodel = ListModel(view, columns)

View File

@@ -58,7 +58,6 @@ from DdTargets import DdTargets
from gui.editors import EditPerson
from Filters.SideBar import PersonSidebarFilter
from gen.plug import CATEGORY_QR_PERSON
import gui.widgets.progressdialog as progressdlg
#-------------------------------------------------------------------------
#
@@ -66,7 +65,6 @@ import gui.widgets.progressdialog as progressdlg
#
#-------------------------------------------------------------------------
from gen.ggettext import sgettext as _
from bisect import insort_left
#-------------------------------------------------------------------------
#
@@ -106,7 +104,8 @@ class BasePersonView(ListView):
CONFIGSETTINGS = (
('columns.visible', [COL_NAME, COL_ID, COL_GEN, COL_BDAT, COL_DDAT]),
('columns.rank', [COL_NAME, COL_ID, COL_GEN, COL_BDAT, COL_BPLAC,
COL_DDAT, COL_DPLAC, COL_SPOUSE, COL_TAGS, COL_CHAN]),
COL_DDAT, COL_DPLAC, COL_SPOUSE, COL_TAGS,
COL_CHAN]),
('columns.size', [250, 75, 75, 100, 175, 100, 175, 100, 100, 100])
)
ADD_MSG = _("Add a new person")
@@ -146,6 +145,9 @@ class BasePersonView(ListView):
uistate.connect('nameformat-changed', self.build_tree)
def navigation_type(self):
"""
Return the navigation type of the view.
"""
return 'Person'
def get_bookmarks(self):
@@ -163,8 +165,9 @@ class BasePersonView(ListView):
def exact_search(self):
"""
Returns a tuple indicating columns requiring an exact search
'female' contains the string 'male' so we need an exact search
"""
return (BasePersonView.COL_GEN,) # Gender ('female' contains the string 'male')
return (BasePersonView.COL_GEN,)
def get_stock(self):
"""
@@ -242,6 +245,9 @@ class BasePersonView(ListView):
</ui>'''
def get_handle_from_gramps_id(self, gid):
"""
Return the handle of the person having the given Gramps ID.
"""
obj = self.dbstate.db.get_person_from_gramps_id(gid)
if obj:
return obj.get_handle()
@@ -249,6 +255,9 @@ class BasePersonView(ListView):
return None
def add(self, obj):
"""
Add a new person to the database.
"""
person = gen.lib.Person()
try:
@@ -257,6 +266,9 @@ class BasePersonView(ListView):
pass
def edit(self, obj):
"""
Edit an existing person in the database.
"""
for handle in self.selected_handles():
person = self.dbstate.db.get_person_from_handle(handle)
try:
@@ -265,6 +277,9 @@ class BasePersonView(ListView):
pass
def remove(self, obj):
"""
Remove a person from the database.
"""
for sel in self.selected_handles():
person = self.dbstate.db.get_person_from_handle(sel)
self.active_person = person
@@ -331,8 +346,8 @@ class BasePersonView(ListView):
self.all_action.add_actions([
('FilterEdit', None, _('Person Filter Editor'), None, None,
self.filter_editor),
('Edit', gtk.STOCK_EDIT, _("action|_Edit..."), "<control>Return",
_("Edit the selected person"), self.edit),
('Edit', gtk.STOCK_EDIT, _("action|_Edit..."),
"<control>Return", _("Edit the selected person"), self.edit),
('QuickReport', None, _("Quick View"), None, None, None),
('WebConnect', None, _("Web Connection"), None, None, None),
('Dummy', None, ' ', None, None, self.dummy_report),
@@ -347,19 +362,26 @@ class BasePersonView(ListView):
_("Remove the Selected Person"), self.remove),
('Merge', 'gramps-merge', _('_Merge...'), None, None,
self.merge),
('ExportTab', None, _('Export View...'), None, None, self.export),
('ExportTab', None, _('Export View...'), None, None,
self.export),
])
self._add_action_group(self.edit_action)
self._add_action_group(self.all_action)
def enable_action_group(self, obj):
"""
Turns on the visibility of the View's action group.
"""
ListView.enable_action_group(self, obj)
self.all_action.set_visible(True)
self.edit_action.set_visible(True)
self.edit_action.set_sensitive(not self.dbstate.db.readonly)
def disable_action_group(self):
"""
Turns off the visibility of the View's action group.
"""
ListView.disable_action_group(self)
self.all_action.set_visible(False)
@@ -395,56 +417,22 @@ class BasePersonView(ListView):
import Merge
Merge.MergePeople(self.dbstate, self.uistate, mlist[0], mlist[1])
def tag_updated(self, tag_name, tag_color):
def tag_updated(self, handle_list):
"""
Update tagged rows when a tag color changes.
"""
self.model.update_tag(tag_name, tag_color)
if not self.active:
return
items = self.dbstate.db.get_number_of_people()
pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog,
popup_time=2)
status = progressdlg.LongOpStatus(msg=_("Updating View"),
total_steps=items, interval=items//20,
can_cancel=True)
pmon.add_op(status)
for handle in self.dbstate.db.get_person_handles():
person = self.dbstate.db.get_person_from_handle(handle)
status.heartbeat()
if status.should_cancel():
break
tags = person.get_tag_list()
if len(tags) > 0 and tags[0] == tag_name:
self.row_update([handle])
if not status.was_cancelled():
status.end()
all_links = set([])
for tag_handle in handle_list:
links = set([link[1] for link in
self.dbstate.db.find_backlink_handles(tag_handle,
include_classes='Person')])
all_links = all_links.union(links)
self.row_update(list(all_links))
def add_tag(self, tag):
def add_tag(self, transaction, person_handle, tag_handle):
"""
Add the given tag to the selected objects.
Add the given tag to the given person.
"""
selected = self.selected_handles()
items = len(selected)
pmon = progressdlg.ProgressMonitor(progressdlg.GtkProgressDialog,
popup_time=2)
status = progressdlg.LongOpStatus(msg=_("Adding Tags"),
total_steps=items,
interval=items//20,
can_cancel=True)
pmon.add_op(status)
trans = self.dbstate.db.transaction_begin()
for handle in selected:
status.heartbeat()
if status.should_cancel():
break
person = self.dbstate.db.get_person_from_handle(handle)
tags = person.get_tag_list()
if tag not in tags:
insort_left(tags, tag)
person.set_tag_list(tags)
self.dbstate.db.commit_person(person, trans)
if not status.was_cancelled():
msg = _('Tag people with %s') % tag
self.dbstate.db.transaction_commit(trans, msg)
status.end()
person = self.dbstate.db.get_person_from_handle(person_handle)
person.add_tag(tag_handle)
self.dbstate.db.commit_person(person, transaction)