""" 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.lang import Builder from kivy.uix.gridlayout import GridLayout from kivy.uix.stacklayout import StackLayout from kivy.uix.widget import Widget import kivy.properties as kp # tagit imports from tagit import config from tagit.actions import ActionBuilder from tagit.tiles import TileBuilder from tagit.utils import errors from tagit.utils.builder import InvalidFactoryName # inner-module imports from .session import ConfigAwareMixin # exports __all__ = ('Dock', ) ## code ## logger = logging.getLogger(__name__) # load kv Builder.load_file(os.path.join(os.path.dirname(__file__), 'dock.kv')) # classes class DockBase(Widget, ConfigAwareMixin): """A Dock is a container that holds configurable items.""" # root reference root = kp.ObjectProperty(None) def on_cfg(self, wx, cfg): """Construct the dock from config.""" errors.abstract() def populate(self, config): """Fill the dock with content.""" errors.abstract() class TileDock(GridLayout, DockBase): """A TileDock holds a number of Tiles.""" # dock's name for loading from config name = kp.StringProperty('') # tile decoration decoration = kp.ObjectProperty(None) # tile visiblity visible = kp.BooleanProperty(False) def on_config_changed(self, session, key, value): if key == ('ui', 'standalone', 'tiledocks'): self.on_cfg(session, session.cfg) def on_cfg(self, wx, cfg): """Construct the Tiles from the config item matching dock's name.""" if self.name != '': self.populate(cfg('ui', 'standalone', 'tiledocks').get(self.name, {})) # FIXME: Since dictionaries are not ordered, the tiles might change # their position at every application start. Switching to a list would # solve this issue. E.g. [{tile: 'tile name', **kwargs}] def populate(self, tiles): """Construct the Tiles.""" # clear old items self.clear_widgets() # add new items n_tiles_max = self.cols * self.rows builder = TileBuilder() for idx, tid in enumerate(sorted(tiles)): if idx >= n_tiles_max: logger.warn(f'number of tiles exceeds space ({len(tiles)} > {n_tiles_max})') break try: kwargs = tiles[tid] tile = builder.build(tid, root=self.root, **kwargs) self.add_widget(self.decoration(client=tile)) except InvalidFactoryName: logger.error(f'invalid tile name: {tid}') # create and attach widgets before setting visibility # to ensure that the widget initialization has finished. self.on_visible(self, self.visible) def on_size(self, *args): # FIXME: If dashboard is loaded, resizing the window becomes painfully slow. # Something to do with the code here, e.g. delayed sizing? for child in self.children: # TODO: Allow default_size or tile_size to specify relative sizes (<1) # determine size width = self.tile_width width = child.default_size[0] if width is None else width #width = self.width if width is None and self.size_hint_x is None else width height = self.tile_height height = child.default_size[1] if height is None else height #height = self.height if height is None and self.size_hint_y is None else height size = width if width is not None else 1, height if height is not None else 1 size_hint = None if width is not None else 1, None if height is not None else 1 # set size; will be propagated from the decorator to the client child.size = size child.size_hint = size_hint def on_visible(self, wx, visible): """Propagate visibility update to Tiles.""" for child in self.children: child.client.visible = visible # FIXME: move events in the browser are only triggered if the move event is also # handled here with an empty body (no super!). # No idea why this happens (e.g. doing it in desktop or tab doesn't work). def on_touch_move(self, touch): pass class ButtonDock(StackLayout, DockBase): """A ButtonDock holds a number of Actions.""" # dock's name for loading from config name = kp.StringProperty('') def on_config_changed(self, session, key, value): if key == ('ui', 'standalone', 'buttondocks'): self.on_cfg(session, session.cfg) def on_cfg(self, wx, cfg): """Construct the Actions from config item matching the dock's name.""" if self.name != '': # name is empty if created via the Buttons tile self.populate(cfg('ui', 'standalone', 'buttondocks').get(self.name, [])) def populate(self, actions): """Construct the Actions.""" # clear old items self.clear_widgets() # add new items n_buttons_max = float('inf') if self.n_buttons_max is None else self.n_buttons_max builder = ActionBuilder() for idx, action in enumerate(actions): if idx >= n_buttons_max: logger.warn(f'number of buttons exceeds space ({len(actions)} > {n_buttons_max})') break try: self.add_widget(builder.build(action, root=self.root, size=(self.button_width, self.button_height), show=self.button_show, autowidth=False, )) except InvalidFactoryName: logger.error(f'invalid button name: {action}') class KeybindDock(DockBase): """The KeybindDock holds a number of invisible Actions that can be triggered by key presses.""" def on_config_changed(self, session, key, value): if key == ('ui', 'standalone', 'keytriggers'): self.on_cfg(session, session.cfg) def on_cfg(self, wx, cfg): """Construct the Actions from config.""" self.populate(cfg('ui', 'standalone', 'keytriggers')) def populate(self, actions): """Construct the Actions.""" # clear old items self.clear_widgets() # add new items builder = ActionBuilder() for action in actions: try: self.add_widget(builder.build( action, root=self.root, # process key events only touch_trigger=False, key_trigger=True, # no need to specify show (default is empty) )) except InvalidFactoryName: logger.error(f'invalid button name: {action}') ## config ## config.declare(('ui', 'standalone', 'keytriggers'), config.List(config.Enum(set(ActionBuilder.keys()))), [], __name__, 'Key triggers', 'Actions that can be triggered by a key but have no visible button', '') config.declare(('ui', 'standalone', 'tiledocks'), config.Dict(config.String(), config.Dict(config.String(), config.Dict(config.String(), config.Any()))), {}, __name__, 'Tile docks', '''Tiles can be placed in several locations of the UI. A tile usually displays some information about the current program state, such as information about the library in general, visible or selected items, etc. The configuration of a tile consists the its name as string and additional parameters to that tile as a dict. A tile dock is configured by a dictionary with the tile names as key and their parameters as value: { "Hints": {}, "ButtonDock": {"buttons: ["Save", "SaveAs", "Index"]} } The order of the items in the UI is generally the same as in the config dict. To show a list of available tiles, execute: $ tagger info tile ''') config.declare(('ui', 'standalone', 'buttondocks'), config.Dict(config.String(), config.List(config.Enum(set(ActionBuilder.keys())))), {}, __name__, 'Buttons', '''Every possible action in the UI is triggered via a button. Hence, buttons are found in various places in the UI, organized in button docks. Each dock is identified by name and lists the names of the buttons it contains. To show a list of available buttons, execute: $ tagger info action ''') ## EOF ##