From 37f1ac3f456b6677d9ce14274f3987ccda752f03 Mon Sep 17 00:00:00 2001 From: Matthias Baumgartner Date: Sat, 7 Jan 2023 16:26:26 +0100 Subject: browser and misc actions --- tagit/actions/__init__.py | 60 +- tagit/actions/action.kv | 45 ++ tagit/actions/action.py | 257 +++++++++ tagit/actions/browser.kv | 99 ++++ tagit/actions/browser.py | 628 +++++++++++++++++++++ tagit/actions/misc.py | 3 +- tagit/apps/port-config.yaml | 6 +- tagit/assets/icons/kivy/browser-0.png | Bin 0 -> 7317 bytes tagit/assets/icons/kivy/browser.atlas | 1 + tagit/assets/icons/scalable/README | 5 + .../assets/icons/scalable/browser/cursor_down.svg | 164 ++++++ .../assets/icons/scalable/browser/cursor_first.svg | 155 +++++ .../assets/icons/scalable/browser/cursor_last.svg | 154 +++++ .../assets/icons/scalable/browser/cursor_left.svg | 159 ++++++ .../assets/icons/scalable/browser/cursor_right.svg | 163 ++++++ tagit/assets/icons/scalable/browser/cursor_up.svg | 163 ++++++ tagit/assets/icons/scalable/browser/next_page.svg | 164 ++++++ .../icons/scalable/browser/previous_page.svg | 164 ++++++ .../assets/icons/scalable/browser/scroll_down.svg | 147 +++++ tagit/assets/icons/scalable/browser/scroll_up.svg | 147 +++++ tagit/assets/icons/scalable/browser/select.svg | 291 ++++++++++ tagit/assets/icons/scalable/browser/select_add.svg | 169 ++++++ tagit/assets/icons/scalable/browser/select_all.svg | 158 ++++++ .../icons/scalable/browser/select_invert.svg | 255 +++++++++ .../assets/icons/scalable/browser/select_multi.svg | 158 ++++++ .../assets/icons/scalable/browser/select_none.svg | 158 ++++++ .../assets/icons/scalable/browser/select_range.svg | 312 ++++++++++ .../icons/scalable/browser/select_single.svg | 158 ++++++ tagit/assets/icons/scalable/browser/select_sub.svg | 162 ++++++ tagit/assets/icons/scalable/browser/zoom_in.svg | 241 ++++++++ tagit/assets/icons/scalable/browser/zoom_out.svg | 241 ++++++++ tagit/config/port-config.yaml | 115 ---- tagit/utils/shared.py | 17 + 33 files changed, 4969 insertions(+), 150 deletions(-) create mode 100644 tagit/actions/action.kv create mode 100644 tagit/actions/action.py create mode 100644 tagit/actions/browser.kv create mode 100644 tagit/actions/browser.py create mode 100644 tagit/assets/icons/kivy/browser-0.png create mode 100644 tagit/assets/icons/kivy/browser.atlas create mode 100644 tagit/assets/icons/scalable/README create mode 100644 tagit/assets/icons/scalable/browser/cursor_down.svg create mode 100644 tagit/assets/icons/scalable/browser/cursor_first.svg create mode 100644 tagit/assets/icons/scalable/browser/cursor_last.svg create mode 100644 tagit/assets/icons/scalable/browser/cursor_left.svg create mode 100644 tagit/assets/icons/scalable/browser/cursor_right.svg create mode 100644 tagit/assets/icons/scalable/browser/cursor_up.svg create mode 100644 tagit/assets/icons/scalable/browser/next_page.svg create mode 100644 tagit/assets/icons/scalable/browser/previous_page.svg create mode 100644 tagit/assets/icons/scalable/browser/scroll_down.svg create mode 100644 tagit/assets/icons/scalable/browser/scroll_up.svg create mode 100644 tagit/assets/icons/scalable/browser/select.svg create mode 100644 tagit/assets/icons/scalable/browser/select_add.svg create mode 100644 tagit/assets/icons/scalable/browser/select_all.svg create mode 100644 tagit/assets/icons/scalable/browser/select_invert.svg create mode 100644 tagit/assets/icons/scalable/browser/select_multi.svg create mode 100644 tagit/assets/icons/scalable/browser/select_none.svg create mode 100644 tagit/assets/icons/scalable/browser/select_range.svg create mode 100644 tagit/assets/icons/scalable/browser/select_single.svg create mode 100644 tagit/assets/icons/scalable/browser/select_sub.svg create mode 100644 tagit/assets/icons/scalable/browser/zoom_in.svg create mode 100644 tagit/assets/icons/scalable/browser/zoom_out.svg delete mode 100644 tagit/config/port-config.yaml (limited to 'tagit') diff --git a/tagit/actions/__init__.py b/tagit/actions/__init__.py index ecff701..876ca1f 100644 --- a/tagit/actions/__init__.py +++ b/tagit/actions/__init__.py @@ -11,7 +11,7 @@ import typing from tagit.utils.builder import BuilderBase # inner-module imports -#from . import browser +from . import browser from . import filter from . import grouping #from . import library @@ -32,30 +32,30 @@ __all__: typing.Sequence[str] = ( class ActionBuilder(BuilderBase): _factories = { ## browser - #'NextPage': browser.NextPage, - #'PreviousPage': browser.PreviousPage, - #'ScrollDown': browser.ScrollDown, - #'ScrollUp': browser.ScrollUp, - #'JumpToPage': browser.JumpToPage, - #'SetCursor': browser.SetCursor, - #'ZoomIn': browser.ZoomIn, - #'ZoomOut': browser.ZoomOut, - #'JumpToCursor': browser.JumpToCursor, - #'MoveCursorFirst': browser.MoveCursorFirst, - #'MoveCursorLast': browser.MoveCursorLast, - #'MoveCursorUp': browser.MoveCursorUp, - #'MoveCursorDown': browser.MoveCursorDown, - #'MoveCursorLeft': browser.MoveCursorLeft, - #'MoveCursorRight': browser.MoveCursorRight, - #'SelectRange': browser.SelectRange, - #'SelectAdditive': browser.SelectAdditive, - #'SelectSubtractive': browser.SelectSubtractive, - #'SelectMulti': browser.SelectMulti, - #'SelectSingle': browser.SelectSingle, - #'SelectAll': browser.SelectAll, - #'SelectNone': browser.SelectNone, - #'SelectInvert': browser.SelectInvert, - #'Select': browser.Select, + 'NextPage': browser.NextPage, + 'PreviousPage': browser.PreviousPage, + 'ScrollDown': browser.ScrollDown, + 'ScrollUp': browser.ScrollUp, + 'JumpToPage': browser.JumpToPage, + 'SetCursor': browser.SetCursor, + 'ZoomIn': browser.ZoomIn, + 'ZoomOut': browser.ZoomOut, + 'JumpToCursor': browser.JumpToCursor, + 'MoveCursorFirst': browser.MoveCursorFirst, + 'MoveCursorLast': browser.MoveCursorLast, + 'MoveCursorUp': browser.MoveCursorUp, + 'MoveCursorDown': browser.MoveCursorDown, + 'MoveCursorLeft': browser.MoveCursorLeft, + 'MoveCursorRight': browser.MoveCursorRight, + 'SelectRange': browser.SelectRange, + 'SelectAdditive': browser.SelectAdditive, + 'SelectSubtractive': browser.SelectSubtractive, + 'SelectMulti': browser.SelectMulti, + 'SelectSingle': browser.SelectSingle, + 'SelectAll': browser.SelectAll, + 'SelectNone': browser.SelectNone, + 'SelectInvert': browser.SelectInvert, + 'Select': browser.Select, ## filter #'AddToken': filter.AddToken, #'RemoveToken': filter.RemoveToken, @@ -84,14 +84,14 @@ class ActionBuilder(BuilderBase): #'SyncObjects': library.SyncObjects, #'ItemExport': library.ItemExport, ## misc - #'ShellDrop': misc.ShellDrop, - #'OpenExternal': misc.OpenExternal, - #'Menu': misc.Menu, + 'ShellDrop': misc.ShellDrop, + 'OpenExternal': misc.OpenExternal, + 'Menu': misc.Menu, 'ShowConsole': misc.ShowConsole, 'ShowHelp': misc.ShowHelp, 'ShowSettings': misc.ShowSettings, - #'ClipboardCopy': misc.ClipboardCopy, - #'ClipboardPaste': misc.ClipboardPaste, + 'ClipboardCopy': misc.ClipboardCopy, + 'ClipboardPaste': misc.ClipboardPaste, ## objects #'RotateLeft': objects.RotateLeft, #'RotateRight': objects.RotateRight, diff --git a/tagit/actions/action.kv b/tagit/actions/action.kv new file mode 100644 index 0000000..5352964 --- /dev/null +++ b/tagit/actions/action.kv @@ -0,0 +1,45 @@ + +: + # internas + orientation: 'horizontal' + + # responsiveness + # *touch_trigger* is enabled automatically if an image or text is shown. + # If that is undesired, *touch_trigger* has to be disabled **after** the + # declaration of *show*. + key_trigger: True + touch_trigger: False + + # size + # By default the width expands as necessary. To get a fixed width, + # set the width manually or via size_hint_x and set *autowidth* to False. + # If something is shown, *height* is automatically set to *default_height* + # unless specified otherwise in by the caller. + default_height: 30 + size: 0, 0 + size_hint: None, None + autowidth: True + + # behaviour + # The default is that no buttons are shown and touch triggers are disabled. + # NOTE: Callers need to declare show **last** to ensure that all other + # properties are set. The only exception is *touch_trigger* which has + # to be disabled **after** show. + show: [] + + # decoration + canvas.before: + Color: + rgba: 17 / 255, 32 / 255, 148 / 255, self.selected_alpha + Rectangle: + pos: self.x, self.y + 1 + size: self.size + + canvas.after: + Color: + rgba: 17 / 255, 32 / 255, 148 / 255, self.selected_alpha + Line: + width: 2 + rectangle: self.x, self.y, self.width, self.height + + ## EOF ## diff --git a/tagit/actions/action.py b/tagit/actions/action.py new file mode 100644 index 0000000..e8866ce --- /dev/null +++ b/tagit/actions/action.py @@ -0,0 +1,257 @@ +"""Button for proxy actions. + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# standard imports +import logging +import os + +# kivy imports +from kivy import metrics +from kivy.animation import Animation +from kivy.lang import Builder +from kivy.uix.behaviors import ButtonBehavior +from kivy.uix.boxlayout import BoxLayout +from kivy.uix.image import Image +from kivy.uix.label import Label +import kivy.properties as kp + +# tagit imports +from tagit.external.tooltip import Tooltip + +# exports +__all__ = ('Action', ) + + +## code ## + +logger = logging.getLogger(__name__) + +# load kv +Builder.load_file(os.path.join(os.path.dirname(__file__), 'action.kv')) + +class Action(ButtonBehavior, BoxLayout, Tooltip): + """ + + An Action can be triggered in three ways: + * Touch event: Clicking or touching on the button + * Key event: Entering a keyboard shortcut + * Single-shot: Programmatically triggered once, without UI attachment + + For the last, use the *single_shot* classmethod. + + For the first two, declare the Action in a kv file or in code. + Note that the remarks below about kv do not apply if the object is + created in code. + + + When an Action is declared in kv, two restrictions apply (also see + examples below): + * To disable touch_trigger, it must be declared last + * show must be declared after all other properties + + Enable key triggers, but hide the Action in the UI: + Action: + show: [] + + Action: + # alias for the one above + + Show text, image, or both, with default height and the width + stretched as necessary: + Action: + show: 'text', + + Action: + show: 'image', + + Action: + show: 'text', 'image' + + Action: + width: 200 # has no effect unless autowidth is False + show: 'image', 'text' + + Make the Action larger: + Action: + # increased height. The image width scales accordingly + height: 80 + show: 'image', 'text' + + Action: + # scales to parent widget's width + autowidth: False + size_hint_x: 1 + show: 'image', 'text' + + Action: + # fixed width and height + width: 150 + height: 80 + autowidth: False + show: 'image', 'text' # must be declared **last** + + Show the button but disable touch events: + Action: + height: 80 + show: 'image', 'text' + touch_trigger: False # must be declared **after** show + + Do the same in code: + >>> Action( + ... size=(130, 80), + ... autowidth=False, + ... show=('image', 'text'), + ... text='foobar', + ... touch_trigger=False) + + """ + # content + tooltip = kp.StringProperty() + source = kp.StringProperty() + text = kp.StringProperty() + + # visibility flags + show = kp.ListProperty([]) + + # sizing + default_height = kp.NumericProperty(30) + autowidth = kp.BooleanProperty(True) + + # responsiveness + key_trigger = kp.BooleanProperty(True) + touch_trigger = kp.BooleanProperty(True) + + # required such that properties can be set via constructor + font_size = kp.StringProperty("15sp") + # FIXME: Check why I have to pass size instead of height/width + height = kp.NumericProperty(0) + width = kp.NumericProperty(0) + + # internal properties + root = kp.ObjectProperty(None) + selected_alpha = kp.NumericProperty(0) + _image = kp.ObjectProperty(None) + _label = kp.ObjectProperty(None) + + def __init__(self, **kwargs): + show = kwargs.pop('show', []) + touch_trigger = kwargs.pop('touch_trigger', None) + super(Action, self).__init__(**kwargs) + # delay such that on_show is executed once the other + # properties are set + self.show = show + if touch_trigger is not None: + self.touch_trigger = touch_trigger + + ## display + + def on_show(self, wx, show): + if self._image is not None: + self.remove_widget(self._image) + if self._label is not None: + self.remove_widget(self._label) + + if 'image' in show: + self.height = self.default_height if self.height == 0 else self.height + self.touch_trigger = True + self._image = Image( + source=self.source, + # size + height=self.height, + width=self.height, # square image + size_hint=(None, None), + ) + self.add_widget(self._image) + if self.autowidth: + self.width = self.height + if 'text' in show: + self.height = self.default_height if self.height == 0 else self.height + self.touch_trigger = True + self._label = Label( + # text + text=self.text, + halign='left', + valign='middle', + padding=(metrics.dp(10), 0), + # size + font_size=self.font_size, + height=self.height, + size_hint=(None, None), + ) + self._label.bind(texture_size=self.on_texture_size) + self.add_widget(self._label) + + def on_size(self, wx, size): + for child in self.children: + child.height = self.height + if isinstance(child, Image): + child.width = self.height + elif not self.autowidth: # must be the label + self.on_texture_size(child, None) + + def on_texture_size(self, label, texture_size): + if self.autowidth: + # adapt the width to the label's width + self.width = max(0, sum([child.width for child in self.children])) + else: + # adapt the label's width + others = sum([child.width for child in self.children if child != label]) + label.width = self.width - others + label.height = self.height + label.text_size = (self.width - others, self.height) + + def on_tooltip(self, callee, text): + self.set_tooltip(text) + + + ## properties + + @property + def cfg(self): + return self.root.session.cfg + + + ## action triggering + + @classmethod + def single_shot(cls, root, *args, **kwargs): + #logger.info(f'action: {cls.__name__}, {args}, {kwargs}') + root.action_log.append(str(cls.__name__)) + return cls(root=root).apply(*args, **kwargs) + + def on_root(self, wx, root): + root.keys.bind(on_press=self.on_keyboard) + + def on_press(self): + self.selected_alpha = 1 + + def on_release(self): + if self.touch_trigger: + Animation(selected_alpha=0, d=.25, t='out_quad').start(self) + #logger.info(f'action: {type(self).__name__}') + self.root.action_log.append(str(type(self).__name__)) + self.apply() + + def on_keyboard(self, wx, evt): + if self.key_trigger and self.ktrigger(evt): + #logger.info(f'action: {type(self).__name__}') + self.root.action_log.append(str(type(self).__name__)) + self.apply() + # stop the event from further processing + return True + + + ## interfaces for subtypes + + def ktrigger(self, evt): + """Return True if the action should be triggered by keyboard event *evt*.""" + return False + + def apply(self, *args, **kwargs): + """Execute the action.""" + pass + +## EOF ## diff --git a/tagit/actions/browser.kv b/tagit/actions/browser.kv new file mode 100644 index 0000000..adcfbd6 --- /dev/null +++ b/tagit/actions/browser.kv @@ -0,0 +1,99 @@ +#:import resource_find kivy.resources.resource_find + +: + source: resource_find('atlas://browser/next_page') + tooltip: 'One page down' + +: + source: resource_find('atlas://browser/previous_page') + tooltip: 'One page up' + +: + source: resource_find('atlas://browser/scroll_up') + tooltip: 'One row up' + +: + source: resource_find('atlas://browser/scroll_down') + tooltip: 'One row down' + +: + source: resource_find('atlas://browser/jump_to_page') + tooltip: 'Jump to a specified page' + +: + source: resource_find('atlas://browser/zoom_in') + tooltip: 'Zoom in' + +: + source: resource_find('atlas://browser/zoom_out') + tooltip: 'Zoom out' + +: + source: resource_find('atlas://browser/jump_to_cursor') + tooltip: 'Jump to cursor' + +: + source: resource_find('atlas://browser/set_cursor') + tooltip: 'Set the cursor' + +: + source: resource_find('atlas://browser/cursor_first') + tooltip: 'Go to first image' + +: + source: resource_find('atlas://browser/cursor_last') + tooltip: 'Go to last image' + +: + source: resource_find('atlas://browser/cursor_up') + tooltip: 'Cursor up' + +: + source: resource_find('atlas://browser/cursor_down') + tooltip: 'Cursor down' + +: + source: resource_find('atlas://browser/cursor_left') + tooltip: 'Cursor left' + +: + source: resource_find('atlas://browser/cursor_right') + tooltip: 'Cursor right' + +: + source: resource_find('atlas://browser/select_all') + tooltip: 'Select all' + +: + source: resource_find('atlas://browser/select_none') + tooltip: 'Clear selection' + +: + source: resource_find('atlas://browser/select_invert') + tooltip: 'Invert selection' + +: + source: resource_find('atlas://browser/select_single') + tooltip: 'Select one' + +: + source: resource_find('atlas://browser/select_multi') + tooltip: 'Select many' + +: + source: resource_find('atlas://browser/select_add') + tooltip: 'Add to selection' + +: + source: resource_find('atlas://browser/select_sub') + tooltip: 'Remove from selection' + +: + source: resource_find('atlas://browser/select_range') + tooltip: 'Select range' + +