diff options
Diffstat (limited to 'tagit/windows')
-rw-r--r-- | tagit/windows/__init__.py | 10 | ||||
-rw-r--r-- | tagit/windows/desktop.kv | 107 | ||||
-rw-r--r-- | tagit/windows/desktop.py | 151 |
3 files changed, 268 insertions, 0 deletions
diff --git a/tagit/windows/__init__.py b/tagit/windows/__init__.py new file mode 100644 index 0000000..c3ec3c0 --- /dev/null +++ b/tagit/windows/__init__.py @@ -0,0 +1,10 @@ +""" + +Part of the tagit module. +A copy of the license is provided with the project. +Author: Matthias Baumgartner, 2022 +""" +# inner-module imports +from .desktop import MainWindow + +## EOF ## diff --git a/tagit/windows/desktop.kv b/tagit/windows/desktop.kv new file mode 100644 index 0000000..d2ca0e7 --- /dev/null +++ b/tagit/windows/desktop.kv @@ -0,0 +1,107 @@ +#:import TileDecorationRoundedBorder tagit.tiles.decoration.TileDecorationRoundedBorder +#:import TileDecorationBorder tagit.tiles.decoration.TileDecorationBorder +#:import TileDecorationFilledRectangle tagit.tiles.decoration.TileDecorationFilledRectangle + +<HGuide@Widget>: + + +<MainWindow>: + # main content + # required by most tiles and actions + browser: browser + filter: filter + status: status + # required by Menu + context: context + + BoxLayout: + orientation: 'vertical' + + Widget: + height: 5 + size_hint: 1, None + + Filter: + id: filter + root: root + size_hint: 1, None + height: 40 + + HGuide: + height: 20 + size_hint: 1, None + + Widget: # spacer + height: 20 + size_hint: 1, None + + BoxLayout: + orientation: 'horizontal' + + ButtonDock: # one column of buttons on the left + root: root + orientation: 'lr-tb' + # one column of buttons + width: 1*30 + 2*10 + name: 'sidebar_left' + spacing: 10 + padding: 10 + size_hint: None, None + button_height: 30 + button_show: 'image', + # adjust height automatically to content + height: self.minimum_height + pos_hint: {'center_y': 0.5} + + Widget: # spacer + width: 20 # ButtonDock already has a space of 10px + size_hint: None, 1 + + Browser: # browsing space + id: browser + root: root + size_hint: 1, 1 + + Widget: # spacer + width: 30 + size_hint: None, 1 + + TileDock: # context info to the right + root: root + name: 'sidebar_right' + decoration: TileDecorationRoundedBorder + visible: True + cols: 1 + rows: 1 + width: 220 + size_hint: None, 0.5 + pos_hint: {'center_y': 0.5} + + Widget: # spacer + height: 20 + size_hint: 1, None + + HGuide: + height: 20 + size_hint: 1, None + + Status: + id: status + root: root + size_hint: 1, None + height: 30 + + Context: # context menu + id: context + root: root + cancel_handler_widget: root + bounding_box_widget: root + name: 'context' + + KeybindDock: + # key-only actions + root: root + size_hint: None, None + size: 0, 0 + +## EOF ## diff --git a/tagit/windows/desktop.py b/tagit/windows/desktop.py new file mode 100644 index 0000000..2c087b2 --- /dev/null +++ b/tagit/windows/desktop.py @@ -0,0 +1,151 @@ +"""Main container of the tagit UI. + +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 +import typing + +# kivy imports +from kivy.clock import Clock +from kivy.lang import Builder +from kivy.uix.floatlayout import FloatLayout +import kivy.properties as kp + +# import Image and Loader to overwrite their caches later on +from kivy.cache import Cache +from kivy.loader import Loader +from kivy.resources import resource_find + +# tagit imports +from tagit import actions, config, dialogues +from tagit.widgets.browser import Browser +from tagit.widgets.context import Context +from tagit.widgets.dock import TileDock, ButtonDock, KeybindDock +from tagit.widgets.filter import Filter +from tagit.widgets.keyboard import Keyboard +from tagit.widgets.session import Session +from tagit.widgets.status import Status + +# exports +__all__: typing.Sequence[str] = ( + 'KIVY_IMAGE_CACHE_SIZE', + 'KIVY_IMAGE_CACHE_TIMEOUT', + 'MainWindow', + ) + + +## code ## + +logger = logging.getLogger(__name__) + +# load kv +Builder.load_file(os.path.join(os.path.dirname(__file__), 'desktop.kv')) +# load styles +Builder.load_file(resource_find('default/style.kv')) + +# classes +class MainWindow(FloatLayout): + """A self-contained user interface for desktop usage. + See `tagit.apps.gui` for an example of how to invoke it. + """ + + keys = kp.ObjectProperty(None) + + # unnecessary but nicely explicit + browser = kp.ObjectProperty(None) + filter = kp.ObjectProperty(None) + keytriggers = kp.ObjectProperty(None) + + # FIXME: log actions and and replay them + action_log = kp.ListProperty() + + def __init__ (self, cfg, stor, log, **kwargs): + # initialize the session + self._session = Session(cfg, stor, log) + # initialize key-only actions + self.keys = Keyboard() + + # initialize the cache + cache_size = max(0, cfg('ui', 'standalone', 'browser', 'cache_size')) + cache_size = cache_size if cache_size > 0 else None + cache_timeout = max(0, cfg('ui', 'standalone', 'browser', 'cache_timeout')) + cache_timeout = cache_timeout if cache_timeout > 0 else None + Cache.register('kv.loader', limit=cache_size, timeout=cache_timeout) + + # initialize the widget + super(MainWindow, self).__init__(**kwargs) + + # bind pre-close checks + from kivy.core.window import Window + Window.bind(on_request_close=self.on_request_close) + Window.size = tuple(cfg('ui', 'standalone', 'window_size')) + if cfg('ui', 'standalone', 'maximize'): + Window.maximize() + + + ## properties + + @property + def session(self): + return self._session + + def trigger(self, action, *args, **kwargs): + """Trigger an action once.""" + actions.ActionBuilder().get(action).single_shot(self, *args, **kwargs) + + + ## startup and shutdown + + def on_startup(self): + # run script + for args in self.session.cfg('session', 'script'): + if isinstance(args, str): + cmd, args = args, [] + else: + cmd = args.pop(0) + Clock.schedule_once( + lambda dt, cmd=cmd, args=args: self.trigger(cmd, *args), + self.session.cfg('session', 'script_delay')) + + # FIXME: mb/port: debugging only + #return + #Clock.schedule_once(lambda dt: self.trigger('Search'), 0) + #Clock.schedule_once(lambda dt: self.trigger('ZoomOut'), 0) + #Clock.schedule_once(lambda dt: self.trigger('ZoomOut'), 0) + #from kivy.app import App + #App.get_running_app().stop() + + def on_request_close(self, *args): + #with open('.action_history', 'a') as ofile: + # for itm in self.action_log: + # ofile.write(f'{itm}\n') + #App.get_running_app().stop() # FIXME: mb/port: from CloseSessionAndExit + return False + + +## config ## + +config.declare(('session', 'script'), config.List(config.Any()), [], + __name__, 'start script', 'Actions to run after startup. Intended for testing.') + +config.declare(('session', 'script_delay'), config.Unsigned(), 0, + __name__, 'script delay', 'Start script execution delay in seconds.') + +config.declare(('ui', 'standalone', 'maximize'), config.Bool(), False, + __name__, 'Window maximization', 'Maximize the window upon startup.') + +config.declare(('ui', 'standalone', 'window_size'), config.List(config.Unsigned()), (1024, 768), + __name__, 'Wndow size', 'Set the window size upon startup.') + +config.declare(('ui', 'standalone', 'browser', 'cache_size'), config.Unsigned(), 1000, + __name__, 'Cache size', 'Number of preview images that are held in the cache. Should be high or zero if memory is not an issue. Set to a small value to preserve memory, but should be at least the most common page size. It is advised to set a value in accordance with `ui.standalone.browser.cache_items`. If zero, no limit applies.') + +config.declare(('ui', 'standalone', 'browser', 'cache_timeout'), config.Unsigned(), 0, + __name__, 'Cache timeout', 'Number of seconds until cached items are discarded. Should be high or zero if memory is not an issue. Set it to a small value to preserve memory when browsing through many images. If zero, no limit applies. Specify in seconds.') + +## EOF ## |