Initial support for multiple-records dataforms - displaying data.
This commit is contained in:
parent
9b29c4c8b8
commit
f91d9192e5
|
@ -473,8 +473,7 @@
|
|||
<child>
|
||||
<widget class="GtkNotebook" id="data_form_types_notebook">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="show_tabs">True</property>
|
||||
<property name="show_tabs">False</property>
|
||||
<property name="show_border">True</property>
|
||||
<property name="tab_pos">GTK_POS_TOP</property>
|
||||
<property name="scrollable">False</property>
|
||||
|
@ -566,8 +565,8 @@
|
|||
<widget class="GtkScrolledWindow" id="scrolledwindow38">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
|
||||
|
||||
|
|
|
@ -490,8 +490,6 @@ class CommandWindow:
|
|||
else:
|
||||
self.stage3_next_form(response.getTag('command'))
|
||||
|
||||
print stanza
|
||||
|
||||
self.account.connection.SendAndCallForResponse(stanza, callback)
|
||||
|
||||
def send_cancel(self):
|
||||
|
|
|
@ -56,6 +56,9 @@ class BadDataFormNode(Exception): pass
|
|||
|
||||
class DataForm(xmpp.Node, object):
|
||||
""" Data form as described in JEP-0004. """
|
||||
# NOTES: single forms contain children nodes 'field' of type DataField,
|
||||
# NOTES: multiple forms contain children do not contain 'item' elements of type DataRecord
|
||||
# NOTES: - these are created only when needed
|
||||
|
||||
def __init__(self, typ=None, title=None, instructions=None, fields=None, records=None, node=None, tofill=None):
|
||||
""" Create new node, based on the node given by 'node' parameter, or new.
|
||||
|
@ -64,7 +67,7 @@ class DataForm(xmpp.Node, object):
|
|||
You can also set 'tofill' to DataForm to get a form of type 'submit' with the
|
||||
same fields as in the form given, but without unnecessary data like
|
||||
instructions or title. Also the type will be set to 'submit' by default."""
|
||||
# assert that exactly one of fields 'node' and 'tofill' is filled
|
||||
# assert that at most one of these fields (node and tofill) is filled
|
||||
assert node is None or isinstance(node, xmpp.Node)
|
||||
assert tofill is None or isinstance(tofill, xmpp.Node)
|
||||
assert node is None or tofill is None
|
||||
|
@ -87,33 +90,37 @@ class DataForm(xmpp.Node, object):
|
|||
del field.label
|
||||
del field.options
|
||||
del field.description
|
||||
|
||||
elif node is not None:
|
||||
# if there is <reported/> element, the form contains multiple records
|
||||
if self.getTag('reported') is not None:
|
||||
# helper function - convert all <field/> elements to DataField
|
||||
def convert_fields(node):
|
||||
toadd=[]
|
||||
for field in iter_elements(node, 'field'):
|
||||
if not isinstance(field, DataField):
|
||||
field=DataField(node=field)
|
||||
toadd.append(field)
|
||||
del_multiple_tag_value(node, 'field')
|
||||
for field in toadd:
|
||||
node.addChild(node=field)
|
||||
|
||||
# if there is <recorded/> element, the form contains multiple records
|
||||
if self.getTag('recorded') is not None:
|
||||
# in multiple-record forms there must not be any fields not in <items/>
|
||||
if self.getTag('field') is not None: raise BadDataNodeForm
|
||||
self._mode = DATAFORM_MULTIPLE
|
||||
|
||||
# change every <field/> to DataField object
|
||||
# change every <field/> in every <item/> element to DataField object
|
||||
for item in iter_elements(self, 'item'):
|
||||
for field in iter_elements(item, 'field'):
|
||||
item.delChild(field)
|
||||
field.addChild(node=DataField(node=field))
|
||||
convert_fields(item)
|
||||
|
||||
# the same in <recorded/> element
|
||||
convert_fields(self.getTag('recorded'))
|
||||
else:
|
||||
self._mode = DATAFORM_SINGLE
|
||||
|
||||
# change every <field/> to DataField object
|
||||
toadd=[]
|
||||
for field in self.getChildren():
|
||||
if not isinstance(field, xmpp.Node): continue
|
||||
if not field.getName()=='field': continue
|
||||
toadd.append(DataField(node=field))
|
||||
convert_fields(self)
|
||||
|
||||
del_multiple_tag_value(self, 'field')
|
||||
|
||||
for field in toadd:
|
||||
self.addChild(node=field)
|
||||
else: # neither tofill nor node has a Node
|
||||
else: # both tofill and node are None
|
||||
if typ is None: typ='result'
|
||||
if records is not None and len(records)>1:
|
||||
self._mode = DATAFORM_MULTIPLE
|
||||
|
@ -185,35 +192,53 @@ class DataForm(xmpp.Node, object):
|
|||
|
||||
def iter_records(self):
|
||||
if self.mode is DATAFORM_SINGLE:
|
||||
yield DataRecord(node=self)
|
||||
yield DataRecord(node=self, associated=self)
|
||||
else:
|
||||
for i in iter_elements(self, 'item'):
|
||||
yield i
|
||||
yield DataRecord(node=i, associated=self)
|
||||
|
||||
def get_records(self):
|
||||
if self.mode is DATAFORM_SINGLE:
|
||||
return [DataRecord(node=self),]
|
||||
else:
|
||||
return list(iter_elements(self, 'item'))
|
||||
return list(self.iter_records())
|
||||
|
||||
def set_records(self, records):
|
||||
if self.mode is DATAFORM_SINGLE:
|
||||
assert len(records)==1
|
||||
''' Set new records. For single dataform only the first element in
|
||||
'records' iterable will be chosen.
|
||||
|
||||
record = records[0]
|
||||
assert isinstance(record, DataRecord)
|
||||
for field in record.iter_fields():
|
||||
self[field.var]=field.value
|
||||
Elements from the iterable must be a dictionary (with keys equal to
|
||||
field names and values filled with field value) or any xmpp.Node
|
||||
containing <field/> elements with appropriate format according to JEP-0004.
|
||||
Therefore, you can pass an array of single forms, all of them with the
|
||||
same fields, to create one big multiple form.'''
|
||||
if self.mode is DATAFORM_SINGLE:
|
||||
# get only first record...
|
||||
record = records.__iter__().next()
|
||||
|
||||
if isinstance(record, dict):
|
||||
for var, value in record:
|
||||
self[var]=value
|
||||
elif isinstance(record, xmpp.Node):
|
||||
for field in iter_elements(record, 'field'):
|
||||
self[field.getAttr('var')]=field.getAttr('value')
|
||||
else:
|
||||
assert 'This should not happen' and False
|
||||
else:
|
||||
self.del_records()
|
||||
for record in records:
|
||||
assert isinstance(record, dict)
|
||||
newitem = self.addChild('item')
|
||||
if isinstance(record, dict):
|
||||
for key, value in record.iteritems():
|
||||
newitem.addChild(node=DataField(
|
||||
var=key,
|
||||
value=value,
|
||||
typ=self.get_field(key).type))
|
||||
elif isinstance(record, xmpp.Node):
|
||||
for field in iter_elements(record, 'field'):
|
||||
newitem.addChild(node=DataField(
|
||||
var=field.getAttr('var'),
|
||||
value=field.getAttr('value'),
|
||||
typ=field.getAttr('type')))
|
||||
else:
|
||||
assert 'This should not happen' and False
|
||||
|
||||
def del_records(self):
|
||||
if self.mode is DATAFORM_SINGLE:
|
||||
|
@ -234,9 +259,7 @@ class DataForm(xmpp.Node, object):
|
|||
else:
|
||||
container = self.getTag("recorded")
|
||||
|
||||
for child in container.getChildren():
|
||||
if isinstance(child, DataField):
|
||||
yield child
|
||||
return iter_elements(container, 'field')
|
||||
|
||||
def get_field(self, fieldvar):
|
||||
''' Find DataField that has fieldvar as var. '''
|
||||
|
@ -245,12 +268,7 @@ class DataForm(xmpp.Node, object):
|
|||
raise KeyError, "This form does not contain %r field." % fieldvar
|
||||
|
||||
def get_fields(self):
|
||||
if self.mode is DATAFORM_SINGLE:
|
||||
container = self
|
||||
else:
|
||||
container = self.getTag("recorded")
|
||||
|
||||
return container.getTags("field")
|
||||
return list(self.iter_fields)
|
||||
|
||||
def set_fields(self, fields):
|
||||
if self.mode is DATAFORM_SINGLE:
|
||||
|
@ -264,7 +282,11 @@ class DataForm(xmpp.Node, object):
|
|||
self.delChild('recorded')
|
||||
except ValueError:
|
||||
pass
|
||||
self.addChild('recorded', {}, fields)
|
||||
recorded = self.addChild('recorded')
|
||||
for field in fields:
|
||||
if not isinstance(field, DataField):
|
||||
field = DataField(field)
|
||||
recorded.addChild(node=field)
|
||||
|
||||
def del_fields(self):
|
||||
if self.mode is DATAFORM_SINGLE:
|
||||
|
@ -489,18 +511,24 @@ class DataRecord(xmpp.Node):
|
|||
""" Class to store fields. May be used as temporary storage (for example when reading a list of
|
||||
fields from DataForm in DATAFORM_SINGLE mode), may be used as permanent storage place (for example
|
||||
for DataForms in DATAFORM_MULTIPLE mode). It expects that every <field/> element is actually
|
||||
a DataField instance."""
|
||||
def __init__(self, fields=None, node=None):
|
||||
a DataField instance. Read-only."""
|
||||
def __init__(self, fields=None, node=None, associated=None):
|
||||
''' Create new instance of DataRecord. You can use 'associated' to provide form which
|
||||
contains field descriptions. This way iterating over fields in this record will be
|
||||
in the same order as fields in given DataForm, with None when field does not exist
|
||||
in this record. '''
|
||||
assert (fields is None) or (node is None)
|
||||
assert (fields is None) or (fields.__iter__)
|
||||
assert (node is None) or (isinstance(node, xmpp.Node))
|
||||
assert (associated is None) or (isinstance(associated, DataForm))
|
||||
|
||||
self.vars = {}
|
||||
self.associated = associated
|
||||
|
||||
xmpp.Node.__init__(self, node=node)
|
||||
|
||||
if node is not None:
|
||||
for field in node.getTags('field'):
|
||||
for field in iter_elements(node, 'field'):
|
||||
assert isinstance(field, DataField)
|
||||
self.vars[field.var] = field
|
||||
|
||||
|
@ -513,9 +541,20 @@ class DataRecord(xmpp.Node):
|
|||
# if there will be ever needed access to all fields as a list, write it here, in form of property
|
||||
|
||||
def iter_fields(self):
|
||||
for field in self.getChildren():
|
||||
if not isinstance(field, xmpp.DataField): continue
|
||||
if self.associated is not None:
|
||||
for field in self.associated.iter_fields():
|
||||
if field.var in self.vars:
|
||||
yield self.vars[field.var]
|
||||
else:
|
||||
yield None
|
||||
else:
|
||||
for field in self.vars.itervalues():
|
||||
yield field
|
||||
|
||||
def __getitem__(self, item):
|
||||
# if the field is in associated but not here - return None
|
||||
# NOTE: last predicate is meant to raise KeyError if no such item in associated
|
||||
if self.associated is not None and item not in self.vars and \
|
||||
self.associated[item] is not None:
|
||||
return None
|
||||
return self.vars[item]
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
""" This module contains widget that can display data form (JEP-0004).
|
||||
Words single and multiple refers here to types of data forms:
|
||||
single means these with one record of data (without <recorded/> element),
|
||||
multiple - these that may contain more data (with <recorded/> element)."""
|
||||
multiple - these which may contain more data (with <recorded/> element)."""
|
||||
|
||||
# TODO: forms of type='result' should be read-only
|
||||
# TODO: remove tabs from dialog
|
||||
|
@ -30,6 +30,8 @@ import gtkgui_helpers
|
|||
import common.xmpp as xmpp
|
||||
import common.dataforms as dataforms
|
||||
|
||||
import itertools
|
||||
|
||||
class DataFormWidget(gtk.Alignment, object):
|
||||
# "public" interface
|
||||
""" Data Form widget. Use like any other widget. """
|
||||
|
@ -51,9 +53,8 @@ class DataFormWidget(gtk.Alignment, object):
|
|||
if dataformnode is not None:
|
||||
self.set_data_form(dataformnode)
|
||||
|
||||
def set_data_form(self, dataform=None):
|
||||
""" Set the data form (xmpp.DataForm) displayed in widget.
|
||||
Set to None to erase the form. """
|
||||
def set_data_form(self, dataform):
|
||||
""" Set the data form (xmpp.DataForm) displayed in widget. """
|
||||
assert isinstance(dataform, dataforms.DataForm)
|
||||
|
||||
self.del_data_form()
|
||||
|
@ -150,23 +151,24 @@ class DataFormWidget(gtk.Alignment, object):
|
|||
# we just do not display them.
|
||||
# TODO: boolean fields
|
||||
#elif field.type=='boolean': fieldtypes.append(bool)
|
||||
fieldtypes.append(unicode)
|
||||
fieldtypes.append(str)
|
||||
|
||||
self.multiplemodel = gtk.ListModel(*fieldtypes)
|
||||
self.multiplemodel = gtk.ListStore(*fieldtypes)
|
||||
|
||||
# moving all data to model
|
||||
for item in self._data_form.iter_records():
|
||||
self.multiplemodel.append(field.value for field in item.iter_fields())
|
||||
|
||||
# contructing columns...
|
||||
for field in self._data_form.iter_fields():
|
||||
self.records_treeview.append(
|
||||
gtk.TreeViewColumn(
|
||||
title=field.label,
|
||||
cell_renderer=gtk.CellRendererText(),
|
||||
text=field.value))
|
||||
self.multiplemodel.append([field.value for field in item.iter_fields()])
|
||||
|
||||
self.records.show_all()
|
||||
# constructing columns...
|
||||
for field, counter in zip(self._data_form.iter_fields(), itertools.count()):
|
||||
print repr(field), repr(counter)
|
||||
self.records_treeview.append_column(
|
||||
gtk.TreeViewColumn(field.label, gtk.CellRendererText(),
|
||||
text=counter))
|
||||
|
||||
self.records_treeview.set_model(self.multiplemodel)
|
||||
self.records_treeview.show_all()
|
||||
|
||||
self.data_form_types_notebook.set_current_page(
|
||||
self.data_form_types_notebook.page_num(
|
||||
|
@ -174,10 +176,18 @@ class DataFormWidget(gtk.Alignment, object):
|
|||
|
||||
self.clean_data_form = self.clean_multiple_data_form
|
||||
|
||||
# refresh list look
|
||||
self.refresh_multiple_buttons()
|
||||
|
||||
def clean_multiple_data_form(self):
|
||||
'''(Called as clean_data_form, read the docs of clean_data_form()).
|
||||
Remove form from widget.'''
|
||||
pass
|
||||
del self.multiplemodel
|
||||
|
||||
def refresh_multiple_buttons(self):
|
||||
''' Checks for treeview state and makes control buttons sensitive.'''
|
||||
selection = self.records_treeview.get_selection()
|
||||
|
||||
|
||||
class SingleForm(gtk.Table, object):
|
||||
""" Widget that represent DATAFORM_SINGLE mode form. Because this is used
|
||||
|
|
Loading…
Reference in New Issue