Ad-Hoc commands window now uses new dataform wrapper. /me dances

This commit is contained in:
Tomasz Melcer 2006-07-07 15:49:44 +00:00
parent 8c9711d7ea
commit c75bdcb63c
5 changed files with 233 additions and 190 deletions

View file

@ -208,7 +208,7 @@
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
<child>
<widget class="GtkVBox" id="vbox111">
<widget class="GtkVBox" id="container_vbox">
<property name="border_width">5</property>
<property name="visible">True</property>
<property name="homogeneous">False</property>
@ -251,19 +251,7 @@
</child>
<child>
<widget class="GtkTable" id="form_table">
<property name="visible">True</property>
<property name="n_rows">1</property>
<property name="n_columns">3</property>
<property name="homogeneous">False</property>
<property name="row_spacing">0</property>
<property name="column_spacing">0</property>
</widget>
<packing>
<property name="padding">0</property>
<property name="expand">True</property>
<property name="fill">True</property>
</packing>
<placeholder/>
</child>
</widget>
</child>

View file

@ -26,11 +26,10 @@
import gobject
import gtk
import common.xmpp as xmpp
import common.gajim as gajim
from common import xmpp, gajim, dataforms
import gtkgui_helpers
import dataforms
import dataforms as dataformwidget
class CommandWindow:
'''Class for a window for single ad-hoc commands session. Note, that
@ -38,7 +37,9 @@ class CommandWindow:
TODO: maybe put this window into MessageWindow? consider this when
TODO: it will be possible to manage more than one window of one
TODO: account/jid pair in MessageWindowMgr.'''
TODO: account/jid pair in MessageWindowMgr.
TODO: gtk 2.10 has a special wizard-widget, consider using it...'''
def __init__(self, account, jid):
'''Create new window.'''
@ -68,7 +69,8 @@ class CommandWindow:
self.__dict__[name] = self.xml.get_widget(name)
# creating data forms widget
self.data_form_widget = dataforms.DataFormWidget()
self.data_form_widget = dataformwidget.DataFormWidget()
self.data_form_widget.show()
self.sending_form_stage_vbox.pack_start(self.data_form_widget)
# setting initial stage
@ -264,13 +266,14 @@ class CommandWindow:
self.remove_pulsing()
self.sending_form_progressbar.hide()
if self.sessionid is None:
self.sessionid = command.getAttr('sessionid')
self.dataform = xmpp.DataForm(node=command.getTag('x'))
self.dataform = dataforms.DataForm(node=command.getTag('x'))
self.data_form_widget.show()
self.data_form_widget.set_sensitive(True)
self.data_form_widget.set_data_form(self.dataform)
self.data_form_widget.show()
action = command.getTag('action')
if action is None:
@ -367,6 +370,7 @@ class CommandWindow:
def callback(response):
'''Called on response to query.'''
# TODO: move to connection_handlers.py
# is error => error stage
error = response.getError()
if error is not None:
@ -405,10 +409,11 @@ class CommandWindow:
cmdnode.setAttr('sessionid', self.sessionid)
if self.data_form_widget.data_form is not None:
cmdnode.addChild(node=self.data_form_widget.filled_data_form())
cmdnode.addChild(node=dataforms.DataForm(tofill=self.data_form_widget.data_form))
def callback(response):
# TODO: error handling
# TODO: move to connection_handlers.py
self.stage3_next_form(response.getTag('command'))
print stanza

View file

@ -37,8 +37,11 @@ def del_multiple_tag_value(node, childname):
childname in node.
"""
try:
while node.delChild(childname):
pass
except ValueError:
pass
def iter_elements(node, childname):
""" Iterate over childname children of node element. """
@ -65,7 +68,7 @@ class DataForm(xmpp.Node, object):
assert (isinstance(tofill, DataForm) or tofill is None)
assert not (node is not None and tofill is not None)
assert typ in (None, 'form', 'submit', 'cancel', 'return')
assert typ in (None, 'form', 'submit', 'cancel', 'result')
assert isinstance(title, basestring) or title is None
assert isinstance(instructions, basestring) or instructions is None
assert (fields is None or fields.__iter__)
@ -94,11 +97,16 @@ class DataForm(xmpp.Node, object):
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
self.delChild(field)
self.addChild(node=DataField(node=field)
toadd.append(DataField(node=field))
del_multiple_tag_value(self, 'field')
for field in toadd:
self.addChild(node=field)
else: # neither tofill nor node has a Node
if typ is None: typ='result'
if records is not None and len(records)>1:
@ -133,7 +141,10 @@ class DataForm(xmpp.Node, object):
self.setTagData('title')
def del_title(self):
try:
self.delChild('title')
except ValueError:
pass
title = property(get_title, set_title, del_title,
"Form title, in unicode, from <title/> element.")
@ -168,22 +179,22 @@ class DataForm(xmpp.Node, object):
def iter_records(self):
if self.mode is DATAFORM_SINGLE:
yield DataRecord(self)
yield DataRecord(node=self)
else:
for item in self.getChildren():
if not isinstance(item, xmpp.Node): continue
if not item.getName()=='item': continue
yield DataRecord(item)
yield DataRecord(node=item)
def get_records(self):
if self.mode is DATAFORM_SINGLE:
return [DataRecord(self),]
return [DataRecord(node=self),]
else:
items = []
for node in self.getChildren():
if not isinstance(node, xmpp.Node): continue
if not node.getName()=='item': continue
items.append(DataRecord(node))
items.append(DataRecord(node=node))
return items
def set_records(self, records):
@ -191,9 +202,9 @@ class DataForm(xmpp.Node, object):
assert len(records)==1
record = records[0]
assert isinstance(record, dict)
for name, value in record.iteritems():
self[name]=value
assert isinstance(record, DataRecord)
for field in record.iter_fields():
self[field.var]=field.value
else:
self.del_records(self)
for record in records:
@ -229,19 +240,48 @@ class DataForm(xmpp.Node, object):
self.addChild(node=field)
else:
assert len(self.records)==0
try:
self.delChild('recorded')
except ValueError:
pass
self.addChild('recorded', None, fields)
def del_fields(self):
if self.mode is DATAFORM_SINGLE:
del_multiple_tag_value(self, "field")
else:
try:
self.delChild('recorded')
except ValueError:
pass
def iter_fields(self):
if self.mode is DATAFORM_SINGLE:
container = self
else:
container = self.getTag("recorded")
for child in container.getChildren():
if isinstance(child, DataField):
yield child
fields = property(get_fields, set_fields, del_fields,
"""Fields in this form; a list; if in DATAFORM_SINGLE mode, you should not
set their values directly.""")
def __getitem__(self, var):
for field in self.iter_fields():
if field.var==var:
return field.value
raise KeyError, "This form does not contain %r field." % var
def __setitem__(self, var, value):
for field in self.iter_fields():
if field.var==var:
field.value=value
return
raise KeyError, "This form does not contain %r field." % var
class DataField(xmpp.Node, object):
def __init__(self, typ='text-single', desc=None, required=None, value=None, options=None, node=None):
assert typ in ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi',
@ -306,7 +346,10 @@ class DataField(xmpp.Node, object):
self.setTagData('desc', desc)
def del_description(self):
try:
self.delChild('desc')
except ValueError:
pass
description = property(get_description, set_description, del_description,
""" A natural-language description of the field. It should not contain
@ -369,16 +412,30 @@ class DataField(xmpp.Node, object):
""" The value of field. Depending on the type, it is a boolean, a unicode string or a list
of stings. """)
def iter_options(self):
""" Yields a pair: label, value """
for element in self.getChildren():
if not isinstance(element, xmpp.Node): continue
if not element.getName()=='option': continue
yield element.getAttr('label'), element.getTag('value').getData()
def get_options(self):
return [tag.getData() for tag in self.getTags('option')]
""" Returns a list of tuples: (label, value). """
return [(tag.getAttr('label'), tag.getTag('value').getData()) for tag in self.getTags('option')]
def set_options(self, options):
""" Options need to be a list of tuples (label, value), both unicode. """
assert options.__iter__
del_multiple_tag_value(self, 'option')
for option in options:
assert isinstance(option, basestring)
self.addChild('option', None, (option,))
assert option[0] is None or isinstance(option[0], unicode)
assert isinstance(option[1], unicode)
if option[0] is None:
attr=None
else:
attr={'label': option[0].encode('utf-8')}
self.addChild('option', attr, (option[1].encode('utf-8'),))
def del_options(self):
del_multiple_tag_value(self, 'option')
@ -386,22 +443,30 @@ class DataField(xmpp.Node, object):
options = property(get_options, set_options, del_options,
""" Options to choose between in list-* fields. """)
class DataRecord(xmpp.Node, dict):
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)."""
for DataForms in DATAFORM_MULTIPLE mode). It expects that every <field/> element is actually
a DataField instance."""
def __init__(self, fields=None, node=None):
assert (fields is None) or (node is None)
assert (fields is None) or (fields.__iter__)
assert (node is None) or (isinstance(node, xmpp.Node))
dict.__init__(self)
self.vars = {}
xmpp.Node.__init__(self, node=node)
if node is not None:
for field in node.getTags('field'):
assert isinstance(field, DataField)
self.vars[field.var] = field
if fields is not None:
for field in fields:
assert isinstance(field, DataField)
self.addChild(node=field)
self[field.name] = field
self.vars[field.var] = field
# if there will be ever needed access to all fields as a list, write it here, in form of property
@ -409,3 +474,6 @@ class DataRecord(xmpp.Node, dict):
for field in self.getChildren():
if not isinstance(field, xmpp.DataField): continue
yield field
def __getitem__(self, item):
return self.vars[item]

View file

@ -58,9 +58,9 @@ class Node:
"node" and other arguments is provided then the node initially created as replica of "node"
provided and then modified to be compliant with other arguments."""
if node:
if self.FORCE_NODE_RECREATION and type(node)==type(self):
if self.FORCE_NODE_RECREATION and isinstance(node, Node):
node=str(node)
if type(node)<>type(self):
if not isinstance(node, Node):
node=NodeBuilder(node,self)
else:
self.name,self.namespace,self.attrs,self.data,self.kids,self.parent = node.name,node.namespace,{},[],[],node.parent

View file

@ -20,8 +20,9 @@ import gtk
import gtkgui_helpers
import common.xmpp as xmpp
import common.dataforms as dataforms
class DataFormWidget(gtk.Alignment):
class DataFormWidget(gtk.Alignment, object):
# "public" interface
""" Data Form widget. Use like any other widget. """
def __init__(self, dataformnode=None):
@ -32,25 +33,40 @@ class DataFormWidget(gtk.Alignment):
self.xml=gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_scrolledwindow')
self.instructions = self.xml.get_widget('form_instructions_label')
self.form = self.xml.get_widget('form_table')
self.container = self.xml.get_widget('container_vbox')
self.add(self.xml.get_widget('data_form_scrolledwindow'))
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. """
assert (isinstance(dataform, xmpp.Node) or (dataform is None))
assert isinstance(dataform, dataforms.DataForm)
if self._data_form is not None: self._cleanWidgets()
self.del_data_form()
self._data_form = dataform
if self._data_form is not None: self._buildWidgets()
if dataform.mode==dataforms.DATAFORM_SINGLE:
self.form = self.__class__.SingleForm(dataform)
else:
self.form = self.__class__.MultipleForm(dataform)
self.form.show()
self.container.pack_end(self.form)
def get_data_form(self):
""" Data form displayed in the widget or None if no form. """
return self._data_form
def del_data_form(self):
if self._data_form is not None:
self.container.remove(self.form)
self.form = None
self._data_form = None
data_form = property(get_data_form, set_data_form, del_data_form,
"Data form presented in a widget")
def get_title(self):
""" Get the title of data form, as a unicode object. If no
title or no form, returns u''. Useful for setting window title. """
@ -59,17 +75,19 @@ class DataFormWidget(gtk.Alignment):
return self._data_form['title'].encode('utf-8')
return u''
title = property(get_title, None, None, "Data form title")
def show(self):
""" Treat 'us' as one widget. """
self.show_all()
def filled_data_form(self):
""" Generates form that contains values filled by user. This
won't be DataForm object, as the DataFields seem to be uncapable
of some things. """
assert isinstance(self._data_form, xmpp.DataForm)
""" Generates form that contains values filled by user. """
assert isinstance(self._data_form, dataforms.DataForm)
form = xmpp.Node('x', {'xmlns':xmpp.NS_DATA, 'type':'submit'})
for field in self._data_form.kids:
if not isinstance(field, xmpp.DataField): continue
@ -96,158 +114,122 @@ class DataFormWidget(gtk.Alignment):
return form
data_form = property(get_data_form, set_data_form, None, "Data form presented in a widget")
title = property(get_title, None, None, "Data form title")
# "private" methods
def _buildWidgets(self):
""" Create all sub-widgets according to self._data_form and
JEP-0004. """
assert self._data_form is not None
assert len(self.form.get_children())==0
# it is *very* often used here
df = self._data_form
# we have actually two different kinds of data forms: one is a simple form to fill,
# second is a table with several records; we will treat second as read-only, but still
# we should have a way to show it
instructions = df.getInstructions()
if instructions is not None:
self.instructions.set_text(instructions)
# we will place both types in two different private classes, so the code will be clean;
# both will have the same interface
class SingleForm(gtk.Table, object):
""" Widget that represent DATAFORM_SINGLE mode form. """
def __init__(self, dataform):
assert dataform.mode==dataforms.DATAFORM_SINGLE
gtk.Table.__init__(self)
self._data_form = dataform
# building widget
linecounter = 0
for field in df.kids:
if not isinstance(field, xmpp.DataField): continue
# TODO: rewrite that when xmpp.DataField will be rewritten
ftype = field.getType()
if ftype not in ('boolean', 'fixed', 'hidden', 'jid-multi',
'jid-single', 'list-multi', 'list-single',
'text-multi', 'text-private', 'text-single'):
ftype = 'text-single'
if ftype == 'hidden': continue
# field label
flabel = field.getAttr('label')
if flabel is None:
flabel = field.getVar()
# field description
fdesc = field.getDesc()
# field value (if one)
fvalue = field.getValue()
# field values (if one or more)
fvalues = field.getValues()
# field options
foptions = field.getOptions()
# for each field...
for field in self._data_form.iter_fields():
if field.type=='hidden': continue
commonlabel = True
commondesc = True
commonwidget = True
widget = None
if ftype == 'boolean':
if field.type=='boolean':
widget = gtk.CheckButton()
widget.connect('toggled', self.on_boolean_checkbutton_toggled, field)
if fvalue in ('1', 'true'):
widget.set_active(True)
else:
field.setValue('0')
widget.set_active(field.value)
elif ftype == 'fixed':
elif field.type=='fixed':
leftattach = 1
rightattach = 2
if flabel is None:
if field.label is None:
commonlabel = False
leftattach = 0
if fdesc is None:
if field.description is None:
commondesc = False
rightattach = 3
commonwidget = False
widget = gtk.Label(fvalue)
commonwidget=False
widget = gtk.Label(field.value)
widget.set_line_wrap(True)
self.form.attach(widget, leftattach, rightattach, linecounter, linecounter+1)
self.attach(widget, leftattach, rightattach, linecounter, linecounter+1)
elif ftype == 'jid-multi':
widget = gtk.Label('jid-multi field')
elif field.type in ('jid-multi', 'jid-single', 'list-multi', 'text-multi',
'text-private'):
widget = gtk.Label(field.type)
elif ftype == 'jid-single':
widget = gtk.Label('jid-single field')
elif ftype == 'list-multi':
widget = gtk.Label('list-multi field')
elif ftype == 'list-single':
elif field.type == 'list-single':
# TODO: When more than few choices, make a list
# TODO: Think of moving that to another function (it could be used
# TODO: in stage2 of adhoc commands too).
# TODO: What if we have radio buttons and non-required field?
# TODO: We cannot deactivate them all...
widget = gtk.VBox()
first_radio = None
right_value = False
for label, value in foptions:
for label, value in field.iter_options():
radio = gtk.RadioButton(first_radio, label=label)
radio.connect('toggled', self.on_list_single_radiobutton_toggled,
field, value)
if first_radio is None:
first_radio = radio
first_value = value
if value == fvalue:
right_value = True
widget.pack_end(radio, expand=False)
if not right_value:
field.setValue(first_value)
if field.value is None:
field.value = value
if value == field.value:
radio.set_active(True)
widget.pack_start(radio, expand=False)
elif ftype == 'text-multi':
widget = gtk.Label('text-multi field')
elif ftype == 'text-private':
widget = gtk.Label('text-private field')
elif ftype == 'text-single':
else:# field.type == 'text-single' or field.type is nonstandard:
# JEP says that if we don't understand some type, we
# should handle it as text-single
widget = gtk.Entry()
widget.connect('changed', self.on_text_single_entry_changed, field)
if fvalue is None:
field.setValue('')
fvalue = ''
widget.set_text(fvalue)
if field.value is None:
field.value = u''
widget.set_text(field.value)
else:
widget = gtk.Label('Unhandled widget type!')
if commonlabel and flabel is not None:
label = gtk.Label(flabel)
if commonlabel and field.label is not None:
label = gtk.Label(field.label)
label.set_justify(gtk.JUSTIFY_RIGHT)
self.form.attach(label, 0, 1, linecounter, linecounter+1)
self.attach(label, 0, 1, linecounter, linecounter+1)
if commonwidget:
self.form.attach(widget, 1, 2, linecounter, linecounter+1)
assert widget is not None
self.attach(widget, 1, 2, linecounter, linecounter+1)
widget.show_all()
if commondesc and fdesc is not None:
label = gtk.Label(fdesc)
if commondesc and field.description is not None:
# TODO: with smaller font
label = gtk.Label(field.description)
label.set_line_wrap(True)
self.form.attach(label, 2, 3, linecounter, linecounter+1)
self.attach(label, 2, 3, linecounter, linecounter+1)
linecounter += 1
linecounter+=1
if self.get_property('visible'):
self.show_all()
self.form.show_all()
def _cleanWidgets(self):
""" Destroy all sub-widgets used to build current data form. """
def remove(widget):
self.form.remove(widget)
self.form.foreach(remove)
self.instructions.set_text(u"")
def show(self):
# simulate that we are one widget
self.show_all()
def on_boolean_checkbutton_toggled(self, widget, field):
if widget.get_active():
field.setValue('true')
else:
field.setValue('false')
field.value = widget.get_active()
def on_list_single_radiobutton_toggled(self, widget, field, value):
field.setValue(value)
field.value = value
def on_text_single_entry_changed(self, widget, field):
# TODO: check for encoding?
field.setValue(widget.get_text())
field.value = widget.get_text()
class MultipleForm(gtk.Alignment, object):
def __init__(self, dataform):
assert dataform.mode==dataforms.DATAFORM_MULTIPLE