Mercurial > libervia-backend
diff sat_frontends/jp/xmlui_manager.py @ 3040:fee60f17ebac
jp: jp asyncio port:
/!\ this commit is huge. Jp is temporarily not working with `dbus` bridge /!\
This patch implements the port of jp to asyncio, so it is now correctly using the bridge
asynchronously, and it can be used with bridges like `pb`. This also simplify the code,
notably for things which were previously implemented with many callbacks (like pagination
with RSM).
During the process, some behaviours have been modified/fixed, in jp and backends, check
diff for details.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 25 Sep 2019 08:56:41 +0200 |
parents | ab2696e34d29 |
children | d909473a76cc |
line wrap: on
line diff
--- a/sat_frontends/jp/xmlui_manager.py Wed Sep 25 08:53:38 2019 +0200 +++ b/sat_frontends/jp/xmlui_manager.py Wed Sep 25 08:56:41 2019 +0200 @@ -17,14 +17,14 @@ # 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 functools import partial from sat.core.log import getLogger - -log = getLogger(__name__) from sat_frontends.tools import xmlui as xmlui_base 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 + +log = getLogger(__name__) # workflow constants @@ -67,7 +67,7 @@ def name(self): return self._xmlui_name - def show(self): + async def show(self): """display current widget must be overriden by subclasses @@ -178,14 +178,14 @@ def __init__(self, xmlui_parent): Widget.__init__(self, xmlui_parent) - def show(self): + async def show(self): self.host.disp('') class TextWidget(xmlui_base.TextWidget, ValueWidget): type = "text" - def show(self): + async def show(self): self.host.disp(self.value) @@ -199,7 +199,7 @@ except AttributeError: return None - def show(self, no_lf=False, ansi=""): + async def show(self, no_lf=False, ansi=""): """show label @param no_lf(bool): same as for [JP.disp] @@ -214,16 +214,16 @@ class StringWidget(xmlui_base.StringWidget, InputWidget): type = "string" - def show(self): + async def show(self): if self.read_only or self.root.read_only: self.disp(self.value) else: elems = [] self.verboseName(elems) if self.value: - elems.append(_("(enter: {default})").format(default=self.value)) + elems.append(_(f"(enter: {self.value})")) elems.extend([C.A_HEADER, "> "]) - value = input(A.color(*elems).encode('utf-8')) + value = await self.host.ainput(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 @@ -238,7 +238,7 @@ type = "textbox" # TODO: use a more advanced input method - def show(self): + async def show(self): self.verboseName() if self.read_only or self.root.read_only: self.disp(self.value) @@ -251,9 +251,9 @@ while True: try: if not values: - line = input(A.color(C.A_HEADER, "[Ctrl-D to finish]> ")) + line = await self.host.ainput(A.color(C.A_HEADER, "[Ctrl-D to finish]> ")) else: - line = input() + line = await self.host.ainput() values.append(line) except EOFError: break @@ -264,20 +264,20 @@ class XHTMLBoxWidget(xmlui_base.XHTMLBoxWidget, StringWidget): type = "xhtmlbox" - def show(self): + async def show(self): # FIXME: we use bridge in a blocking way as permitted by python-dbus # this only for now to make it simpler, it must be refactored # to use async when jp will be fully async (expected for 0.8) - self.value = self.host.bridge.syntaxConvert( + self.value = await self.host.bridge.syntaxConvert( self.value, C.SYNTAX_XHTML, "markdown", False, self.host.profile) - super(XHTMLBoxWidget, self).show() + await super(XHTMLBoxWidget, self).show() class ListWidget(xmlui_base.ListWidget, OptionsWidget): type = "list" # TODO: handle flags, notably multi - def show(self): + async def show(self): if self.root.values_only: for value in self.values: self.disp(self.value) @@ -308,8 +308,8 @@ choice = None limit_max = len(self.options) - 1 while choice is None or choice < 0 or choice > limit_max: - choice = input( - A.color(C.A_HEADER, _("your choice (0-{max}): ").format(max=limit_max)) + choice = await self.host.ainput( + A.color(C.A_HEADER, _(f"your choice (0-{limit_max}): ")) ) try: choice = int(choice) @@ -322,7 +322,7 @@ class BoolWidget(xmlui_base.BoolWidget, InputWidget): type = "bool" - def show(self): + async def show(self): disp_true = A.color(A.FG_GREEN, "TRUE") disp_false = A.color(A.FG_RED, "FALSE") if self.read_only or self.root.read_only: @@ -338,7 +338,7 @@ while choice not in ("0", "1"): elems = [C.A_HEADER, _("your choice (0,1): ")] self.verboseName(elems) - choice = input(A.color(*elems)) + choice = await self.host.ainput(A.color(*elems)) self.value = bool(int(choice)) self.disp("") @@ -365,9 +365,9 @@ def _xmluiRemove(self, widget): self.children.remove(widget) - def show(self): + async def show(self): for child in self.children: - child.show() + await child.show() class VerticalContainer(xmlui_base.VerticalContainer, Container): @@ -381,7 +381,7 @@ class LabelContainer(xmlui_base.PairsContainer, Container): type = "label" - def show(self): + async def show(self): for child in self.children: no_lf = False # we check linked widget type @@ -399,9 +399,9 @@ 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) + await child.show(no_lf=no_lf, ansi=A.FG_CYAN) else: - child.show() + await child.show() ## Dialogs ## @@ -415,24 +415,61 @@ def disp(self, *args, **kwargs): self.host.disp(*args, **kwargs) - def show(self): + async def show(self): """display current dialog must be overriden by subclasses """ raise NotImplementedError(self.__class__) +class MessageDialog(xmlui_base.MessageDialog, Dialog): + + def __init__(self, xmlui_parent, title, message, level): + Dialog.__init__(self, xmlui_parent) + xmlui_base.MessageDialog.__init__(self, xmlui_parent) + self.title, self.message, self.level = title, message, level + + async def show(self): + # TODO: handle level + if self.title: + self.disp(A.color(C.A_HEADER, self.title)) + self.disp(self.message) + class NoteDialog(xmlui_base.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_base.NoteDialog.__init__(self, xmlui_parent) self.title, self.message, self.level = title, message, level + async def show(self): + # TODO: handle title and level + self.disp(self.message) + + +class ConfirmDialog(xmlui_base.ConfirmDialog, Dialog): + + def __init__(self, xmlui_parent, title, message, level, buttons_set): + Dialog.__init__(self, xmlui_parent) + xmlui_base.ConfirmDialog.__init__(self, xmlui_parent) + self.title, self.message, self.level, self.buttons_set = ( + title, message, level, buttons_set) + + async def show(self): + # TODO: handle buttons_set and level + self.disp(self.message) + if self.title: + self.disp(A.color(C.A_HEADER, self.title)) + input_ = None + while input_ not in ('y', 'n'): + input_ = await self.host.ainput(f"{self.message} (y/n)? ") + input_ = input_.lower() + if input_ == 'y': + self._xmluiValidated() + else: + self._xmluiCancelled() + ## Factory ## @@ -444,7 +481,7 @@ return cls -class XMLUIPanel(xmlui_base.XMLUIPanel): +class XMLUIPanel(xmlui_base.AIOXMLUIPanel): widget_factory = WidgetFactory() _actions = 0 # use to keep track of bridge's launchAction calls read_only = False @@ -470,7 +507,7 @@ def command(self): return self.host.command - def show(self, workflow=None, read_only=False, values_only=False): + async def show(self, workflow=None, read_only=False, values_only=False): """display the panel @param workflow(list, None): command to execute if not None @@ -489,11 +526,11 @@ if workflow: XMLUIPanel.workflow = workflow if XMLUIPanel.workflow: - self.runWorkflow() + await self.runWorkflow() else: - self.main_cont.show() + await self.main_cont.show() - def runWorkflow(self): + async def runWorkflow(self): """loop into workflow commands and execute commands SUBMIT will interrupt workflow (which will be continue on callback) @@ -506,7 +543,7 @@ except IndexError: break if cmd == SUBMIT: - self.onFormSubmitted() + await self.onFormSubmitted() self.submit_id = None # avoid double submit return elif isinstance(cmd, list): @@ -515,32 +552,32 @@ if widget.type == "bool": value = C.bool(value) widget.value = value - self.show() + await self.show() - def submitForm(self, callback=None): + async def submitForm(self, callback=None): XMLUIPanel._submit_cb = callback - self.onFormSubmitted() + await self.onFormSubmitted() - def onFormSubmitted(self, ignore=None): - # self.submitted is a Q&D workaround to avoid + async 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) + await super(XMLUIPanel, self).onFormSubmitted(ignore) def _xmluiClose(self): pass - def _launchActionCb(self, data): + async def _launchActionCb(self, data): XMLUIPanel._actions -= 1 assert XMLUIPanel._actions >= 0 if "xmlui" in data: xmlui_raw = data["xmlui"] xmlui = create(self.host, xmlui_raw) - xmlui.show() + await xmlui.show() if xmlui.submit_id: - xmlui.onFormSubmitted() + await xmlui.onFormSubmitted() # TODO: handle data other than XMLUI if not XMLUIPanel._actions: if self._submit_cb is None: @@ -548,19 +585,19 @@ else: self._submit_cb() - def _xmluiLaunchAction(self, action_id, data): + async 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=_("can't launch XMLUI action: {}"), - exit_code=C.EXIT_BRIDGE_ERRBACK, - ), - ) + try: + data = await self.host.bridge.launchAction( + action_id, + data, + self.profile, + ) + except Exception as e: + self.disp(f"can't launch XMLUI action: {e}", error=True) + self.host.quit(C.EXIT_BRIDGE_ERRBACK) + else: + await self._launchActionCb(data) class XMLUIDialog(xmlui_base.XMLUIDialog): @@ -568,8 +605,8 @@ dialog_factory = WidgetFactory() read_only = False - def show(self, __=None): - self.dlg.show() + async def show(self, __=None): + await self.dlg.show() def _xmluiClose(self): pass