Initial support for multiple-records dataforms - displaying data.

This commit is contained in:
Tomasz Melcer 2006-09-15 08:38:01 +00:00
parent 9b29c4c8b8
commit f91d9192e5
4 changed files with 120 additions and 74 deletions

View File

@ -473,8 +473,7 @@
<child> <child>
<widget class="GtkNotebook" id="data_form_types_notebook"> <widget class="GtkNotebook" id="data_form_types_notebook">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="show_tabs">False</property>
<property name="show_tabs">True</property>
<property name="show_border">True</property> <property name="show_border">True</property>
<property name="tab_pos">GTK_POS_TOP</property> <property name="tab_pos">GTK_POS_TOP</property>
<property name="scrollable">False</property> <property name="scrollable">False</property>
@ -566,8 +565,8 @@
<widget class="GtkScrolledWindow" id="scrolledwindow38"> <widget class="GtkScrolledWindow" id="scrolledwindow38">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can_focus">True</property> <property name="can_focus">True</property>
<property name="hscrollbar_policy">GTK_POLICY_ALWAYS</property> <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="vscrollbar_policy">GTK_POLICY_ALWAYS</property> <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
<property name="shadow_type">GTK_SHADOW_IN</property> <property name="shadow_type">GTK_SHADOW_IN</property>
<property name="window_placement">GTK_CORNER_TOP_LEFT</property> <property name="window_placement">GTK_CORNER_TOP_LEFT</property>

View File

@ -490,8 +490,6 @@ class CommandWindow:
else: else:
self.stage3_next_form(response.getTag('command')) self.stage3_next_form(response.getTag('command'))
print stanza
self.account.connection.SendAndCallForResponse(stanza, callback) self.account.connection.SendAndCallForResponse(stanza, callback)
def send_cancel(self): def send_cancel(self):

View File

@ -56,6 +56,9 @@ class BadDataFormNode(Exception): pass
class DataForm(xmpp.Node, object): class DataForm(xmpp.Node, object):
""" Data form as described in JEP-0004. """ """ 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): 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. """ 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 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 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.""" 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 node is None or isinstance(node, xmpp.Node)
assert tofill is None or isinstance(tofill, xmpp.Node) assert tofill is None or isinstance(tofill, xmpp.Node)
assert node is None or tofill is None assert node is None or tofill is None
@ -87,33 +90,37 @@ class DataForm(xmpp.Node, object):
del field.label del field.label
del field.options del field.options
del field.description del field.description
elif node is not None: elif node is not None:
# if there is <reported/> element, the form contains multiple records # helper function - convert all <field/> elements to DataField
if self.getTag('reported') is not None: 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/> # in multiple-record forms there must not be any fields not in <items/>
if self.getTag('field') is not None: raise BadDataNodeForm if self.getTag('field') is not None: raise BadDataNodeForm
self._mode = DATAFORM_MULTIPLE 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 item in iter_elements(self, 'item'):
for field in iter_elements(item, 'field'): convert_fields(item)
item.delChild(field)
field.addChild(node=DataField(node=field)) # the same in <recorded/> element
convert_fields(self.getTag('recorded'))
else: else:
self._mode = DATAFORM_SINGLE self._mode = DATAFORM_SINGLE
# change every <field/> to DataField object convert_fields(self)
toadd=[]
for field in self.getChildren():
if not isinstance(field, xmpp.Node): continue
if not field.getName()=='field': continue
toadd.append(DataField(node=field))
del_multiple_tag_value(self, 'field') else: # both tofill and node are None
for field in toadd:
self.addChild(node=field)
else: # neither tofill nor node has a Node
if typ is None: typ='result' if typ is None: typ='result'
if records is not None and len(records)>1: if records is not None and len(records)>1:
self._mode = DATAFORM_MULTIPLE self._mode = DATAFORM_MULTIPLE
@ -185,35 +192,53 @@ class DataForm(xmpp.Node, object):
def iter_records(self): def iter_records(self):
if self.mode is DATAFORM_SINGLE: if self.mode is DATAFORM_SINGLE:
yield DataRecord(node=self) yield DataRecord(node=self, associated=self)
else: else:
for i in iter_elements(self, 'item'): for i in iter_elements(self, 'item'):
yield i yield DataRecord(node=i, associated=self)
def get_records(self): def get_records(self):
if self.mode is DATAFORM_SINGLE: return list(self.iter_records())
return [DataRecord(node=self),]
else:
return list(iter_elements(self, 'item'))
def set_records(self, records): def set_records(self, records):
if self.mode is DATAFORM_SINGLE: ''' Set new records. For single dataform only the first element in
assert len(records)==1 'records' iterable will be chosen.
record = records[0] Elements from the iterable must be a dictionary (with keys equal to
assert isinstance(record, DataRecord) field names and values filled with field value) or any xmpp.Node
for field in record.iter_fields(): containing <field/> elements with appropriate format according to JEP-0004.
self[field.var]=field.value 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: else:
self.del_records() self.del_records()
for record in records: for record in records:
assert isinstance(record, dict)
newitem = self.addChild('item') newitem = self.addChild('item')
for key, value in record.iteritems(): if isinstance(record, dict):
newitem.addChild(node=DataField( for key, value in record.iteritems():
var=key, newitem.addChild(node=DataField(
value=value, var=key,
typ=self.get_field(key).type)) 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): def del_records(self):
if self.mode is DATAFORM_SINGLE: if self.mode is DATAFORM_SINGLE:
@ -234,9 +259,7 @@ class DataForm(xmpp.Node, object):
else: else:
container = self.getTag("recorded") container = self.getTag("recorded")
for child in container.getChildren(): return iter_elements(container, 'field')
if isinstance(child, DataField):
yield child
def get_field(self, fieldvar): def get_field(self, fieldvar):
''' Find DataField that has fieldvar as var. ''' ''' 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 raise KeyError, "This form does not contain %r field." % fieldvar
def get_fields(self): def get_fields(self):
if self.mode is DATAFORM_SINGLE: return list(self.iter_fields)
container = self
else:
container = self.getTag("recorded")
return container.getTags("field")
def set_fields(self, fields): def set_fields(self, fields):
if self.mode is DATAFORM_SINGLE: if self.mode is DATAFORM_SINGLE:
@ -264,7 +282,11 @@ class DataForm(xmpp.Node, object):
self.delChild('recorded') self.delChild('recorded')
except ValueError: except ValueError:
pass 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): def del_fields(self):
if self.mode is DATAFORM_SINGLE: 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 """ 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 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 for DataForms in DATAFORM_MULTIPLE mode). It expects that every <field/> element is actually
a DataField instance.""" a DataField instance. Read-only."""
def __init__(self, fields=None, node=None): 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 (node is None)
assert (fields is None) or (fields.__iter__) assert (fields is None) or (fields.__iter__)
assert (node is None) or (isinstance(node, xmpp.Node)) assert (node is None) or (isinstance(node, xmpp.Node))
assert (associated is None) or (isinstance(associated, DataForm))
self.vars = {} self.vars = {}
self.associated = associated
xmpp.Node.__init__(self, node=node) xmpp.Node.__init__(self, node=node)
if node is not None: if node is not None:
for field in node.getTags('field'): for field in iter_elements(node, 'field'):
assert isinstance(field, DataField) assert isinstance(field, DataField)
self.vars[field.var] = field 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 # if there will be ever needed access to all fields as a list, write it here, in form of property
def iter_fields(self): def iter_fields(self):
for field in self.getChildren(): if self.associated is not None:
if not isinstance(field, xmpp.DataField): continue for field in self.associated.iter_fields():
yield field 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): 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] return self.vars[item]

View File

@ -17,7 +17,7 @@
""" This module contains widget that can display data form (JEP-0004). """ This module contains widget that can display data form (JEP-0004).
Words single and multiple refers here to types of data forms: Words single and multiple refers here to types of data forms:
single means these with one record of data (without <recorded/> element), 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: forms of type='result' should be read-only
# TODO: remove tabs from dialog # TODO: remove tabs from dialog
@ -30,6 +30,8 @@ import gtkgui_helpers
import common.xmpp as xmpp import common.xmpp as xmpp
import common.dataforms as dataforms import common.dataforms as dataforms
import itertools
class DataFormWidget(gtk.Alignment, object): class DataFormWidget(gtk.Alignment, object):
# "public" interface # "public" interface
""" Data Form widget. Use like any other widget. """ """ Data Form widget. Use like any other widget. """
@ -51,9 +53,8 @@ class DataFormWidget(gtk.Alignment, object):
if dataformnode is not None: if dataformnode is not None:
self.set_data_form(dataformnode) self.set_data_form(dataformnode)
def set_data_form(self, dataform=None): def set_data_form(self, dataform):
""" Set the data form (xmpp.DataForm) displayed in widget. """ Set the data form (xmpp.DataForm) displayed in widget. """
Set to None to erase the form. """
assert isinstance(dataform, dataforms.DataForm) assert isinstance(dataform, dataforms.DataForm)
self.del_data_form() self.del_data_form()
@ -150,23 +151,24 @@ class DataFormWidget(gtk.Alignment, object):
# we just do not display them. # we just do not display them.
# TODO: boolean fields # TODO: boolean fields
#elif field.type=='boolean': fieldtypes.append(bool) #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 # moving all data to model
for item in self._data_form.iter_records(): for item in self._data_form.iter_records():
self.multiplemodel.append(field.value for field in item.iter_fields())
self.multiplemodel.append([field.value for field in item.iter_fields()])
# contructing columns... # constructing columns...
for field in self._data_form.iter_fields(): for field, counter in zip(self._data_form.iter_fields(), itertools.count()):
self.records_treeview.append( print repr(field), repr(counter)
gtk.TreeViewColumn( self.records_treeview.append_column(
title=field.label, gtk.TreeViewColumn(field.label, gtk.CellRendererText(),
cell_renderer=gtk.CellRendererText(), text=counter))
text=field.value))
self.records.show_all() 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.set_current_page(
self.data_form_types_notebook.page_num( 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 self.clean_data_form = self.clean_multiple_data_form
# refresh list look
self.refresh_multiple_buttons()
def clean_multiple_data_form(self): def clean_multiple_data_form(self):
'''(Called as clean_data_form, read the docs of clean_data_form()). '''(Called as clean_data_form, read the docs of clean_data_form()).
Remove form from widget.''' 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): class SingleForm(gtk.Table, object):
""" Widget that represent DATAFORM_SINGLE mode form. Because this is used """ Widget that represent DATAFORM_SINGLE mode form. Because this is used