GEPS 011: Tagging
svn: r15830
@ -17,6 +17,7 @@ pkgdata_PYTHON = \
|
||||
_HasNoteSubstrBase.py\
|
||||
_HasReferenceCountBase.py \
|
||||
_HasSourceBase.py \
|
||||
_HasTagBase.py \
|
||||
_HasTextMatchingRegexpOf.py\
|
||||
_HasTextMatchingSubstringOf.py\
|
||||
__init__.py\
|
||||
|
@ -28,6 +28,7 @@ pkgdata_PYTHON = \
|
||||
_HasRelationship.py \
|
||||
_HasSource.py \
|
||||
_HasSourceOf.py \
|
||||
_HasTag.py \
|
||||
_HasTextMatchingRegexpOf.py \
|
||||
_HasTextMatchingSubstringOf.py \
|
||||
_HasUnknownGender.py \
|
||||
|
50
src/Filters/Rules/Person/_HasTag.py
Normal file
@ -0,0 +1,50 @@
|
||||
#
|
||||
# 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$
|
||||
"""
|
||||
Rule that checks for a person with a particular tag.
|
||||
"""
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Standard Python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.ggettext import gettext as _
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GRAMPS modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from Filters.Rules._HasTagBase import HasTagBase
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# HasTag
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class HasTag(HasTagBase):
|
||||
"""
|
||||
Rule that checks for a person with a particular tag.
|
||||
"""
|
||||
labels = [ _('Tag:') ]
|
||||
name = _('People with the <tag>')
|
||||
description = _("Matches people with the particular tag")
|
@ -49,6 +49,7 @@ from _HasNoteRegexp import HasNoteRegexp
|
||||
from _HasRelationship import HasRelationship
|
||||
from _HasSource import HasSource
|
||||
from _HasSourceOf import HasSourceOf
|
||||
from _HasTag import HasTag
|
||||
from _HasTextMatchingRegexpOf import HasTextMatchingRegexpOf
|
||||
from _HasTextMatchingSubstringOf import HasTextMatchingSubstringOf
|
||||
from _HasUnknownGender import HasUnknownGender
|
||||
@ -126,6 +127,7 @@ editor_rule_list = [
|
||||
HasFamilyEvent,
|
||||
HasAttribute,
|
||||
HasFamilyAttribute,
|
||||
HasTag,
|
||||
HasSource,
|
||||
HasSourceOf,
|
||||
HasMarkerOf,
|
||||
|
60
src/Filters/Rules/_HasTagBase.py
Normal file
@ -0,0 +1,60 @@
|
||||
#
|
||||
# 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$
|
||||
"""
|
||||
Rule that checks for an object with a particular tag.
|
||||
"""
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Standard Python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.ggettext import gettext as _
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GRAMPS modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from Filters.Rules import Rule
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# HasTag
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class HasTagBase(Rule):
|
||||
"""
|
||||
Rule that checks for an object with a particular tag.
|
||||
"""
|
||||
|
||||
labels = [ _('Tag:') ]
|
||||
name = _('Objects with the <tag>')
|
||||
description = _("Matches objects with the given tag")
|
||||
category = _('General filters')
|
||||
|
||||
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()
|
@ -26,6 +26,7 @@
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.ggettext import gettext as _
|
||||
import locale
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -45,8 +46,8 @@ import DateHandler
|
||||
|
||||
from Filters.SideBar import SidebarFilter
|
||||
from Filters.Rules.Person import (RegExpName, SearchName, RegExpIdOf,
|
||||
MatchIdOf, IsMale, IsFemale,
|
||||
HasUnknownGender, HasMarkerOf, HasEvent,
|
||||
MatchIdOf, IsMale, IsFemale, HasUnknownGender,
|
||||
HasMarkerOf, HasEvent, HasTag,
|
||||
HasBirth, HasDeath, HasNoteRegexp,
|
||||
HasNoteMatchingSubstringOf, MatchesFilter)
|
||||
from Filters import GenericFilter, build_filter_model, Rules
|
||||
@ -91,6 +92,8 @@ class PersonSidebarFilter(SidebarFilter):
|
||||
self.filter_marker.set_marker,
|
||||
self.filter_marker.get_marker)
|
||||
|
||||
self.tag = gtk.ComboBox()
|
||||
|
||||
self.filter_note = gtk.Entry()
|
||||
self.filter_gender = gtk.combo_box_new_text()
|
||||
map(self.filter_gender.append_text,
|
||||
@ -103,6 +106,8 @@ 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)
|
||||
@ -111,6 +116,12 @@ class PersonSidebarFilter(SidebarFilter):
|
||||
self.generic.add_attribute(cell, 'text', 0)
|
||||
self.on_filters_changed('Person')
|
||||
|
||||
cell = gtk.CellRendererText()
|
||||
cell.set_property('width', self._FILTER_WIDTH)
|
||||
cell.set_property('ellipsize', self._FILTER_ELLIPSIZE)
|
||||
self.tag.pack_start(cell, True)
|
||||
self.tag.add_attribute(cell, 'text', 0)
|
||||
|
||||
exdate1 = gen.lib.Date()
|
||||
exdate2 = gen.lib.Date()
|
||||
exdate1.set(gen.lib.Date.QUAL_NONE, gen.lib.Date.MOD_RANGE,
|
||||
@ -131,6 +142,7 @@ class PersonSidebarFilter(SidebarFilter):
|
||||
_('example: "%s" or "%s"') % (msg1, msg2))
|
||||
self.add_entry(_('Event'), self.etype)
|
||||
self.add_entry(_('Marker'), self.mtype)
|
||||
self.add_entry(_('Tag'), self.tag)
|
||||
self.add_text_entry(_('Note'), self.filter_note)
|
||||
self.add_filter_entry(_('Custom filter'), self.generic)
|
||||
self.add_entry(None, self.filter_regex)
|
||||
@ -144,6 +156,7 @@ class PersonSidebarFilter(SidebarFilter):
|
||||
self.filter_gender.set_active(0)
|
||||
self.etype.child.set_text(u'')
|
||||
self.mtype.child.set_text(u'')
|
||||
self.tag.set_active(0)
|
||||
self.generic.set_active(0)
|
||||
|
||||
def get_filter(self):
|
||||
@ -165,12 +178,13 @@ class PersonSidebarFilter(SidebarFilter):
|
||||
gender = self.filter_gender.get_active()
|
||||
regex = self.filter_regex.get_active()
|
||||
generic = self.generic.get_active() > 0
|
||||
tag = self.tag.get_active() > 0
|
||||
|
||||
# check to see if the filter is empty. If it is empty, then
|
||||
# we don't build a filter
|
||||
|
||||
empty = not (name or gid or birth or death or etype or mtype
|
||||
or note or gender or regex or generic)
|
||||
or note or gender or regex or generic or tag)
|
||||
if empty:
|
||||
generic_filter = None
|
||||
else:
|
||||
@ -209,6 +223,14 @@ class PersonSidebarFilter(SidebarFilter):
|
||||
rule = HasMarkerOf([mtype])
|
||||
generic_filter.add_rule(rule)
|
||||
|
||||
# check the Tag
|
||||
if tag:
|
||||
model = self.tag.get_model()
|
||||
node = self.tag.get_active_iter()
|
||||
attr = model.get_value(node, 0)
|
||||
rule = HasTag([attr])
|
||||
generic_filter.add_rule(rule)
|
||||
|
||||
# Build an event filter if needed
|
||||
if etype:
|
||||
rule = HasEvent([etype, u'', u'', u''])
|
||||
@ -251,3 +273,26 @@ class PersonSidebarFilter(SidebarFilter):
|
||||
all_filter.add_rule(Rules.Person.Everyone([]))
|
||||
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):
|
||||
"""
|
||||
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,))
|
||||
self.tag.set_model(model)
|
||||
self.tag.set_active(0)
|
||||
|
@ -46,6 +46,7 @@ class SidebarFilter(object):
|
||||
|
||||
self._init_interface()
|
||||
uistate.connect('filters-changed', self.on_filters_changed)
|
||||
dbstate.connect('database-changed', self._db_changed)
|
||||
self.uistate = uistate
|
||||
self.dbstate = dbstate
|
||||
self.namespace = namespace
|
||||
@ -137,6 +138,28 @@ class SidebarFilter(object):
|
||||
self.position += 1
|
||||
|
||||
def on_filters_changed(self, namespace):
|
||||
"""
|
||||
Called when filters are changed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def _db_changed(self, db):
|
||||
"""
|
||||
Called when the database is changed.
|
||||
"""
|
||||
db.connect('tags-changed', self.on_tags_changed)
|
||||
self.on_db_changed(db)
|
||||
|
||||
def on_db_changed(self, db):
|
||||
"""
|
||||
Called when the database is changed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def on_tags_changed(self):
|
||||
"""
|
||||
Called when tags are changed.
|
||||
"""
|
||||
pass
|
||||
|
||||
def add_filter_entry(self, text, widget):
|
||||
|
@ -765,6 +765,30 @@ 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.
|
||||
@ -1386,6 +1410,14 @@ 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.
|
||||
|
@ -291,6 +291,7 @@ 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
|
||||
@ -697,6 +698,30 @@ 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
|
||||
|
@ -26,6 +26,26 @@ from gen.db import BSDDBTxn
|
||||
upgrade
|
||||
"""
|
||||
|
||||
def gramps_upgrade_15(self):
|
||||
"""Upgrade database from version 14 to 15."""
|
||||
# This upgrade adds tagging
|
||||
length = len(self.person_map)
|
||||
self.set_total(length)
|
||||
|
||||
# ---------------------------------
|
||||
# Modify Person
|
||||
# ---------------------------------
|
||||
# Append the new tag field
|
||||
for handle in self.person_map.keys():
|
||||
person = self.person_map[handle]
|
||||
with BSDDBTxn(self.env, self.person_map) as txn:
|
||||
txn.put(str(handle), person.append([]))
|
||||
self.update()
|
||||
|
||||
# Bump up database version. Separate transaction to save metadata.
|
||||
with BSDDBTxn(self.env, self.metadata) as txn:
|
||||
txn.put('version', 15)
|
||||
|
||||
def gramps_upgrade_14(self):
|
||||
"""Upgrade database from version 13 to 14."""
|
||||
# This upgrade modifies notes and dates
|
||||
|
@ -61,7 +61,7 @@ import Errors
|
||||
|
||||
_LOG = logging.getLogger(DBLOGNAME)
|
||||
_MINVERSION = 9
|
||||
_DBVERSION = 14
|
||||
_DBVERSION = 15
|
||||
|
||||
IDTRANS = "person_id"
|
||||
FIDTRANS = "family_id"
|
||||
@ -73,6 +73,7 @@ NIDTRANS = "note_id"
|
||||
SIDTRANS = "source_id"
|
||||
SURNAMES = "surnames"
|
||||
NAME_GROUP = "name_group"
|
||||
TAGS = "tags"
|
||||
META = "meta_data"
|
||||
|
||||
FAMILY_TBL = "family"
|
||||
@ -197,6 +198,10 @@ 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."""
|
||||
|
||||
@ -463,6 +468,9 @@ 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
|
||||
@ -1006,6 +1014,7 @@ 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()
|
||||
@ -1042,7 +1051,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
|
||||
self.media_map = None
|
||||
self.event_map = None
|
||||
self.surnames = None
|
||||
self.name_group = None
|
||||
self.tags = None
|
||||
self.env = None
|
||||
self.metadata = None
|
||||
self.db_is_open = False
|
||||
@ -1280,6 +1289,24 @@ 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)
|
||||
|
||||
@ -1652,9 +1679,11 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
|
||||
|
||||
t = time.time()
|
||||
|
||||
import upgrade
|
||||
if version < 14:
|
||||
import upgrade
|
||||
upgrade.gramps_upgrade_14(self)
|
||||
if version < 15:
|
||||
upgrade.gramps_upgrade_15(self)
|
||||
|
||||
print "Upgrade time:", int(time.time()-t), "seconds"
|
||||
|
||||
|
@ -60,6 +60,7 @@ pkgdata_PYTHON = \
|
||||
styledtext.py \
|
||||
styledtexttag.py \
|
||||
styledtexttagtype.py \
|
||||
tagbase.py \
|
||||
urlbase.py \
|
||||
url.py \
|
||||
urltype.py \
|
||||
|
@ -38,6 +38,7 @@ from gen.lib.attrbase import AttributeBase
|
||||
from gen.lib.addressbase import AddressBase
|
||||
from gen.lib.ldsordbase import LdsOrdBase
|
||||
from gen.lib.urlbase import UrlBase
|
||||
from gen.lib.tagbase import TagBase
|
||||
from gen.lib.name import Name
|
||||
from gen.lib.eventref import EventRef
|
||||
from gen.lib.personref import PersonRef
|
||||
@ -53,7 +54,7 @@ from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
||||
AddressBase, UrlBase, LdsOrdBase, PrimaryObject):
|
||||
AddressBase, UrlBase, LdsOrdBase, TagBase, PrimaryObject):
|
||||
"""
|
||||
The Person record is the GRAMPS in-memory representation of an
|
||||
individual person. It contains all the information related to
|
||||
@ -91,6 +92,7 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
||||
AddressBase.__init__(self)
|
||||
UrlBase.__init__(self)
|
||||
LdsOrdBase.__init__(self)
|
||||
TagBase.__init__(self)
|
||||
self.primary_name = Name()
|
||||
self.marker = MarkerType()
|
||||
self.event_ref_list = []
|
||||
@ -153,7 +155,8 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
||||
self.change, # 17
|
||||
self.marker.serialize(), # 18
|
||||
self.private, # 19
|
||||
[pr.serialize() for pr in self.person_ref_list] # 20
|
||||
[pr.serialize() for pr in self.person_ref_list], # 20
|
||||
TagBase.serialize(self) # 21
|
||||
)
|
||||
|
||||
def unserialize(self, data):
|
||||
@ -186,6 +189,7 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
||||
marker, # 18
|
||||
self.private, # 19
|
||||
person_ref_list, # 20
|
||||
tag_list, # 21
|
||||
) = data
|
||||
|
||||
self.marker = MarkerType()
|
||||
@ -205,6 +209,7 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
||||
UrlBase.unserialize(self, urls)
|
||||
SourceBase.unserialize(self, source_list)
|
||||
NoteBase.unserialize(self, note_list)
|
||||
TagBase.unserialize(self, tag_list)
|
||||
return self
|
||||
|
||||
def _has_handle_reference(self, classname, handle):
|
||||
|
107
src/gen/lib/tagbase.py
Normal file
@ -0,0 +1,107 @@
|
||||
#
|
||||
# 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$
|
||||
|
||||
"""
|
||||
TagBase class for Gramps.
|
||||
"""
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# TagBase class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class TagBase(object):
|
||||
"""
|
||||
Base class for tag-aware objects.
|
||||
"""
|
||||
|
||||
def __init__(self, source=None):
|
||||
"""
|
||||
Initialize a TagBase.
|
||||
|
||||
If the source is not None, then object is initialized from values of
|
||||
the source object.
|
||||
|
||||
:param source: Object used to initialize the new object
|
||||
:type source: TagBase
|
||||
"""
|
||||
if source:
|
||||
self.tag_list = source.tag_list
|
||||
else:
|
||||
self.tag_list = []
|
||||
|
||||
def serialize(self):
|
||||
"""
|
||||
Convert the object to a serialized tuple of data.
|
||||
"""
|
||||
return self.tag_list
|
||||
|
||||
def unserialize(self, data):
|
||||
"""
|
||||
Convert a serialized tuple of data to an object.
|
||||
"""
|
||||
self.tag_list = data
|
||||
|
||||
def add_tag(self, tag):
|
||||
"""
|
||||
Add the tag to the object's list of tags.
|
||||
|
||||
:param tag: unicode tag to add.
|
||||
:type tag: unicode
|
||||
"""
|
||||
if tag not in self.tag_list:
|
||||
self.tag_list.append(tag)
|
||||
|
||||
def remove_tag(self, tag):
|
||||
"""
|
||||
Remove the specified tag from the tag list.
|
||||
|
||||
If the tag does not exist in the list, the operation has no effect.
|
||||
|
||||
:param tag: tag to remove from the list.
|
||||
:type tag: unicode
|
||||
|
||||
:returns: True if the tag was removed, False if it was not in the list.
|
||||
:rtype: bool
|
||||
"""
|
||||
if tag in self.tag_list:
|
||||
self.tag_list.remove(tag)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_tag_list(self):
|
||||
"""
|
||||
Return the list of tags associated with the object.
|
||||
|
||||
:returns: Returns the list of tags.
|
||||
:rtype: list
|
||||
"""
|
||||
return self.tag_list
|
||||
|
||||
def set_tag_list(self, tag_list):
|
||||
"""
|
||||
Assign the passed list to the objects's list of tags.
|
||||
|
||||
:param tag_list: List of tags to ba associated with the object.
|
||||
:type tag_list: list
|
||||
"""
|
||||
self.tag_list = tag_list
|
@ -17,7 +17,7 @@
|
||||
<child>
|
||||
<object class="GtkTable" id="table15">
|
||||
<property name="visible">True</property>
|
||||
<property name="n_rows">6</property>
|
||||
<property name="n_rows">7</property>
|
||||
<property name="n_columns">6</property>
|
||||
<property name="column_spacing">12</property>
|
||||
<property name="row_spacing">6</property>
|
||||
@ -490,6 +490,60 @@ Title: A title used to refer to the person, such as 'Dr.' or 'Rev.'</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label1">
|
||||
<property name="visible">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="label" translatable="yes">_Tags:</property>
|
||||
<property name="use_underline">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">6</property>
|
||||
<property name="bottom_attach">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox1">
|
||||
<property name="visible">True</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="tag_label">
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="tag_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="tag_image">
|
||||
<property name="visible">True</property>
|
||||
<property name="stock">gramps-tag</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">6</property>
|
||||
<property name="top_attach">6</property>
|
||||
<property name="bottom_attach">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
|
@ -31,7 +31,6 @@ to edit information about a particular Person.
|
||||
# Standard python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import locale
|
||||
from gen.ggettext import sgettext as _
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@ -306,6 +305,15 @@ class EditPerson(EditPrimary):
|
||||
self.db.readonly,
|
||||
autolist=self.db.get_surname_list() if not self.db.readonly else [])
|
||||
|
||||
self.tags = widgets.MonitoredTagList(
|
||||
self.top.get_object("tag_label"),
|
||||
self.top.get_object("tag_button"),
|
||||
self.obj.set_tag_list,
|
||||
self.obj.get_tag_list,
|
||||
self.db.get_all_tags(),
|
||||
self.uistate, self.track,
|
||||
self.db.readonly)
|
||||
|
||||
self.gid = widgets.MonitoredEntry(
|
||||
self.top.get_object("gid"),
|
||||
self.obj.set_gramps_id,
|
||||
@ -724,9 +732,9 @@ class EditPerson(EditPrimary):
|
||||
name = self.name_displayer.display(prim_object)
|
||||
msg1 = _("Cannot save person. ID already exists.")
|
||||
msg2 = _("You have attempted to use the existing Gramps ID with "
|
||||
"value %(id)s. This value is already used by '"
|
||||
"%(prim_object)s'. Please enter a different ID or leave "
|
||||
"blank to get the next available ID value.") % {
|
||||
"value %(id)s. This value is already used by '"
|
||||
"%(prim_object)s'. Please enter a different ID or leave "
|
||||
"blank to get the next available ID value.") % {
|
||||
'id' : id, 'prim_object' : name }
|
||||
ErrorDialog(msg1, msg2)
|
||||
self.ok_button.set_sensitive(True)
|
||||
|
@ -136,6 +136,8 @@ def register_stock_icons ():
|
||||
('gramps-repository', _('Repositories'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||
('gramps-source', _('Sources'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||
('gramps-spouse', _('Add Spouse'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||
('gramps-tag', _('Tag'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||
('gramps-tag-new', _('New Tag'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||
('gramps-tools', _('Tools'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||
('gramps-tree-group', _('Grouped List'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||
('gramps-tree-list', _('List'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||
|
@ -89,6 +89,7 @@ from gen.db.backup import backup
|
||||
from gen.db.exceptions import DbException
|
||||
from GrampsAboutDialog import GrampsAboutDialog
|
||||
from gui.sidebar import Sidebar
|
||||
from gui.views.tags import Tags
|
||||
from gen.utils.configmanager import safe_eval
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
@ -120,6 +121,8 @@ UIDEFAULT = '''<ui>
|
||||
<separator/>
|
||||
<placeholder name="CommonEdit"/>
|
||||
<separator/>
|
||||
<placeholder name="TagMenu"/>
|
||||
<separator/>
|
||||
<menuitem action="ScratchPad"/>
|
||||
<separator/>
|
||||
<menuitem action="Preferences"/>
|
||||
@ -172,6 +175,8 @@ UIDEFAULT = '''<ui>
|
||||
<toolitem action="Reports"/>
|
||||
<toolitem action="Tools"/>
|
||||
<separator/>
|
||||
<placeholder name="TagTool"/>
|
||||
<separator/>
|
||||
<placeholder name="CommonEdit"/>
|
||||
<separator/>
|
||||
<placeholder name="ViewsInCategory"/>
|
||||
@ -536,6 +541,8 @@ class ViewManager(CLIManager):
|
||||
|
||||
self.dbstate.connect('database-changed', self.uistate.db_changed)
|
||||
|
||||
self.tags = Tags(self.uistate, self.dbstate)
|
||||
|
||||
self.filter_menu = self.uimanager.get_widget(
|
||||
'/MenuBar/ViewMenu/Filter/')
|
||||
|
||||
@ -1700,34 +1707,34 @@ def by_menu_name(first, second):
|
||||
return cmp(first.name, second.name)
|
||||
|
||||
def run_plugin(pdata, dbstate, uistate):
|
||||
"""
|
||||
run a plugin based on it's PluginData:
|
||||
1/ load plugin.
|
||||
2/ the report is run
|
||||
"""
|
||||
mod = GuiPluginManager.get_instance().load_plugin(pdata)
|
||||
if not mod:
|
||||
#import of plugin failed
|
||||
ErrorDialog(
|
||||
_('Failed Loading Plugin'),
|
||||
_('The plugin did not load. See Help Menu, Plugin Manager'
|
||||
' for more info.\nUse http://bugs.gramps-project.org to'
|
||||
' submit bugs of official plugins, contact the plugin '
|
||||
'author otherwise. '))
|
||||
return
|
||||
"""
|
||||
run a plugin based on it's PluginData:
|
||||
1/ load plugin.
|
||||
2/ the report is run
|
||||
"""
|
||||
mod = GuiPluginManager.get_instance().load_plugin(pdata)
|
||||
if not mod:
|
||||
#import of plugin failed
|
||||
ErrorDialog(
|
||||
_('Failed Loading Plugin'),
|
||||
_('The plugin did not load. See Help Menu, Plugin Manager'
|
||||
' for more info.\nUse http://bugs.gramps-project.org to'
|
||||
' submit bugs of official plugins, contact the plugin '
|
||||
'author otherwise. '))
|
||||
return
|
||||
|
||||
if pdata.ptype == REPORT:
|
||||
report(dbstate, uistate, uistate.get_active('Person'),
|
||||
getattr(mod, pdata.reportclass),
|
||||
getattr(mod, pdata.optionclass),
|
||||
pdata.name, pdata.id,
|
||||
pdata.category, pdata.require_active)
|
||||
else:
|
||||
tool.gui_tool(dbstate, uistate,
|
||||
getattr(mod, pdata.toolclass),
|
||||
getattr(mod, pdata.optionclass),
|
||||
pdata.name, pdata.id, pdata.category,
|
||||
dbstate.db.request_rebuild)
|
||||
if pdata.ptype == REPORT:
|
||||
report(dbstate, uistate, uistate.get_active('Person'),
|
||||
getattr(mod, pdata.reportclass),
|
||||
getattr(mod, pdata.optionclass),
|
||||
pdata.name, pdata.id,
|
||||
pdata.category, pdata.require_active)
|
||||
else:
|
||||
tool.gui_tool(dbstate, uistate,
|
||||
getattr(mod, pdata.toolclass),
|
||||
getattr(mod, pdata.optionclass),
|
||||
pdata.name, pdata.id, pdata.category,
|
||||
dbstate.db.request_rebuild)
|
||||
|
||||
def make_plugin_callback(pdata, dbstate, uistate):
|
||||
"""
|
||||
|
@ -12,7 +12,8 @@ pkgdata_PYTHON = \
|
||||
__init__.py \
|
||||
listview.py \
|
||||
navigationview.py \
|
||||
pageview.py
|
||||
pageview.py \
|
||||
tags.py
|
||||
|
||||
pkgpyexecdir = @pkgpyexecdir@/gui/views
|
||||
pkgpythondir = @pkgpythondir@/gui/views
|
||||
|
440
src/gui/views/tags.py
Normal file
@ -0,0 +1,440 @@
|
||||
# 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 021111307 USA
|
||||
#
|
||||
|
||||
# $Id$
|
||||
"""
|
||||
Provide tagging functionality.
|
||||
"""
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import locale
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GTK/Gnome modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import gtk
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.ggettext import sgettext as _
|
||||
from ListModel import ListModel, NOSORT, COLOR
|
||||
import const
|
||||
import GrampsDisplay
|
||||
from QuestionDialog import QuestionDialog2
|
||||
import gui.widgets.progressdialog as progressdlg
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Constants
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
TAG_1 = '''<ui>
|
||||
<menubar name="MenuBar">
|
||||
<menu action="EditMenu">
|
||||
<placeholder name="TagMenu">
|
||||
<menu action="Tag">
|
||||
'''
|
||||
|
||||
TAG_2 = '''
|
||||
</menu>
|
||||
</placeholder>
|
||||
</menu>
|
||||
</menubar>
|
||||
<toolbar name="ToolBar">
|
||||
<placeholder name="TagTool">
|
||||
<toolitem action="TagButton"/>
|
||||
</placeholder>
|
||||
</toolbar>
|
||||
<popup name="TagPopup">
|
||||
'''
|
||||
|
||||
TAG_3 = '''
|
||||
</popup>
|
||||
</ui>'''
|
||||
|
||||
WIKI_HELP_PAGE = '%s_-_Entering_and_Editing_Data:_Detailed_-_part_3' % \
|
||||
const.URL_MANUAL_PAGE
|
||||
WIKI_HELP_SEC = _('manual|Tags')
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Tags
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class Tags(object):
|
||||
"""
|
||||
Provide tagging functionality.
|
||||
"""
|
||||
def __init__(self, uistate, dbstate):
|
||||
self.db = dbstate.db
|
||||
self.uistate = uistate
|
||||
|
||||
self.tag_id = None
|
||||
self.tag_ui = None
|
||||
self.tag_action = None
|
||||
|
||||
dbstate.connect('database-changed', self.db_changed)
|
||||
|
||||
self._build_tag_menu()
|
||||
|
||||
def tag_enable(self):
|
||||
"""
|
||||
Enables the UI and action groups for the tag menu.
|
||||
"""
|
||||
self.uistate.uimanager.insert_action_group(self.tag_action, 1)
|
||||
self.tag_id = self.uistate.uimanager.add_ui_from_string(self.tag_ui)
|
||||
self.uistate.uimanager.ensure_update()
|
||||
|
||||
def tag_disable(self):
|
||||
"""
|
||||
Remove the UI and action groups for the tag menu.
|
||||
"""
|
||||
self.uistate.uimanager.remove_ui(self.tag_id)
|
||||
self.uistate.uimanager.remove_action_group(self.tag_action)
|
||||
self.uistate.uimanager.ensure_update()
|
||||
self.tag_id = None
|
||||
|
||||
def db_changed(self, db):
|
||||
"""
|
||||
When the database chages update the tag list and rebuild the menus.
|
||||
"""
|
||||
self.db = db
|
||||
self.db.connect('tags-changed', self.update_tag_menu)
|
||||
self.update_tag_menu()
|
||||
|
||||
def update_tag_menu(self):
|
||||
"""
|
||||
Re-build the menu when a tag is added or removed.
|
||||
"""
|
||||
enabled = self.tag_id is not None
|
||||
if enabled:
|
||||
self.tag_disable()
|
||||
self._build_tag_menu()
|
||||
if enabled:
|
||||
self.tag_enable()
|
||||
|
||||
def _build_tag_menu(self):
|
||||
"""
|
||||
Builds the UI and action group for the tag menu.
|
||||
"""
|
||||
actions = []
|
||||
|
||||
if self.db is None:
|
||||
self.tag_ui = ''
|
||||
self.tag_action = gtk.ActionGroup('Tag')
|
||||
return
|
||||
|
||||
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):
|
||||
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)))
|
||||
|
||||
self.tag_ui = TAG_1 + tag_menu + TAG_2 + tag_menu + TAG_3
|
||||
|
||||
actions.append(('Tag', 'gramps-tag', _('Tag'), None, None, None))
|
||||
actions.append(('NewTag', 'gramps-tag-new', _('New Tag...'), None, None,
|
||||
self.cb_new_tag))
|
||||
actions.append(('OrganizeTags', None, _('Organize Tags...'), None, None,
|
||||
self.cb_organize_tags))
|
||||
actions.append(('TagButton', 'gramps-tag', _('Tag'), None,
|
||||
_('Tag selected rows'), self.cb_tag_button))
|
||||
|
||||
self.tag_action = gtk.ActionGroup('Tag')
|
||||
self.tag_action.add_actions(actions)
|
||||
|
||||
def cb_tag_button(self, action):
|
||||
"""
|
||||
Display the popup menu when the toolbar button is clicked.
|
||||
"""
|
||||
menu = self.uistate.uimanager.get_widget('/TagPopup')
|
||||
button = self.uistate.uimanager.get_widget('/ToolBar/TagTool/TagButton')
|
||||
menu.popup(None, None, cb_menu_position, 0, 0, button)
|
||||
|
||||
def cb_organize_tags(self, action):
|
||||
"""
|
||||
Display the Organize Tags dialog.
|
||||
"""
|
||||
organize_dialog = OrganizeTagsDialog(self.db, self.uistate.window)
|
||||
organize_dialog.run()
|
||||
|
||||
def cb_new_tag(self, action):
|
||||
"""
|
||||
Create a new tag and tag the selected objects.
|
||||
"""
|
||||
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()
|
||||
|
||||
def tag_selected(self, tag_name):
|
||||
"""
|
||||
Tag the selected objects with the given tag.
|
||||
"""
|
||||
view = self.uistate.viewmanager.active_page
|
||||
view.add_tag(tag_name)
|
||||
|
||||
def cb_menu_position(menu, button):
|
||||
"""
|
||||
Determine the position of the popup menu.
|
||||
"""
|
||||
x_pos, y_pos = button.window.get_origin()
|
||||
x_pos += button.allocation.x
|
||||
y_pos += button.allocation.y + button.allocation.height
|
||||
|
||||
return (x_pos, y_pos, False)
|
||||
|
||||
def make_callback(func, tag_name):
|
||||
"""
|
||||
Generates a callback function based off the passed arguments
|
||||
"""
|
||||
return lambda x: func(tag_name)
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Organize Tags Dialog
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class OrganizeTagsDialog(object):
|
||||
"""
|
||||
A dialog to enable the user to organize tags.
|
||||
"""
|
||||
def __init__(self, db, parent_window):
|
||||
self.db = db
|
||||
self.parent_window = parent_window
|
||||
self.namelist = None
|
||||
self.namemodel = None
|
||||
self.top = self._create_dialog()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run the dialog and return the result.
|
||||
"""
|
||||
self._populate_model()
|
||||
while True:
|
||||
response = self.top.run()
|
||||
if response == gtk.RESPONSE_HELP:
|
||||
GrampsDisplay.help(webpage=WIKI_HELP_PAGE,
|
||||
section=WIKI_HELP_SEC)
|
||||
else:
|
||||
break
|
||||
self.top.destroy()
|
||||
|
||||
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)])
|
||||
|
||||
def _create_dialog(self):
|
||||
"""
|
||||
Create a dialog box to organize tags.
|
||||
"""
|
||||
# pylint: disable-msg=E1101
|
||||
title = _("%(title)s - Gramps") % {'title': _("Organize Tags")}
|
||||
top = gtk.Dialog(title)
|
||||
top.set_default_size(400, 350)
|
||||
top.set_modal(True)
|
||||
top.set_transient_for(self.parent_window)
|
||||
top.set_has_separator(False)
|
||||
top.vbox.set_spacing(5)
|
||||
label = gtk.Label('<span size="larger" weight="bold">%s</span>'
|
||||
% _("Organize Tags"))
|
||||
label.set_use_markup(True)
|
||||
top.vbox.pack_start(label, 0, 0, 5)
|
||||
box = gtk.HBox()
|
||||
top.vbox.pack_start(box, 1, 1, 5)
|
||||
|
||||
name_titles = [(_('Name'), NOSORT, 200),
|
||||
(_('Color'), NOSORT, 50, COLOR)]
|
||||
self.namelist = gtk.TreeView()
|
||||
self.namemodel = ListModel(self.namelist, name_titles)
|
||||
|
||||
slist = gtk.ScrolledWindow()
|
||||
slist.add_with_viewport(self.namelist)
|
||||
slist.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
box.pack_start(slist, 1, 1, 5)
|
||||
bbox = gtk.VButtonBox()
|
||||
bbox.set_layout(gtk.BUTTONBOX_START)
|
||||
bbox.set_spacing(6)
|
||||
add = gtk.Button(stock=gtk.STOCK_ADD)
|
||||
edit = gtk.Button(stock=gtk.STOCK_EDIT)
|
||||
remove = gtk.Button(stock=gtk.STOCK_REMOVE)
|
||||
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(add)
|
||||
bbox.add(edit)
|
||||
bbox.add(remove)
|
||||
box.pack_start(bbox, 0, 0, 5)
|
||||
top.show_all()
|
||||
return top
|
||||
|
||||
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()
|
||||
|
||||
def cb_edit_clicked(self, button):
|
||||
"""
|
||||
Edit the color of an existing tag.
|
||||
"""
|
||||
# pylint: disable-msg=E1101
|
||||
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))
|
||||
|
||||
title = _("%(title)s - Gramps") % {'title': _("Pick a Color")}
|
||||
colorseldlg = gtk.ColorSelectionDialog(title)
|
||||
colorseldlg.set_transient_for(self.top)
|
||||
colorseldlg.colorsel.set_current_color(old_color)
|
||||
colorseldlg.colorsel.set_previous_color(old_color)
|
||||
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)
|
||||
colorseldlg.destroy()
|
||||
|
||||
def cb_remove_clicked(self, button, top):
|
||||
"""
|
||||
Remove the selected tag.
|
||||
"""
|
||||
store, iter_ = self.namemodel.get_selected()
|
||||
if iter_ is None:
|
||||
return
|
||||
tag_name = store.get_value(iter_, 0)
|
||||
|
||||
yes_no = QuestionDialog2(
|
||||
_("Remove tag '%s'?") % tag_name,
|
||||
_("The tag definition will be removed. "
|
||||
"The tag will be also removed from all objects in the database."),
|
||||
_("Yes"),
|
||||
_("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()
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# New Tag Dialog
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class NewTagDialog(object):
|
||||
"""
|
||||
A dialog to enable the user to create a new tag.
|
||||
"""
|
||||
def __init__(self, parent_window):
|
||||
self.parent_window = parent_window
|
||||
self.entry = None
|
||||
self.color = None
|
||||
self.top = self._create_dialog()
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Run the dialog and return the result.
|
||||
"""
|
||||
result = (None, None)
|
||||
response = self.top.run()
|
||||
if response == gtk.RESPONSE_OK:
|
||||
result = (self.entry.get_text(), self.color.get_color().to_string())
|
||||
self.top.destroy()
|
||||
return result
|
||||
|
||||
def _create_dialog(self):
|
||||
"""
|
||||
Create a dialog box to enter a new tag.
|
||||
"""
|
||||
# pylint: disable-msg=E1101
|
||||
title = _("%(title)s - Gramps") % {'title': _("New Tag")}
|
||||
top = gtk.Dialog(title)
|
||||
top.set_default_size(300, 100)
|
||||
top.set_modal(True)
|
||||
top.set_transient_for(self.parent_window)
|
||||
top.set_has_separator(False)
|
||||
top.vbox.set_spacing(5)
|
||||
|
||||
hbox = gtk.HBox()
|
||||
top.vbox.pack_start(hbox, False, False, 10)
|
||||
|
||||
label = gtk.Label(_('Tag Name:'))
|
||||
self.entry = gtk.Entry()
|
||||
self.color = gtk.ColorButton()
|
||||
title = _("%(title)s - Gramps") % {'title': _("Pick a Color")}
|
||||
self.color.set_title(title)
|
||||
hbox.pack_start(label, False, False, 5)
|
||||
hbox.pack_start(self.entry, True, True, 5)
|
||||
hbox.pack_start(self.color, False, False, 5)
|
||||
|
||||
top.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
|
||||
top.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
|
||||
top.show_all()
|
||||
return top
|
@ -78,6 +78,7 @@ COLUMN_DEATH = 5
|
||||
COLUMN_BIRTH = 6
|
||||
COLUMN_EVENT = 7
|
||||
COLUMN_FAMILY = 8
|
||||
COLUMN_TAGS = 21
|
||||
COLUMN_CHANGE = 17
|
||||
COLUMN_MARKER = 18
|
||||
|
||||
@ -103,6 +104,7 @@ class PeopleBaseModel(object):
|
||||
"""
|
||||
Initialize the model building the initial data
|
||||
"""
|
||||
self.db = db
|
||||
self.gen_cursor = db.get_person_cursor
|
||||
self.map = db.get_raw_person_data
|
||||
|
||||
@ -115,10 +117,11 @@ class PeopleBaseModel(object):
|
||||
self.column_death_day,
|
||||
self.column_death_place,
|
||||
self.column_spouse,
|
||||
self.column_tags,
|
||||
self.column_change,
|
||||
self.column_int_id,
|
||||
self.column_marker_text,
|
||||
self.column_marker_color,
|
||||
self.column_tag_color,
|
||||
self.column_tooltip,
|
||||
]
|
||||
self.smap = [
|
||||
@ -130,10 +133,11 @@ class PeopleBaseModel(object):
|
||||
self.sort_death_day,
|
||||
self.column_death_place,
|
||||
self.column_spouse,
|
||||
self.column_tags,
|
||||
self.sort_change,
|
||||
self.column_int_id,
|
||||
self.column_marker_text,
|
||||
self.column_marker_color,
|
||||
self.column_tag_color,
|
||||
self.column_tooltip,
|
||||
]
|
||||
|
||||
@ -145,11 +149,26 @@ 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.
|
||||
"""
|
||||
return 11
|
||||
return 12
|
||||
|
||||
def clear_local_cache(self, handle=None):
|
||||
""" Clear the LRU cache """
|
||||
@ -419,19 +438,6 @@ class PeopleBaseModel(object):
|
||||
return str(data[COLUMN_MARKER])
|
||||
return ""
|
||||
|
||||
def column_marker_color(self, data):
|
||||
try:
|
||||
if data[COLUMN_MARKER]:
|
||||
if data[COLUMN_MARKER][0] == MarkerType.COMPLETE:
|
||||
return self.complete_color
|
||||
if data[COLUMN_MARKER][0] == MarkerType.TODO_TYPE:
|
||||
return self.todo_color
|
||||
if data[COLUMN_MARKER][0] == MarkerType.CUSTOM:
|
||||
return self.custom_color
|
||||
except IndexError:
|
||||
pass
|
||||
return None
|
||||
|
||||
def column_tooltip(self, data):
|
||||
if const.USE_TIPS:
|
||||
return ToolTips.TipFromFunction(
|
||||
@ -444,6 +450,14 @@ class PeopleBaseModel(object):
|
||||
def column_int_id(self, data):
|
||||
return data[0]
|
||||
|
||||
def column_tag_color(self, data):
|
||||
if len(data[COLUMN_TAGS]) > 0:
|
||||
return self.tag_colors.get(data[COLUMN_TAGS][0])
|
||||
return None
|
||||
|
||||
def column_tags(self, data):
|
||||
return ','.join(data[COLUMN_TAGS])
|
||||
|
||||
class PersonListModel(PeopleBaseModel, FlatBaseModel):
|
||||
"""
|
||||
Listed people model.
|
||||
@ -453,7 +467,7 @@ class PersonListModel(PeopleBaseModel, FlatBaseModel):
|
||||
|
||||
PeopleBaseModel.__init__(self, db)
|
||||
FlatBaseModel.__init__(self, db, search=search, skip=skip,
|
||||
tooltip_column=12,
|
||||
tooltip_column=13,
|
||||
scol=scol, order=order, sort_map=sort_map)
|
||||
|
||||
def clear_cache(self, handle=None):
|
||||
@ -468,7 +482,7 @@ class PersonTreeModel(PeopleBaseModel, TreeBaseModel):
|
||||
skip=set(), sort_map=None):
|
||||
|
||||
PeopleBaseModel.__init__(self, db)
|
||||
TreeBaseModel.__init__(self, db, 12, search=search, skip=skip,
|
||||
TreeBaseModel.__init__(self, db, 13, search=search, skip=skip,
|
||||
scol=scol, order=order, sort_map=sort_map)
|
||||
|
||||
def _set_base_data(self):
|
||||
|
@ -21,6 +21,7 @@ pkgdata_PYTHON = \
|
||||
statusbar.py \
|
||||
styledtextbuffer.py \
|
||||
styledtexteditor.py \
|
||||
tageditor.py \
|
||||
toolcomboentry.py \
|
||||
undoablebuffer.py \
|
||||
validatedcomboentry.py \
|
||||
|
@ -23,7 +23,7 @@
|
||||
__all__ = ["MonitoredCheckbox", "MonitoredEntry", "MonitoredSpinButton",
|
||||
"MonitoredText", "MonitoredType", "MonitoredDataType",
|
||||
"MonitoredMenu", "MonitoredStrMenu", "MonitoredDate",
|
||||
"MonitoredComboSelectedEntry"]
|
||||
"MonitoredComboSelectedEntry", "MonitoredTagList"]
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -47,8 +47,10 @@ import gtk
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.ggettext import gettext as _
|
||||
import AutoComp
|
||||
import DateEdit
|
||||
from tageditor import TagEditor
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -597,3 +599,55 @@ class MonitoredComboSelectedEntry(object):
|
||||
Eg: name editor save brings you back to person editor that must update
|
||||
"""
|
||||
self.entry_reinit()
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# MonitoredTagList class
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
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,
|
||||
uistate, track, readonly=False):
|
||||
|
||||
self.uistate = uistate
|
||||
self.track = track
|
||||
|
||||
self.set_list = set_list
|
||||
self.tag_list = get_list()
|
||||
self.all_tags = full_list
|
||||
self.label = label
|
||||
self.label.set_alignment(0, 0.5)
|
||||
image = gtk.Image()
|
||||
image.set_from_stock('gramps-tag', gtk.ICON_SIZE_BUTTON)
|
||||
button.set_image (image)
|
||||
#button.set_label('...')
|
||||
button.set_tooltip_text(_('Edit the tag list'))
|
||||
button.connect('button-press-event', self.cb_edit)
|
||||
button.connect('key-press-event', self.cb_edit)
|
||||
button.set_sensitive(not readonly)
|
||||
|
||||
self._display()
|
||||
|
||||
def _display(self):
|
||||
"""
|
||||
Display the tag list.
|
||||
"""
|
||||
tag_text = ','.join(self.tag_list)
|
||||
self.label.set_text(tag_text)
|
||||
self.label.set_tooltip_text(tag_text)
|
||||
|
||||
def cb_edit(self, button, event):
|
||||
"""
|
||||
Invoke the tag editor.
|
||||
"""
|
||||
editor = TagEditor(self.tag_list, self.all_tags,
|
||||
self.uistate, self.track)
|
||||
if editor.return_list is not None:
|
||||
self.tag_list = editor.return_list
|
||||
self._display()
|
||||
self.set_list(self.tag_list)
|
||||
|
||||
|
133
src/gui/widgets/tageditor.py
Normal file
@ -0,0 +1,133 @@
|
||||
#
|
||||
# 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 editing module for Gramps.
|
||||
"""
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import locale
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# GNOME modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
import gtk
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Gramps modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.ggettext import sgettext as _
|
||||
import ManagedWindow
|
||||
import const
|
||||
import GrampsDisplay
|
||||
from ListModel import ListModel, TOGGLE
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# Constants
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
WIKI_HELP_PAGE = '%s_-_Entering_and_Editing_Data:_Detailed_-_part_3' % \
|
||||
const.URL_MANUAL_PAGE
|
||||
WIKI_HELP_SEC = _('manual|Tags')
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# TagEditor
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
class TagEditor(ManagedWindow.ManagedWindow):
|
||||
"""
|
||||
Dialog to allow the user to edit a list of tags.
|
||||
"""
|
||||
|
||||
def __init__(self, tag_list, full_list, uistate, track):
|
||||
"""
|
||||
Initiate and display the dialog.
|
||||
"""
|
||||
ManagedWindow.ManagedWindow.__init__(self, uistate, track, self)
|
||||
|
||||
self.namemodel = None
|
||||
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])
|
||||
self.namemodel.connect_model()
|
||||
|
||||
# The dialog is modal. We don't want to have several open dialogs of
|
||||
# this type, since then the user will loose track of which is which.
|
||||
self.return_list = None
|
||||
self.show()
|
||||
|
||||
while True:
|
||||
response = self.window.run()
|
||||
if response == gtk.RESPONSE_HELP:
|
||||
GrampsDisplay.help(webpage=WIKI_HELP_PAGE,
|
||||
section=WIKI_HELP_SEC)
|
||||
elif response == gtk.RESPONSE_DELETE_EVENT:
|
||||
break
|
||||
else:
|
||||
if response == gtk.RESPONSE_OK:
|
||||
self.return_list = [row[0] for row in self.namemodel.model
|
||||
if row[1]]
|
||||
self.close()
|
||||
break
|
||||
|
||||
def _create_dialog(self):
|
||||
"""
|
||||
Create a dialog box to select tags.
|
||||
"""
|
||||
# pylint: disable-msg=E1101
|
||||
title = _("%(title)s - Gramps") % {'title': _("Edit Tags")}
|
||||
top = gtk.Dialog(title)
|
||||
top.set_default_size(340, 400)
|
||||
top.set_modal(True)
|
||||
top.set_has_separator(False)
|
||||
top.vbox.set_spacing(5)
|
||||
|
||||
columns = [(_('Tag'), -1, 300),
|
||||
(_(' '), -1, 25, TOGGLE, True, None)]
|
||||
view = gtk.TreeView()
|
||||
self.namemodel = ListModel(view, columns)
|
||||
|
||||
slist = gtk.ScrolledWindow()
|
||||
slist.add_with_viewport(view)
|
||||
slist.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
|
||||
top.vbox.pack_start(slist, 1, 1, 5)
|
||||
|
||||
top.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
|
||||
top.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
|
||||
top.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
|
||||
top.show_all()
|
||||
return top
|
||||
|
||||
def build_menu_names(self, obj):
|
||||
"""
|
||||
Define the menu entry for the ManagedWindows.
|
||||
"""
|
||||
return (_("Tag selection"), None)
|
@ -47,6 +47,8 @@ dist_pkgdata_DATA = \
|
||||
gramps-repository.png \
|
||||
gramps-source.png \
|
||||
gramps-spouse.png \
|
||||
gramps-tag.png \
|
||||
gramps-tag-new.png \
|
||||
gramps-tools.png \
|
||||
gramps-tree-group.png \
|
||||
gramps-tree-list.png \
|
||||
|
BIN
src/images/16x16/gramps-tag-new.png
Normal file
After Width: | Height: | Size: 747 B |
BIN
src/images/16x16/gramps-tag.png
Normal file
After Width: | Height: | Size: 681 B |
@ -47,6 +47,8 @@ dist_pkgdata_DATA = \
|
||||
gramps-repository.png \
|
||||
gramps-source.png \
|
||||
gramps-spouse.png \
|
||||
gramps-tag.png \
|
||||
gramps-tag-new.png \
|
||||
gramps-tools.png \
|
||||
gramps-tree-group.png \
|
||||
gramps-tree-list.png \
|
||||
|
BIN
src/images/22x22/gramps-tag-new.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
src/images/22x22/gramps-tag.png
Normal file
After Width: | Height: | Size: 967 B |
@ -47,6 +47,8 @@ dist_pkgdata_DATA = \
|
||||
gramps-repository.png \
|
||||
gramps-source.png \
|
||||
gramps-spouse.png \
|
||||
gramps-tag.png \
|
||||
gramps-tag-new.png \
|
||||
gramps-tools.png \
|
||||
gramps-tree-group.png \
|
||||
gramps-tree-list.png \
|
||||
|
BIN
src/images/48x48/gramps-tag-new.png
Normal file
After Width: | Height: | Size: 3.0 KiB |
BIN
src/images/48x48/gramps-tag.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
@ -47,6 +47,8 @@ dist_pkgdata_DATA = \
|
||||
gramps-repository.svg \
|
||||
gramps-source.svg \
|
||||
gramps-spouse.svg \
|
||||
gramps-tag.svg \
|
||||
gramps-tag-new.svg \
|
||||
gramps-tools.svg \
|
||||
gramps-tree-group.svg \
|
||||
gramps-tree-list.svg \
|
||||
|
269
src/images/scalable/gramps-tag-new.svg
Normal file
@ -0,0 +1,269 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="22"
|
||||
height="22"
|
||||
id="svg4542"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.45+devel"
|
||||
sodipodi:docbase="/home/jimmac/gfx/icons/application-icons/tomboy/22x22"
|
||||
sodipodi:docname="f-spot-new-tag-22.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
version="1.0">
|
||||
<defs
|
||||
id="defs4544">
|
||||
<linearGradient
|
||||
id="linearGradient8668">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8670" />
|
||||
<stop
|
||||
id="stop8676"
|
||||
offset="0.5"
|
||||
style="stop-color:#edd400;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#edd400;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop8672" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5154">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5156" />
|
||||
<stop
|
||||
style="stop-color:#d3d7cf;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop5158" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient5146">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5148" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop5150" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient5135">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5137" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop5139" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5135"
|
||||
id="radialGradient5141"
|
||||
cx="7.9991455"
|
||||
cy="6.3656702"
|
||||
fx="7.9991455"
|
||||
fy="6.3656702"
|
||||
r="3.019068"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5146"
|
||||
id="linearGradient5152"
|
||||
x1="9.1521292"
|
||||
y1="15"
|
||||
x2="8.0315027"
|
||||
y2="0.062499985"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.3320387,0,0,1.2877478,-0.1982722,0.8276555)" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5154"
|
||||
id="radialGradient5160"
|
||||
cx="8.0300226"
|
||||
cy="11.565026"
|
||||
fx="8.0300226"
|
||||
fy="11.565026"
|
||||
r="6.0042586"
|
||||
gradientTransform="matrix(1.3188775,0,0,1.7490954,-5.4317078e-2,-3.3022448)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
x="-0.11192513"
|
||||
width="1.2238503"
|
||||
y="-0.50945233"
|
||||
height="2.0189047"
|
||||
id="filter5242">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.76948529"
|
||||
id="feGaussianBlur5244" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient8668"
|
||||
id="radialGradient8674"
|
||||
cx="16.528622"
|
||||
cy="4.3223305"
|
||||
fx="16.528622"
|
||||
fy="4.3223305"
|
||||
r="5.2149124"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#ebebeb"
|
||||
borderopacity="1"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="13.72296"
|
||||
inkscape:cy="14.108264"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
borderlayer="true"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="820"
|
||||
inkscape:window-height="764"
|
||||
inkscape:window-x="730"
|
||||
inkscape:window-y="408"
|
||||
width="22px"
|
||||
height="22px">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid8089" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4547">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<rect
|
||||
style="opacity:0.35882353;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter5242);enable-background:accumulate"
|
||||
id="rect5212"
|
||||
width="16.5"
|
||||
height="3.625"
|
||||
x="2.25"
|
||||
y="17.25"
|
||||
rx="1.8125"
|
||||
ry="1.8125" />
|
||||
<path
|
||||
style="color:#000000;fill:url(#radialGradient5160);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
d="M 8.0075554,1.5000049 C 7.9261297,1.5000049 7.1704875,1.5648405 6.6633357,2.2083456 C 5.8189525,3.2797502 4.6237633,4.9273413 3.8958247,6.1042196 C 3.4977305,6.7478297 3.5795378,7.3012987 3.5795377,7.4815488 L 3.5795377,19.129819 C 3.5795377,19.837629 4.1810296,20.389091 4.9237574,20.389091 L 16.151945,20.389091 C 16.894673,20.389091 17.496165,19.837629 17.496165,19.129819 L 17.496165,7.4815488 C 17.496165,7.3065686 17.558573,6.7602173 17.179878,6.1435719 C 16.420161,4.9064935 15.235183,3.1290493 14.372831,2.0509366 C 13.926232,1.4926004 13.221994,1.500005 13.147219,1.5000049 L 8.0075554,1.5000049 z M 10.537851,3.4880253 C 11.646305,3.4880253 12.552287,4.3897989 12.552287,5.493106 C 12.552287,6.5964131 11.646306,7.4981868 10.537851,7.4981868 C 9.4293957,7.4981865 8.5234152,6.5964132 8.5234152,5.493106 C 8.5234153,4.3897989 9.4293962,3.4880253 10.537851,3.4880253 z"
|
||||
id="rect4550"
|
||||
sodipodi:nodetypes="cssccccccsscccsssc" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#radialGradient5141);stroke-width:0.88136292;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path5133"
|
||||
sodipodi:cx="7.9991455"
|
||||
sodipodi:cy="3.8907964"
|
||||
sodipodi:rx="2.519068"
|
||||
sodipodi:ry="2.519068"
|
||||
d="M 10.518214,3.8907964 A 2.519068,2.519068 0 1 1 5.4800775,3.8907964 A 2.519068,2.519068 0 1 1 10.518214,3.8907964 z"
|
||||
transform="matrix(1.1372503,0,0,1.1319689,1.4002041,1.2894864)" />
|
||||
<path
|
||||
style="opacity:0.48235294;color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient5152);stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
d="M 7.8355863,2.7592772 C 8.1479175,2.7592772 7.858081,2.7617019 7.7523339,2.7995194 C 7.6465867,2.8373367 7.5430527,2.8995475 7.4609504,3.00073 C 6.6044824,4.0562358 5.3465131,5.7394375 4.6303682,6.8639734 C 4.5315216,7.0191886 4.5178409,7.1273694 4.5054895,7.2261524 C 4.4931381,7.3249356 4.5054899,7.3022556 4.5054895,7.5883316 L 4.5054895,19.499999 C 4.5054895,19.497658 4.4691641,19.499999 4.588742,19.499999 L 16.410586,19.499999 C 16.530165,19.499999 16.493838,19.497658 16.493838,19.499999 L 16.493838,7.5883316 C 16.493838,7.3464779 16.507701,7.3260914 16.493838,7.2261524 C 16.479974,7.1262134 16.460034,7.0482525 16.368959,6.9042155 C 15.594416,5.6792417 14.330701,3.8524027 13.49675,2.8397614 C 13.455128,2.7892186 13.414069,2.7592772 13.246994,2.7592772 L 10.499664,2.7592772 L 7.8355863,2.7592772 z"
|
||||
id="path5143"
|
||||
sodipodi:nodetypes="cssssccccccsssccc" />
|
||||
<rect
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect5162"
|
||||
width="0.99999976"
|
||||
height="8"
|
||||
x="6"
|
||||
y="10"
|
||||
rx="0.5"
|
||||
ry="0.5" />
|
||||
<rect
|
||||
ry="0.5"
|
||||
rx="0.5"
|
||||
y="12"
|
||||
x="8"
|
||||
height="6"
|
||||
width="0.99999976"
|
||||
id="rect5204"
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect5206"
|
||||
width="0.99999976"
|
||||
height="8"
|
||||
x="10"
|
||||
y="10"
|
||||
rx="0.5"
|
||||
ry="0.5" />
|
||||
<rect
|
||||
ry="0.5"
|
||||
rx="0.5"
|
||||
y="11"
|
||||
x="12"
|
||||
height="7"
|
||||
width="0.99999976"
|
||||
id="rect5208"
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect5210"
|
||||
width="0.99999976"
|
||||
height="6"
|
||||
x="14"
|
||||
y="12"
|
||||
rx="0.5"
|
||||
ry="0.5" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;color:#000000;fill:url(#radialGradient8674);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path8087"
|
||||
sodipodi:cx="16.528622"
|
||||
sodipodi:cy="4.3223305"
|
||||
sodipodi:rx="5.2149124"
|
||||
sodipodi:ry="5.2149124"
|
||||
d="M 21.743534,4.3223305 A 5.2149124,5.2149124 0 1 1 11.313709,4.3223305 A 5.2149124,5.2149124 0 1 1 21.743534,4.3223305 z"
|
||||
transform="matrix(1.0423729,0,0,1.0423729,-0.7445595,0.9217048)" />
|
||||
<path
|
||||
sodipodi:type="star"
|
||||
style="opacity:1;color:#000000;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path8678"
|
||||
sodipodi:sides="5"
|
||||
sodipodi:cx="16.970564"
|
||||
sodipodi:cy="7.0623693"
|
||||
sodipodi:r1="4.6359005"
|
||||
sodipodi:r2="2.7623203"
|
||||
sodipodi:arg1="0.96157066"
|
||||
sodipodi:arg2="1.5898892"
|
||||
inkscape:flatsided="false"
|
||||
inkscape:rounded="0"
|
||||
inkscape:randomized="0"
|
||||
d="M 19.623377,10.864231 L 16.917826,9.8241862 L 14.174543,10.760185 L 14.327623,7.8656614 L 12.589714,5.545883 L 15.389874,4.7970143 L 17.059071,2.4273138 L 18.636585,4.8590109 L 21.406114,5.7142338 L 19.580911,7.9659739 L 19.623377,10.864231 z"
|
||||
transform="translate(-0.5303301,-1.4273138)" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 13 KiB |
256
src/images/scalable/gramps-tag.svg
Normal file
@ -0,0 +1,256 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="22"
|
||||
height="22"
|
||||
id="svg4542"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.46+devel"
|
||||
sodipodi:docbase="/home/jimmac/gfx/icons/application-icons/tomboy/22x22"
|
||||
sodipodi:docname="tag-new-22.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape"
|
||||
version="1.0"
|
||||
inkscape:export-filename="/home/nx/Desktop/tag-22.png"
|
||||
inkscape:export-xdpi="90"
|
||||
inkscape:export-ydpi="90">
|
||||
<defs
|
||||
id="defs4544">
|
||||
<inkscape:perspective
|
||||
sodipodi:type="inkscape:persp3d"
|
||||
inkscape:vp_x="0 : 11 : 1"
|
||||
inkscape:vp_y="0 : 1000 : 0"
|
||||
inkscape:vp_z="22 : 11 : 1"
|
||||
inkscape:persp3d-origin="11 : 7.3333333 : 1"
|
||||
id="perspective38" />
|
||||
<linearGradient
|
||||
id="linearGradient8668">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop8670" />
|
||||
<stop
|
||||
id="stop8676"
|
||||
offset="0.5"
|
||||
style="stop-color:#edd400;stop-opacity:1;" />
|
||||
<stop
|
||||
style="stop-color:#edd400;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop8672" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5154">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5156" />
|
||||
<stop
|
||||
style="stop-color:#d3d7cf;stop-opacity:1;"
|
||||
offset="1"
|
||||
id="stop5158" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient5146">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5148" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop5150" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient5135">
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5137" />
|
||||
<stop
|
||||
style="stop-color:#ffffff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop5139" />
|
||||
</linearGradient>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5135"
|
||||
id="radialGradient5141"
|
||||
cx="7.9991455"
|
||||
cy="6.3656702"
|
||||
fx="7.9991455"
|
||||
fy="6.3656702"
|
||||
r="3.019068"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5146"
|
||||
id="linearGradient5152"
|
||||
x1="9.1521292"
|
||||
y1="15"
|
||||
x2="8.0315027"
|
||||
y2="0.062499985"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(1.3320387,0,0,1.2877478,-0.1982722,0.8276555)" />
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient5154"
|
||||
id="radialGradient5160"
|
||||
cx="8.0300226"
|
||||
cy="11.565026"
|
||||
fx="8.0300226"
|
||||
fy="11.565026"
|
||||
r="6.0042586"
|
||||
gradientTransform="matrix(1.3188775,0,0,1.7490954,-5.4317078e-2,-3.3022448)"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<filter
|
||||
inkscape:collect="always"
|
||||
x="-0.11192513"
|
||||
width="1.2238503"
|
||||
y="-0.50945233"
|
||||
height="2.0189047"
|
||||
id="filter5242">
|
||||
<feGaussianBlur
|
||||
inkscape:collect="always"
|
||||
stdDeviation="0.76948529"
|
||||
id="feGaussianBlur5244" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient8668"
|
||||
id="radialGradient8674"
|
||||
cx="16.528622"
|
||||
cy="4.3223305"
|
||||
fx="16.528622"
|
||||
fy="4.3223305"
|
||||
r="5.2149124"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#ebebeb"
|
||||
borderopacity="1"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="4"
|
||||
inkscape:cx="-14.069783"
|
||||
inkscape:cy="11.175821"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:grid-bbox="true"
|
||||
inkscape:document-units="px"
|
||||
borderlayer="true"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="820"
|
||||
inkscape:window-height="764"
|
||||
inkscape:window-x="730"
|
||||
inkscape:window-y="408"
|
||||
width="22px"
|
||||
height="22px">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid8089"
|
||||
empspacing="5"
|
||||
visible="true"
|
||||
enabled="true" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4547">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer">
|
||||
<rect
|
||||
style="opacity:0.35882353;color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;filter:url(#filter5242);enable-background:accumulate"
|
||||
id="rect5212"
|
||||
width="16.5"
|
||||
height="3.625"
|
||||
x="2.25"
|
||||
y="17.25"
|
||||
rx="1.8125"
|
||||
ry="1.8125" />
|
||||
<path
|
||||
style="color:#000000;fill:url(#radialGradient5160);fill-opacity:1;fill-rule:evenodd;stroke:#888a85;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
d="M 8.0075554,1.5000049 C 7.9261297,1.5000049 7.1704875,1.5648405 6.6633357,2.2083456 C 5.8189525,3.2797502 4.6237633,4.9273413 3.8958247,6.1042196 C 3.4977305,6.7478297 3.5795378,7.3012987 3.5795377,7.4815488 L 3.5795377,19.129819 C 3.5795377,19.837629 4.1810296,20.389091 4.9237574,20.389091 L 16.151945,20.389091 C 16.894673,20.389091 17.496165,19.837629 17.496165,19.129819 L 17.496165,7.4815488 C 17.496165,7.3065686 17.558573,6.7602173 17.179878,6.1435719 C 16.420161,4.9064935 15.235183,3.1290493 14.372831,2.0509366 C 13.926232,1.4926004 13.221994,1.500005 13.147219,1.5000049 L 8.0075554,1.5000049 z M 10.537851,3.4880253 C 11.646305,3.4880253 12.552287,4.3897989 12.552287,5.493106 C 12.552287,6.5964131 11.646306,7.4981868 10.537851,7.4981868 C 9.4293957,7.4981865 8.5234152,6.5964132 8.5234152,5.493106 C 8.5234153,4.3897989 9.4293962,3.4880253 10.537851,3.4880253 z"
|
||||
id="rect4550"
|
||||
sodipodi:nodetypes="cssccccccsscccsssc" />
|
||||
<path
|
||||
sodipodi:type="arc"
|
||||
style="opacity:1;color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#radialGradient5141);stroke-width:0.88136292;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="path5133"
|
||||
sodipodi:cx="7.9991455"
|
||||
sodipodi:cy="3.8907964"
|
||||
sodipodi:rx="2.519068"
|
||||
sodipodi:ry="2.519068"
|
||||
d="M 10.518214,3.8907964 A 2.519068,2.519068 0 1 1 5.4800775,3.8907964 A 2.519068,2.519068 0 1 1 10.518214,3.8907964 z"
|
||||
transform="matrix(1.1372503,0,0,1.1319689,1.4002041,1.2894864)" />
|
||||
<path
|
||||
style="opacity:0.48235294;color:#000000;fill:none;fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient5152);stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
d="M 7.8355863,2.7592772 C 8.1479175,2.7592772 7.858081,2.7617019 7.7523339,2.7995194 C 7.6465867,2.8373367 7.5430527,2.8995475 7.4609504,3.00073 C 6.6044824,4.0562358 5.3465131,5.7394375 4.6303682,6.8639734 C 4.5315216,7.0191886 4.5178409,7.1273694 4.5054895,7.2261524 C 4.4931381,7.3249356 4.5054899,7.3022556 4.5054895,7.5883316 L 4.5054895,19.499999 C 4.5054895,19.497658 4.4691641,19.499999 4.588742,19.499999 L 16.410586,19.499999 C 16.530165,19.499999 16.493838,19.497658 16.493838,19.499999 L 16.493838,7.5883316 C 16.493838,7.3464779 16.507701,7.3260914 16.493838,7.2261524 C 16.479974,7.1262134 16.460034,7.0482525 16.368959,6.9042155 C 15.594416,5.6792417 14.330701,3.8524027 13.49675,2.8397614 C 13.455128,2.7892186 13.414069,2.7592772 13.246994,2.7592772 L 10.499664,2.7592772 L 7.8355863,2.7592772 z"
|
||||
id="path5143"
|
||||
sodipodi:nodetypes="cssssccccccsssccc" />
|
||||
<rect
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect5162"
|
||||
width="0.99999976"
|
||||
height="8"
|
||||
x="6"
|
||||
y="10"
|
||||
rx="0.5"
|
||||
ry="0.5" />
|
||||
<rect
|
||||
ry="0.5"
|
||||
rx="0.5"
|
||||
y="12"
|
||||
x="8"
|
||||
height="6"
|
||||
width="0.99999976"
|
||||
id="rect5204"
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect5206"
|
||||
width="0.99999976"
|
||||
height="8"
|
||||
x="10"
|
||||
y="10"
|
||||
rx="0.5"
|
||||
ry="0.5" />
|
||||
<rect
|
||||
ry="0.5"
|
||||
rx="0.5"
|
||||
y="11"
|
||||
x="12"
|
||||
height="7"
|
||||
width="0.99999976"
|
||||
id="rect5208"
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate" />
|
||||
<rect
|
||||
style="opacity:1;color:#000000;fill:#babdb6;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;marker:none;marker-start:none;marker-mid:none;marker-end:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
|
||||
id="rect5210"
|
||||
width="0.99999976"
|
||||
height="6"
|
||||
x="14"
|
||||
y="12"
|
||||
rx="0.5"
|
||||
ry="0.5" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
@ -58,13 +58,15 @@ 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
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
# internationalization
|
||||
# Python modules
|
||||
#
|
||||
#-------------------------------------------------------------------------
|
||||
from gen.ggettext import sgettext as _
|
||||
from bisect import insort_left
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
#
|
||||
@ -83,7 +85,8 @@ class BasePersonView(ListView):
|
||||
COL_DDAT = 5
|
||||
COL_DPLAC = 6
|
||||
COL_SPOUSE = 7
|
||||
COL_CHAN = 8
|
||||
COL_TAGS = 8
|
||||
COL_CHAN = 9
|
||||
#name of the columns
|
||||
COLUMN_NAMES = [
|
||||
_('Name'),
|
||||
@ -94,6 +97,7 @@ class BasePersonView(ListView):
|
||||
_('Death Date'),
|
||||
_('Death Place'),
|
||||
_('Spouse'),
|
||||
_('Tags'),
|
||||
_('Last Changed'),
|
||||
]
|
||||
# columns that contain markup
|
||||
@ -102,8 +106,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_CHAN]),
|
||||
('columns.size', [250, 75, 75, 100, 175, 100, 175, 100, 100])
|
||||
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")
|
||||
EDIT_MSG = _("Edit the selected person")
|
||||
@ -121,6 +125,7 @@ class BasePersonView(ListView):
|
||||
'person-delete' : self.row_delete,
|
||||
'person-rebuild' : self.object_build,
|
||||
'person-groupname-rebuild' : self.object_build,
|
||||
'tag-update' : self.tag_updated
|
||||
}
|
||||
|
||||
ListView.__init__(
|
||||
@ -360,6 +365,20 @@ class BasePersonView(ListView):
|
||||
self.all_action.set_visible(False)
|
||||
self.edit_action.set_visible(False)
|
||||
|
||||
def set_active(self):
|
||||
"""
|
||||
Called when the page is displayed.
|
||||
"""
|
||||
ListView.set_active(self)
|
||||
self.uistate.viewmanager.tags.tag_enable()
|
||||
|
||||
def set_inactive(self):
|
||||
"""
|
||||
Called when the page is no longer displayed.
|
||||
"""
|
||||
ListView.set_inactive(self)
|
||||
self.uistate.viewmanager.tags.tag_disable()
|
||||
|
||||
def merge(self, obj):
|
||||
"""
|
||||
Merge the selected people.
|
||||
@ -375,3 +394,57 @@ class BasePersonView(ListView):
|
||||
else:
|
||||
import Merge
|
||||
Merge.MergePeople(self.dbstate, self.uistate, mlist[0], mlist[1])
|
||||
|
||||
def tag_updated(self, tag_name, tag_color):
|
||||
"""
|
||||
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()
|
||||
|
||||
def add_tag(self, tag):
|
||||
"""
|
||||
Add the given tag to the selected objects.
|
||||
"""
|
||||
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()
|
||||
|