GEPS 011: Tagging - Converted to use new database table object
svn: r15921
This commit is contained in:
@@ -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()
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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),
|
||||
]
|
||||
|
@@ -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.
|
||||
|
@@ -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
|
||||
|
@@ -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_<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
|
||||
|
@@ -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):
|
||||
|
@@ -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 = {}
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -60,6 +60,8 @@ pkgdata_PYTHON = \
|
||||
styledtext.py \
|
||||
styledtexttag.py \
|
||||
styledtexttagtype.py \
|
||||
tableobj.py \
|
||||
tag.py \
|
||||
tagbase.py \
|
||||
urlbase.py \
|
||||
url.py \
|
||||
|
@@ -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
|
||||
|
@@ -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):
|
||||
"""
|
||||
|
@@ -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
139
src/gen/lib/tableobj.py
Normal 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
202
src/gen/lib/tag.py
Normal 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')
|
||||
|
@@ -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]
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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()
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
|
@@ -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):
|
||||
"""
|
||||
|
@@ -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])
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user