""" 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 ##