aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Baumgartner <dev@igsor.net>2023-03-04 16:31:12 +0100
committerMatthias Baumgartner <dev@igsor.net>2023-03-04 16:31:12 +0100
commit6dbd055531b311d02613ccefa9bc1cdb78507b98 (patch)
tree630dd6322fd4bc29908ccf4ed01d97b52f7dde4f
parent1f916fe4ef821d38af20057b6d1386c155663105 (diff)
downloadtagit-6dbd055531b311d02613ccefa9bc1cdb78507b98.tar.gz
tagit-6dbd055531b311d02613ccefa9bc1cdb78507b98.tar.bz2
tagit-6dbd055531b311d02613ccefa9bc1cdb78507b98.zip
documentation
-rw-r--r--doc/source/actions.rst72
-rw-r--r--doc/source/core.rst75
-rw-r--r--doc/source/development.rst14
-rw-r--r--doc/source/index.rst25
-rw-r--r--doc/source/installation.rst45
-rw-r--r--doc/source/tiles.rst77
-rw-r--r--doc/source/usage.rst114
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
+