GEPS 011: Tagging
svn: r15830
@ -17,6 +17,7 @@ pkgdata_PYTHON = \
|
|||||||
_HasNoteSubstrBase.py\
|
_HasNoteSubstrBase.py\
|
||||||
_HasReferenceCountBase.py \
|
_HasReferenceCountBase.py \
|
||||||
_HasSourceBase.py \
|
_HasSourceBase.py \
|
||||||
|
_HasTagBase.py \
|
||||||
_HasTextMatchingRegexpOf.py\
|
_HasTextMatchingRegexpOf.py\
|
||||||
_HasTextMatchingSubstringOf.py\
|
_HasTextMatchingSubstringOf.py\
|
||||||
__init__.py\
|
__init__.py\
|
||||||
|
@ -28,6 +28,7 @@ pkgdata_PYTHON = \
|
|||||||
_HasRelationship.py \
|
_HasRelationship.py \
|
||||||
_HasSource.py \
|
_HasSource.py \
|
||||||
_HasSourceOf.py \
|
_HasSourceOf.py \
|
||||||
|
_HasTag.py \
|
||||||
_HasTextMatchingRegexpOf.py \
|
_HasTextMatchingRegexpOf.py \
|
||||||
_HasTextMatchingSubstringOf.py \
|
_HasTextMatchingSubstringOf.py \
|
||||||
_HasUnknownGender.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 _HasRelationship import HasRelationship
|
||||||
from _HasSource import HasSource
|
from _HasSource import HasSource
|
||||||
from _HasSourceOf import HasSourceOf
|
from _HasSourceOf import HasSourceOf
|
||||||
|
from _HasTag import HasTag
|
||||||
from _HasTextMatchingRegexpOf import HasTextMatchingRegexpOf
|
from _HasTextMatchingRegexpOf import HasTextMatchingRegexpOf
|
||||||
from _HasTextMatchingSubstringOf import HasTextMatchingSubstringOf
|
from _HasTextMatchingSubstringOf import HasTextMatchingSubstringOf
|
||||||
from _HasUnknownGender import HasUnknownGender
|
from _HasUnknownGender import HasUnknownGender
|
||||||
@ -126,6 +127,7 @@ editor_rule_list = [
|
|||||||
HasFamilyEvent,
|
HasFamilyEvent,
|
||||||
HasAttribute,
|
HasAttribute,
|
||||||
HasFamilyAttribute,
|
HasFamilyAttribute,
|
||||||
|
HasTag,
|
||||||
HasSource,
|
HasSource,
|
||||||
HasSourceOf,
|
HasSourceOf,
|
||||||
HasMarkerOf,
|
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 _
|
from gen.ggettext import gettext as _
|
||||||
|
import locale
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -45,8 +46,8 @@ import DateHandler
|
|||||||
|
|
||||||
from Filters.SideBar import SidebarFilter
|
from Filters.SideBar import SidebarFilter
|
||||||
from Filters.Rules.Person import (RegExpName, SearchName, RegExpIdOf,
|
from Filters.Rules.Person import (RegExpName, SearchName, RegExpIdOf,
|
||||||
MatchIdOf, IsMale, IsFemale,
|
MatchIdOf, IsMale, IsFemale, HasUnknownGender,
|
||||||
HasUnknownGender, HasMarkerOf, HasEvent,
|
HasMarkerOf, HasEvent, HasTag,
|
||||||
HasBirth, HasDeath, HasNoteRegexp,
|
HasBirth, HasDeath, HasNoteRegexp,
|
||||||
HasNoteMatchingSubstringOf, MatchesFilter)
|
HasNoteMatchingSubstringOf, MatchesFilter)
|
||||||
from Filters import GenericFilter, build_filter_model, Rules
|
from Filters import GenericFilter, build_filter_model, Rules
|
||||||
@ -91,6 +92,8 @@ class PersonSidebarFilter(SidebarFilter):
|
|||||||
self.filter_marker.set_marker,
|
self.filter_marker.set_marker,
|
||||||
self.filter_marker.get_marker)
|
self.filter_marker.get_marker)
|
||||||
|
|
||||||
|
self.tag = gtk.ComboBox()
|
||||||
|
|
||||||
self.filter_note = gtk.Entry()
|
self.filter_note = gtk.Entry()
|
||||||
self.filter_gender = gtk.combo_box_new_text()
|
self.filter_gender = gtk.combo_box_new_text()
|
||||||
map(self.filter_gender.append_text,
|
map(self.filter_gender.append_text,
|
||||||
@ -103,6 +106,8 @@ class PersonSidebarFilter(SidebarFilter):
|
|||||||
|
|
||||||
SidebarFilter.__init__(self, dbstate, uistate, "Person")
|
SidebarFilter.__init__(self, dbstate, uistate, "Person")
|
||||||
|
|
||||||
|
self.update_tag_list()
|
||||||
|
|
||||||
def create_widget(self):
|
def create_widget(self):
|
||||||
cell = gtk.CellRendererText()
|
cell = gtk.CellRendererText()
|
||||||
cell.set_property('width', self._FILTER_WIDTH)
|
cell.set_property('width', self._FILTER_WIDTH)
|
||||||
@ -111,6 +116,12 @@ class PersonSidebarFilter(SidebarFilter):
|
|||||||
self.generic.add_attribute(cell, 'text', 0)
|
self.generic.add_attribute(cell, 'text', 0)
|
||||||
self.on_filters_changed('Person')
|
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()
|
exdate1 = gen.lib.Date()
|
||||||
exdate2 = gen.lib.Date()
|
exdate2 = gen.lib.Date()
|
||||||
exdate1.set(gen.lib.Date.QUAL_NONE, gen.lib.Date.MOD_RANGE,
|
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))
|
_('example: "%s" or "%s"') % (msg1, msg2))
|
||||||
self.add_entry(_('Event'), self.etype)
|
self.add_entry(_('Event'), self.etype)
|
||||||
self.add_entry(_('Marker'), self.mtype)
|
self.add_entry(_('Marker'), self.mtype)
|
||||||
|
self.add_entry(_('Tag'), self.tag)
|
||||||
self.add_text_entry(_('Note'), self.filter_note)
|
self.add_text_entry(_('Note'), self.filter_note)
|
||||||
self.add_filter_entry(_('Custom filter'), self.generic)
|
self.add_filter_entry(_('Custom filter'), self.generic)
|
||||||
self.add_entry(None, self.filter_regex)
|
self.add_entry(None, self.filter_regex)
|
||||||
@ -144,6 +156,7 @@ class PersonSidebarFilter(SidebarFilter):
|
|||||||
self.filter_gender.set_active(0)
|
self.filter_gender.set_active(0)
|
||||||
self.etype.child.set_text(u'')
|
self.etype.child.set_text(u'')
|
||||||
self.mtype.child.set_text(u'')
|
self.mtype.child.set_text(u'')
|
||||||
|
self.tag.set_active(0)
|
||||||
self.generic.set_active(0)
|
self.generic.set_active(0)
|
||||||
|
|
||||||
def get_filter(self):
|
def get_filter(self):
|
||||||
@ -165,12 +178,13 @@ class PersonSidebarFilter(SidebarFilter):
|
|||||||
gender = self.filter_gender.get_active()
|
gender = self.filter_gender.get_active()
|
||||||
regex = self.filter_regex.get_active()
|
regex = self.filter_regex.get_active()
|
||||||
generic = self.generic.get_active() > 0
|
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
|
# check to see if the filter is empty. If it is empty, then
|
||||||
# we don't build a filter
|
# we don't build a filter
|
||||||
|
|
||||||
empty = not (name or gid or birth or death or etype or mtype
|
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:
|
if empty:
|
||||||
generic_filter = None
|
generic_filter = None
|
||||||
else:
|
else:
|
||||||
@ -209,6 +223,14 @@ class PersonSidebarFilter(SidebarFilter):
|
|||||||
rule = HasMarkerOf([mtype])
|
rule = HasMarkerOf([mtype])
|
||||||
generic_filter.add_rule(rule)
|
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
|
# Build an event filter if needed
|
||||||
if etype:
|
if etype:
|
||||||
rule = HasEvent([etype, u'', u'', u''])
|
rule = HasEvent([etype, u'', u'', u''])
|
||||||
@ -251,3 +273,26 @@ class PersonSidebarFilter(SidebarFilter):
|
|||||||
all_filter.add_rule(Rules.Person.Everyone([]))
|
all_filter.add_rule(Rules.Person.Everyone([]))
|
||||||
self.generic.set_model(build_filter_model('Person', [all_filter]))
|
self.generic.set_model(build_filter_model('Person', [all_filter]))
|
||||||
self.generic.set_active(0)
|
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()
|
self._init_interface()
|
||||||
uistate.connect('filters-changed', self.on_filters_changed)
|
uistate.connect('filters-changed', self.on_filters_changed)
|
||||||
|
dbstate.connect('database-changed', self._db_changed)
|
||||||
self.uistate = uistate
|
self.uistate = uistate
|
||||||
self.dbstate = dbstate
|
self.dbstate = dbstate
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
@ -137,6 +138,28 @@ class SidebarFilter(object):
|
|||||||
self.position += 1
|
self.position += 1
|
||||||
|
|
||||||
def on_filters_changed(self, namespace):
|
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
|
pass
|
||||||
|
|
||||||
def add_filter_entry(self, text, widget):
|
def add_filter_entry(self, text, widget):
|
||||||
|
@ -765,6 +765,30 @@ class DbReadBase(object):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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):
|
def has_note_handle(self, handle):
|
||||||
"""
|
"""
|
||||||
Return True if the handle exists in the current Note database.
|
Return True if the handle exists in the current Note database.
|
||||||
@ -1386,6 +1410,14 @@ class DbWriteBase(object):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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):
|
def sort_surname_list(self):
|
||||||
"""
|
"""
|
||||||
Sort the list of surnames contained in the database by locale ordering.
|
Sort the list of surnames contained in the database by locale ordering.
|
||||||
|
@ -291,6 +291,7 @@ class DbBsddbRead(DbReadBase, Callback):
|
|||||||
self.event_map = {}
|
self.event_map = {}
|
||||||
self.metadata = {}
|
self.metadata = {}
|
||||||
self.name_group = {}
|
self.name_group = {}
|
||||||
|
self.tags = {}
|
||||||
self.undo_callback = None
|
self.undo_callback = None
|
||||||
self.redo_callback = None
|
self.redo_callback = None
|
||||||
self.undo_history_callback = None
|
self.undo_history_callback = None
|
||||||
@ -697,6 +698,30 @@ class DbBsddbRead(DbReadBase, Callback):
|
|||||||
"""
|
"""
|
||||||
return self.name_group.has_key(str(name))
|
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):
|
def get_number_of_records(self, table):
|
||||||
if not self.db_is_open:
|
if not self.db_is_open:
|
||||||
return 0
|
return 0
|
||||||
|
@ -26,6 +26,26 @@ from gen.db import BSDDBTxn
|
|||||||
upgrade
|
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):
|
def gramps_upgrade_14(self):
|
||||||
"""Upgrade database from version 13 to 14."""
|
"""Upgrade database from version 13 to 14."""
|
||||||
# This upgrade modifies notes and dates
|
# This upgrade modifies notes and dates
|
||||||
|
@ -61,7 +61,7 @@ import Errors
|
|||||||
|
|
||||||
_LOG = logging.getLogger(DBLOGNAME)
|
_LOG = logging.getLogger(DBLOGNAME)
|
||||||
_MINVERSION = 9
|
_MINVERSION = 9
|
||||||
_DBVERSION = 14
|
_DBVERSION = 15
|
||||||
|
|
||||||
IDTRANS = "person_id"
|
IDTRANS = "person_id"
|
||||||
FIDTRANS = "family_id"
|
FIDTRANS = "family_id"
|
||||||
@ -73,6 +73,7 @@ NIDTRANS = "note_id"
|
|||||||
SIDTRANS = "source_id"
|
SIDTRANS = "source_id"
|
||||||
SURNAMES = "surnames"
|
SURNAMES = "surnames"
|
||||||
NAME_GROUP = "name_group"
|
NAME_GROUP = "name_group"
|
||||||
|
TAGS = "tags"
|
||||||
META = "meta_data"
|
META = "meta_data"
|
||||||
|
|
||||||
FAMILY_TBL = "family"
|
FAMILY_TBL = "family"
|
||||||
@ -197,6 +198,10 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
|
|||||||
# 4. Signal for change in person group name, parameters are
|
# 4. Signal for change in person group name, parameters are
|
||||||
__signals__['person-groupname-rebuild'] = (unicode, unicode)
|
__signals__['person-groupname-rebuild'] = (unicode, unicode)
|
||||||
|
|
||||||
|
# 5. Signals for change ins tags
|
||||||
|
__signals__['tags-changed'] = None
|
||||||
|
__signals__['tag-update'] = (str, str)
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Create a new GrampsDB."""
|
"""Create a new GrampsDB."""
|
||||||
|
|
||||||
@ -463,6 +468,9 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
|
|||||||
self.name_group = self.__open_db(self.full_name, NAME_GROUP,
|
self.name_group = self.__open_db(self.full_name, NAME_GROUP,
|
||||||
db.DB_HASH, db.DB_DUP)
|
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.
|
# Here we take care of any changes in the tables related to new code.
|
||||||
# If secondary indices change, then they should removed
|
# If secondary indices change, then they should removed
|
||||||
# or rebuilt by upgrade as well. In any case, the
|
# or rebuilt by upgrade as well. In any case, the
|
||||||
@ -1006,6 +1014,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
|
|||||||
|
|
||||||
self.__close_metadata()
|
self.__close_metadata()
|
||||||
self.name_group.close()
|
self.name_group.close()
|
||||||
|
self.tags.close()
|
||||||
self.surnames.close()
|
self.surnames.close()
|
||||||
self.id_trans.close()
|
self.id_trans.close()
|
||||||
self.fid_trans.close()
|
self.fid_trans.close()
|
||||||
@ -1042,7 +1051,7 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
|
|||||||
self.media_map = None
|
self.media_map = None
|
||||||
self.event_map = None
|
self.event_map = None
|
||||||
self.surnames = None
|
self.surnames = None
|
||||||
self.name_group = None
|
self.tags = None
|
||||||
self.env = None
|
self.env = None
|
||||||
self.metadata = None
|
self.metadata = None
|
||||||
self.db_is_open = False
|
self.db_is_open = False
|
||||||
@ -1280,6 +1289,24 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
|
|||||||
grouppar = group
|
grouppar = group
|
||||||
self.emit('person-groupname-rebuild', (name, grouppar))
|
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):
|
def sort_surname_list(self):
|
||||||
self.surname_list.sort(key=locale.strxfrm)
|
self.surname_list.sort(key=locale.strxfrm)
|
||||||
|
|
||||||
@ -1652,9 +1679,11 @@ class DbBsddb(DbBsddbRead, DbWriteBase, UpdateCallback):
|
|||||||
|
|
||||||
t = time.time()
|
t = time.time()
|
||||||
|
|
||||||
|
import upgrade
|
||||||
if version < 14:
|
if version < 14:
|
||||||
import upgrade
|
|
||||||
upgrade.gramps_upgrade_14(self)
|
upgrade.gramps_upgrade_14(self)
|
||||||
|
if version < 15:
|
||||||
|
upgrade.gramps_upgrade_15(self)
|
||||||
|
|
||||||
print "Upgrade time:", int(time.time()-t), "seconds"
|
print "Upgrade time:", int(time.time()-t), "seconds"
|
||||||
|
|
||||||
|
@ -60,6 +60,7 @@ pkgdata_PYTHON = \
|
|||||||
styledtext.py \
|
styledtext.py \
|
||||||
styledtexttag.py \
|
styledtexttag.py \
|
||||||
styledtexttagtype.py \
|
styledtexttagtype.py \
|
||||||
|
tagbase.py \
|
||||||
urlbase.py \
|
urlbase.py \
|
||||||
url.py \
|
url.py \
|
||||||
urltype.py \
|
urltype.py \
|
||||||
|
@ -38,6 +38,7 @@ from gen.lib.attrbase import AttributeBase
|
|||||||
from gen.lib.addressbase import AddressBase
|
from gen.lib.addressbase import AddressBase
|
||||||
from gen.lib.ldsordbase import LdsOrdBase
|
from gen.lib.ldsordbase import LdsOrdBase
|
||||||
from gen.lib.urlbase import UrlBase
|
from gen.lib.urlbase import UrlBase
|
||||||
|
from gen.lib.tagbase import TagBase
|
||||||
from gen.lib.name import Name
|
from gen.lib.name import Name
|
||||||
from gen.lib.eventref import EventRef
|
from gen.lib.eventref import EventRef
|
||||||
from gen.lib.personref import PersonRef
|
from gen.lib.personref import PersonRef
|
||||||
@ -53,7 +54,7 @@ from gen.lib.const import IDENTICAL, EQUAL, DIFFERENT
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
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
|
The Person record is the GRAMPS in-memory representation of an
|
||||||
individual person. It contains all the information related to
|
individual person. It contains all the information related to
|
||||||
@ -91,6 +92,7 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
|||||||
AddressBase.__init__(self)
|
AddressBase.__init__(self)
|
||||||
UrlBase.__init__(self)
|
UrlBase.__init__(self)
|
||||||
LdsOrdBase.__init__(self)
|
LdsOrdBase.__init__(self)
|
||||||
|
TagBase.__init__(self)
|
||||||
self.primary_name = Name()
|
self.primary_name = Name()
|
||||||
self.marker = MarkerType()
|
self.marker = MarkerType()
|
||||||
self.event_ref_list = []
|
self.event_ref_list = []
|
||||||
@ -153,7 +155,8 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
|||||||
self.change, # 17
|
self.change, # 17
|
||||||
self.marker.serialize(), # 18
|
self.marker.serialize(), # 18
|
||||||
self.private, # 19
|
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):
|
def unserialize(self, data):
|
||||||
@ -186,6 +189,7 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
|||||||
marker, # 18
|
marker, # 18
|
||||||
self.private, # 19
|
self.private, # 19
|
||||||
person_ref_list, # 20
|
person_ref_list, # 20
|
||||||
|
tag_list, # 21
|
||||||
) = data
|
) = data
|
||||||
|
|
||||||
self.marker = MarkerType()
|
self.marker = MarkerType()
|
||||||
@ -205,6 +209,7 @@ class Person(SourceBase, NoteBase, AttributeBase, MediaBase,
|
|||||||
UrlBase.unserialize(self, urls)
|
UrlBase.unserialize(self, urls)
|
||||||
SourceBase.unserialize(self, source_list)
|
SourceBase.unserialize(self, source_list)
|
||||||
NoteBase.unserialize(self, note_list)
|
NoteBase.unserialize(self, note_list)
|
||||||
|
TagBase.unserialize(self, tag_list)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def _has_handle_reference(self, classname, handle):
|
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>
|
<child>
|
||||||
<object class="GtkTable" id="table15">
|
<object class="GtkTable" id="table15">
|
||||||
<property name="visible">True</property>
|
<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="n_columns">6</property>
|
||||||
<property name="column_spacing">12</property>
|
<property name="column_spacing">12</property>
|
||||||
<property name="row_spacing">6</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>
|
<property name="y_options">GTK_FILL</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</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>
|
<child>
|
||||||
<placeholder/>
|
<placeholder/>
|
||||||
</child>
|
</child>
|
||||||
|
@ -31,7 +31,6 @@ to edit information about a particular Person.
|
|||||||
# Standard python modules
|
# Standard python modules
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
import locale
|
|
||||||
from gen.ggettext import sgettext as _
|
from gen.ggettext import sgettext as _
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
@ -306,6 +305,15 @@ class EditPerson(EditPrimary):
|
|||||||
self.db.readonly,
|
self.db.readonly,
|
||||||
autolist=self.db.get_surname_list() if not self.db.readonly else [])
|
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.gid = widgets.MonitoredEntry(
|
||||||
self.top.get_object("gid"),
|
self.top.get_object("gid"),
|
||||||
self.obj.set_gramps_id,
|
self.obj.set_gramps_id,
|
||||||
@ -724,9 +732,9 @@ class EditPerson(EditPrimary):
|
|||||||
name = self.name_displayer.display(prim_object)
|
name = self.name_displayer.display(prim_object)
|
||||||
msg1 = _("Cannot save person. ID already exists.")
|
msg1 = _("Cannot save person. ID already exists.")
|
||||||
msg2 = _("You have attempted to use the existing Gramps ID with "
|
msg2 = _("You have attempted to use the existing Gramps ID with "
|
||||||
"value %(id)s. This value is already used by '"
|
"value %(id)s. This value is already used by '"
|
||||||
"%(prim_object)s'. Please enter a different ID or leave "
|
"%(prim_object)s'. Please enter a different ID or leave "
|
||||||
"blank to get the next available ID value.") % {
|
"blank to get the next available ID value.") % {
|
||||||
'id' : id, 'prim_object' : name }
|
'id' : id, 'prim_object' : name }
|
||||||
ErrorDialog(msg1, msg2)
|
ErrorDialog(msg1, msg2)
|
||||||
self.ok_button.set_sensitive(True)
|
self.ok_button.set_sensitive(True)
|
||||||
|
@ -136,6 +136,8 @@ def register_stock_icons ():
|
|||||||
('gramps-repository', _('Repositories'), gtk.gdk.CONTROL_MASK, 0, ''),
|
('gramps-repository', _('Repositories'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||||
('gramps-source', _('Sources'), gtk.gdk.CONTROL_MASK, 0, ''),
|
('gramps-source', _('Sources'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||||
('gramps-spouse', _('Add Spouse'), 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-tools', _('Tools'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||||
('gramps-tree-group', _('Grouped List'), gtk.gdk.CONTROL_MASK, 0, ''),
|
('gramps-tree-group', _('Grouped List'), gtk.gdk.CONTROL_MASK, 0, ''),
|
||||||
('gramps-tree-list', _('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 gen.db.exceptions import DbException
|
||||||
from GrampsAboutDialog import GrampsAboutDialog
|
from GrampsAboutDialog import GrampsAboutDialog
|
||||||
from gui.sidebar import Sidebar
|
from gui.sidebar import Sidebar
|
||||||
|
from gui.views.tags import Tags
|
||||||
from gen.utils.configmanager import safe_eval
|
from gen.utils.configmanager import safe_eval
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
@ -120,6 +121,8 @@ UIDEFAULT = '''<ui>
|
|||||||
<separator/>
|
<separator/>
|
||||||
<placeholder name="CommonEdit"/>
|
<placeholder name="CommonEdit"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
|
<placeholder name="TagMenu"/>
|
||||||
|
<separator/>
|
||||||
<menuitem action="ScratchPad"/>
|
<menuitem action="ScratchPad"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<menuitem action="Preferences"/>
|
<menuitem action="Preferences"/>
|
||||||
@ -172,6 +175,8 @@ UIDEFAULT = '''<ui>
|
|||||||
<toolitem action="Reports"/>
|
<toolitem action="Reports"/>
|
||||||
<toolitem action="Tools"/>
|
<toolitem action="Tools"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
|
<placeholder name="TagTool"/>
|
||||||
|
<separator/>
|
||||||
<placeholder name="CommonEdit"/>
|
<placeholder name="CommonEdit"/>
|
||||||
<separator/>
|
<separator/>
|
||||||
<placeholder name="ViewsInCategory"/>
|
<placeholder name="ViewsInCategory"/>
|
||||||
@ -536,6 +541,8 @@ class ViewManager(CLIManager):
|
|||||||
|
|
||||||
self.dbstate.connect('database-changed', self.uistate.db_changed)
|
self.dbstate.connect('database-changed', self.uistate.db_changed)
|
||||||
|
|
||||||
|
self.tags = Tags(self.uistate, self.dbstate)
|
||||||
|
|
||||||
self.filter_menu = self.uimanager.get_widget(
|
self.filter_menu = self.uimanager.get_widget(
|
||||||
'/MenuBar/ViewMenu/Filter/')
|
'/MenuBar/ViewMenu/Filter/')
|
||||||
|
|
||||||
@ -1700,34 +1707,34 @@ def by_menu_name(first, second):
|
|||||||
return cmp(first.name, second.name)
|
return cmp(first.name, second.name)
|
||||||
|
|
||||||
def run_plugin(pdata, dbstate, uistate):
|
def run_plugin(pdata, dbstate, uistate):
|
||||||
"""
|
"""
|
||||||
run a plugin based on it's PluginData:
|
run a plugin based on it's PluginData:
|
||||||
1/ load plugin.
|
1/ load plugin.
|
||||||
2/ the report is run
|
2/ the report is run
|
||||||
"""
|
"""
|
||||||
mod = GuiPluginManager.get_instance().load_plugin(pdata)
|
mod = GuiPluginManager.get_instance().load_plugin(pdata)
|
||||||
if not mod:
|
if not mod:
|
||||||
#import of plugin failed
|
#import of plugin failed
|
||||||
ErrorDialog(
|
ErrorDialog(
|
||||||
_('Failed Loading Plugin'),
|
_('Failed Loading Plugin'),
|
||||||
_('The plugin did not load. See Help Menu, Plugin Manager'
|
_('The plugin did not load. See Help Menu, Plugin Manager'
|
||||||
' for more info.\nUse http://bugs.gramps-project.org to'
|
' for more info.\nUse http://bugs.gramps-project.org to'
|
||||||
' submit bugs of official plugins, contact the plugin '
|
' submit bugs of official plugins, contact the plugin '
|
||||||
'author otherwise. '))
|
'author otherwise. '))
|
||||||
return
|
return
|
||||||
|
|
||||||
if pdata.ptype == REPORT:
|
if pdata.ptype == REPORT:
|
||||||
report(dbstate, uistate, uistate.get_active('Person'),
|
report(dbstate, uistate, uistate.get_active('Person'),
|
||||||
getattr(mod, pdata.reportclass),
|
getattr(mod, pdata.reportclass),
|
||||||
getattr(mod, pdata.optionclass),
|
getattr(mod, pdata.optionclass),
|
||||||
pdata.name, pdata.id,
|
pdata.name, pdata.id,
|
||||||
pdata.category, pdata.require_active)
|
pdata.category, pdata.require_active)
|
||||||
else:
|
else:
|
||||||
tool.gui_tool(dbstate, uistate,
|
tool.gui_tool(dbstate, uistate,
|
||||||
getattr(mod, pdata.toolclass),
|
getattr(mod, pdata.toolclass),
|
||||||
getattr(mod, pdata.optionclass),
|
getattr(mod, pdata.optionclass),
|
||||||
pdata.name, pdata.id, pdata.category,
|
pdata.name, pdata.id, pdata.category,
|
||||||
dbstate.db.request_rebuild)
|
dbstate.db.request_rebuild)
|
||||||
|
|
||||||
def make_plugin_callback(pdata, dbstate, uistate):
|
def make_plugin_callback(pdata, dbstate, uistate):
|
||||||
"""
|
"""
|
||||||
|
@ -12,7 +12,8 @@ pkgdata_PYTHON = \
|
|||||||
__init__.py \
|
__init__.py \
|
||||||
listview.py \
|
listview.py \
|
||||||
navigationview.py \
|
navigationview.py \
|
||||||
pageview.py
|
pageview.py \
|
||||||
|
tags.py
|
||||||
|
|
||||||
pkgpyexecdir = @pkgpyexecdir@/gui/views
|
pkgpyexecdir = @pkgpyexecdir@/gui/views
|
||||||
pkgpythondir = @pkgpythondir@/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_BIRTH = 6
|
||||||
COLUMN_EVENT = 7
|
COLUMN_EVENT = 7
|
||||||
COLUMN_FAMILY = 8
|
COLUMN_FAMILY = 8
|
||||||
|
COLUMN_TAGS = 21
|
||||||
COLUMN_CHANGE = 17
|
COLUMN_CHANGE = 17
|
||||||
COLUMN_MARKER = 18
|
COLUMN_MARKER = 18
|
||||||
|
|
||||||
@ -103,6 +104,7 @@ class PeopleBaseModel(object):
|
|||||||
"""
|
"""
|
||||||
Initialize the model building the initial data
|
Initialize the model building the initial data
|
||||||
"""
|
"""
|
||||||
|
self.db = db
|
||||||
self.gen_cursor = db.get_person_cursor
|
self.gen_cursor = db.get_person_cursor
|
||||||
self.map = db.get_raw_person_data
|
self.map = db.get_raw_person_data
|
||||||
|
|
||||||
@ -115,10 +117,11 @@ class PeopleBaseModel(object):
|
|||||||
self.column_death_day,
|
self.column_death_day,
|
||||||
self.column_death_place,
|
self.column_death_place,
|
||||||
self.column_spouse,
|
self.column_spouse,
|
||||||
|
self.column_tags,
|
||||||
self.column_change,
|
self.column_change,
|
||||||
self.column_int_id,
|
self.column_int_id,
|
||||||
self.column_marker_text,
|
self.column_marker_text,
|
||||||
self.column_marker_color,
|
self.column_tag_color,
|
||||||
self.column_tooltip,
|
self.column_tooltip,
|
||||||
]
|
]
|
||||||
self.smap = [
|
self.smap = [
|
||||||
@ -130,10 +133,11 @@ class PeopleBaseModel(object):
|
|||||||
self.sort_death_day,
|
self.sort_death_day,
|
||||||
self.column_death_place,
|
self.column_death_place,
|
||||||
self.column_spouse,
|
self.column_spouse,
|
||||||
|
self.column_tags,
|
||||||
self.sort_change,
|
self.sort_change,
|
||||||
self.column_int_id,
|
self.column_int_id,
|
||||||
self.column_marker_text,
|
self.column_marker_text,
|
||||||
self.column_marker_color,
|
self.column_tag_color,
|
||||||
self.column_tooltip,
|
self.column_tooltip,
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -145,11 +149,26 @@ class PeopleBaseModel(object):
|
|||||||
self.lru_bdate = LRU(PeopleBaseModel._CACHE_SIZE)
|
self.lru_bdate = LRU(PeopleBaseModel._CACHE_SIZE)
|
||||||
self.lru_ddate = 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):
|
def marker_column(self):
|
||||||
"""
|
"""
|
||||||
Return the column for marker colour.
|
Return the column for marker colour.
|
||||||
"""
|
"""
|
||||||
return 11
|
return 12
|
||||||
|
|
||||||
def clear_local_cache(self, handle=None):
|
def clear_local_cache(self, handle=None):
|
||||||
""" Clear the LRU cache """
|
""" Clear the LRU cache """
|
||||||
@ -419,19 +438,6 @@ class PeopleBaseModel(object):
|
|||||||
return str(data[COLUMN_MARKER])
|
return str(data[COLUMN_MARKER])
|
||||||
return ""
|
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):
|
def column_tooltip(self, data):
|
||||||
if const.USE_TIPS:
|
if const.USE_TIPS:
|
||||||
return ToolTips.TipFromFunction(
|
return ToolTips.TipFromFunction(
|
||||||
@ -444,6 +450,14 @@ class PeopleBaseModel(object):
|
|||||||
def column_int_id(self, data):
|
def column_int_id(self, data):
|
||||||
return data[0]
|
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):
|
class PersonListModel(PeopleBaseModel, FlatBaseModel):
|
||||||
"""
|
"""
|
||||||
Listed people model.
|
Listed people model.
|
||||||
@ -453,7 +467,7 @@ class PersonListModel(PeopleBaseModel, FlatBaseModel):
|
|||||||
|
|
||||||
PeopleBaseModel.__init__(self, db)
|
PeopleBaseModel.__init__(self, db)
|
||||||
FlatBaseModel.__init__(self, db, search=search, skip=skip,
|
FlatBaseModel.__init__(self, db, search=search, skip=skip,
|
||||||
tooltip_column=12,
|
tooltip_column=13,
|
||||||
scol=scol, order=order, sort_map=sort_map)
|
scol=scol, order=order, sort_map=sort_map)
|
||||||
|
|
||||||
def clear_cache(self, handle=None):
|
def clear_cache(self, handle=None):
|
||||||
@ -468,7 +482,7 @@ class PersonTreeModel(PeopleBaseModel, TreeBaseModel):
|
|||||||
skip=set(), sort_map=None):
|
skip=set(), sort_map=None):
|
||||||
|
|
||||||
PeopleBaseModel.__init__(self, db)
|
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)
|
scol=scol, order=order, sort_map=sort_map)
|
||||||
|
|
||||||
def _set_base_data(self):
|
def _set_base_data(self):
|
||||||
|
@ -21,6 +21,7 @@ pkgdata_PYTHON = \
|
|||||||
statusbar.py \
|
statusbar.py \
|
||||||
styledtextbuffer.py \
|
styledtextbuffer.py \
|
||||||
styledtexteditor.py \
|
styledtexteditor.py \
|
||||||
|
tageditor.py \
|
||||||
toolcomboentry.py \
|
toolcomboentry.py \
|
||||||
undoablebuffer.py \
|
undoablebuffer.py \
|
||||||
validatedcomboentry.py \
|
validatedcomboentry.py \
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
__all__ = ["MonitoredCheckbox", "MonitoredEntry", "MonitoredSpinButton",
|
__all__ = ["MonitoredCheckbox", "MonitoredEntry", "MonitoredSpinButton",
|
||||||
"MonitoredText", "MonitoredType", "MonitoredDataType",
|
"MonitoredText", "MonitoredType", "MonitoredDataType",
|
||||||
"MonitoredMenu", "MonitoredStrMenu", "MonitoredDate",
|
"MonitoredMenu", "MonitoredStrMenu", "MonitoredDate",
|
||||||
"MonitoredComboSelectedEntry"]
|
"MonitoredComboSelectedEntry", "MonitoredTagList"]
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -47,8 +47,10 @@ import gtk
|
|||||||
# Gramps modules
|
# Gramps modules
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
|
from gen.ggettext import gettext as _
|
||||||
import AutoComp
|
import AutoComp
|
||||||
import DateEdit
|
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
|
Eg: name editor save brings you back to person editor that must update
|
||||||
"""
|
"""
|
||||||
self.entry_reinit()
|
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-repository.png \
|
||||||
gramps-source.png \
|
gramps-source.png \
|
||||||
gramps-spouse.png \
|
gramps-spouse.png \
|
||||||
|
gramps-tag.png \
|
||||||
|
gramps-tag-new.png \
|
||||||
gramps-tools.png \
|
gramps-tools.png \
|
||||||
gramps-tree-group.png \
|
gramps-tree-group.png \
|
||||||
gramps-tree-list.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-repository.png \
|
||||||
gramps-source.png \
|
gramps-source.png \
|
||||||
gramps-spouse.png \
|
gramps-spouse.png \
|
||||||
|
gramps-tag.png \
|
||||||
|
gramps-tag-new.png \
|
||||||
gramps-tools.png \
|
gramps-tools.png \
|
||||||
gramps-tree-group.png \
|
gramps-tree-group.png \
|
||||||
gramps-tree-list.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-repository.png \
|
||||||
gramps-source.png \
|
gramps-source.png \
|
||||||
gramps-spouse.png \
|
gramps-spouse.png \
|
||||||
|
gramps-tag.png \
|
||||||
|
gramps-tag-new.png \
|
||||||
gramps-tools.png \
|
gramps-tools.png \
|
||||||
gramps-tree-group.png \
|
gramps-tree-group.png \
|
||||||
gramps-tree-list.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-repository.svg \
|
||||||
gramps-source.svg \
|
gramps-source.svg \
|
||||||
gramps-spouse.svg \
|
gramps-spouse.svg \
|
||||||
|
gramps-tag.svg \
|
||||||
|
gramps-tag-new.svg \
|
||||||
gramps-tools.svg \
|
gramps-tools.svg \
|
||||||
gramps-tree-group.svg \
|
gramps-tree-group.svg \
|
||||||
gramps-tree-list.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 gui.editors import EditPerson
|
||||||
from Filters.SideBar import PersonSidebarFilter
|
from Filters.SideBar import PersonSidebarFilter
|
||||||
from gen.plug import CATEGORY_QR_PERSON
|
from gen.plug import CATEGORY_QR_PERSON
|
||||||
|
import gui.widgets.progressdialog as progressdlg
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
# internationalization
|
# Python modules
|
||||||
#
|
#
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
from gen.ggettext import sgettext as _
|
from gen.ggettext import sgettext as _
|
||||||
|
from bisect import insort_left
|
||||||
|
|
||||||
#-------------------------------------------------------------------------
|
#-------------------------------------------------------------------------
|
||||||
#
|
#
|
||||||
@ -83,7 +85,8 @@ class BasePersonView(ListView):
|
|||||||
COL_DDAT = 5
|
COL_DDAT = 5
|
||||||
COL_DPLAC = 6
|
COL_DPLAC = 6
|
||||||
COL_SPOUSE = 7
|
COL_SPOUSE = 7
|
||||||
COL_CHAN = 8
|
COL_TAGS = 8
|
||||||
|
COL_CHAN = 9
|
||||||
#name of the columns
|
#name of the columns
|
||||||
COLUMN_NAMES = [
|
COLUMN_NAMES = [
|
||||||
_('Name'),
|
_('Name'),
|
||||||
@ -94,6 +97,7 @@ class BasePersonView(ListView):
|
|||||||
_('Death Date'),
|
_('Death Date'),
|
||||||
_('Death Place'),
|
_('Death Place'),
|
||||||
_('Spouse'),
|
_('Spouse'),
|
||||||
|
_('Tags'),
|
||||||
_('Last Changed'),
|
_('Last Changed'),
|
||||||
]
|
]
|
||||||
# columns that contain markup
|
# columns that contain markup
|
||||||
@ -102,8 +106,8 @@ class BasePersonView(ListView):
|
|||||||
CONFIGSETTINGS = (
|
CONFIGSETTINGS = (
|
||||||
('columns.visible', [COL_NAME, COL_ID, COL_GEN, COL_BDAT, COL_DDAT]),
|
('columns.visible', [COL_NAME, COL_ID, COL_GEN, COL_BDAT, COL_DDAT]),
|
||||||
('columns.rank', [COL_NAME, COL_ID, COL_GEN, COL_BDAT, COL_BPLAC,
|
('columns.rank', [COL_NAME, COL_ID, COL_GEN, COL_BDAT, COL_BPLAC,
|
||||||
COL_DDAT, COL_DPLAC, COL_SPOUSE, COL_CHAN]),
|
COL_DDAT, COL_DPLAC, COL_SPOUSE, COL_TAGS, COL_CHAN]),
|
||||||
('columns.size', [250, 75, 75, 100, 175, 100, 175, 100, 100])
|
('columns.size', [250, 75, 75, 100, 175, 100, 175, 100, 100, 100])
|
||||||
)
|
)
|
||||||
ADD_MSG = _("Add a new person")
|
ADD_MSG = _("Add a new person")
|
||||||
EDIT_MSG = _("Edit the selected person")
|
EDIT_MSG = _("Edit the selected person")
|
||||||
@ -121,6 +125,7 @@ class BasePersonView(ListView):
|
|||||||
'person-delete' : self.row_delete,
|
'person-delete' : self.row_delete,
|
||||||
'person-rebuild' : self.object_build,
|
'person-rebuild' : self.object_build,
|
||||||
'person-groupname-rebuild' : self.object_build,
|
'person-groupname-rebuild' : self.object_build,
|
||||||
|
'tag-update' : self.tag_updated
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView.__init__(
|
ListView.__init__(
|
||||||
@ -360,6 +365,20 @@ class BasePersonView(ListView):
|
|||||||
self.all_action.set_visible(False)
|
self.all_action.set_visible(False)
|
||||||
self.edit_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):
|
def merge(self, obj):
|
||||||
"""
|
"""
|
||||||
Merge the selected people.
|
Merge the selected people.
|
||||||
@ -375,3 +394,57 @@ class BasePersonView(ListView):
|
|||||||
else:
|
else:
|
||||||
import Merge
|
import Merge
|
||||||
Merge.MergePeople(self.dbstate, self.uistate, mlist[0], mlist[1])
|
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()
|
||||||
|