diff options
author | Matthias Baumgartner <dev@igsor.net> | 2023-03-04 16:31:12 +0100 |
---|---|---|
committer | Matthias Baumgartner <dev@igsor.net> | 2023-03-04 16:31:12 +0100 |
commit | 6dbd055531b311d02613ccefa9bc1cdb78507b98 (patch) | |
tree | 630dd6322fd4bc29908ccf4ed01d97b52f7dde4f | |
parent | 1f916fe4ef821d38af20057b6d1386c155663105 (diff) | |
download | tagit-6dbd055531b311d02613ccefa9bc1cdb78507b98.tar.gz tagit-6dbd055531b311d02613ccefa9bc1cdb78507b98.tar.bz2 tagit-6dbd055531b311d02613ccefa9bc1cdb78507b98.zip |
documentation
-rw-r--r-- | doc/source/actions.rst | 72 | ||||
-rw-r--r-- | doc/source/core.rst | 75 | ||||
-rw-r--r-- | doc/source/development.rst | 14 | ||||
-rw-r--r-- | doc/source/index.rst | 25 | ||||
-rw-r--r-- | doc/source/installation.rst | 45 | ||||
-rw-r--r-- | doc/source/tiles.rst | 77 | ||||
-rw-r--r-- | doc/source/usage.rst | 114 |
7 files changed, 422 insertions, 0 deletions
diff --git a/doc/source/actions.rst b/doc/source/actions.rst new file mode 100644 index 0000000..2b41286 --- /dev/null +++ b/doc/source/actions.rst @@ -0,0 +1,72 @@ + +.. _Actions: + +Actions +======= + +*Actions* provide small, specific, isolated functionality like creating a search filter +or adding a tag to a selected item. +The advantage of Actions is their lightweight implementation, so that the code can be reused easily, and added or removed to the application on demand. +For example, a feature that introduces large dependencies could be isolated in this manner, only being offered if the dependencies are met. +Or, the user can configure the appearance and button toolbars. + + +Triggers and execution +---------------------- + +An action is triggered by some event like a key press or a touch event. +Implementation-wise, touch triggers are implemented via kivy's default `on_touch_down` functionality. +Key events are handled by overwriting the `tagit.action.Action.ktrigger` method. +It is supposed to return a boolean that indicates whether or not the key event triggers +the action. The `tagit.widget.Binding` class helps you to check if a specific key event +matches your default binding. +Note that the binding needs not to be unique, i.e., multiple actions may be triggered +by the same key event. You can use this to your advantage, but should carefully regard +other actions when adding new default bindings. + +Once triggered, the `tagit.action.Action.apply` function of the action is invoked. +Actions are automatically linked to the root window (*self.root*) and can use +*widget contexts* to query or manipulate them. + +See the following snipped as an example:: + + from tagit import config + from tagit.actions.action import Action + from tagit.utils import clamp + from tagit.widgets import Binding + + class NextPage(Action): + """Scroll one page downwards without moving the cursor.""" + + def ktrigger(self, evt): + """Check if *evt* matches the configured key binding for this action (PGDN).""" + return Binding.check(evt, self.cfg('bindings', 'browser', 'page_next')) + + def apply(self): + """Execute the NextPage action.""" + with self.root.browser as browser: + browser.offset = clamp(browser.offset + browser.page_size, browser.max_offset) + + config.declare(('bindings', 'browser', 'page_next'), + config.Keybind(), Binding.simple(Binding.PGDN)) + + + +Action appearance +----------------- + +The action appearance is normally defined in a respective *.kv* file. +Any actions may be displayed as text and/or as image, hence you should provide both options. +The `tagit.actions.Action` base class provides the configuration flags to control the +display style. The configuration is usually specified by the parent element like a widget, +or a dock (e.g., `tagit.widgets.dock.ButtonDock`). + +The following snipped defines the appearance of the *NextPage* action:: + + #:import resource_find kivy.resources.resource_find + + <NextPage>: + source: resource_find('atlas://browser/next_page') + text: 'Next Page' + +.. EOF .. diff --git a/doc/source/core.rst b/doc/source/core.rst new file mode 100644 index 0000000..b4f8ab3 --- /dev/null +++ b/doc/source/core.rst @@ -0,0 +1,75 @@ + +Core +==== + +*tagit* provides a number of submodules that are + +Windows +------- + +A window arranges several UI elements into a consistent view, +optimized for a specific platform. +Currently, the only window instance (`tagit.window.desktop`) that exists is tuned for desktop environments. + + +Widgets +------- + +*tagit* defines three main widgets: + +* `tagit.widgets.browser.Browser` +* `tagit.widgets.filter.Filter` +* `tagit.widgets.session.Session` + +Each of those widgets can be used as context, +whereas the widget updates if its state was changed when the context expires. +Clients (e.g. Actions, Tiles) can thus safely access and modify the widgets directly. + +Additionally, there exists widgets for key bindings (`tagit.widgets.bindings.Binding`), a status bar (`tagit.widgets.status.Status`), and docks for :ref:`Actions` and :ref:`Tiles` (`taigt.widgets.dock`). +These widgets are mostly used internally to arrange UI elements, or to provide additional input methods to the user. + + +config +------ + +*tagit* comes with an intricate configuration system. +The core idea is to provide a means to define config keys and defaults in the code files where they are used, +but also have the ability to check a config file's validity upon application startup, +and to provide reasonable and meaningful documentation for each config item. + +Configuration items are declared and documented in the source file that typically uses them:: + + from tagit import config + + config.declare(('ui', 'standalone', 'browser', 'cols'), config.Unsigned(), 3, + __name__, 'Browser columns', 'Default number of columns in the browser.') + + config.declare(('ui', 'standalone', 'browser', 'rows'), config.Unsigned(), 3, + __name__, 'Browser rows', 'Default number of rows in the grid view.') + + def n_items(cfg): + """Return the number of items shown on one page.""" + # config values are automatically converted to integers + cols = cfg('ui', 'standalone', 'browser', 'cols') + rows = cfg('ui', 'standalone', 'browser', 'rows') + return cols * rows + +With `config.declare`, you specify the config key(*('ui', 'standalone', 'browser', 'cols')*), +a type (*config.Unsigned()*), +the default value (*3*), +and some documentation arguments (module name, abstract, description). + +On application-level, loading (and type checking) a config file is as simple as calling:: + + from tagit.config import Settings + cfg = Settings.Open(path) + +The user can the write a config file in json, yaml, or another structured file format:: + + ui: + standalone: + browser: + cols: 4 + rows: 3 + + diff --git a/doc/source/development.rst b/doc/source/development.rst new file mode 100644 index 0000000..a7b1bd0 --- /dev/null +++ b/doc/source/development.rst @@ -0,0 +1,14 @@ + +Developer information +===================== + +*tagit* builds upon the `Kivy <https://kivy.org/>`_ framework that offers a great integration for desktop and mobile environments. +To build the UI in a flexible and extensible manner, *tagit* implements several mechanisms to streamline and re-use UI elements. The following pages explain the core ideas behind the *tagit* code structure. + +.. toctree:: + :maxdepth: 1 + + core + actions + tiles + diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..0a93a81 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,25 @@ + +tagit: The BSFS browser +======================= + +*tagit* is a graphical interface to browse through a *bsfs* storage. +It is designed with image collections in mind. +It provides a means to quickly navigate a collection by user-assigned tags, +and to easily edit images' tags. + +.. video:: _static/usage.webm + :width: 640 + + + +Contents +-------- + +.. toctree:: + :maxdepth: 1 + + usage + installation + development + + diff --git a/doc/source/installation.rst b/doc/source/installation.rst new file mode 100644 index 0000000..87ecff0 --- /dev/null +++ b/doc/source/installation.rst @@ -0,0 +1,45 @@ + +Installation +============ + +Install *tagit* via pip:: + + pip install --extra-index-url https://pip.bsfs.io tagit + +This installs the `tagit` python package as well as the `tagit.app` command. +It is recommended to install *tagit* in a virtual environment (via `virtualenv`). +Please note that you may have to separately install the `BSIE <https://www.bsfs.io/bsie>`_ +package into the same environment. + + +License +------- + +This project is released under the terms of the 3-clause BSD License. +By downloading or using the application you agree to the license's terms and conditions. + +.. literalinclude:: ../../LICENSE + + +Source +------ + +Check out our git repository:: + + git clone https://git.bsfs.io/tagit.git + +You can further install *tagit* via the ususal `setuptools <https://setuptools.pypa.io/en/latest/index.html>`_ commands from your tagit source directory:: + + python setup.py develop + +For development, you also need to install some additional dependencies:: + + # code style discipline + pip install mypy coverage pylint + + # documentation + pip install sphinx sphinx-copybutton sphinxcontrib-video furo + + # packaging + pip install build + diff --git a/doc/source/tiles.rst b/doc/source/tiles.rst new file mode 100644 index 0000000..e0f9cd0 --- /dev/null +++ b/doc/source/tiles.rst @@ -0,0 +1,77 @@ + +.. _Tiles: + +Tiles +===== + +Tiles are containers to display information about the file system, or specific items therein. +Their main purpose is to assist the user in navigating and tag their files. +They can also display additional information about selected files, +e.g., by showing metadata or placing geo-tagged files on a map. + + +Content update +-------------- + +A Tile is responsible to update its content itself, whenever appropriate. +The mixin classes `tagit.widgets.browser.BrowserAwareMixin`, `tagit.widgets.session.SessionAwareMixin`, and `tagit.widgets.filter.FilterAwareMixin` provide a series of events that trigger when the UI or storage state changes. +A Tile can trigger its content update by binding to these events. +In addition, a Tile's *visibility* indicates if it is actually visible to the user. +The visibility flag is controlled by the parent widget (mostly a `tagit.widget.dock.TileDock`) and the tile updates automatically if its visibility changes. +Similar to actions, a `tagit.tiles.Tile` is automatically linked to the root window so that it can retrieve contextual information about the UI's state, or to query the storage backed for additional file properties. + +For example, the info tile updates when the browser state changes (e.g., the user moves the cursor), +or when the storage content changes (e.g., the item information changes). +In such an event, the tile fetches the current information from the storage and displays it as a table:: + + from collections import OrderedDict + from tagit.utils import ns, magnitude_fmt + from tagit.widgets.browser import BrowserAwareMixin + from tagit.widgets.session import StorageAwareMixin + from .tile import TileTabular + + class Info(TileTabular, BrowserAwareMixin, StorageAwareMixin): + """Show essential attributes about the cursor.""" + + # bind to events so that the tile updates when the context changes + def on_browser(self, sender, browser): + ... + def on_predicate_modified(self, *args): + ... + + def update(self, *args): + """Update the tile's content.""" + cursor = self.root.browser.cursor + if not self.visible or cursor is None: + # invisible or no cursor, nothing to show + self.tabledata = OrderedDict({}) + else: + # fetch information about the cursor from the storage + data = cursor.get( + ns.bse.filesize, + ns.bse.filename, + (ns.bse.tag, ns.bst.label), + ) + # display the information as a two-column table + self.tabledata = OrderedDict({ + 'Filesize' : magnitude_fmt(data.get(ns.bse.filesize, 0)), + 'Filename' : data.get(ns.bse.filename, 'n/a'), + 'Tags' : ', '.join(sorted(data.get((ns.bse.tag, ns.bst.label), [' ']))), + }) + + +Appearance +---------- + +Tiles can appear at any place in the UI and they could also be re-used in multiple locations. +To enable this, they are typically located within a `tagit.widgets.dock.TileDock` which controls the tile's appearance, position, and size. + +The tile itself only has to define the appearance of its content. +For example, for the info tile the following kivy language snippet suffices:: + + <Info>: + title: 'Item info' + keywidth: min(75, self.width * 0.4) + + +.. EOF .. diff --git a/doc/source/usage.rst b/doc/source/usage.rst new file mode 100644 index 0000000..260f311 --- /dev/null +++ b/doc/source/usage.rst @@ -0,0 +1,114 @@ + +Usage +===== + +.. image:: _static/mainview.jpg + :width: 640 + + +The *tagit* screen mostly shows a search bar and a browser. + +The *search bar* lists the tokens of your search query in the order that you've added them (via the filter button |buttons_filter_add|). +You can nagivate through the tokens (use the arrow buttons |buttons_filter_back| |buttons_filter_forth|) to switch between different searches. +Note that *tagit* remembers your selection and focus at every step. +The active search tokens are highlighted and dangling tokens are greyed out. + +The *browser* shows the search results and lets you scroll and select them. +On the left hand side, you'll find some buttons to add or edit image's tags ( +|buttons_tagging_add_tag|, +|buttons_tagging_edit_tag|), +to create search tokens from your selection ( +|buttons_search_exclusive_filter|, +|buttons_search_exclude_filter| +), +and to navigate and edit your selection ( +|buttons_browser_select_all|, +|buttons_browser_select_none|, +|buttons_browser_select_invert|, +|buttons_browser_select_single|, +|buttons_browser_select_range|, +|buttons_browser_select_multi|, +|buttons_browser_select_add|, +|buttons_browser_select_sub| +). +Note that on a desktop environment you can use your keyboard to select items like in most browsers, e.g, CTRL to select individual items or SHIFT to select a range. +The box on the right shows you some information about the currently selected item. +The navigation buttons at the bottom let you scroll through the browser's pages ( +|buttons_browser_cursor_first|, +|buttons_browser_previous_page|, +|buttons_browser_scroll_up| +|buttons_browser_scroll_down|, +|buttons_browser_next_page|, +|buttons_browser_cursor_last|, +). + +The following video guides you through the some common functionality: + +.. video:: _static/usage.webm + :width: 640 + + + +.. |buttons_filter_add| image:: ../../tagit/assets/icons/kivy/filter/add.png + :width: 20 + +.. |buttons_filter_back| image:: ../../tagit/assets/icons/kivy/filter/go_back.png + :width: 20 + +.. |buttons_filter_forth| image:: ../../tagit/assets/icons/kivy/filter/go_forth.png + :width: 20 + +.. |buttons_browser_cursor_first| image:: ../../tagit/assets/icons/kivy/browser/cursor_first.png + :width: 20 + +.. |buttons_browser_cursor_last| image:: ../../tagit/assets/icons/kivy/browser/cursor_last.png + :width: 20 + +.. |buttons_browser_next_page| image:: ../../tagit/assets/icons/kivy/browser/next_page.png + :width: 20 + +.. |buttons_browser_previous_page| image:: ../../tagit/assets/icons/kivy/browser/previous_page.png + :width: 20 + +.. |buttons_browser_scroll_down| image:: ../../tagit/assets/icons/kivy/browser/scroll_down.png + :width: 20 + +.. |buttons_browser_scroll_up| image:: ../../tagit/assets/icons/kivy/browser/scroll_up.png + :width: 20 + +.. |buttons_browser_select_all| image:: ../../tagit/assets/icons/kivy/browser/select_all.png + :width: 20 + +.. |buttons_browser_select_none| image:: ../../tagit/assets/icons/kivy/browser/select_none.png + :width: 20 + +.. |buttons_browser_select_invert| image:: ../../tagit/assets/icons/kivy/browser/select_invert.png + :width: 20 + +.. |buttons_browser_select_single| image:: ../../tagit/assets/icons/kivy/browser/select_single.png + :width: 20 + +.. |buttons_browser_select_range| image:: ../../tagit/assets/icons/kivy/browser/select_range.png + :width: 20 + +.. |buttons_browser_select_multi| image:: ../../tagit/assets/icons/kivy/browser/select_multi.png + :width: 20 + +.. |buttons_browser_select_add| image:: ../../tagit/assets/icons/kivy/browser/select_add.png + :width: 20 + +.. |buttons_browser_select_sub| image:: ../../tagit/assets/icons/kivy/browser/select_sub.png + :width: 20 + +.. |buttons_tagging_add_tag| image:: ../../tagit/assets/icons/kivy/tagging/add_tag.png + :width: 20 + +.. |buttons_tagging_edit_tag| image:: ../../tagit/assets/icons/kivy/tagging/edit_tag.png + :width: 20 + +.. |buttons_search_exclusive_filter| image:: ../../tagit/assets/icons/kivy/search/exclusive_filter.png + :width: 20 + +.. |buttons_search_exclude_filter| image:: ../../tagit/assets/icons/kivy/search/exclude_filter.png + :width: 20 + |