diff --git a/data/glade/data_form_window.glade b/data/glade/data_form_window.glade index 06dc44d35..3fe56c276 100644 --- a/data/glade/data_form_window.glade +++ b/data/glade/data_form_window.glade @@ -473,8 +473,7 @@ True - True - True + False True GTK_POS_TOP False @@ -566,8 +565,8 @@ True True - GTK_POLICY_ALWAYS - GTK_POLICY_ALWAYS + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC GTK_SHADOW_IN GTK_CORNER_TOP_LEFT diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py index c682b3317..f8dabf205 100644 --- a/src/adhoc_commands.py +++ b/src/adhoc_commands.py @@ -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): diff --git a/src/common/dataforms.py b/src/common/dataforms.py index 476b855c1..f844d5dbf 100644 --- a/src/common/dataforms.py +++ b/src/common/dataforms.py @@ -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 element, the form contains multiple records - if self.getTag('reported') is not None: + # helper function - convert all 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 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 if self.getTag('field') is not None: raise BadDataNodeForm self._mode = DATAFORM_MULTIPLE - # change every to DataField object + # change every in every 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 element + convert_fields(self.getTag('recorded')) else: self._mode = DATAFORM_SINGLE - # change every 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 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') - for key, value in record.iteritems(): - newitem.addChild(node=DataField( - var=key, - value=value, - typ=self.get_field(key).type)) + 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 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 - yield field + 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] diff --git a/src/dataforms_widget.py b/src/dataforms_widget.py index d0fae0c6c..fc746a00c 100644 --- a/src/dataforms_widget.py +++ b/src/dataforms_widget.py @@ -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 element), -multiple - these that may contain more data (with element).""" +multiple - these which may contain more data (with 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()) + + 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)) + # 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.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.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