core, frontends: XMLUI refactoring:

- now there is a base XMLUI class. Frontends inherits from this class, and add their specific widgets/containers/behaviour
    - wix: param.py has been removed, as the same behaviour has been reimplemented through XMLUI
    - removed "misc" argument in XMLUI.__init__
    - severals things are broken (gateway, directory search), following patches will fix them
    - core: elements names in xml_tools.dataForm2XMLUI now use a construction with category_name/param_name to avoid name conflicts (could happen with 2 parameters of the same name in differents categories).
This commit is contained in:
Goffi 2014-02-04 18:02:35 +01:00
commit 256a110471
8 changed files with 757 additions and 602 deletions

View file

@ -60,3 +60,7 @@ class Const(object):
SYNTAX_CURRENT = "@CURRENT@"
NO_SECURITY_LIMIT = -1
#XMLUI
SAT_FORM_PREFIX = "SAT_FORM_"
SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names

View file

@ -22,26 +22,96 @@ import urwid
from urwid_satext import sat_widgets
from logging import debug, info, warning, error
from xml.dom import minidom
from sat_frontends.tools import xmlui
SAT_FORM_PREFIX = "SAT_FORM_"
class PrimitivusEvents(object):
""" Used to manage change event of Primitivus widgets """
def getText(node):
"""Get child text nodes
@param node: dom Node
@return: joined unicode text of all nodes
def _change_callback(self, ctrl, *args, **kwargs):
"""" Call xmlui callback and ignore any extra argument """
args[-1](ctrl)
"""
data = []
for child in node.childNodes:
if child.nodeType == child.TEXT_NODE:
data.append(child.wholeText)
return u"".join(data)
def _xmluiOnChange(self, callback):
""" Call callback with widget as only argument """
urwid.connect_signal(self, 'change', self._change_callback, callback)
class Pairs(urwid.WidgetWrap):
class PrimitivusEmptyWidget(xmlui.EmptyWidget, urwid.Text):
def __init__(self, weight_0='1', weight_1='1'):
def __init__(self, parent):
urwid.Text.__init__(self, '')
class PrimitivusTextWidget(xmlui.TextWidget, urwid.Text):
def __init__(self, parent, value):
urwid.Text.__init__(self, value)
class PrimitivusStringWidget(xmlui.StringWidget, sat_widgets.AdvancedEdit, PrimitivusEvents):
def __init__(self, parent, value):
sat_widgets.AdvancedEdit.__init__(self, edit_text=value)
def _xmluiGetValue(self):
return self.get_edit_text()
class PrimitivusPasswordWidget(xmlui.PasswordWidget, sat_widgets.Password, PrimitivusEvents):
def __init__(self, parent, value):
sat_widgets.Password.__init__(self, edit_text=value)
def _xmluiGetValue(self):
return self.get_edit_text()
class PrimitivusTextBoxWidget(xmlui.TextBoxWidget, sat_widgets.AdvancedEdit, PrimitivusEvents):
def __init__(self, parent, value):
sat_widgets.AdvancedEdit.__init__(self, edit_text=value, multiline=True)
def _xmluiGetValue(self):
return self.getValue()
class PrimitivusBoolWidget(xmlui.BoolWidget, urwid.CheckBox, PrimitivusEvents):
def __init__(self, parent, state):
urwid.CheckBox.__init__(self, '', state = state)
def _xmluiGetValue(self):
return "true" if self.get_state() else "false"
class PrimitivusButtonWidget(xmlui.ButtonWidget, sat_widgets.CustomButton, PrimitivusEvents):
def __init__(self, parent, value, click_callback):
sat_widgets.CustomButton.__init__(self, value, on_press=click_callback)
class PrimitivusListWidget(xmlui.ListWidget, sat_widgets.List, PrimitivusEvents):
def __init__(self, parent, options, flags):
sat_widgets.List.__init__(self, options=options, style=flags)
def _xmluiSelectValue(self, value):
return self.selectValue(value)
def _xmluiGetSelectedValues(self):
return [option.value for option in self.getSelectedValues()]
class PrimitivusAdvancedListWidget(PrimitivusListWidget, PrimitivusEvents):
def __init__(self, parent, options, flags):
sat_widgets.List.__init__(self, options=options, style=flags, max_height=20)
class PrimitivusPairsContainer(xmlui.PairsContainer, urwid.WidgetWrap):
def __init__(self, parent, weight_0='1', weight_1='1'):
self.idx = 0
self.weight_0 = weight_0
self.weight_1 = weight_1
@ -49,7 +119,7 @@ class Pairs(urwid.WidgetWrap):
#XXX: empty Text hack needed because Pile doesn't support empty list
urwid.WidgetWrap.__init__(self,columns)
def append(self, widget):
def _xmluiAppend(self, widget):
pile = self._w.widget_list[self.idx]
if isinstance(pile, urwid.Text):
self._w.widget_list[self.idx] = urwid.Pile([widget])
@ -59,162 +129,74 @@ class Pairs(urwid.WidgetWrap):
pile.contents.append((widget,('weight',getattr(self,'weight_'+str(self.idx)))))
self.idx = (self.idx + 1) % 2
class InvalidXMLUI(Exception):
pass
class XMLUI(urwid.WidgetWrap):
class PrimitivusTabsContainer(xmlui.TabsContainer, sat_widgets.TabsContainer):
def __init__(self, host, xml_data, title = None, options = None, misc = None):
self.host = host
self.title = title
self.options = options or []
self.misc = misc or {}
self.__dest = "window"
self.ctrl_list = {} # usefull to access ctrl
widget = self.constructUI(xml_data)
urwid.WidgetWrap.__init__(self,widget)
def __init__(self, parent):
sat_widgets.TabsContainer.__init__(self)
def __parseElems(self, node, parent):
"""Parse elements inside a <layout> tags, and add them to the parent"""
for elem in node.childNodes:
if elem.nodeName != "elem":
message=_("Unmanaged tag")
error(message)
raise Exception(message)
id_ = elem.getAttribute("id")
name = elem.getAttribute("name")
type_ = elem.getAttribute("type")
value = elem.getAttribute("value") if elem.hasAttribute('value') else u''
if type_=="empty":
ctrl = urwid.Text('')
elif type_=="text":
try:
value = elem.childNodes[0].wholeText
except IndexError:
warning (_("text node has no child !"))
ctrl = urwid.Text(value)
elif type_=="label":
ctrl = urwid.Text(value+": ")
elif type_=="string":
ctrl = sat_widgets.AdvancedEdit(edit_text = value)
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="password":
ctrl = sat_widgets.Password(edit_text = value)
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="textbox":
ctrl = sat_widgets.AdvancedEdit(edit_text = value, multiline=True)
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="bool":
ctrl = urwid.CheckBox('', state = value=="true")
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="list":
style=[] if elem.getAttribute("multi")=='yes' else ['single']
ctrl = sat_widgets.List(options=[(option.getAttribute("value"), option.getAttribute("label")) for option in elem.getElementsByTagName("option")], style=style, on_change=self.onParamChange if self.type == "param" else None)
ctrl.selectValue(elem.getAttribute("value"))
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="button":
callback_id = elem.getAttribute("callback")
ctrl = sat_widgets.CustomButton(value, on_press=self.onButtonPress)
ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
elif type_=="advanced_list":
ctrl = sat_widgets.List(options=[getText(txt_elt) for txt_elt in elem.getElementsByTagName("text")], style=['can_select_none'], max_height=20, on_change=self.onParamChange)
ctrl.selectValue(elem.getAttribute("value"))
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
else:
error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_) #FIXME !
raise NotImplementedError
if self.type == 'param':
if isinstance(ctrl, urwid.Edit) or isinstance(ctrl, urwid.CheckBox):
urwid.connect_signal(ctrl,'change',self.onParamChange)
elif isinstance(ctrl, sat_widgets.List):
# the GenericList member triggers the events, not List itself
# TODO: create a method GenericList.setParamData() for that,
# or later in onSaveParams do something like ctrl.getParent()
ctrl.genericList._param_category = self._current_category
ctrl.genericList._param_name = name
ctrl._param_category = self._current_category
ctrl._param_name = name
parent.append(ctrl)
def _xmluiAppend(self, widget):
self.body.append(widget)
def __parseChilds(self, current, elem, wanted = ['layout'], data = None):
"""Recursively parse childNodes of an elemen
@param current: widget container with 'append' method
@param elem: 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"""
for node in elem.childNodes:
if wanted and not node.nodeName in wanted:
raise InvalidXMLUI
if node.nodeName == "layout":
type = node.getAttribute('type')
if type == "tabs":
tab_cont = sat_widgets.TabsContainer()
self.__parseChilds(current, node, ['category'], tab_cont)
current.append(tab_cont)
elif type == "vertical":
self.__parseElems(node, current)
elif type == "pairs":
pairs = Pairs()
self.__parseElems(node, pairs)
current.append(pairs)
else:
warning(_("Unknown layout, using default one"))
self.__parseElems(node, current)
elif node.nodeName == "category":
name = node.getAttribute('name')
label = node.getAttribute('label')
if not name or not isinstance(data,sat_widgets.TabsContainer):
raise InvalidXMLUI
if self.type == 'param':
self._current_category = name #XXX: awful hack because params need category and we don't keep parent
tab_cont = data
listbox = tab_cont.addTab(label or name)
self.__parseChilds(listbox.body, node, ['layout'])
else:
message=_("Unknown tag")
error(message)
raise NotImplementedError
def _xmluiAddTab(self, label):
list_box = super(PrimitivusTabsContainer, self).addTab(label)
if hasattr(PrimitivusVerticalContainer, "_PrimitivusVerticalContainer__super"): # workaround for Urwid's metaclass baviour
del PrimitivusVerticalContainer._PrimitivusVerticalContainer__super
PrimitivusVerticalContainer._xmluiAdapt(list_box)
return list_box
class PrimitivusVerticalContainer(xmlui.VerticalContainer, urwid.ListBox):
def __init__(self, parent):
urwid.ListBox.__init__(self, urwid.SimpleListWalker([]))
def _xmluiAppend(self, widget):
self.body.append(widget)
class WidgetFactory(object):
def __getattr__(self, attr):
if attr.startswith("create"):
return 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
class XMLUI(xmlui.XMLUI, urwid.WidgetWrap):
widget_factory = WidgetFactory()
def __init__(self, host, xml_data, title = None, flags = None):
self._dest = "window"
xmlui.XMLUI.__init__(self, host, xml_data, title, flags)
def constructUI(self, xml_data):
def postTreat(ret_wid):
assert ret_wid.body
ret_wid = urwid.ListBox(urwid.SimpleListWalker([]))
if isinstance(ret_wid.body[0],sat_widgets.TabsContainer):
ret_wid = ret_wid.body[0] #xxx: awfull hack cause TabsContainer is a BoxWidget, can't be inside a ListBox
cat_dom = minidom.parseString(xml_data.encode('utf-8'))
top=cat_dom.documentElement
self.type = top.getAttribute("type")
self.title = top.getAttribute("title") or self.title
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']:
raise InvalidXMLUI
if self.type == 'form':
buttons = []
buttons.append(urwid.Button(_('Submit'),self.onFormSubmitted))
if not 'NO_CANCEL' in self.flags:
buttons.append(urwid.Button(_('Cancel'),self.onFormCancelled))
max_len = max([len(button.get_label()) for button in buttons])
grid_wid = urwid.GridFlow(buttons,max_len+4,1,0,'center')
ret_wid.body.append(grid_wid)
elif self.type == 'param':
assert(isinstance(ret_wid,sat_widgets.TabsContainer))
buttons = []
buttons.append(sat_widgets.CustomButton(_('Save'),self.onSaveParams))
buttons.append(sat_widgets.CustomButton(_('Cancel'),lambda x:self.host.removeWindow()))
max_len = max([button.getSize() for button in buttons])
grid_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
ret_wid.addFooter(grid_wid)
return ret_wid
if self.type == 'param':
self.param_changed = set()
self.__parseChilds(ret_wid.body, cat_dom.documentElement)
assert ret_wid.body
if isinstance(ret_wid.body[0],sat_widgets.TabsContainer):
ret_wid = ret_wid.body[0] #xxx: awfull hack cause TabsContainer is a BoxWidget, can't be inside a ListBox
if self.type == 'form':
buttons = []
buttons.append(urwid.Button(_('Submit'),self.onFormSubmitted))
if not 'NO_CANCEL' in self.options:
buttons.append(urwid.Button(_('Cancel'),self.onFormCancelled))
max_len = max([len(button.get_label()) for button in buttons])
grid_wid = urwid.GridFlow(buttons,max_len+4,1,0,'center')
ret_wid.body.append(grid_wid)
elif self.type == 'param':
assert(isinstance(ret_wid,sat_widgets.TabsContainer))
buttons = []
buttons.append(sat_widgets.CustomButton(_('Save'),self.onSaveParams))
buttons.append(sat_widgets.CustomButton(_('Cancel'),lambda x:self.host.removeWindow()))
max_len = max([button.getSize() for button in buttons])
grid_wid = urwid.GridFlow(buttons,max_len,1,0,'center')
ret_wid.addFooter(grid_wid)
return ret_wid
widget = super(XMLUI, self).constructUI(xml_data, postTreat)
urwid.WidgetWrap.__init__(self, widget)
def show(self, show_type='popup', valign='middle'):
"""Show the constructed UI
@ -223,8 +205,9 @@ class XMLUI(urwid.WidgetWrap):
- window
@param valign: vertical alignment when show_type is 'popup'.
Ignored when show_type is 'window'.
"""
self.__dest = "popup"
self._dest = "popup"
decorated = sat_widgets.LabelLine(self, sat_widgets.SurroundedText(self.title or ''))
if show_type == 'popup':
self.host.showPopUp(decorated, valign=valign)
@ -236,67 +219,8 @@ class XMLUI(urwid.WidgetWrap):
self.host.redraw()
##EVENTS##
def onButtonPress(self, button):
callback_id, fields = button.param_id
data = {}
for field in fields:
ctrl = self.ctrl_list[field]
if isinstance(ctrl['control'],sat_widgets.List):
data[field] = u'\t'.join(ctrl['control'].getSelectedValues())
else:
data[field] = ctrl['control'].getValue()
self.host.launchAction(callback_id, data, profile_key = self.host.profile)
def onParamChange(self, widget, extra=None):
"""Called when type is param and a widget to save is modified"""
assert(self.type == "param")
self.param_changed.add(widget)
def onFormSubmitted(self, button):
selected_values = []
for ctrl_name in self.ctrl_list:
escaped = u"%s%s" % (SAT_FORM_PREFIX, ctrl_name)
ctrl = self.ctrl_list[ctrl_name]
if isinstance(ctrl['control'], sat_widgets.List):
selected_values.append((escaped, u'\t'.join([option.value for option in ctrl['control'].getSelectedValues()])))
elif isinstance(ctrl['control'], urwid.CheckBox):
selected_values.append((escaped, "true" if ctrl['control'].get_state() else "false"))
else:
selected_values.append((escaped, ctrl['control'].get_edit_text()))
if self.misc.has_key('action_back'): #FIXME FIXME FIXME: WTF ! Must be cleaned
raise NotImplementedError
elif 'callback' in self.misc: # FIXME: this part is not needed anymore
try:
self.misc['callback'](selected_values, submit_id=self.submit_id, *self.misc['callback_args'])
except KeyError:
self.misc['callback'](selected_values, submit_id=self.submit_id)
elif self.submit_id is not None:
data = dict(selected_values)
if self.session_id is not None:
data["session_id"] = self.session_id
self.host.launchAction(self.submit_id, data, profile_key=self.host.profile)
else:
warning (_("The form data is not sent back, the type is not managed properly"))
self.host.removePopUp()
def onFormCancelled(self, button):
if self.__dest == 'window':
def _xmluiClose(self):
if self._dest == 'window':
self.host.removeWindow()
else:
self.host.removePopUp()
def onSaveParams(self, button):
for ctrl in self.param_changed:
if isinstance(ctrl, urwid.CheckBox):
value = "true" if ctrl.get_state() else "false"
elif isinstance(ctrl, sat_widgets.GenericList):
value = u'\t'.join(ctrl.getSelectedValues())
else:
value = ctrl.get_edit_text()
self.host.bridge.setParam(ctrl._param_name, value, ctrl._param_category,
profile_key=self.host.profile)
self.host.removeWindow()

View file

@ -0,0 +1,48 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# SAT: a jabber client
# Copyright (C) 2009, 2010, 2011, 2012, 2013 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/>.
"""This library help manage XML used in SàT frontends """
# we don't import minidom as a different class can be used in frontends
# (e.g. NativeDOM in Libervia)
def inlineRoot(doc):
""" make the root attribute inline
@param root_node: minidom's Document compatible class
@return: plain XML
"""
root_elt = doc.documentElement
if root_elt.hasAttribute('style'):
styles_raw = root_elt.getAttribute('style')
styles = styles_raw.split(';')
new_styles = []
for style in styles:
try:
key, value = style.split(':')
except ValueError:
continue
if key.strip().lower() == 'display':
value = 'inline'
new_styles.append('%s: %s' % (key.strip(), value.strip()))
root_elt.setAttribute('style', "; ".join(new_styles))
else:
root_elt.setAttribute('style', 'display: inline')
return root_elt.toxml()

View file

@ -0,0 +1,374 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# SàT frontend tools
# Copyright (C) 2009, 2010, 2011, 2012, 2013 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.i18n import _
from sat_frontends.constants import Const
from logging import debug, info, warning, error
class InvalidXMLUI(Exception):
pass
def getText(node):
"""Get child text nodes
@param node: dom Node
@return: joined unicode text of all nodes
"""
data = []
for child in node.childNodes:
if child.nodeType == child.TEXT_NODE:
data.append(child.wholeText)
return u"".join(data)
class Widget(object):
""" base Widget """
pass
class EmptyWidget(Widget):
""" Just a placeholder widget """
pass
class TextWidget(Widget):
""" Non interactive text """
pass
class StringWidget(Widget):
""" Input widget with require a string
often called Edit in toolkits
"""
class PasswordWidget(Widget):
""" Input widget with require a masked string
"""
class TextBoxWidget(Widget):
""" Input widget with require a long, possibly multilines string
often called TextArea in toolkits
"""
class BoolWidget(Widget):
""" Input widget with require a boolean value
often called CheckBox in toolkits
"""
class ButtonWidget(Widget):
""" A clickable widget """
class ListWidget(Widget):
""" A widget able to show/choose one or several strings in a list """
class AdvancedListWidget(Widget):
pass #TODO
class Container(Widget):
""" Widget which can contain other ones with a specific layout """
@classmethod
def _xmluiAdapt(cls, instance):
""" 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
class TabsContainer(Container):
""" A container which several other containers in tabs
Often called Notebook in toolkits
"""
class VerticalContainer(Container):
""" Widgets are disposed vertically """
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
"""
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
@param host: %(doc_host)s
@param xml_data: the raw XML containing the UI
@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 ""
if flags is None:
flags = []
self.flags = flags
self.ctrl_list = {} # usefull to access ctrl
self.constructUI(xml_data)
def _parseElems(self, node, parent, post_treat=None):
""" Parse elements inside a <layout> tags, and add them to the parent
@param node: current XMLUI node
@param parent: parent container
@param post_treat: frontend specific treatments do to on each element
"""
for elem in node.childNodes:
if elem.nodeName != "elem":
raise NotImplementedError(_('Unknown tag [%s]') % elem.nodeName)
id_ = elem.getAttribute("id")
name = elem.getAttribute("name")
type_ = elem.getAttribute("type")
value = elem.getAttribute("value") if elem.hasAttribute('value') else u''
if type_=="empty":
ctrl = self.widget_factory.createEmptyWidget(parent)
elif type_=="text":
try:
value = elem.childNodes[0].wholeText
except IndexError:
warning (_("text node has no child !"))
ctrl = self.widget_factory.createTextWidget(parent, value)
elif type_=="label":
ctrl = self.widget_factory.createTextWidget(parent, value+": ")
elif type_=="string":
ctrl = self.widget_factory.createStringWidget(parent, value)
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="password":
ctrl = self.widget_factory.createPasswordWidget(parent, value)
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="textbox":
ctrl = self.widget_factory.createTextBoxWidget(parent, value)
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="bool":
ctrl = self.widget_factory.createBoolWidget(parent, value=='true')
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="list":
style=[] if elem.getAttribute("multi")=='yes' else ['single']
_options = [(option.getAttribute("value"), option.getAttribute("label")) for option in elem.getElementsByTagName("option")]
ctrl = self.widget_factory.createListWidget(parent, _options, style)
ctrl._xmluiSelectValue(elem.getAttribute("value"))
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
elif type_=="button":
callback_id = elem.getAttribute("callback")
ctrl = self.widget_factory.createButtonWidget(parent, value, self.onButtonPress)
ctrl._xmlui_param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
elif type_=="advanced_list":
_options = [getText(txt_elt) for txt_elt in elem.getElementsByTagName("text")]
ctrl = self.widget_factory.createListWidget(parent, _options, ['can_select_none'])
ctrl._xmluiSelectValue(elem.getAttribute("value"))
self.ctrl_list[name] = ({'type':type_, 'control':ctrl})
else:
error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
raise NotImplementedError(_("FIXME FIXME FIXME: type [%s] is not implemented") % type_)
if self.type == 'param':
try:
ctrl._xmluiOnChange(self.onParamChange)
ctrl._param_category = self._current_category
ctrl._param_name = name.split(Const.SAT_PARAM_SEPARATOR)[1]
except AttributeError:
if not isinstance(ctrl, (EmptyWidget, TextWidget)):
warning(_("No change listener on [%s]" % ctrl))
if post_treat is not None:
post_treat(ctrl, id_, name, type_, value)
parent._xmluiAppend(ctrl)
def _parseChilds(self, current, elem, wanted = ['layout'], data = None):
""" Recursively parse childNodes of an elemen
@param current: widget container with '_xmluiAppend' method
@param elem: 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 elem.childNodes:
if wanted and not node.nodeName in wanted:
raise InvalidXMLUI
if node.nodeName == "layout":
type_ = node.getAttribute('type')
if type_ == "tabs":
tab_cont = self.widget_factory.createTabsContainer(current)
self._parseChilds(current, node, ['category'], tab_cont)
current._xmluiAppend(tab_cont)
elif type_ == "vertical":
self._parseElems(node, current)
elif type_ == "pairs":
pairs = self.widget_factory.createPairsContainer(current)
self._parseElems(node, pairs)
current._xmluiAppend(pairs)
else:
warning(_("Unknown layout [%s], using default one") % type_)
self._parseElems(node, current)
elif node.nodeName == "category":
name = node.getAttribute('name')
label = node.getAttribute('label')
if not name or not isinstance(data, TabsContainer):
raise InvalidXMLUI
if self.type == 'param':
self._current_category = name #XXX: awful hack because params need category and we don't keep parent
tab_cont = data
new_tab = tab_cont._xmluiAddTab(label or name)
self._parseChilds(new_tab, node, ['layout'])
else:
raise NotImplementedError(_('Unknown tag'))
def constructUI(self, xml_data, post_treat=None):
""" Actually construct the UI
@param xml_data: raw XMLUI
@param post_treat: frontend specific treatments to do once the UI is constructed
@return: constructed widget
"""
ret_wid = self.widget_factory.createVerticalContainer(self)
cat_dom = self.dom_parse(xml_data)
top=cat_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']:
raise InvalidXMLUI
if self.type == 'param':
self.param_changed = set()
self._parseChilds(ret_wid, cat_dom.documentElement)
if post_treat is not None:
ret_wid = post_treat(ret_wid)
self.dom_free(cat_dom)
return ret_wid
def _xmluiClose(self):
""" Close the window/popup/... where the constructeur XMLUI is
this method must be overrided
"""
raise NotImplementedError
##EVENTS##
def onParamChange(self, ctrl):
""" 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)
def onButtonPress(self, button):
""" 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
data = {}
for field in fields:
ctrl = self.ctrl_list[field]
if isinstance(ctrl['control'], ListWidget):
data[field] = u'\t'.join(ctrl['control']._xmluiGetSelected())
else:
data[field] = ctrl['control']._xmluiGetValue()
self.host.launchAction(callback_id, data, profile_key = self.host.profile)
def onFormSubmitted(self, ignore=None):
""" An XMLUI form has been submited
call the submit action associated with this form
"""
selected_values = []
for ctrl_name in self.ctrl_list:
escaped = u"%s%s" % (Const.SAT_FORM_PREFIX, ctrl_name)
ctrl = self.ctrl_list[ctrl_name]
if isinstance(ctrl['control'], ListWidget):
selected_values.append((escaped, u'\t'.join(ctrl['control']._xmluiGetSelectedValues())))
else:
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.host.launchAction(self.submit_id, data, profile_key=self.host.profile)
else:
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 """
debug(_("Cancelling form"))
self._xmluiClose()
def onSaveParams(self, ignore=None):
""" Params are saved, we send them to backend
self.type must be param
"""
assert(self.type == 'param')
for ctrl in self.param_changed:
if isinstance(ctrl, ListWidget):
value = u'\t'.join(ctrl._xmluiGetSelectedValues())
else:
value = ctrl._xmluiGetValue()
self.host.bridge.setParam(ctrl._param_name, value, ctrl._param_category,
profile_key=self.host.profile)
self._xmluiClose()

View file

@ -24,7 +24,6 @@ from sat_frontends.quick_frontend.quick_app import QuickApp
import wx
from sat_frontends.wix.contact_list import ContactList
from sat_frontends.wix.chat import Chat
from sat_frontends.wix.param import Param
from sat_frontends.wix.xmlui import XMLUI
from sat_frontends.wix.gateways import GatewaysManager
from sat_frontends.wix.profile import Profile
@ -56,7 +55,7 @@ class MainWindow(wx.Frame, QuickApp):
def __init__(self):
QuickApp.__init__(self)
wx.Frame.__init__(self,None, title="SàT Wix", size=(300,500))
wx.Frame.__init__(self,None, title="SàT Wix", size=(350,500))
#sizer
self.sizer = wx.BoxSizer(wx.VERTICAL)
@ -407,7 +406,17 @@ class MainWindow(wx.Frame, QuickApp):
def onParam(self, e):
debug(_("Param request"))
param = Param(self)
def success(params):
XMLUI(self, xml_data=params, title=_("Configuration"))
def failure(error):
dlg = wx.MessageDialog(self, unicode(error),
_('Error'),
wx.OK | wx.ICON_ERROR
)
dlg.ShowModal()
dlg.Destroy()
self.bridge.getParamsUI(app=Const.APP_NAME, profile_key=self.profile, callback=success, errback=failure)
def onAbout(self, e):
about = wx.AboutDialogInfo()

View file

@ -1,152 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# wix: a SAT frontend
# Copyright (C) 2009, 2010, 2011, 2012, 2013 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.i18n import _
import wx
import pdb
from xml.dom import minidom
from logging import debug, info, error
from sat.tools.jid import JID
from sat_frontends.wix.constants import Const
class Param(wx.Frame):
def __init__(self, host, title=_("Configuration")):
super(Param, self).__init__(None, title=title)
self.host = host
self.modified = {} # dict of modified data (i.e. what we have to save)
self.ctl_list = {} # usefull to access ctrl, key = (name, category)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.notebook=wx.Notebook(self, -1, style=wx.NB_LEFT)
self.sizer.Add(self.notebook, 1, flag=wx.EXPAND)
self.SetSizer(self.sizer)
self.SetAutoLayout(True)
#events
self.Bind(wx.EVT_CLOSE, self.onClose, self)
self.MakeModal()
for category in self.host.bridge.getParamsCategories():
self.addCategory(category)
self.Show()
def addCategory(self, category):
panel=wx.Panel(self.notebook)
panel.sizer = wx.BoxSizer(wx.VERTICAL)
def errorGettingParams(ignore):
wx.MessageDialog(self, _("Can't get parameters"), _("Parameters error"), wx.ICON_ERROR).ShowModal()
def gotParams(result):
cat_dom = minidom.parseString(result.encode('utf-8'))
for param in cat_dom.documentElement.getElementsByTagName("param"):
name = param.getAttribute("name")
label = param.getAttribute("label")
type = param.getAttribute("type")
value = param.getAttribute("value")
sizer = wx.BoxSizer(wx.HORIZONTAL)
if type=="string":
label=wx.StaticText(panel, -1, (label or name)+" ")
ctrl = wx.TextCtrl(panel, -1, value)
sizer.Add(label)
elif type=="password":
label=wx.StaticText(panel, -1, (label or name)+" ")
ctrl = wx.TextCtrl(panel, -1, value, style=wx.TE_PASSWORD)
sizer.Add(label)
elif type=="bool":
ctrl = wx.CheckBox(panel, -1, label or name, style = wx.CHK_2STATE)
ctrl.SetValue(value=="true")
elif type=="button":
ctrl = wx.Button(panel, -1, value)
ctrl.callback_id = param.getAttribute("callback_id")
else:
error(_("FIXME FIXME FIXME")) #FIXME !
raise NotImplementedError
if name:
ctrl.param_id=(name, category)
self.ctl_list[(name, category)] = ctrl
sizer.Add(ctrl, 1, flag=wx.EXPAND)
panel.sizer.Add(sizer, flag=wx.EXPAND)
if type=="string" or type=="password":
panel.Bind(wx.EVT_TEXT, self.onTextChanged, ctrl)
elif type=="bool":
panel.Bind(wx.EVT_CHECKBOX, self.onCheckBoxClicked, ctrl)
elif type=="button":
panel.Bind(wx.EVT_BUTTON, self.onButtonClicked, ctrl)
panel.SetSizer(panel.sizer)
panel.SetAutoLayout(True)
self.notebook.AddPage(panel, category)
cat_dom.unlink()
self.host.bridge.getParamsForCategory(category,
app=Const.APP_NAME,
profile_key=self.host.profile,
callback=gotParams,
errback=errorGettingParams)
def onTextChanged(self, event):
"""Called when a string paramater is modified"""
self.modified[event.GetEventObject().param_id]=event.GetString()
### FIXME # Some hacks for better presentation, should be generic # FIXME ###
if event.GetEventObject().param_id == ('JabberID', 'Connection'):
domain = JID(event.GetString()).domain
self.ctl_list[('Server', 'Connection')].SetValue(domain)
self.modified[('Server', 'Connection')] = domain
event.Skip()
def onCheckBoxClicked(self, event):
"""Called when a bool paramater is modified"""
self.modified[event.GetEventObject().param_id]="true" if event.GetEventObject().GetValue() else "false"
event.Skip()
def onButtonClicked(self, event):
"""Called when a button paramater is modified"""
self.__save_parameters()
name, category = event.GetEventObject().param_id
callback_id = event.GetEventObject().callback_id
self.host.launchAction(callback_id, None, profile_key = self.host.profile)
event.Skip()
def __save_parameters(self):
for param in self.modified:
self.host.bridge.setParam(param[0], self.modified[param], param[1],
profile_key=self.host.profile)
self.modified.clear()
def onClose(self, event):
"""Close event: we have to save the params."""
debug(_("close"))
#now we save the modifier params
self.__save_parameters()
self.MakeModal(False)
event.Skip()

View file

@ -21,239 +21,235 @@
from sat.core.i18n import _
import wx
import pdb
from xml.dom import minidom
from logging import debug, info, warning, error
from sat.tools.jid import JID
SAT_FORM_PREFIX = "SAT_FORM_"
from sat_frontends.tools import xmlui
class XMLUI(wx.Frame):
"""Create an user interface from a SàT xml"""
class EventWidget(object):
""" Used to manage change event of widgets """
def __init__(self, host, xml_data='', title="Form", options = None, misc = None):
if options is None:
options = []
style = wx.DEFAULT_FRAME_STYLE & ~wx.CLOSE_BOX if 'NO_CANCEL' in options else wx.DEFAULT_FRAME_STYLE #FIXME: Q&D tmp hack
super(XMLUI, self).__init__(None, title=title, style=style)
self.host = host
self.options = options
self.misc = misc or {}
self.ctrl_list = {} # usefull to access ctrl
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.sizer)
self.SetAutoLayout(True)
#events
if not 'NO_CANCEL' in self.options:
self.Bind(wx.EVT_CLOSE, self.onClose, self)
self.MakeModal()
self.constructUI(xml_data)
self.Show()
def __parseElems(self, node, parent):
"""Parse elements inside a <layout> tags, and add them to the parent sizer"""
for elem in node.childNodes:
if elem.nodeName != "elem":
message=_("Unmanaged tag")
error(message)
raise Exception(message)
_proportion = 0
id = elem.getAttribute("id")
name = elem.getAttribute("name")
type = elem.getAttribute("type")
value = elem.getAttribute("value") if elem.hasAttribute('value') else u''
if type=="empty":
ctrl = wx.Window(parent, -1)
elif type=="text":
try:
value = elem.childNodes[0].wholeText
except IndexError:
warning (_("text node has no child !"))
ctrl = wx.StaticText(parent, -1, value)
elif type=="label":
ctrl = wx.StaticText(parent, -1, value+": ")
elif type=="string":
ctrl = wx.TextCtrl(parent, -1, value)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
_proportion = 1
elif type=="password":
ctrl = wx.TextCtrl(parent, -1, value, style=wx.TE_PASSWORD)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
_proportion = 1
elif type=="textbox":
ctrl = wx.TextCtrl(parent, -1, value, style=wx.TE_MULTILINE)
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
_proportion = 1
elif type=="bool":
ctrl = wx.CheckBox(panel, -1, "", style = wx.CHK_2STATE)
ctrl.SetValue(value=="true")
self.ctrl_list[name] = ({'type':type, 'control':ctrl})
_proportion = 1
elif type=="list":
style=wx.LB_MULTIPLE if elem.getAttribute("multi")=='yes' else wx.LB_SINGLE
_options = [(option.getAttribute("label"), option.getAttribute("value")) for option in elem.getElementsByTagName("option")]
attr_map = {label: value for label, value in _options}
ctrl = wx.ListBox(parent, -1, choices=[option[0] for option in _options], style=style)
self.ctrl_list[name] = ({'type':type, 'control':ctrl, 'attr_map': attr_map})
_proportion = 1
elif type=="button":
callback_id = elem.getAttribute("callback_id")
ctrl = wx.Button(parent, -1, value)
ctrl.param_id = (callback_id,[field.getAttribute('name') for field in elem.getElementsByTagName("field_back")])
parent.Bind(wx.EVT_BUTTON, self.onButtonClicked, ctrl)
else:
error(_("FIXME FIXME FIXME: type [%s] is not implemented") % type) #FIXME !
raise NotImplementedError
parent.sizer.Add(ctrl, _proportion, flag=wx.EXPAND)
def __parseChilds(self, parent, current_param, elem, wanted = ['layout']):
"""Recursively parse childNodes of an elemen
@param parent: parent wx.Window
@param current_param: current wx.Window (often wx.Panel) or None if we must create one
@param elem: 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"""
for node in elem.childNodes:
if wanted and not node.nodeName in wanted:
raise Exception("Invalid XMLUI") #TODO: make a custom exception
if node.nodeName == "layout":
_proportion = 0
type = node.getAttribute('type')
if type == "tabs":
current = wx.Notebook(parent, -1, style=wx.NB_LEFT if self.type=='param' else 0)
self.__parseChilds(current, None, node, ['category'])
_proportion = 1
else:
if current_param == None:
current = wx.Panel(parent, -1)
else:
current = current_param
if type == "vertical":
current.sizer = wx.BoxSizer(wx.VERTICAL)
elif type == "pairs":
current.sizer = wx.FlexGridSizer(cols=2)
current.sizer.AddGrowableCol(1) #The growable column need most of time to be the right one in pairs
else:
warning(_("Unknown layout, using default one"))
current.sizer = wx.BoxSizer(wx.VERTICAL)
current.SetSizer(current.sizer)
self.__parseElems(node, current)
if parent:
parent.sizer.Add(current, _proportion, flag=wx.EXPAND)
elif node.nodeName == "category":
name = node.getAttribute('name')
label = node.getAttribute('label')
if not node.nodeName in wanted or not name or not isinstance(parent,wx.Notebook):
raise Exception("Invalid XMLUI") #TODO: make a custom exception
notebook = parent
tab_panel = wx.Panel(notebook, -1)
tab_panel.sizer = wx.BoxSizer(wx.VERTICAL)
tab_panel.SetSizer(tab_panel.sizer)
notebook.AddPage(tab_panel, label or name)
self.__parseChilds(tab_panel, None, node, ['layout'])
else:
message=_("Unknown tag")
error(message)
raise Exception(message) #TODO: raise a custom exception here
def _xmluiOnChange(self, callback):
""" Call callback with widget as only argument """
def change_cb(event):
callback(self)
self.Bind(self._xmlui_change_event, change_cb)
def constructUI(self, xml_data):
panel=wx.Panel(self)
panel.sizer = wx.BoxSizer(wx.VERTICAL)
class WixWidget(object):
_xmlui_proportion = 0
cat_dom = minidom.parseString(xml_data.encode('utf-8'))
top= cat_dom.documentElement
self.type = top.getAttribute("type")
self.title = top .getAttribute("title")
self.session_id = top.getAttribute("session_id") or None
self.submit_id = top.getAttribute("submit") or None
if self.title:
self.SetTitle(self.title)
if top.nodeName != "sat_xmlui" or not self.type in ['form', 'param', 'window']:
raise Exception("Invalid XMLUI") #TODO: make a custom exception
self.__parseChilds(panel, None, cat_dom.documentElement)
class ValueWidget(WixWidget):
def _xmluiGetValue(self):
return self.GetValue()
if self.type == 'form':
dialogButtons = wx.StdDialogButtonSizer()
submitButton = wx.Button(panel,wx.ID_OK, label=_("Submit"))
dialogButtons.AddButton(submitButton)
panel.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton)
if not 'NO_CANCEL' in self.options:
cancelButton = wx.Button(panel,wx.ID_CANCEL)
dialogButtons.AddButton(cancelButton)
panel.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton)
dialogButtons.Realize()
panel.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL)
panel.SetSizer(panel.sizer)
panel.SetAutoLayout(True)
panel.sizer.Fit(self)
self.sizer.Add(panel, 1, flag=wx.EXPAND)
cat_dom.unlink()
class EmptyWidget(WixWidget, xmlui.EmptyWidget, wx.Window):
###events
def __init__(self, parent):
wx.Window.__init__(self, parent, -1)
def onButtonClicked(self, event):
"""Called when a button is pushed"""
callback_id, fields = event.GetEventObject().param_id
for field in fields:
ctrl = self.ctrl_list[field]
if isinstance(ctrl['control'], wx.ListBox):
data[field] = '\t'.join([ctrl['control'].GetString(idx) for idx in ctrl['control'].GetSelections()])
else:
data[field] = ctrl['control'].GetValue()
self.host.launchAction(callback_id, None, profile_key = self.host.profile)
class TextWidget(WixWidget, xmlui.TextWidget, wx.StaticText):
def __init__(self, parent, value):
wx.StaticText.__init__(self, parent, -1, value)
class StringWidget(EventWidget, ValueWidget, xmlui.StringWidget, wx.TextCtrl):
_xmlui_change_event = wx.EVT_TEXT
def __init__(self, parent, value):
wx.TextCtrl.__init__(self, parent, -1, value)
self._xmlui_proportion = 1
class PasswordWidget(EventWidget, ValueWidget, xmlui.PasswordWidget, wx.TextCtrl):
_xmlui_change_event = wx.EVT_TEXT
def __init__(self, parent, value):
wx.TextCtrl.__init__(self, parent, -1, value, style=wx.TE_PASSWORD)
self._xmlui_proportion = 1
class TextBoxWidget(EventWidget, ValueWidget, xmlui.TextBoxWidget, wx.TextCtrl):
_xmlui_change_event = wx.EVT_TEXT
def __init__(self, parent, value):
wx.TextCtrl.__init__(self, parent, -1, value, style=wx.TE_MULTILINE)
self._xmlui_proportion = 1
class BoolWidget(EventWidget, ValueWidget, xmlui.BoolWidget, wx.CheckBox):
_xmlui_change_event = wx.EVT_CHECKBOX
def __init__(self, parent, state):
wx.CheckBox.__init__(self, parent, -1, "", style=wx.CHK_2STATE)
self.SetValue(state)
self._xmlui_proportion = 1
def _xmluiGetValue(self):
return "true" if self.GetValue() else "false"
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)
self._xmlui_click_callback = click_callback
parent.Bind(wx.EVT_BUTTON, lambda evt: click_callback(evt.GetEventObject()), self)
def _xmluiOnClick(self, event):
self._xmlui_click_callback(event.GetEventObject())
event.Skip()
def onFormSubmitted(self, event):
"""Called when submit button is clicked"""
debug(_("Submitting form"))
selected_values = []
for ctrl_name in self.ctrl_list:
escaped = u"%s%s" % (SAT_FORM_PREFIX, ctrl_name)
ctrl = self.ctrl_list[ctrl_name]
if isinstance(ctrl['control'], wx.ListBox):
label = ctrl['control'].GetStringSelection()
value = ctrl['attr_map'][label]
selected_values.append((escaped, value))
elif isinstance(ctrl['control'], wx.CheckBox):
selected_values.append((escaped, "true" if ctrl['control'].GetValue() else "false"))
else:
selected_values.append((escaped, ctrl['control'].GetValue()))
if self.misc.has_key('action_back'): #FIXME FIXME FIXME: WTF ! Must be cleaned
id = self.misc['action_back']("SUBMIT",self.misc['target'], selected_values)
self.host.current_action_ids.add(id)
elif self.misc.has_key('callback'):
self.misc['callback'](selected_values)
class ListWidget(EventWidget, WixWidget, xmlui.ListWidget, wx.ListBox):
_xmlui_change_event = wx.EVT_LISTBOX
elif self.submit_id is not None:
data = dict(selected_values)
if self.session_id is not None:
data["session_id"] = self.session_id
self.host.launchAction(self.submit_id, data, profile_key=self.host.profile)
else:
warning (_("The form data is not sent back, the type is not managed properly"))
def __init__(self, parent, options, 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)
self._xmlui_attr_map = {label: value for value, label in options}
self._xmlui_proportion = 1
def _xmluiSelectValue(self, value):
try:
label = [label for label, _value in self._xmlui_attr_map.items() if _value == value][0]
except IndexError:
warning(_("Can't find value [%s] to select" % value))
return
for idx in xrange(self.GetCount()):
self.SetSelection(idx, self.GetString(idx) == label)
def _xmluiGetSelectedValues(self):
ret = []
labels = [self.GetString(idx) for idx in self.GetSelections()]
for label in labels:
ret.append(self._xmlui_attr_map[label])
return ret
class AdvancedListWidget(ListWidget):
#TODO
def __init__(self, parent, options, flags):
super(ListWidget, self).__init__(parent, options, flags)
class WixContainer(object):
_xmlui_proportion = 1
def _xmluiAppend(self, widget):
self.sizer.Add(widget, self._xmlui_proportion, flag=wx.EXPAND)
class PairsContainer(WixContainer, xmlui.PairsContainer, wx.Panel):
def __init__(self, parent, weight_0='1', weight_1='1'):
wx.Panel.__init__(self, 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)
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 _xmluiAddTab(self, label):
tab_panel = wx.Panel(self, -1)
tab_panel.sizer = wx.BoxSizer(wx.VERTICAL)
tab_panel.SetSizer(tab_panel.sizer)
self.AddPage(tab_panel, label)
VerticalContainer._xmluiAdapt(tab_panel)
return tab_panel
class VerticalContainer(WixContainer, xmlui.VerticalContainer, wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.sizer)
class WidgetFactory(object):
def __getattr__(self, attr):
if attr.startswith("create"):
cls = globals()[attr[6:]]
cls._xmlui_main = self._xmlui_main
return cls
class XMLUI(xmlui.XMLUI, wx.Frame, WixContainer):
"""Create an user interface from a SàT XML"""
widget_factory = WidgetFactory()
def __init__(self, host, xml_data, title=None, flags = None,):
self.widget_factory._xmlui_main = self
xmlui.XMLUI.__init__(self, host, xml_data, title, flags)
def constructUI(self, xml_data):
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)
self.SetSizer(self.sizer)
def postTreat(ret_wid):
if self.title:
self.SetTitle(self.title)
if self.type == 'form':
dialogButtons = wx.StdDialogButtonSizer()
submitButton = wx.Button(ret_wid,wx.ID_OK, label=_("Submit"))
dialogButtons.AddButton(submitButton)
ret_wid.Bind(wx.EVT_BUTTON, self.onFormSubmitted, submitButton)
if not 'NO_CANCEL' in self.flags:
cancelButton = wx.Button(ret_wid,wx.ID_CANCEL)
dialogButtons.AddButton(cancelButton)
ret_wid.Bind(wx.EVT_BUTTON, self.onFormCancelled, cancelButton)
dialogButtons.Realize()
ret_wid.sizer.Add(dialogButtons, flag=wx.ALIGN_CENTER_HORIZONTAL)
self._xmluiAppend(ret_wid)
self.sizer.Fit(self)
self.Show()
return ret_wid
super(XMLUI, self).constructUI(xml_data, postTreat)
if not 'NO_CANCEL' in self.flags:
self.Bind(wx.EVT_CLOSE, self.onClose, self)
self.MakeModal()
def _xmluiClose(self):
self.MakeModal(False)
self.Destroy()
def onFormCancelled(self, event):
"""Called when cancel button is clicked"""
debug(_("Cancelling form"))
self.MakeModal(False)
self.Close()
###events
def onParamChange(self, ctrl):
super(XMLUI, self).onParamChange(ctrl)
### FIXME # Some hacks for better presentation, should be generic # FIXME ###
if (ctrl._param_category, ctrl._param_name) == ('Connection', 'JabberID'):
domain = JID(ctrl._xmluiGetValue()).domain
for widget in (ctl['control'] for ctl in self.ctrl_list.values()):
if (widget._param_category, widget._param_name) == ('Connection', 'Server'):
widget.SetValue(domain)
break
def onFormSubmitted(self, event):
"""Called when submit button is clicked"""
button = event.GetEventObject()
super(XMLUI, self).onFormSubmitted(button)
def onClose(self, event):
"""Close event: we have to send the form."""
debug(_("close"))
self.MakeModal(False)
if self.type == 'param':
self.onSaveParams()
event.Skip()

View file

@ -26,8 +26,8 @@ from sat.core import exceptions
"""This library help manage XML used in SàT (parameters, registration, etc) """
SAT_FORM_PREFIX ="SAT_FORM_"
SAT_FORM_PREFIX = "SAT_FORM_"
SAT_PARAM_SEPARATOR = "_XMLUI_PARAM_" # used to have unique elements names
def dataForm2XMLUI(form, submit_id, session_id=None):
"""Take a data form (xep-0004, Wokkel's implementation) and convert it to a SàT xml"""
@ -152,16 +152,16 @@ def paramsXml2xmlUI(xml):
assert(False)
param_ui = XMLUI("param", "tabs")
for category in top.getElementsByTagName("category"):
name = category.getAttribute('name')
category_name = category.getAttribute('name')
label = category.getAttribute('label')
if not name:
if not category_name:
error(_('INTERNAL ERROR: params categories must have a name'))
assert(False)
param_ui.addCategory(name, 'pairs', label=label)
param_ui.addCategory(category_name, 'pairs', label=label)
for param in category.getElementsByTagName("param"):
name = param.getAttribute('name')
param_name = param.getAttribute('name')
label = param.getAttribute('label')
if not name:
if not param_name:
error(_('INTERNAL ERROR: params must have a name'))
assert(False)
type_ = param.getAttribute('type')
@ -171,8 +171,8 @@ def paramsXml2xmlUI(xml):
if type_ == "button":
param_ui.addEmpty()
else:
param_ui.addLabel(label or name)
param_ui.addElement(name=name, type_=type_, value=value, options=options, callback_id=callback_id)
param_ui.addLabel(label or param_name)
param_ui.addElement(name="%s%s%s" % (category_name, SAT_PARAM_SEPARATOR, param_name), type_=type_, value=value, options=options, callback_id=callback_id)
return param_ui.toXml()