# HG changeset patch # User Goffi # Date 1407934129 -7200 # Node ID e2e1e27a3680d2398c574877d8051c0a7989875e # Parent 018bdd6877478c611a1fde380774db027bafc67b frontends: XMLUI refactoring + dialogs: - there are now XMLUIPanel and XMLUIDialog both inheriting from XMLUIBase - following dialogs are managed: - MessageDialog - NoteDialog - ConfirmDialog - FileDialog - XMLUI creation is now made using xmlui.create(...) instead of instanciating directly XMLUI - classes must be registed in frontends - "parent" attribute renamed to "_xmlui_parent" to avoid name conflicts with frontends toolkits diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/constants.py --- a/frontends/src/constants.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/constants.py Wed Aug 13 14:48:49 2014 +0200 @@ -63,6 +63,8 @@ # XMLUI SAT_FORM_PREFIX = "SAT_FORM_" SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names + XMLUI_STATUS_VALIDATED = "validated" + XMLUI_STATUS_CANCELLED = constants.Const.XMLUI_DATA_CANCELLED # MUC ALL_OCCUPANTS = 1 diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/primitivus/card_game.py --- a/frontends/src/primitivus/card_game.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/primitivus/card_game.py Wed Aug 13 14:48:49 2014 +0200 @@ -22,7 +22,7 @@ from urwid_satext import sat_widgets from sat_frontends.tools.games import TarotCard from sat_frontends.quick_frontend.quick_card_game import QuickCardGame -from sat_frontends.primitivus.xmlui import XMLUI +from sat_frontends.primitivus import xmlui class CardDisplayer(urwid.Text): @@ -269,7 +269,7 @@ def chooseContrat(self, xml_data): """Called when the player has to select his contrat @param xml_data: SàT xml representation of the form""" - form = XMLUI(self.parent.host, xml_data, title=_('Please choose your contrat'), flags=['NO_CANCEL']) + form = xmlui.create(self.parent.host, xml_data, title=_('Please choose your contrat'), flags=['NO_CANCEL']) form.show(valign='top') def showCards(self, game_stage, cards, data): @@ -288,7 +288,7 @@ title = _("Draw game") else: title = _('You win \o/') if self.player_nick in winners else _('You loose :(') - form = XMLUI(self.parent.host, xml_data, title=title, flags=['NO_CANCEL']) + form = xmlui.create(self.parent.host, xml_data, title=title, flags=['NO_CANCEL']) form.show() def invalidCards(self, phase, played_cards, invalid_cards): diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/primitivus/chat.py --- a/frontends/src/primitivus/chat.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/primitivus/chat.py Wed Aug 13 14:48:49 2014 +0200 @@ -24,7 +24,7 @@ from sat_frontends.quick_frontend.quick_chat import QuickChat from sat_frontends.primitivus.card_game import CardGame from sat_frontends.quick_frontend.quick_utils import escapePrivate, unescapePrivate -from sat_frontends.primitivus.xmlui import XMLUI +from sat_frontends.primitivus import xmlui from sat_frontends.primitivus.constants import Const as C import time from sat.tools.jid import JID @@ -352,8 +352,8 @@ self.host.bridge.tarotGameCreate(self.id, list(self.occupants), self.host.profile) def onConfigureRoom(self, menu): - def gotUI(xmlui): - self.host.addWindow(XMLUI(self.host, xmlui)) + def gotUI(xml_ui): + self.host.addWindow(xmlui.create(self.host, xml_ui)) def configureError(failure): self.host.showPopUp(sat_widgets.Alert(failure.fullname, failure.message, ok_cb=self.host.removePopUp)) self.host.bridge.configureRoom(self.id, self.host.profile, callback=gotUI, errback=configureError) diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/primitivus/primitivus --- a/frontends/src/primitivus/primitivus Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/primitivus/primitivus Wed Aug 13 14:48:49 2014 +0200 @@ -33,7 +33,7 @@ from sat_frontends.primitivus.profile_manager import ProfileManager from sat_frontends.primitivus.contact_list import ContactList from sat_frontends.primitivus.chat import Chat -from sat_frontends.primitivus.xmlui import XMLUI +from sat_frontends.primitivus import xmlui from sat_frontends.primitivus.progress import Progress from sat_frontends.primitivus.notify import Notify from sat_frontends.tools.misc import InputHistory @@ -462,7 +462,7 @@ # action was a one shot, nothing to do pass elif "xmlui" in data: - ui = XMLUI(self, xml_data=data['xmlui']) + ui = xmlui.create(self, xml_data=data['xmlui']) ui.show() elif "authenticated_profile" in data: assert("caller" in data) @@ -529,7 +529,7 @@ title = _('Registration') misc['target'] = data['target'] misc['action_back'] = self.bridge.gatewayRegister - ui = XMLUI(self, title=title, xml_data = data['xml'], misc = misc) + ui = xmlui.create(self, title=title, xml_data = data['xml'], misc = misc) if data['type'] == 'registration': ui.show('popup') else: @@ -573,7 +573,7 @@ def onParam(self, menu): def success(params): - self.addWindow(XMLUI(self, xml_data=params)) + self.addWindow(xmlui.create(self, xml_data=params)) def failure(error): self.showPopUp(sat_widgets.Alert(_("Error"), _("Can't get parameters (%s)") % error, ok_cb=self.removePopUp)) diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/primitivus/status.py --- a/frontends/src/primitivus/status.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/primitivus/status.py Wed Aug 13 14:48:49 2014 +0200 @@ -19,9 +19,7 @@ from sat.core.i18n import _ import urwid -import copy from urwid_satext import sat_widgets -from sat_frontends.primitivus.xmlui import XMLUI from sat_frontends.constants import Const as commonConst from sat_frontends.primitivus.constants import Const diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/primitivus/xmlui.py --- a/frontends/src/primitivus/xmlui.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/primitivus/xmlui.py Wed Aug 13 14:48:49 2014 +0200 @@ -21,15 +21,17 @@ import urwid import copy from urwid_satext import sat_widgets +from urwid_satext import files_management from sat.core.log import getLogger log = getLogger(__name__) +from sat_frontends.primitivus.constants import Const as C from sat_frontends.tools import xmlui class PrimitivusEvents(object): """ Used to manage change event of Primitivus widgets """ - def _event_callback(self, ctrl, *args, **ktkwargs): + def _event_callback(self, ctrl, *args, **kwargs): """" Call xmlui callback and ignore any extra argument """ args[-1](ctrl) @@ -40,20 +42,20 @@ class PrimitivusEmptyWidget(xmlui.EmptyWidget, urwid.Text): - def __init__(self, parent): + def __init__(self, _xmlui_parent): urwid.Text.__init__(self, '') class PrimitivusTextWidget(xmlui.TextWidget, urwid.Text): - def __init__(self, parent, value, read_only=False): + def __init__(self, _xmlui_parent, value, read_only=False): urwid.Text.__init__(self, value) class PrimitivusLabelWidget(xmlui.LabelWidget, PrimitivusTextWidget): - def __init__(self, parent, value): - super(PrimitivusLabelWidget, self).__init__(parent, value+": ") + def __init__(self, _xmlui_parent, value): + super(PrimitivusLabelWidget, self).__init__(_xmlui_parent, value+": ") class PrimitivusJidWidget(xmlui.JidWidget, PrimitivusTextWidget): @@ -62,7 +64,7 @@ class PrimitivusDividerWidget(xmlui.DividerWidget, urwid.Divider): - def __init__(self, parent, style='line'): + def __init__(self, _xmlui_parent, style='line'): if style == 'line': div_char = u'─' elif style == 'dot': @@ -82,7 +84,7 @@ class PrimitivusStringWidget(xmlui.StringWidget, sat_widgets.AdvancedEdit, PrimitivusEvents): - def __init__(self, parent, value, read_only=False): + def __init__(self, _xmlui_parent, value, read_only=False): sat_widgets.AdvancedEdit.__init__(self, edit_text=value) self.read_only = read_only @@ -100,7 +102,7 @@ class PrimitivusPasswordWidget(xmlui.PasswordWidget, sat_widgets.Password, PrimitivusEvents): - def __init__(self, parent, value, read_only=False): + def __init__(self, _xmlui_parent, value, read_only=False): sat_widgets.Password.__init__(self, edit_text=value) self.read_only = read_only @@ -118,7 +120,7 @@ class PrimitivusTextBoxWidget(xmlui.TextBoxWidget, sat_widgets.AdvancedEdit, PrimitivusEvents): - def __init__(self, parent, value, read_only=False): + def __init__(self, _xmlui_parent, value, read_only=False): sat_widgets.AdvancedEdit.__init__(self, edit_text=value, multiline=True) self.read_only = read_only @@ -136,7 +138,7 @@ class PrimitivusBoolWidget(xmlui.BoolWidget, urwid.CheckBox, PrimitivusEvents): - def __init__(self, parent, state, read_only=False): + def __init__(self, _xmlui_parent, state, read_only=False): urwid.CheckBox.__init__(self, '', state=state) self.read_only = read_only @@ -154,7 +156,7 @@ class PrimitivusButtonWidget(xmlui.ButtonWidget, sat_widgets.CustomButton, PrimitivusEvents): - def __init__(self, parent, value, click_callback): + def __init__(self, _xmlui_parent, value, click_callback): sat_widgets.CustomButton.__init__(self, value, on_press=click_callback) def _xmluiOnClick(self, callback): @@ -163,7 +165,7 @@ class PrimitivusListWidget(xmlui.ListWidget, sat_widgets.List, PrimitivusEvents): - def __init__(self, parent, options, selected, flags): + def __init__(self, _xmlui_parent, options, selected, flags): sat_widgets.List.__init__(self, options=options, style=flags) self._xmluiSelectValues(selected) @@ -194,7 +196,7 @@ class PrimitivusAdvancedListContainer(xmlui.AdvancedListContainer, sat_widgets.TableContainer, PrimitivusEvents): - def __init__(self, parent, columns, selectable='no'): + def __init__(self, _xmlui_parent, columns, selectable='no'): options = {'ADAPT':()} if selectable != 'no': options['HIGHLIGHT'] = () @@ -219,7 +221,7 @@ class PrimitivusPairsContainer(xmlui.PairsContainer, sat_widgets.TableContainer): - def __init__(self, parent): + def __init__(self, _xmlui_parent): options = {'ADAPT':(0,), 'HIGHLIGHT':(0,)} if self._xmlui_main.type == 'param': options['FOCUS_ATTR'] = 'param_selected' @@ -234,7 +236,7 @@ class PrimitivusTabsContainer(xmlui.TabsContainer, sat_widgets.TabsContainer): - def __init__(self, parent): + def __init__(self, _xmlui_parent): sat_widgets.TabsContainer.__init__(self) def _xmluiAppend(self, widget): @@ -249,7 +251,7 @@ class PrimitivusVerticalContainer(xmlui.VerticalContainer, urwid.ListBox): BOX_HEIGHT = 5 - def __init__(self, parent): + def __init__(self, _xmlui_parent): urwid.ListBox.__init__(self, urwid.SimpleListWalker([])) self._last_size = None @@ -269,24 +271,81 @@ return super(PrimitivusVerticalContainer, self).render(size, focus) -class WidgetFactory(object): +### Dialogs ### + + +class PrimitivusDialog(object): + + def __init__(self, _xmlui_parent): + self.host = _xmlui_parent.host + + def _xmluiShow(self): + self.host.showPopUp(self) + + def _xmluiClose(self): + self.host.removePopUp() + + +class PrimitivusMessageDialog(PrimitivusDialog, xmlui.MessageDialog, sat_widgets.Alert): + + def __init__(self, _xmlui_parent, title, message, level): + PrimitivusDialog.__init__(self, _xmlui_parent) + xmlui.MessageDialog.__init__(self, _xmlui_parent) + sat_widgets.Alert.__init__(self, title, message, ok_cb=lambda dummy: self._xmluiValidated()) + + +class PrimitivusNoteDialog(xmlui.NoteDialog, PrimitivusMessageDialog): + # TODO: separate NoteDialog + pass + + +class PrimitivusConfirmDialog(PrimitivusDialog, xmlui.ConfirmDialog, sat_widgets.ConfirmDialog): + + def __init__(self, _xmlui_parent, title, message, level, buttons_set): + PrimitivusDialog.__init__(self, _xmlui_parent) + xmlui.ConfirmDialog.__init__(self, _xmlui_parent) + sat_widgets.ConfirmDialog.__init__(self, title, message, no_cb=lambda dummy: self._xmluiCancelled(), yes_cb=lambda dummy: self._xmluiValidated()) + + +class PrimitivusFileDialog(PrimitivusDialog, xmlui.FileDialog, files_management.FileDialog): + + def __init__(self, _xmlui_parent, title, message, level, filetype): + # TODO: message is not managed yet + PrimitivusDialog.__init__(self, _xmlui_parent) + xmlui.FileDialog.__init__(self, _xmlui_parent) + style = [] + if filetype == C.XMLUI_DATA_FILETYPE_DIR: + style.append('dir') + files_management.FileDialog.__init__(self, ok_cb=lambda path: self._xmluiValidated({'path': path}), cancel_cb=lambda dummy: self._xmluiCancelled(), title=title) + + +class GenericFactory(object): def __getattr__(self, attr): if attr.startswith("create"): cls = globals()["Primitivus" + attr[6:]] # XXX: we prefix with "Primitivus" to work around an Urwid bug, WidgetMeta in Urwid don't manage multiple inheritance with same names + return cls + + +class WidgetFactory(GenericFactory): + + def __getattr__(self, attr): + if attr.startswith("create"): + cls = GenericFactory.__getattr__(self, attr) cls._xmlui_main = self._xmlui_main return cls -class XMLUI(xmlui.XMLUI, urwid.WidgetWrap): + +class XMLUIPanel(xmlui.XMLUIPanel, urwid.WidgetWrap): widget_factory = WidgetFactory() - def __init__(self, host, xml_data, title = None, flags = None): + def __init__(self, host, parsed_xml, title = None, flags = None): self.widget_factory._xmlui_main = self self._dest = None - xmlui.XMLUI.__init__(self, host, xml_data, title, flags) + xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags) urwid.WidgetWrap.__init__(self, self.main_cont) - def constructUI(self, xml_data): + def constructUI(self, parsed_dom): def postTreat(): assert self.main_cont.body @@ -311,7 +370,7 @@ grid_wid = urwid.GridFlow(buttons,max_len,1,0,'center') tabs_cont.addFooter(grid_wid) - super(XMLUI, self).constructUI(xml_data, postTreat) + xmlui.XMLUIPanel.constructUI(self, parsed_dom, postTreat) urwid.WidgetWrap.__init__(self, self.main_cont) def show(self, show_type=None, valign='middle'): @@ -321,7 +380,7 @@ - 'popup' - 'window' @param valign: vertical alignment when show_type is 'popup'. - Ignored when show_type is 'window'. + Ignored when show_type is 'window'. """ if show_type is None: @@ -343,9 +402,17 @@ assert(False) self.host.redraw() - def _xmluiClose(self): if self._dest == 'window': self.host.removeWindow() else: self.host.removePopUp() + + +class XMLUIDialog(xmlui.XMLUIDialog): + dialog_factory = GenericFactory() + + +xmlui.registerClass(xmlui.CLASS_PANEL, XMLUIPanel) +xmlui.registerClass(xmlui.CLASS_DIALOG, XMLUIDialog) +create = xmlui.create diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/tools/xmlui.py --- a/frontends/src/tools/xmlui.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/tools/xmlui.py Wed Aug 13 14:48:49 2014 +0200 @@ -20,14 +20,21 @@ from sat.core.i18n import _ from sat.core.log import getLogger log = getLogger(__name__) -from sat_frontends.constants import Const +from sat_frontends.constants import Const as C from sat.core.exceptions import DataError +class_map = {} +CLASS_PANEL = 'panel' +CLASS_DIALOG = 'dialog' + class InvalidXMLUI(Exception): pass +class ClassNotRegistedError(Exception): + pass + def getText(node): """Get child text nodes @param node: dom Node @@ -42,166 +49,179 @@ class Widget(object): - """ base Widget """ + """base Widget""" pass class EmptyWidget(Widget): - """ Just a placeholder widget """ + """Just a placeholder widget""" pass class TextWidget(Widget): - """ Non interactive text """ + """Non interactive text""" pass class LabelWidget(Widget): - """ Non interactive text """ + """Non interactive text""" pass class JidWidget(Widget): - """ Jabber ID """ + """Jabber ID""" pass class DividerWidget(Widget): - """ Separator """ + """Separator""" pass class StringWidget(Widget): - """ Input widget with require a string + """Input widget wich require a string + often called Edit in toolkits - """ class PasswordWidget(Widget): - """ Input widget with require a masked string - - """ + """Input widget with require a masked string""" class TextBoxWidget(Widget): - """ Input widget with require a long, possibly multilines string + """Input widget with require a long, possibly multilines string often called TextArea in toolkits - """ class BoolWidget(Widget): - """ Input widget with require a boolean value + """Input widget with require a boolean value often called CheckBox in toolkits - """ class ButtonWidget(Widget): - """ A clickable widget """ + """A clickable widget""" class ListWidget(Widget): - """ A widget able to show/choose one or several strings in a list """ + """A widget able to show/choose one or several strings in a list""" class Container(Widget): - """ Widget which can contain other ones with a specific layout """ + """Widget which can contain other ones with a specific layout""" @classmethod def _xmluiAdapt(cls, instance): - """ Make cls as instance.__class__ + """Make cls as instance.__class__ + cls must inherit from original instance class Usefull when you get a class from UI toolkit - """ assert instance.__class__ in cls.__bases__ instance.__class__ = type(cls.__name__, cls.__bases__, dict(cls.__dict__)) class PairsContainer(Container): - """ Widgets are disposed in rows of two (usually label/input) """ - pass + """Widgets are disposed in rows of two (usually label/input) """ class TabsContainer(Container): - """ A container which several other containers in tabs + """A container which several other containers in tabs + Often called Notebook in toolkits - """ - class VerticalContainer(Container): - """ Widgets are disposed vertically """ - pass + """Widgets are disposed vertically""" class AdvancedListContainer(Container): - """ Widgets are disposed in rows with advaned features """ - pass + """Widgets are disposed in rows with advaned features""" + + +class Dialog(object): + """base dialog""" + + def __init__(self, _xmlui_parent): + self._xmlui_parent = _xmlui_parent + + def _xmluiValidated(self, data=None): + if data is None: + data = {} + self._xmluiSetData(C.XMLUI_STATUS_VALIDATED, data) + self._xmluiSubmit(data) + self._xmluiClose() + + def _xmluiCancelled(self): + data = {C.XMLUI_DATA_CANCELLED: C.BOOL_TRUE} + self._xmluiSetData(C.XMLUI_STATUS_CANCELLED, data) + self._xmluiSubmit(data) + self._xmluiClose() + + def _xmluiSubmit(self, data): + self._xmlui_parent.submit(data) + + def _xmluiSetData(self, status, data): + pass -class XMLUI(object): - """ Base class to construct SàT XML User Interface - New frontends can inherite this class to easily implement XMLUI - @property widget_factory: factory to create frontend-specific widgets - @proporety dialog_factory: factory to create frontend-specific dialogs +class MessageDialog(Dialog): + """Dialog with a OK/Cancel type configuration""" + + +class NoteDialog(Dialog): + """Dialog with a OK/Cancel type configuration""" + + +class ConfirmDialog(Dialog): + """Dialog with a OK/Cancel type configuration""" + def _xmluiSetData(self, status, data): + if status == C.XMLUI_STATUS_VALIDATED: + data[C.XMLUI_DATA_ANSWER] = C.BOOL_TRUE + elif status == C.XMLUI_STATUS_CANCELLED: + data[C.XMLUI_DATA_ANSWER] = C.BOOL_FALSE + + +class FileDialog(Dialog): + """Dialog with a OK/Cancel type configuration""" + + +class XMLUIBase(object): + """Base class to construct SàT XML User Interface + + This class must not be instancied directly """ - widget_factory = None - dialog_factory = None # TODO - def __init__(self, host, xml_data, title = None, flags = None, dom_parse=None, dom_free=None): - """ Initialise the XMLUI instance + def __init__(self, host, parsed_dom, title = None, flags = None): + """Initialise the XMLUI instance + @param host: %(doc_host)s - @param xml_data: the raw XML containing the UI + @param parsed_dom: main parsed dom @param title: force the title, or use XMLUI one if None @param flags: list of string which can be: - NO_CANCEL: the UI can't be cancelled - @param dom_parse: methode equivalent to minidom.parseString (but which must manage unicode), or None to use default one - @param dom_free: method used to free the parsed DOM - """ - if dom_parse is None: - from xml.dom import minidom - self.dom_parse = lambda xml_data: minidom.parseString(xml_data.encode('utf-8')) - self.dom_free = lambda cat_dom: cat_dom.unlink() - else: - self.dom_parse = dom_parse - self.dom_free = dom_free or (lambda cat_dom: None) self.host = host - self.title = title or "" + top=parsed_dom.documentElement + self.session_id = top.getAttribute("session_id") or None + self.submit_id = top.getAttribute("submit") or None + self.title = title or top.getAttribute("title") or u"" if flags is None: flags = [] self.flags = flags - self.ctrl_list = {} # usefull to access ctrl - self._main_cont = None - self.constructUI(xml_data) - - def escape(self, name): - """ return escaped name for forms """ - return u"%s%s" % (Const.SAT_FORM_PREFIX, name) - - @property - def main_cont(self): - return self._main_cont - - @main_cont.setter - def main_cont(self, value): - if self._main_cont is not None: - raise ValueError(_("XMLUI can have only one main container")) - self._main_cont = value def _isAttrSet(self, name, node): """Returnw widget boolean attribute status @param name: name of the attribute (e.g. "read_only") @param node: Node instance - @return (bool): True if widget's attribute is set ("true") + @return (bool): True if widget's attribute is set (C.BOOL_TRUE) """ - read_only = node.getAttribute(name) or "false" - return read_only.lower().strip() == "true" + read_only = node.getAttribute(name) or C.BOOL_FALSE + return read_only.lower().strip() == C.BOOL_TRUE def _getChildNode(self, node, name): """Return the first child node with the given name @@ -216,13 +236,55 @@ return child return None - def _parseChilds(self, parent, current_node, wanted = ('container',), data = None): + def submit(self, data): + if self.submit_id is None: + raise ValueError("Can't submit is self.submit_id is not set") + if "session_id" in data: + raise ValueError("session_id must no be used in data, it is automaticaly filled with self.session_id if present") + if self.session_id is not None: + data["session_id"] = self.session_id + self._xmluiLaunchAction(self.submit_id, data) + + def _xmluiLaunchAction(self, action_id, data): + self.host.launchAction(action_id, data, profile_key = self.host.profile) + + +class XMLUIPanel(XMLUIBase): + """XMLUI Panel + + New frontends can inherite this class to easily implement XMLUI + @property widget_factory: factory to create frontend-specific widgets + @proporety dialog_factory: factory to create frontend-specific dialogs + """ + widget_factory = None + + def __init__(self, host, parsed_dom, title = None, flags = None): + super(XMLUIPanel, self).__init__(host, parsed_dom, title = None, flags = None) + self.ctrl_list = {} # usefull to access ctrl + self._main_cont = None + self.constructUI(parsed_dom) + + def escape(self, name): + """Return escaped name for forms""" + return u"%s%s" % (C.SAT_FORM_PREFIX, name) + + @property + def main_cont(self): + return self._main_cont + + @main_cont.setter + def main_cont(self, value): + if self._main_cont is not None: + raise ValueError(_("XMLUI can have only one main container")) + self._main_cont = value + + def _parseChilds(self, _xmlui_parent, current_node, wanted = ('container',), data = None): """Recursively parse childNodes of an elemen - @param parent: widget container with '_xmluiAppend' method + + @param _xmlui_parent: widget container with '_xmluiAppend' method @param current_node: element from which childs will be parsed @param wanted: list of tag names that can be present in the childs to be SàT XMLUI compliant @param data: additionnal data which are needed in some cases - """ for node in current_node.childNodes: if wanted and not node.nodeName in wanted: @@ -230,18 +292,18 @@ if node.nodeName == "container": type_ = node.getAttribute('type') - if parent is self and type_ != 'vertical': + if _xmlui_parent is self and type_ != 'vertical': # main container is not a VerticalContainer and we want one, so we create one to wrap it - parent = self.widget_factory.createVerticalContainer(self) - self.main_cont = parent + _xmlui_parent = self.widget_factory.createVerticalContainer(self) + self.main_cont = _xmlui_parent if type_ == "tabs": - cont = self.widget_factory.createTabsContainer(parent) - self._parseChilds(parent, node, ('tab',), cont) + cont = self.widget_factory.createTabsContainer(_xmlui_parent) + self._parseChilds(_xmlui_parent, node, ('tab',), cont) elif type_ == "vertical": - cont = self.widget_factory.createVerticalContainer(parent) + cont = self.widget_factory.createVerticalContainer(_xmlui_parent) self._parseChilds(cont, node, ('widget', 'container')) elif type_ == "pairs": - cont = self.widget_factory.createPairsContainer(parent) + cont = self.widget_factory.createPairsContainer(_xmlui_parent) self._parseChilds(cont, node, ('widget', 'container')) elif type_ == "advanced_list": try: @@ -249,9 +311,9 @@ except (TypeError, ValueError): raise DataError("Invalid columns") selectable = node.getAttribute('selectable') or 'no' - auto_index = node.getAttribute('auto_index') == 'true' + auto_index = node.getAttribute('auto_index') == C.BOOL_TRUE data = {'index': 0} if auto_index else None - cont = self.widget_factory.createAdvancedListContainer(parent, columns, selectable) + cont = self.widget_factory.createAdvancedListContainer(_xmlui_parent, columns, selectable) callback_id = node.getAttribute("callback") or None if callback_id is not None: if selectable == 'no': @@ -262,12 +324,12 @@ self._parseChilds(cont, node, ('row',), data) else: log.warning(_("Unknown container [%s], using default one") % type_) - cont = self.widget_factory.createVerticalContainer(parent) + cont = self.widget_factory.createVerticalContainer(_xmlui_parent) self._parseChilds(cont, node, ('widget', 'container')) try: - parent._xmluiAppend(cont) + _xmlui_parent._xmluiAppend(cont) except (AttributeError, TypeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError - if parent is self: + if _xmlui_parent is self: self.main_cont = cont else: raise Exception(_("Internal Error, container has not _xmluiAppend method")) @@ -289,11 +351,10 @@ data['index'] += 1 except TypeError: index = node.getAttribute('index') or None - parent._xmluiAddRow(index) - self._parseChilds(parent, node, ('widget', 'container')) + _xmlui_parent._xmluiAddRow(index) + self._parseChilds(_xmlui_parent, node, ('widget', 'container')) elif node.nodeName == "widget": - id_ = node.getAttribute("id") name = node.getAttribute("name") type_ = node.getAttribute("type") value_elt = self._getChildNode(node, "value") @@ -302,37 +363,37 @@ else: value = node.getAttribute("value") if node.hasAttribute('value') else u'' if type_=="empty": - ctrl = self.widget_factory.createEmptyWidget(parent) + ctrl = self.widget_factory.createEmptyWidget(_xmlui_parent) elif type_=="text": - ctrl = self.widget_factory.createTextWidget(parent, value) + ctrl = self.widget_factory.createTextWidget(_xmlui_parent, value) elif type_=="label": - ctrl = self.widget_factory.createLabelWidget(parent, value) + ctrl = self.widget_factory.createLabelWidget(_xmlui_parent, value) elif type_=="jid": - ctrl = self.widget_factory.createJidWidget(parent, value) + ctrl = self.widget_factory.createJidWidget(_xmlui_parent, value) elif type_=="divider": style = node.getAttribute("style") or 'line' - ctrl = self.widget_factory.createDividerWidget(parent, style) + ctrl = self.widget_factory.createDividerWidget(_xmlui_parent, style) elif type_=="string": - ctrl = self.widget_factory.createStringWidget(parent, value, self._isAttrSet("read_only", node)) + ctrl = self.widget_factory.createStringWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) elif type_=="password": - ctrl = self.widget_factory.createPasswordWidget(parent, value, self._isAttrSet("read_only", node)) + ctrl = self.widget_factory.createPasswordWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) elif type_=="textbox": - ctrl = self.widget_factory.createTextBoxWidget(parent, value, self._isAttrSet("read_only", node)) + ctrl = self.widget_factory.createTextBoxWidget(_xmlui_parent, value, self._isAttrSet("read_only", node)) self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) elif type_=="bool": - ctrl = self.widget_factory.createBoolWidget(parent, value=='true', self._isAttrSet("read_only", node)) + ctrl = self.widget_factory.createBoolWidget(_xmlui_parent, value==C.BOOL_TRUE, self._isAttrSet("read_only", node)) self.ctrl_list[name] = ({'type':type_, 'control':ctrl}) elif type_ == "list": style = [] if node.getAttribute("multi") == 'yes' else ['single'] _options = [(option.getAttribute("value"), option.getAttribute("label")) for option in node.getElementsByTagName("option")] - _selected = [option.getAttribute("value") for option in node.getElementsByTagName("option") if option.getAttribute('selected') == 'true'] - ctrl = self.widget_factory.createListWidget(parent, _options, _selected, style) + _selected = [option.getAttribute("value") for option in node.getElementsByTagName("option") if option.getAttribute('selected') == C.BOOL_TRUE] + ctrl = self.widget_factory.createListWidget(_xmlui_parent, _options, _selected, style) self.ctrl_list[name] = ({'type': type_, 'control': ctrl}) elif type_=="button": callback_id = node.getAttribute("callback") - ctrl = self.widget_factory.createButtonWidget(parent, value, self.onButtonPress) + ctrl = self.widget_factory.createButtonWidget(_xmlui_parent, value, self.onButtonPress) ctrl._xmlui_param_id = (callback_id, [field.getAttribute('name') for field in node.getElementsByTagName("field_back")]) else: log.error(_("FIXME FIXME FIXME: widget type [%s] is not implemented") % type_) @@ -358,55 +419,47 @@ ctrl._xmluiOnChange(self.onChangeInternal) ctrl._xmlui_name = name - parent._xmluiAppend(ctrl) + _xmlui_parent._xmluiAppend(ctrl) else: raise NotImplementedError(_('Unknown tag [%s]') % node.nodeName) - def constructUI(self, xml_data, post_treat=None): - """ Actually construct the UI - @param xml_data: raw XMLUI + def constructUI(self, parsed_dom, post_treat=None): + """Actually construct the UI + + @param parsed_dom: main parsed dom @param post_treat: frontend specific treatments to do once the UI is constructed @return: constructed widget """ - cat_dom = self.dom_parse(xml_data) - top=cat_dom.documentElement + top=parsed_dom.documentElement self.type = top.getAttribute("type") - self.title = self.title or top.getAttribute("title") or u"" - self.session_id = top.getAttribute("session_id") or None - self.submit_id = top.getAttribute("submit") or None if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window', 'popup']: raise InvalidXMLUI if self.type == 'param': self.param_changed = set() - self._parseChilds(self, cat_dom.documentElement) + self._parseChilds(self, parsed_dom.documentElement) if post_treat is not None: post_treat() - self.dom_free(cat_dom) + def _xmluiClose(self): + """Close the window/popup/... where the constructeur XMLUI is - def _xmluiClose(self): - """ Close the window/popup/... where the constructeur XMLUI is this method must be overrided - """ raise NotImplementedError - def _xmluiLaunchAction(self, action_id, data): - self.host.launchAction(action_id, data, profile_key = self.host.profile) - def _xmluiSetParam(self, name, value, category): self.host.bridge.setParam(name, value, category, profile_key=self.host.profile) ##EVENTS## def onParamChange(self, ctrl): - """ Called when type is param and a widget to save is modified + """Called when type is param and a widget to save is modified + @param ctrl: widget modified - """ assert(self.type == "param") self.param_changed.add(ctrl) @@ -431,10 +484,10 @@ self._xmluiLaunchAction(callback_id, data) def onButtonPress(self, button): - """ Called when an XMLUI button is clicked + """Called when an XMLUI button is clicked + Launch the action associated to the button @param button: the button clicked - """ callback_id, fields = button._xmlui_param_id if not callback_id: # the button is probably bound to an internal action @@ -450,7 +503,7 @@ self._xmluiLaunchAction(callback_id, data) def onChangeInternal(self, ctrl): - """ Called when a widget that has been bound to an internal callback is changed. + """Called when a widget that has been bound to an internal callback is changed. This is used to perform some UI actions without communicating with the backend. See sat.tools.xml_tools.Widget.setInternalCallback for more details. @@ -507,14 +560,14 @@ def getInternalCallbackData(self, action, node): """Retrieve from node the data needed to perform given action. - TODO: it would be better to not have a specific way to retrieve - data for each action, but instead to have a generic method to - extract any kind of data structure from the 'internal_data' element. - @param action (string): a value from the one that can be passed to the 'callback' parameter of sat.tools.xml_tools.Widget.setInternalCallback @param node (DOM Element): the node of the widget that triggers the callback """ + # TODO: it would be better to not have a specific way to retrieve + # data for each action, but instead to have a generic method to + # extract any kind of data structure from the 'internal_data' element. + try: # data is stored in the first 'internal_data' element of the node data_elts = node.getElementsByTagName('internal_data')[0].childNodes except IndexError: @@ -529,9 +582,9 @@ return data def onFormSubmitted(self, ignore=None): - """ An XMLUI form has been submited + """An XMLUI form has been submited + call the submit action associated with this form - """ selected_values = [] for ctrl_name in self.ctrl_list: @@ -543,23 +596,20 @@ selected_values.append((escaped, ctrl['control']._xmluiGetValue())) if self.submit_id is not None: data = dict(selected_values) - if self.session_id is not None: - data["session_id"] = self.session_id - self._xmluiLaunchAction(self.submit_id, data) - + self.submit(data) else: log.warning(_("The form data is not sent back, the type is not managed properly")) self._xmluiClose() def onFormCancelled(self, ignore=None): - """ Called when a form is cancelled """ + """Called when a form is cancelled""" log.debug(_("Cancelling form")) self._xmluiClose() def onSaveParams(self, ignore=None): - """ Params are saved, we send them to backend + """Params are saved, we send them to backend + self.type must be param - """ assert(self.type == 'param') for ctrl in self.param_changed: @@ -567,7 +617,92 @@ value = u'\t'.join(ctrl._xmluiGetSelectedValues()) else: value = ctrl._xmluiGetValue() - param_name = ctrl._xmlui_name.split(Const.SAT_PARAM_SEPARATOR)[1] + param_name = ctrl._xmlui_name.split(C.SAT_PARAM_SEPARATOR)[1] self._xmluiSetParam(param_name, value, ctrl._param_category) self._xmluiClose() + + def show(self, *args, **kwargs): + pass + + +class XMLUIDialog(XMLUIBase): + dialog_factory = None + + def __init__(self, host, parsed_dom, title = None, flags = None): + super(XMLUIDialog, self).__init__(host, parsed_dom, title = None, flags = None) + top=parsed_dom.documentElement + dlg_elt = self._getChildNode(top, "dialog") + if dlg_elt is None: + raise ValueError("Invalid XMLUI: no Dialog element found !") + dlg_type = dlg_elt.getAttribute("type") or C.XMLUI_DIALOG_MESSAGE + try: + mess_elt = self._getChildNode(dlg_elt, C.XMLUI_DATA_MESS) + message = getText(mess_elt) + except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + message = "" + level = dlg_elt.getAttribute(C.XMLUI_DATA_LVL) or C.XMLUI_DATA_LVL_INFO + + if dlg_type == C.XMLUI_DIALOG_MESSAGE: + self.dlg = self.dialog_factory.createMessageDialog(self, self.title, message, level) + elif dlg_type == C.XMLUI_DIALOG_NOTE: + self.dlg = self.dialog_factory.createNoteDialog(self, self.title, message, level) + elif dlg_type == C.XMLUI_DIALOG_CONFIRM: + try: + buttons_elt = self._getChildNode(dlg_elt, "buttons") + buttons_set = buttons_elt.getAttribute("set") or C.XMLUI_DATA_BTNS_SET_DEFAULT + except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + buttons_set = C.XMLUI_DATA_BTNS_SET_DEFAULT + self.dlg = self.dialog_factory.createConfirmDialog(self, self.title, message, level, buttons_set) + elif dlg_type == C.XMLUI_DIALOG_FILE: + try: + file_elt = self._getChildNode(dlg_elt, "file") + filetype = file_elt.getAttribute("type") or C.XMLUI_DATA_FILETYPE_DEFAULT + except (TypeError, AttributeError): # XXX: TypeError is here because pyjamas raise a TypeError instead of an AttributeError + filetype = C.XMLUI_DATA_FILETYPE_DEFAULT + self.dlg = self.dialog_factory.createFileDialog(self, self.title, message, level, filetype) + else: + raise ValueError("Unknown dialog type [%s]" % dlg_type) + + def show(self): + self.dlg._xmluiShow() + + +def registerClass(type_, class_): + """Register the class to use with the factory + + @param type_: one of: + CLASS_PANEL: classical XMLUI interface + CLASS_DIALOG: XMLUI dialog + @param class_: the class to use to instanciate given type + """ + assert type_ in (CLASS_PANEL, CLASS_DIALOG) + class_map[type_] = class_ + + +def create(host, xml_data, title = None, flags = None, dom_parse=None, dom_free=None): + """ + @param dom_parse: methode equivalent to minidom.parseString (but which must manage unicode), or None to use default one + @param dom_free: method used to free the parsed DOM + """ + if dom_parse is None: + from xml.dom import minidom + dom_parse = lambda xml_data: minidom.parseString(xml_data.encode('utf-8')) + dom_free = lambda parsed_dom: parsed_dom.unlink() + else: + dom_parse = dom_parse + dom_free = dom_free or (lambda parsed_dom: None) + parsed_dom = dom_parse(xml_data) + top=parsed_dom.documentElement + ui_type = top.getAttribute("type") + try: + if ui_type != C.XMLUI_DIALOG: + cls = class_map[CLASS_PANEL] + else: + cls = class_map[CLASS_DIALOG] + except KeyError: + raise ClassNotRegistedError(_("You must register classes with registerClass before creating a XMLUI")) + + xmlui = cls(host, parsed_dom, title, flags) + dom_free(parsed_dom) + return xmlui diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/wix/card_game.py --- a/frontends/src/wix/card_game.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/wix/card_game.py Wed Aug 13 14:48:49 2014 +0200 @@ -25,7 +25,7 @@ log = getLogger(__name__) from sat_frontends.tools.games import TarotCard from sat_frontends.quick_frontend.quick_card_game import QuickCardGame -from sat_frontends.wix.xmlui import XMLUI +from sat_frontends.wix import xmlui CARD_WIDTH = 74 CARD_HEIGHT = 136 @@ -103,7 +103,7 @@ def chooseContrat(self, xml_data): """Called when the player has to select his contrat @param xml_data: SàT xml representation of the form""" - XMLUI(self.parent.host, xml_data, title=_('Please choose your contrat'), flags=['NO_CANCEL']) + xmlui.create(self.parent.host, xml_data, title=_('Please choose your contrat'), flags=['NO_CANCEL']) def showScores(self, xml_data, winners, loosers): """Called when the round is over, display the scores @@ -112,7 +112,7 @@ title = _("Draw game") else: title = _('You win \o/') if self.player_nick in winners else _('You loose :(') - XMLUI(self.parent.host, xml_data, title=title, flags=['NO_CANCEL']) + xmlui.create(self.parent.host, xml_data, title=title, flags=['NO_CANCEL']) def cardsPlayed(self, player, cards): """A card has been played by player""" diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/wix/main_window.py --- a/frontends/src/wix/main_window.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/wix/main_window.py Wed Aug 13 14:48:49 2014 +0200 @@ -25,7 +25,7 @@ import wx from sat_frontends.wix.contact_list import ContactList from sat_frontends.wix.chat import Chat -from sat_frontends.wix.xmlui import XMLUI +from sat_frontends.wix import xmlui from sat_frontends.wix.profile import Profile from sat_frontends.wix.profile_manager import ProfileManager import os.path @@ -192,16 +192,16 @@ popup.Show() wx.CallLater(5000,popup.Destroy) - def showDialog(self, message, title="", type="info", answer_cb = None, answer_data = None): - if type == 'info': + def showDialog(self, message, title="", type_="info", answer_cb = None, answer_data = None): + if type_ == 'info': flags = wx.OK | wx.ICON_INFORMATION - elif type == 'error': + elif type_ == 'error': flags = wx.OK | wx.ICON_ERROR - elif type == 'yes/no': + elif type_ == 'yes/no': flags = wx.YES_NO | wx.ICON_QUESTION else: flags = wx.OK | wx.ICON_INFORMATION - log.error(_('unmanaged dialog type: %s'), type) + log.error(_('unmanaged dialog type: %s'), type_) dlg = wx.MessageDialog(self, message, title, flags) answer = dlg.ShowModal() dlg.Destroy() @@ -243,7 +243,8 @@ pass elif "xmlui" in data: log.debug (_("XML user interface received")) - XMLUI(self, xml_data = data['xmlui']) + ui = xmlui.create(self, xml_data = data['xmlui']) + ui.show() elif "authenticated_profile" in data: assert("caller" in data) if data["caller"] == "profile_manager": @@ -345,7 +346,7 @@ title = _('Registration') misc['target'] = data['target'] misc['action_back'] = self.bridge.gatewayRegister - XMLUI(self, title=title, xml_data = data['xml'], misc = misc) + xmlui.create(self, title=title, xml_data = data['xml'], misc = misc) elif type_ == "RESULT": self.current_action_ids.remove(id_) if self.current_action_ids_cb.has_key(id_): @@ -413,7 +414,7 @@ def onParam(self, e): log.debug(_("Param request")) def success(params): - XMLUI(self, xml_data=params, title=_("Configuration")) + xmlui.create(self, xml_data=params, title=_("Configuration")) def failure(error): dlg = wx.MessageDialog(self, error.message, diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/wix/profile_manager.py --- a/frontends/src/wix/profile_manager.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/wix/profile_manager.py Wed Aug 13 14:48:49 2014 +0200 @@ -24,7 +24,6 @@ import wx from sat.core.log import getLogger log = getLogger(__name__) -from sat.tools.jid import JID NO_SELECTION_ENTRY = ' ' @@ -147,7 +146,7 @@ return if self.selected_profile: self.profile_name.SetValue(self.selected_profile) - self.host.profile = profile_name # FIXME: EXTREMELY DIRTY, needed for sat_frontends.tools.xmlui.XMLUI._xmluiLaunchAction + self.host.profile = profile_name # FIXME: EXTREMELY DIRTY, needed for sat_frontends.tools.xmlui.XMLUI.submit self.host.launchAction(C.AUTHENTICATE_PROFILE_ID, {'caller': 'profile_manager'}, profile_key=profile_name) def onConnectButton(self, event): diff -r 018bdd687747 -r e2e1e27a3680 frontends/src/wix/xmlui.py --- a/frontends/src/wix/xmlui.py Mon Aug 11 19:10:24 2014 +0200 +++ b/frontends/src/wix/xmlui.py Wed Aug 13 14:48:49 2014 +0200 @@ -23,9 +23,8 @@ import wx from sat.core.log import getLogger log = getLogger(__name__) -from sat.tools.jid import JID from sat_frontends.tools import xmlui -from sat_frontends.constants import Const +from sat_frontends.constants import Const as C class EventWidget(object): @@ -53,20 +52,20 @@ class EmptyWidget(WixWidget, xmlui.EmptyWidget, wx.Window): - def __init__(self, parent): - wx.Window.__init__(self, parent, -1) + def __init__(self, _xmlui_parent): + wx.Window.__init__(self, _xmlui_parent, -1) class TextWidget(WixWidget, xmlui.TextWidget, wx.StaticText): - def __init__(self, parent, value): - wx.StaticText.__init__(self, parent, -1, value) + def __init__(self, _xmlui_parent, value): + wx.StaticText.__init__(self, _xmlui_parent, -1, value) class LabelWidget(xmlui.LabelWidget, TextWidget): - def __init__(self, parent, value): - super(LabelWidget, self).__init__(parent, value+": ") + def __init__(self, _xmlui_parent, value): + super(LabelWidget, self).__init__(_xmlui_parent, value+": ") class JidWidget(xmlui.JidWidget, TextWidget): @@ -75,49 +74,49 @@ class DividerWidget(WixWidget, xmlui.DividerWidget, wx.StaticLine): - def __init__(self, parent, style='line'): - wx.StaticLine.__init__(self, parent, -1) + def __init__(self, _xmlui_parent, style='line'): + wx.StaticLine.__init__(self, _xmlui_parent, -1) class StringWidget(EventWidget, ValueWidget, xmlui.StringWidget, wx.TextCtrl): _xmlui_change_event = wx.EVT_TEXT - def __init__(self, parent, value, read_only=False): + def __init__(self, _xmlui_parent, value, read_only=False): style = wx.TE_READONLY if read_only else 0 - wx.TextCtrl.__init__(self, parent, -1, value, style=style) + wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style) self._xmlui_proportion = 1 class PasswordWidget(EventWidget, ValueWidget, xmlui.PasswordWidget, wx.TextCtrl): _xmlui_change_event = wx.EVT_TEXT - def __init__(self, parent, value, read_only=False): + def __init__(self, _xmlui_parent, value, read_only=False): style = wx.TE_PASSWORD if read_only: style |= wx.TE_READONLY - wx.TextCtrl.__init__(self, parent, -1, value, style=style) + wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style) self._xmlui_proportion = 1 class TextBoxWidget(EventWidget, ValueWidget, xmlui.TextBoxWidget, wx.TextCtrl): _xmlui_change_event = wx.EVT_TEXT - def __init__(self, parent, value, read_only=False): + def __init__(self, _xmlui_parent, value, read_only=False): style = wx.TE_MULTILINE if read_only: style |= wx.TE_READONLY - wx.TextCtrl.__init__(self, parent, -1, value, style=style) + wx.TextCtrl.__init__(self, _xmlui_parent, -1, value, style=style) self._xmlui_proportion = 1 class BoolWidget(EventWidget, ValueWidget, xmlui.BoolWidget, wx.CheckBox): _xmlui_change_event = wx.EVT_CHECKBOX - def __init__(self, parent, state, read_only=False): + def __init__(self, _xmlui_parent, state, read_only=False): style = wx.CHK_2STATE if read_only: style |= wx.TE_READONLY - wx.CheckBox.__init__(self, parent, -1, "", style=wx.CHK_2STATE) + wx.CheckBox.__init__(self, _xmlui_parent, -1, "", style=wx.CHK_2STATE) self.SetValue(state) self._xmlui_proportion = 1 @@ -131,22 +130,22 @@ class ButtonWidget(EventWidget, WixWidget, xmlui.ButtonWidget, wx.Button): _xmlui_change_event = wx.EVT_BUTTON - def __init__(self, parent, value, click_callback): - wx.Button.__init__(self, parent, -1, value) + def __init__(self, _xmlui_parent, value, click_callback): + wx.Button.__init__(self, _xmlui_parent, -1, value) self._xmlui_click_callback = click_callback - parent.Bind(wx.EVT_BUTTON, lambda evt: click_callback(evt.GetEventObject()), self) - self.parent = parent + _xmlui_parent.Bind(wx.EVT_BUTTON, lambda evt: click_callback(evt.GetEventObject()), self) + self._xmlui_parent = _xmlui_parent def _xmluiOnClick(self, callback): - self.parent.Bind(wx.EVT_BUTTON, lambda evt: callback(evt.GetEventObject()), self) + self._xmlui_parent.Bind(wx.EVT_BUTTON, lambda evt: callback(evt.GetEventObject()), self) class ListWidget(EventWidget, WixWidget, xmlui.ListWidget, wx.ListBox): _xmlui_change_event = wx.EVT_LISTBOX - def __init__(self, parent, options, selected, flags): + def __init__(self, _xmlui_parent, options, selected, flags): styles = wx.LB_MULTIPLE if not 'single' in flags else wx.LB_SINGLE - wx.ListBox.__init__(self, parent, -1, choices=[option[1] for option in options], style=styles) + wx.ListBox.__init__(self, _xmlui_parent, -1, choices=[option[1] for option in options], style=styles) self._xmlui_attr_map = {label: value for value, label in options} self._xmlui_proportion = 1 self._xmluiSelectValues(selected) @@ -192,8 +191,8 @@ class AdvancedListContainer(WixContainer, xmlui.AdvancedListContainer, wx.ScrolledWindow): - def __init__(self, parent, columns, selectable='no'): - wx.ScrolledWindow.__init__(self, parent) + def __init__(self, _xmlui_parent, columns, selectable='no'): + wx.ScrolledWindow.__init__(self, _xmlui_parent) self._xmlui_selectable = selectable != 'no' if selectable: columns += 1 @@ -230,8 +229,8 @@ class PairsContainer(WixContainer, xmlui.PairsContainer, wx.Panel): - def __init__(self, parent): - wx.Panel.__init__(self, parent) + def __init__(self, _xmlui_parent): + wx.Panel.__init__(self, _xmlui_parent) self.sizer = wx.FlexGridSizer(cols=2) self.sizer.AddGrowableCol(1) #The growable column need most of time to be the right one in pairs self.SetSizer(self.sizer) @@ -239,8 +238,8 @@ class TabsContainer(WixContainer, xmlui.TabsContainer, wx.Notebook): - def __init__(self, parent): - wx.Notebook.__init__(self, parent, -1, style=wx.NB_LEFT if self._xmlui_main.type=='param' else 0) + def __init__(self, _xmlui_parent): + wx.Notebook.__init__(self, _xmlui_parent, -1, style=wx.NB_LEFT if self._xmlui_main.type=='param' else 0) def _xmluiAddTab(self, label): tab_panel = wx.Panel(self, -1) @@ -253,30 +252,109 @@ class VerticalContainer(WixContainer, xmlui.VerticalContainer, wx.Panel): - def __init__(self, parent): - wx.Panel.__init__(self, parent) + def __init__(self, _xmlui_parent): + wx.Panel.__init__(self, _xmlui_parent) self.sizer = wx.BoxSizer(wx.VERTICAL) self.SetSizer(self.sizer) -class WidgetFactory(object): +## Dialogs ## + + +class WixDialog(object): + + def __init__(self, _xmlui_parent, level): + self.host = _xmlui_parent.host + self.ok_cb = None + self.cancel_cb = None + if level == C.XMLUI_DATA_LVL_INFO: + self.flags = wx.ICON_INFORMATION + elif level == C.XMLUI_DATA_LVL_ERROR: + self.flags = wx.ICON_ERROR + else: + self.flags = wx.ICON_INFORMATION + log.warning(_("Unmanaged dialog level: %s") % level) + + def _xmluiShow(self): + answer = self.ShowModal() + if answer == wx.ID_YES or answer == wx.ID_OK: + self._xmluiValidated() + else: + self._xmluiCancelled() + + def _xmluiClose(self): + self.Destroy() + + +class MessageDialog(WixDialog, xmlui.MessageDialog, wx.MessageDialog): + + def __init__(self, _xmlui_parent, title, message, level): + WixDialog.__init__(self, _xmlui_parent, level) + xmlui.MessageDialog.__init__(self, _xmlui_parent) + self.flags |= wx.OK + wx.MessageDialog.__init__(self, _xmlui_parent.host, message, title, style = self.flags) + + +class NoteDialog(xmlui.NoteDialog, MessageDialog): + # TODO: separate NoteDialog + pass + + +class ConfirmDialog(WixDialog, xmlui.ConfirmDialog, wx.MessageDialog): + + def __init__(self, _xmlui_parent, title, message, level, buttons_set): + WixDialog.__init__(self, _xmlui_parent, level) + xmlui.ConfirmDialog.__init__(self, _xmlui_parent) + if buttons_set == C.XMLUI_DATA_BTNS_SET_YESNO: + self.flags |= wx.YES_NO + else: + self.flags |= wx.OK | wx.CANCEL + wx.MessageDialog.__init__(self, _xmlui_parent.host, message, title, style = self.flags) + + +class FileDialog(WixDialog, xmlui.FileDialog, wx.FileDialog): + + def __init__(self, _xmlui_parent, title, message, level, filetype): + # TODO: message and filetype are not managed yet + WixDialog.__init__(self, _xmlui_parent, level) + self.flags = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT # FIXME: use the legacy flags, but must manage cases like dir or open + xmlui.FileDialog.__init__(self, _xmlui_parent) + wx.FileDialog.__init__(self, _xmlui_parent.host, title, style = self.flags) + + def _xmluiShow(self): + answer = self.ShowModal() + if answer == wx.ID_OK: + self._xmluiValidated({'path': self.GetPath()}) + else: + self._xmluiCancelled() + + +class GenericFactory(object): def __getattr__(self, attr): if attr.startswith("create"): cls = globals()[attr[6:]] + return cls + + +class WidgetFactory(GenericFactory): + + def __getattr__(self, attr): + if attr.startswith("create"): + cls = GenericFactory.__getattr__(self, attr) cls._xmlui_main = self._xmlui_main return cls -class XMLUI(xmlui.XMLUI, wx.Frame): +class XMLUIPanel(xmlui.XMLUIPanel, wx.Frame): """Create an user interface from a SàT XML""" widget_factory = WidgetFactory() - def __init__(self, host, xml_data, title=None, flags = None,): + def __init__(self, host, parsed_xml, title=None, flags = None,): self.widget_factory._xmlui_main = self - xmlui.XMLUI.__init__(self, host, xml_data, title, flags) + xmlui.XMLUIPanel.__init__(self, host, parsed_xml, title, flags) - def constructUI(self, xml_data): + def constructUI(self, parsed_dom): style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in self.flags else wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, None, style=style) self.sizer = wx.BoxSizer(wx.VERTICAL) @@ -302,7 +380,7 @@ self.sizer.Fit(self) self.Show() - super(XMLUI, self).constructUI(xml_data, postTreat) + super(XMLUIPanel, self).constructUI(parsed_dom, postTreat) if not 'NO_CANCEL' in self.flags: self.Bind(wx.EVT_CLOSE, self.onClose, self) self.MakeModal() @@ -314,12 +392,12 @@ ###events def onParamChange(self, ctrl): - super(XMLUI, self).onParamChange(ctrl) + super(XMLUIPanel, self).onParamChange(ctrl) def onFormSubmitted(self, event): """Called when submit button is clicked""" button = event.GetEventObject() - super(XMLUI, self).onFormSubmitted(button) + super(XMLUIPanel, self).onFormSubmitted(button) def onClose(self, event): """Close event: we have to send the form.""" @@ -330,3 +408,11 @@ self._xmluiClose() event.Skip() + +class XMLUIDialog(xmlui.XMLUIDialog): + dialog_factory = WidgetFactory() + + +xmlui.registerClass(xmlui.CLASS_PANEL, XMLUIPanel) +xmlui.registerClass(xmlui.CLASS_DIALOG, XMLUIDialog) +create = xmlui.create