Mercurial > libervia-backend
view frontends/src/jp/xmlui_manager.py @ 2408:a870daeab15e
jp: XMLUI implementation first draft:
first implementation of XMLUI for jp. The display is simplistic for now by displaying widgets in the order in which they appear, and doing a simple input when a value is needed.
Not all widgets/dialogs are implemented yet, and most flags/options/styles are not handled.
It is possible to automate command, using "workflow" attribute:
it's a list of command that are executed in order. So far only a const (SUBMIT) and fields values can be set.
If verbosity is set, fields name are displayed, which can be useful to automate commands.
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 31 Oct 2017 23:17:37 +0100 |
parents | |
children | 40e6e779a253 |
line wrap: on
line source
#!/usr/bin/env python2 # -*- coding: utf-8 -*- # JP: a SàT frontend # Copyright (C) 2009-2016 Jérôme Poisson (goffi@goffi.org) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. from sat.core.log import getLogger log = getLogger(__name__) from sat_frontends.tools import xmlui as xmlui_manager from sat_frontends.jp.constants import Const as C from sat.tools.common.ansi import ANSI as A from sat.core.i18n import _ from functools import partial # workflow constants SUBMIT = 'SUBMIT' # submit form ## Widgets ## class Base(object): """Base for Widget and Container""" type = None _root = None def __init__(self, xmlui_parent): self.xmlui_parent = xmlui_parent self.host = self.xmlui_parent.host @property def root(self): """retrieve main XMLUI parent class""" if self._root is not None: return self._root root = self while not isinstance(root, xmlui_manager.XMLUIBase): root = root.xmlui_parent self._root = root return root def disp(self, *args, **kwargs): self.host.disp(*args, **kwargs) class Widget(Base): category = u'widget' enabled = True @property def name(self): return self._xmlui_name def show(self): """display current widget must be overriden by subclasses """ raise NotImplementedError(self.__class__) def verboseName(self, elems=None, value=None): """add name in color to the elements helper method to display name which can then be used to automate commands elems is only modified if verbosity is > 0 @param elems(list[unicode], None): elements to display None to display name directly @param value(unicode, None): value to show use self.name if None """ if value is None: value = self.name if self.host.verbosity: to_disp = [A.FG_MAGENTA, u' ' if elems else u'', u'({})'.format(value), A.RESET] if elems is None: self.host.disp(A.color(*to_disp)) else: elems.extend(to_disp) class ValueWidget(Widget): def __init__(self, xmlui_parent, value): super(ValueWidget, self).__init__(xmlui_parent) self.value = value @property def values(self): return [self.value] class InputWidget(ValueWidget): def __init__(self, xmlui_parent, value, read_only=False): super(InputWidget, self).__init__(xmlui_parent, value) self.read_only = read_only def _xmluiGetValue(self): return self.value class OptionsWidget(Widget): def __init__(self, xmlui_parent, options, selected, style): super(OptionsWidget, self).__init__(xmlui_parent) self.options = options self.selected = selected self.style = style @property def values(self): return self.selected @values.setter def values(self, values): self.selected = values @property def value(self): return self.selected[0] @value.setter def value(self, value): self.selected = [value] def _xmluiSelectValue(self, value): self.value = value def _xmluiSelectValues(self, values): self.values = values def _xmluiGetSelectedValues(self): return self.values @property def labels(self): """return only labels from self.items""" for value, label in self.items: yield label @property def items(self): """return suitable items, according to style""" no_select = self.no_select for value,label in self.options: if no_select or value in self.selected: yield value,label @property def inline(self): return u'inline' in self.style @property def no_select(self): return u'noselect' in self.style class EmptyWidget(xmlui_manager.EmptyWidget, Widget): def __init__(self, _xmlui_parent): Widget.__init__(self) class TextWidget(xmlui_manager.TextWidget, ValueWidget): type = u"text" def show(self): self.host.disp(self.value) class LabelWidget(xmlui_manager.LabelWidget, ValueWidget): type = u"label" @property def for_name(self): try: return self._xmlui_for_name except AttributeError: return None def show(self, no_lf=False, ansi=u''): """show label @param no_lf(bool): same as for [JP.disp] @param ansi(unicode): ansi escape code to print before label """ self.disp(A.color(ansi, self.value), no_lf=no_lf) class StringWidget(xmlui_manager.StringWidget, InputWidget): type = u"string" def show(self): if self.read_only: self.disp(self.value) else: elems = [] self.verboseName(elems) if self.value: elems.append(_(u'(enter: {default})').format(default=self.value)) elems.extend([C.A_HEADER, u'> ']) value = raw_input(A.color(*elems)) if value: # TODO: empty value should be possible # an escape key should be used for default instead of enter with empty value self.value = value class JidInputWidget(xmlui_manager.JidInputWidget, StringWidget): type = u'jid_input' class TextBoxWidget(xmlui_manager.TextWidget, StringWidget): type = u"textbox" class ListWidget(xmlui_manager.ListWidget, OptionsWidget): type = u'list' # TODO: handle flags, notably multi def show(self): if not self.options: return # list display self.verboseName() for idx, (value, label) in enumerate(self.options): elems = [] if not self.root.readonly: elems.extend([C.A_SUBHEADER, unicode(idx), A.RESET, u': ']) elems.append(label) self.verboseName(elems, value) self.disp(A.color(*elems)) if self.root.readonly: return if len(self.options) == 1: # we have only one option, no need to ask self.value = self.options[0][0] return # we ask use to choose an option choice = None limit_max = len(self.options)-1 while choice is None or choice<0 or choice>limit_max: choice = raw_input(A.color(C.A_HEADER, _(u'your choice (0-{max}): ').format(max=limit_max))) try: choice = int(choice) except ValueError: choice = None self.value = self.options[choice][0] self.disp('') class BoolWidget(xmlui_manager.BoolWidget, InputWidget): type = u'bool' def show(self): disp_true = A.color(A.FG_GREEN, u'TRUE') disp_false = A.color(A.FG_RED,u'FALSE') if self.read_only: self.disp(disp_true if self.value else disp_false) else: self.disp(A.color(C.A_HEADER, u'0: ', disp_false)) self.disp(A.color(C.A_HEADER, u'1: ', disp_true)) choice = None while choice not in ('0', '1'): elems = [C.A_HEADER, _(u'your choice (0,1): ')] self.verboseName(elems) choice = raw_input(A.color(*elems)) self.value = bool(int(choice)) self.disp('') def _xmluiGetValue(self): return C.boolConst(self.value) ## Containers ## class Container(Base): category = u'container' def __init__(self, xmlui_parent): super(Container, self).__init__(xmlui_parent) self.children = [] def __iter__(self): return iter(self.children) def _xmluiAppend(self, widget): self.children.append(widget) def show(self): for child in self.children: child.show() class VerticalContainer(xmlui_manager.VerticalContainer, Container): type = u'vertical' class PairsContainer(xmlui_manager.PairsContainer, Container): type = u'pairs' class LabelContainer(xmlui_manager.PairsContainer, Container): type = u'label' def show(self): for child in self.children: no_lf = False # we check linked widget type # to see if we want the label on the same line or not if child.type == u'label': for_name = child.for_name if for_name is not None: for_widget = self.root.widgets[for_name] wid_type = for_widget.type if wid_type in ('text', 'string', 'jid_input'): no_lf = True elif wid_type == 'bool' and for_widget.read_only: no_lf = True child.show(no_lf=no_lf, ansi=A.FG_CYAN) else: child.show() ## Dialogs ## class Dialog(object): def __init__(self, xmlui_parent): self.xmlui_parent = xmlui_parent self.host = self.xmlui_parent.host def disp(self, *args, **kwargs): self.host.disp(*args, **kwargs) def show(self): """display current dialog must be overriden by subclasses """ raise NotImplementedError(self.__class__) class NoteDialog(xmlui_manager.NoteDialog, Dialog): def show(self): # TODO: handle title and level self.disp(self.message) def __init__(self, _xmlui_parent, title, message, level): Dialog.__init__(self, _xmlui_parent) xmlui_manager.NoteDialog.__init__(self, _xmlui_parent) self.title, self.message, self.level = title, message, level ## Factory ## class WidgetFactory(object): def __getattr__(self, attr): if attr.startswith("create"): cls = globals()[attr[6:]] return cls class XMLUIPanel(xmlui_manager.XMLUIPanel): widget_factory = WidgetFactory() _actions = 0 # use to keep track of bridge's launchAction calls readonly = False workflow = None _submit_cb = None def __init__(self, host, parsed_dom, title=None, flags=None, callback=None, profile=None): xmlui_manager.XMLUIPanel.__init__(self, host, parsed_dom, title, flags, profile=host.profile) self.submitted = False @property def command(self): return self.host.command def show(self, workflow=None): """display the panel @param workflow(list, None): command to execute if not None put here for convenience, the main workflow is the class attribute (because workflow can continue in subclasses) command are a list of consts or lists: - SUBMIT is the only constant so far, it submits the XMLUI - list must contain widget name/widget value to fill """ if workflow: XMLUIPanel.workflow = workflow if XMLUIPanel.workflow: self.runWorkflow() else: self.main_cont.show() def runWorkflow(self): """loop into workflow commands and execute commands SUBMIT will interrupt workflow (which will be continue on callback) @param workflow(list): same as [show] """ workflow = XMLUIPanel.workflow while True: try: cmd = workflow.pop(0) except IndexError: break if cmd == SUBMIT: self.onFormSubmitted() self.submit_id = None # avoid double submit return elif isinstance(cmd, list): name, value = cmd self.widgets[name].value = value self.show() def submitForm(self, callback=None): XMLUIPanel._submit_cb = callback self.onFormSubmitted() def onFormSubmitted(self, ignore=None): # self.submitted is a Q&D workaround to avoid # double submit when a workflow is set if self.submitted: return self.submitted = True super(XMLUIPanel, self).onFormSubmitted(ignore) def _xmluiClose(self): pass def _launchActionCb(self, data): XMLUIPanel._actions -= 1 assert XMLUIPanel._actions >= 0 if u'xmlui' in data: xmlui_raw = data['xmlui'] xmlui = xmlui_manager.create(self.host, xmlui_raw) xmlui.show() if xmlui.submit_id: xmlui.onFormSubmitted() # TODO: handle data other than XMLUI if not XMLUIPanel._actions: if self._submit_cb is None: self.host.quit() else: self._submit_cb() def _xmluiLaunchAction(self, action_id, data): XMLUIPanel._actions += 1 self.host.bridge.launchAction( action_id, data, self.profile, callback=self._launchActionCb, errback=partial(self.command.errback, msg=_(u"can't launch XMLUI action: {}"), exit_code=C.EXIT_BRIDGE_ERRBACK)) class XMLUIDialog(xmlui_manager.XMLUIDialog): type = 'dialog' dialog_factory = WidgetFactory() readonly = False def show(self): self.dlg.show() def _xmluiClose(self): pass xmlui_manager.registerClass(xmlui_manager.CLASS_PANEL, XMLUIPanel) xmlui_manager.registerClass(xmlui_manager.CLASS_DIALOG, XMLUIDialog) create = xmlui_manager.create