"""Status line. Provides space for some buttons (typically navigation buttons), information about the current viewport, and a status line. Part of the tagit module. A copy of the license is provided with the project. Author: Matthias Baumgartner, 2022 """ # standard imports import os import logging # kivy imports from kivy.clock import mainthread from kivy.lang import Builder from kivy.uix.boxlayout import BoxLayout import kivy.properties as kp # tagit imports from tagit import config, dialogues, logger # inner-module imports from .browser import BrowserAwareMixin from .session import ConfigAwareMixin # exports __all__ = ('Status', ) ## code ## # load kv Builder.load_file(os.path.join(os.path.dirname(__file__), 'status.kv')) # classes class Status(BoxLayout, BrowserAwareMixin, ConfigAwareMixin): """Status line.""" # root reference root = kp.ObjectProperty(None) # log history history = kp.ListProperty() # log handlers handler_history = None handler_status = None # events __events__ = ('on_status', ) def on_status(sender, status): """Event prototype""" pass # bindings to others def on_root(self, wx, root): """Bind events.""" # bind to browser and config BrowserAwareMixin.on_root(self, wx, root) ConfigAwareMixin.on_root(self, wx, root) # bind to status update event self.bind(on_status=self.status_from_event) def on_browser(self, wx, browser): """Bind to current browser properties.""" # remove old binding if self.browser is not None: self.browser.unbind(page_size=self.on_navigation) self.browser.unbind(items=self.on_navigation) self.browser.unbind(offset=self.on_navigation) # add new binding self.browser = browser if self.browser is not None: self.browser.bind(page_size=self.on_navigation) self.browser.bind(items=self.on_navigation) self.browser.bind(offset=self.on_navigation) self.on_navigation(browser, browser.offset) def on_config_changed(self, session, key, value): if key in (('ui', 'standalone', 'logging', 'status'), ('ui', 'standalone', 'logging', 'console')): self.on_cfg(session, session.cfg) def on_cfg(self, wx, cfg): """Register handlers according to config.""" if self.handler_status is not None: logging.getLogger().root.removeHandler(self.handler_status) if self.handler_history is not None: logging.getLogger().root.removeHandler(self.handler_history) # status log event self.handler_status = logger.logger_config( logger.CallbackHandler(self.status_from_log), logger.ColorsMarkup, cfg('ui', 'standalone', 'logging', 'status').to_tree(defaults=True)) logging.getLogger().root.addHandler(self.handler_status) # history (console) self.handler_history = logger.logger_config( logger.CallbackHandler(self.update_history), logger.ColorsMarkup, cfg('ui', 'standalone', 'logging', 'console').to_tree(defaults=True)) logging.getLogger().root.addHandler(self.handler_history) def __del__(self): if self.browser is not None: self.browser.unbind(page_size=self.on_navigation) self.browser.unbind(items=self.on_navigation) self.browser.unbind(offset=self.on_navigation) self.browser = None if self.handler_status is not None: logging.getLogger().root.removeHandler(self.handler_status) self.handler_status = None if self.handler_history is not None: logging.getLogger().root.removeHandler(self.handler_history) self.handler_history = None # console def on_touch_down(self, touch): """Open console dialogue when clicked on the status label.""" if self.status_label.collide_point(*touch.pos): self.console() # show console return True elif self.navigation_label.collide_point(*touch.pos): self.root.trigger('JumpToPage') # show page dialogue return True return super(Status, self).on_touch_down(touch) def console(self): """Open console dialogue.""" dlg = dialogues.Console() self.bind(history=dlg.update) dlg.update(self, self.history) dlg.open() # content updates def on_navigation(self, browser, value): """Update the navigation label if the browser changes.""" first = browser.offset + 1 # first on page last = min(browser.offset + browser.page_size, browser.n_items) # last on page total = browser.n_items # total results self.navigation = f'{first} - {last} of {total}' @mainthread def update_history(self, fmt, record): """Update the history from the logger.""" self.history.append(fmt(record)) def status_from_event(self, wx, status): """Update the status line from the status event.""" self.status = status @mainthread def status_from_log(self, fmt, record): """Update the status line from the logger.""" self.status = fmt(record) ## config ## # status config.declare(('ui', 'standalone', 'logging', 'status', 'level'), config.Enum('debug', 'info', 'warn', 'error', 'critical'), 'info', __name__, 'Log level', 'Maximal log level for which messages are shown. The order is: critical > error > warning > info > volatile > debug') config.declare(('ui', 'standalone', 'logging', 'status', 'filter'), config.List(config.String()), ['tagit'], __name__, 'Module filter', 'Module name for which log messages are accepted.') config.declare(('ui', 'standalone', 'logging', 'status', 'fmt'), config.String(), '{title}{message}', __name__, 'Log format', 'Log message formatting. The formatting follows the typical python format string where each item is enclosed in curly braces (e.g. "{message}"). For printable items see the `logging attributes `_. In addition to those, the item "title" is available, which is a placeholder for the formatted title (if present).') config.declare(('ui', 'standalone', 'logging', 'status', 'title'), config.String(), '{title}: ', __name__, 'Title format', 'Title formatting.') config.declare(('ui', 'standalone', 'logging', 'status', 'maxlen'), config.Unsigned(), 40, __name__, 'Maximal line length', 'Maximal line length (e.g. console width). Use Infinity to set no line length limit.') # console config.declare(('ui', 'standalone', 'logging', 'console', 'level'), config.Enum('debug', 'info', 'warn', 'error', 'critical'), 'info', __name__, 'Log level', 'Maximal log level for which messages are shown. The order is: critical > error > warning > info > volatile > debug') config.declare(('ui', 'standalone', 'logging', 'console', 'filter'), config.List(config.String()), ['tagit'], __name__, 'Module filter', 'Module name for which log messages are accepted.') config.declare(('ui', 'standalone', 'logging', 'console', 'fmt'), config.String(), '[{levelname}] {title}{message}', __name__, 'Log format', 'Log message formatting. The formatting follows the typical python format string where each item is enclosed in curly braces (e.g. "{message}"). For printable items see the `logging attributes `_. In addition to those, the item "title" is available, which is a placeholder for the formatted title (if present).') config.declare(('ui', 'standalone', 'logging', 'console', 'title'), config.String(), '[{title}]', __name__, 'Title format', 'Title formatting.') config.declare(('ui', 'standalone', 'logging', 'console', 'maxlen'), config.Unsigned(), 0, __name__, 'Maximal line length', 'Maximal line length (e.g. console width). Use zero or infinity to set no line length limit.') ## EOF ##