changeset 805:7c05c39156a2

core (XMLUI), frontends: advancedListContainer part 2:
author Goffi <goffi@goffi.org>
date Tue, 04 Feb 2014 18:21:51 +0100
parents 5174657b3378
children 5d6c45d6ee1b
files frontends/src/primitivus/xmlui.py frontends/src/tools/xmlui.py frontends/src/wix/xmlui.py src/tools/xml_tools.py
diffstat 4 files changed, 140 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/frontends/src/primitivus/xmlui.py	Tue Feb 04 18:19:32 2014 +0100
+++ b/frontends/src/primitivus/xmlui.py	Tue Feb 04 18:21:51 2014 +0100
@@ -28,13 +28,13 @@
 class PrimitivusEvents(object):
     """ Used to manage change event of Primitivus widgets """
 
-    def _change_callback(self, ctrl, *args, **kwargs):
+    def _event_callback(self, ctrl, *args, **ktkwargs):
         """" Call xmlui callback and ignore any extra argument """
         args[-1](ctrl)
 
     def _xmluiOnChange(self, callback):
         """ Call callback with widget as only argument """
-        urwid.connect_signal(self, 'change', self._change_callback, callback)
+        urwid.connect_signal(self, 'change', self._event_callback, callback)
 
 
 class PrimitivusEmptyWidget(xmlui.EmptyWidget, urwid.Text):
@@ -132,17 +132,29 @@
         return [option.value for option in self.getSelectedValues()]
 
 
-class PrimitivusAdvancedListContainer(xmlui.AdvancedListContainer, sat_widgets.TableContainer):
+class PrimitivusAdvancedListContainer(xmlui.AdvancedListContainer, sat_widgets.TableContainer, PrimitivusEvents):
 
-    def __init__(self, parent, columns):
-        options = {'ADAPT':(), 'HIGHLIGHT':()}
-        sat_widgets.TableContainer.__init__(self, columns=columns, options=options)
+    def __init__(self, parent, columns, selectable='no'):
+        options = {'ADAPT':()}
+        if selectable != 'no':
+            options['HIGHLIGHT'] = ()
+        sat_widgets.TableContainer.__init__(self, columns=columns, options=options, row_selectable = selectable!='no')
 
     def _xmluiAppend(self, widget):
         self.addWidget(widget)
 
-    def _xmluiAddRow(self):
-        pass
+    def _xmluiAddRow(self, idx):
+        self.setRowIndex(idx)
+
+    def _xmluiGetSelectedWidgets(self):
+        return self.getSelectedWidgets()
+
+    def _xmluiGetSelectedIndex(self):
+        return self.getSelectedIndex()
+
+    def _xmluiOnSelect(self, callback):
+        """ Call callback with widget as only argument """
+        urwid.connect_signal(self, 'click', self._event_callback, callback)
 
 class PrimitivusPairsContainer(xmlui.PairsContainer, sat_widgets.TableContainer):
 
--- a/frontends/src/tools/xmlui.py	Tue Feb 04 18:19:32 2014 +0100
+++ b/frontends/src/tools/xmlui.py	Tue Feb 04 18:21:51 2014 +0100
@@ -177,6 +177,10 @@
         self._main_cont = None
         self.constructUI(xml_data)
 
+    def escape(self, name):
+        """ return escaped name for forms """
+        return u"%s%s" % (Const.SAT_FORM_PREFIX, name)
+
     @property
     def main_cont(self):
         return self._main_cont
@@ -220,8 +224,18 @@
                         columns = int(node.getAttribute('columns'))
                     except (TypeError, ValueError):
                         raise DataError("Invalid columns")
-                    cont = self.widget_factory.createAdvancedListContainer(parent, columns)
-                    self._parseChilds(cont, node, ('row',))
+                    selectable = node.getAttribute('selectable') or 'no'
+                    auto_index = node.getAttribute('auto_index') == 'true'
+                    data = {'index': 0} if auto_index else None
+                    cont = self.widget_factory.createAdvancedListContainer(parent, columns, selectable)
+                    callback_id = node.getAttribute("callback") or None
+                    if callback_id is not None:
+                        if selectable == 'no':
+                            raise ValueError("can't have selectable=='no' and callback_id at the same time")
+                        cont._xmlui_callback_id = callback_id
+                        cont._xmluiOnSelect(self.onAdvListSelect)
+
+                    self._parseChilds(cont, node, ('row',), data)
                 else:
                     warning(_("Unknown container [%s], using default one") % type_)
                     cont = self.widget_factory.createVerticalContainer(parent)
@@ -246,7 +260,12 @@
                 self._parseChilds(new_tab, node, ('widget', 'container'))
 
             elif node.nodeName == 'row':
-                parent._xmluiAddRow()
+                try:
+                    index = str(data['index'])
+                    data['index'] += 1
+                except TypeError:
+                    index = node.getAttribute('index') or None
+                parent._xmluiAddRow(index)
                 self._parseChilds(parent, node, ('widget', 'container'))
 
             elif node.nodeName == "widget":
@@ -299,11 +318,11 @@
                     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))
 
+                ctrl._xmlui_name = name
                 parent._xmluiAppend(ctrl)
 
             else:
@@ -351,6 +370,25 @@
         assert(self.type == "param")
         self.param_changed.add(ctrl)
 
+    def onAdvListSelect(self, ctrl):
+        data = {}
+        widgets = ctrl._xmluiGetSelectedWidgets()
+        for wid in widgets:
+            try:
+                name = self.escape(wid._xmlui_name)
+                value = wid._xmluiGetValue()
+                ret[name] = value
+            except AttributeError:
+                pass
+        idx = ctrl._xmluiGetSelectedIndex()
+        if idx is not None:
+            data['index'] = idx
+        callback_id = ctrl._xmlui_callback_id
+        if callback_id is None:
+            warning(_("No callback_id found"))
+            return
+        self.host.launchAction(callback_id, data, profile_key = self.host.profile)
+
     def onButtonPress(self, button):
         """ Called when an XMLUI button is clicked
         Launch the action associated to the button
@@ -360,11 +398,12 @@
         callback_id, fields = button._xmlui_param_id
         data = {}
         for field in fields:
+            escaped = self.escape(field)
             ctrl = self.ctrl_list[field]
             if isinstance(ctrl['control'], ListWidget):
-                data[field] = u'\t'.join(ctrl['control']._xmluiGetSelected())
+                data[escaped] = u'\t'.join(ctrl['control']._xmluiGetSelected())
             else:
-                data[field] = ctrl['control']._xmluiGetValue()
+                data[escaped] = ctrl['control']._xmluiGetValue()
         self.host.launchAction(callback_id, data, profile_key = self.host.profile)
 
     def onFormSubmitted(self, ignore=None):
@@ -374,7 +413,7 @@
         """
         selected_values = []
         for ctrl_name in self.ctrl_list:
-            escaped = u"%s%s" % (Const.SAT_FORM_PREFIX, ctrl_name)
+            escaped = self.escape(ctrl_name)
             ctrl = self.ctrl_list[ctrl_name]
             if isinstance(ctrl['control'], ListWidget):
                 selected_values.append((escaped, u'\t'.join(ctrl['control']._xmluiGetSelectedValues())))
@@ -407,6 +446,7 @@
                 value = u'\t'.join(ctrl._xmluiGetSelectedValues())
             else:
                 value = ctrl._xmluiGetValue()
-            self.host.bridge.setParam(ctrl._param_name, value, ctrl._param_category,
+            param_name = ctr._xmlui_name.split(Const.SAT_PARAM_SEPARATOR)[1]
+            self.host.bridge.setParam(param_name, value, ctrl._param_category,
                                       profile_key=self.host.profile)
         self._xmluiClose()
--- a/frontends/src/wix/xmlui.py	Tue Feb 04 18:19:32 2014 +0100
+++ b/frontends/src/wix/xmlui.py	Tue Feb 04 18:21:51 2014 +0100
@@ -154,16 +154,43 @@
         self.sizer.Add(widget, self._xmlui_proportion, flag=wx.EXPAND)
 
 
-class AdvancedListContainer(WixContainer, xmlui.AdvancedListContainer, wx.Panel):
+class AdvancedListContainer(WixContainer, xmlui.AdvancedListContainer, wx.ScrolledWindow):
 
-    def __init__(self, parent, columns):
-        wx.Panel.__init__(self, parent)
+    def __init__(self, parent, columns, selectable='no'):
+        wx.ScrolledWindow.__init__(self, parent)
+        self._xmlui_selectable = selectable != 'no'
+        if selectable:
+            columns += 1
         self.sizer = wx.FlexGridSizer(cols=columns)
         self.SetSizer(self.sizer)
+        self._xmlui_select_cb = None
+        self._xmlui_select_idx = None
+        self._xmlui_select_widgets = []
 
-    def _xmluiAddRow(self):
-        pass
+    def _xmluiAddRow(self, idx):
+        # XXX: select_button is a Q&D way to implement row selection
+        # FIXME: must be done properly
+        if not self._xmlui_selectable:
+            return
+        select_button = wx.Button(self, wx.ID_OK, label=_("select"))
+        self.sizer.Add(select_button)
+        def click_cb(event, idx=idx):
+            cb = self._xmlui_select_cb
+            self._xmlui_select_idx = idx
+            # TODO: fill self._xmlui_select_widgets
+            if cb is not None:
+                cb(self)
+            event.Skip()
+        self.Bind(wx.EVT_BUTTON, click_cb)
 
+    def _xmluiGetSelectedWidgets(self):
+        return self._xmlui_select_widgets
+
+    def _xmluiGetSelectedIndex(self):
+        return self._xmlui_select_idx
+
+    def _xmluiOnSelect(self, callback):
+        self._xmlui_select_cb = callback
 
 class PairsContainer(WixContainer, xmlui.PairsContainer, wx.Panel):
 
--- a/src/tools/xml_tools.py	Tue Feb 04 18:19:32 2014 +0100
+++ b/src/tools/xml_tools.py	Tue Feb 04 18:21:51 2014 +0100
@@ -75,18 +75,15 @@
     if form.instructions:
         form_ui.addText('\n'.join(form.instructions), 'instructions')
 
-    labels = [field for field in form.fieldList if field.label]
-    if labels:
-        # if there is no label, we don't need to use pairs
-        form_ui.changeContainer("pairs")
+    form_ui.changeContainer("pairs")
 
     for field in form.fieldList:
         widget_type, widget_args, widget_kwargs = _dataFormField2XMLUIData(field)
-        if labels:
-            if field.label:
-                form_ui.addLabel(field.label)
-            else:
-                form_ui.addEmpty()
+        label = field.label or field.var
+        if label:
+            form_ui.addLabel(label)
+        else:
+            form_ui.addEmpty()
 
         form_ui.addWidget(widget_type, *widget_args, **widget_kwargs)
 
@@ -151,11 +148,15 @@
     """
     return {key[len(SAT_FORM_PREFIX):]: value for key, value in xmlui_data.iteritems() if key.startswith(SAT_FORM_PREFIX)}
 
+def formEscape(name):
+    """ Return escaped name for forms """
+    return u"%s%s"  % (SAT_FORM_PREFIX, name)
+
 def XMLUIResultToElt(xmlui_data):
     """ Construct result domish.Element from XMLUI result
     @xmlui_data: data returned by frontends for XMLUI form
     """
-    form = data_form.Form('result')
+    form = data_form.Form('submit')
     form.makeFields(XMLUIResult2DataFormResult(xmlui_data))
     return form.toElement()
 
@@ -310,6 +311,11 @@
     def __init__(self, parent):
         assert(isinstance(parent, AdvancedListContainer))
         super(RowElement, self).__init__(parent.xmlui, parent)
+        if parent.next_row_idx is not None:
+            if parent.auto_index:
+                raise exceptions.DataError(_("Can't set row index if auto_index is True"))
+            self.elem.setAttribute('index', parent.next_row_idx)
+            parent.next_row_idx = None
 
 
 class HeaderElement(Element):
@@ -393,12 +399,19 @@
 class AdvancedListContainer(Container):
     type = "advanced_list"
 
-    def __init__(self, xmlui, name=None, headers=None, items=None, columns=None, parent=None):
+    def __init__(self, xmlui, callback_id=None, name=None, headers=None, items=None, columns=None, selectable = 'no', auto_index = False, parent=None):
         """Create an advanced list
         @param headers: optional headers informations
-        @param items: list of Item instances
+        @param callback_id: id of the method to call when selection is done
+        @param items: list of widgets to add (just the first row)
+        @param columns: number of columns in this table, or None to autodetect
+        @param selectable: one of:
+            'no': nothing is done
+            'single': one row can be selected
+        @param auto_index: if True, indexes will be generated by frontends, starting from 0
         @return: created element
         """
+        assert selectable in ('no', 'single')
         if not items and columns is None:
             raise DataError(_("either items or columns need do be filled"))
         if headers is None:
@@ -418,19 +431,33 @@
         if items:
             self.addItems(items)
         self.elem.setAttribute('columns', str(self._columns))
+        if callback_id is not None:
+            self.elem.setAttribute('callback', callback_id)
+        self.elem.setAttribute('selectable', selectable)
+        self.auto_index = auto_index
+        if auto_index:
+            self.elem.setAttribute('auto_index', 'true')
+        self.next_row_idx = None
 
     def addHeaders(self, headers):
         for header in headers:
             self.addHeader(header)
 
     def addHeader(self, header):
-
         pass # TODO
 
     def addItems(self, items):
         for item in items:
             self.append(item)
 
+    def setRowIndex(self, idx):
+        """ Set index for next row
+        index are returned when a row is selected, in data's "index" key
+        @param idx: string index to associate to the next row
+
+        """
+        self.next_row_idx = idx
+
     def append(self, child):
         if isinstance(child, RowElement):
             return super(AdvancedListContainer, self).append(child)