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:
parent
41ce40b0e4
commit
256a110471
8 changed files with 757 additions and 602 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
48
frontends/src/tools/xmltools.py
Normal file
48
frontends/src/tools/xmltools.py
Normal 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()
|
||||
|
||||
374
frontends/src/tools/xmlui.py
Normal file
374
frontends/src/tools/xmlui.py
Normal 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()
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue