aboutsummaryrefslogtreecommitdiffstats
path: root/tagit/actions
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2023-01-25 16:19:03 +0100
committerMatthias Baumgartner <dev@igsor.net>2023-01-25 16:19:03 +0100
commit41fdbe254cbfc74896080b9f5820f856067da537 (patch)
treeb55543ee23d2e372f2c4424cd91a7d6ef421562b /tagit/actions
parentf6de8a2f568419fd4ea818f3791242f177a87fba (diff)
downloadtagit-41fdbe254cbfc74896080b9f5820f856067da537.tar.gz
tagit-41fdbe254cbfc74896080b9f5820f856067da537.tar.bz2
tagit-41fdbe254cbfc74896080b9f5820f856067da537.zip
tagging ported
Diffstat (limited to 'tagit/actions')
-rw-r--r--tagit/actions/__init__.py5
-rw-r--r--tagit/actions/tagging.kv11
-rw-r--r--tagit/actions/tagging.py162
3 files changed, 176 insertions, 2 deletions
diff --git a/tagit/actions/__init__.py b/tagit/actions/__init__.py
index bf99807..6416a4b 100644
--- a/tagit/actions/__init__.py
+++ b/tagit/actions/__init__.py
@@ -19,6 +19,7 @@ from . import misc
from . import planes
from . import search
#from . import session
+from . import tagging
# exports
__all__: typing.Sequence[str] = (
@@ -85,8 +86,8 @@ class ActionBuilder(BuilderBase):
#'RotateLeft': objects.RotateLeft,
#'RotateRight': objects.RotateRight,
#'DeleteObject': objects.DeleteObject,
- #'AddTag': objects.AddTag,
- #'EditTag': objects.EditTag,
+ 'AddTag': tagging.AddTag,
+ 'EditTag': tagging.EditTag,
#'SetRank1': objects.SetRank1,
#'SetRank2': objects.SetRank2,
#'SetRank3': objects.SetRank3,
diff --git a/tagit/actions/tagging.kv b/tagit/actions/tagging.kv
new file mode 100644
index 0000000..53ba926
--- /dev/null
+++ b/tagit/actions/tagging.kv
@@ -0,0 +1,11 @@
+#:import resource_find kivy.resources.resource_find
+
+<AddTag>:
+ source: resource_find('atlas://objects/add_tag')
+ tooltip: 'Add tags to items'
+
+<EditTag>:
+ source: resource_find('atlas://objects/edit_tag')
+ tooltip: 'Edit tags of items'
+
+## EOF ##
diff --git a/tagit/actions/tagging.py b/tagit/actions/tagging.py
new file mode 100644
index 0000000..c6d920d
--- /dev/null
+++ b/tagit/actions/tagging.py
@@ -0,0 +1,162 @@
+"""
+
+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
+
+# 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.bsfs.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.bsfs.Tag, TAG_PREFIX[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) # FIXME: mb/port: pass set once supported by Nodes.set
+ # set tags
+ ents.set(ns.bse.tag, tags)
+ session.dispatch('on_predicate_modified', '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.bsfs.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)
+ # 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.bsfs.Tag, TAG_PREFIX[lbl])
+ tag.set(ns.bst.label, lbl)
+ added.add(tag)
+ added = reduce(operator.add, added)
+ # 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', '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 ##