1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
"""
Part of the tagit module.
A copy of the license is provided with the project.
Author: Matthias Baumgartner, 2022
"""
# standard imports
from functools import reduce, partial
import operator
import os
# external imports
import urllib.parse
# kivy imports
from kivy.lang import Builder
import kivy.properties as kp
# tagit imports
from tagit import config, dialogues
from tagit.utils import ns
from tagit.utils.bsfs import Namespace
from tagit.widgets import Binding
# inner-module imports
from .action import Action
# constants
TAGS_SEPERATOR = ','
TAG_PREFIX = Namespace('http://example.com/me/tag')()
# exports
__all__ = []
## code ##
# load kv
Builder.load_file(os.path.join(os.path.dirname(__file__), 'tagging.kv'))
# classes
class AddTag(Action):
"""Add tags to the selected items."""
text = kp.StringProperty('Add tag')
def ktrigger(self, evt):
return Binding.check(evt, self.cfg('bindings', 'objects', 'add_tag'))
def apply(self):
if len(self.root.browser.selection) > 0:
tags = self.root.session.storage.all(ns.bsn.Tag).label(node=True)
dlg = dialogues.SimpleInput(
suggestions=set(tags.values()),
suggestion_sep=TAGS_SEPERATOR)
dlg.bind(on_ok=partial(self.add_tags, tags))
dlg.open()
else:
dialogues.Error(text='You must select some images first.').open()
def add_tags(self, tags, wx):
# user-specified labels
labels = {t.strip() for t in wx.text.split(TAGS_SEPERATOR) if len(t.strip())}
# label to tag mapping
lut = {label: tag for tag, label in tags.items()}
# get previous tags
tags = {lut[lbl] for lbl in labels if lbl in lut}
# create new tag nodes and set their label
# FIXME: deny adding tags if tag vocabulary is fixed (ontology case)
# FIXME: replace with proper tag factory
for lbl in labels:
if lbl not in lut:
tag = self.root.session.storage.node(ns.bsn.Tag,
getattr(TAG_PREFIX, urllib.parse.quote(lbl)))
tag.set(ns.bst.label, lbl)
tags.add(tag)
with self.root.browser as browser, \
self.root.session as session:
# get objects
ents = browser.unfold(browser.selection)
# collect tags
tags = reduce(operator.add, tags, self.root.session.storage.empty(ns.bsn.Tag)) # FIXME: mb/port: pass set once supported by Nodes.set
# set tags
ents.set(ns.bse.tag, tags)
session.dispatch('on_predicate_modified', ns.bse.tag, ents, tags)
# cursor and selection might become invalid. Will be fixed in Browser.
class EditTag(Action):
"""Edit tags of the selected items"""
text = kp.StringProperty('Edit tags')
def ktrigger(self, evt):
return Binding.check(evt, self.cfg('bindings', 'objects', 'edit_tag'))
def apply(self):
with self.root.browser as browser:
if len(browser.selection) > 0:
# get all known tags
all_tags = self.root.session.storage.all(ns.bsn.Tag).label(node=True)
# get selection tags
ent_tags = browser.unfold(browser.selection).tag.label(node=True)
if len(ent_tags) == 0:
text = ''
else:
sep = TAGS_SEPERATOR + ' '
text = sep.join(sorted(set.intersection(*ent_tags.values())))
dlg = dialogues.SimpleInput(
text=text,
suggestions=set(all_tags.values()),
suggestion_sep=TAGS_SEPERATOR,
)
dlg.bind(on_ok=partial(self.edit_tag, ent_tags, all_tags))
dlg.open()
else:
dialogues.Error(text='You must select some images first.').open()
def edit_tag(self, original, tags, wx):
"""Add or remove tags from images.
*original* and *modified* are strings, split at *TAGS_SEPERATOR*.
Tags are added and removed with respect to the difference between those two sets.
"""
# user-specified labels
labels = {t.strip() for t in wx.text.split(TAGS_SEPERATOR) if len(t.strip())}
# label differences
original_labels = {lbl for lbls in original.values() for lbl in lbls}
removed_labels = original_labels - labels
added_labels = labels - original_labels
# get tags of removed labels
removed = {tag for tag, lbl in tags.items() if lbl in removed_labels}
removed = reduce(operator.add, removed, self.root.session.storage.empty(ns.bsn.Tag))
# get tags of added labels
# FIXME: deny adding tags if tag vocabulary is fixed (ontology case)
lut = {label: tag for tag, label in tags.items()}
added = {lut[lbl] for lbl in added_labels if lbl in lut}
# FIXME: replace with proper tag factory
for lbl in added_labels:
if lbl not in lut:
tag = self.root.session.storage.node(ns.bsn.Tag,
getattr(TAG_PREFIX, urllib.parse.quote(lbl)))
tag.set(ns.bst.label, lbl)
added.add(tag)
added = reduce(operator.add, added, self.root.session.storage.empty(ns.bsn.Tag))
# apply differences
with self.root.browser as browser, \
self.root.session as session:
ents = browser.unfold(browser.selection)
ents.set(ns.bse.tag, added)
#ents.remove(ns.bse.tag, removed) # FIXME: mb/port
session.dispatch('on_predicate_modified', ns.bse.tag, ents, added | removed)
# cursor and selection might become invalid. Will be fixed in Browser.
## config ##
# keybindings
config.declare(('bindings', 'objects', 'add_tag'),
config.Keybind(), Binding.simple('t', Binding.mCTRL, Binding.mSHIFT),
__name__, AddTag.text.defaultvalue, AddTag.__doc__)
config.declare(('bindings', 'objects', 'edit_tag'),
config.Keybind(), Binding.simple('e', Binding.mCTRL),
__name__, EditTag.text.defaultvalue, EditTag.__doc__)
## EOF ##
|