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()
self.sessionid = command.getAttr('sessionid')
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,7 +37,10 @@ def del_multiple_tag_value(node, childname):
childname in node.
"""
while node.delChild(childname):
try:
while node.delChild(childname):
pass
except ValueError:
pass
def iter_elements(node, childname):
@ -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):
self.delChild('title')
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
self.delChild('recorded')
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:
self.delChild('recorded')
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):
self.delChild('desc')
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'))
self.set_data_form(dataformnode)
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
linecounter = 0
class SingleForm(gtk.Table, object):
""" Widget that represent DATAFORM_SINGLE mode form. """
def __init__(self, dataform):
assert dataform.mode==dataforms.DATAFORM_SINGLE
for field in df.kids:
if not isinstance(field, xmpp.DataField): continue
gtk.Table.__init__(self)
# 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'
self._data_form = dataform
if ftype == 'hidden': continue
# building widget
linecounter = 0
# field label
flabel = field.getAttr('label')
if flabel is None:
flabel = field.getVar()
# for each field...
for field in self._data_form.iter_fields():
if field.type=='hidden': continue
# field description
fdesc = field.getDesc()
commonlabel = True
commondesc = True
commonwidget = True
widget = None
# field value (if one)
fvalue = field.getValue()
if field.type=='boolean':
widget = gtk.CheckButton()
widget.connect('toggled', self.on_boolean_checkbutton_toggled, field)
widget.set_active(field.value)
# field values (if one or more)
fvalues = field.getValues()
elif field.type=='fixed':
leftattach = 1
rightattach = 2
if field.label is None:
commonlabel = False
leftattach = 0
if field.description is None:
commondesc = False
rightattach = 3
commonwidget=False
widget = gtk.Label(field.value)
widget.set_line_wrap(True)
self.attach(widget, leftattach, rightattach, linecounter, linecounter+1)
# field options
foptions = field.getOptions()
elif field.type in ('jid-multi', 'jid-single', 'list-multi', 'text-multi',
'text-private'):
widget = gtk.Label(field.type)
commonlabel = True
commondesc = True
commonwidget = True
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
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
if field.value is None:
field.value = value
if value == field.value:
radio.set_active(True)
widget.pack_start(radio, expand=False)
if ftype == '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')
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 field.value is None:
field.value = u''
widget.set_text(field.value)
elif ftype == 'fixed':
leftattach = 1
rightattach = 2
if flabel is None:
commonlabel = False
leftattach = 0
if fdesc is None:
commondesc = False
rightattach = 3
commonwidget = False
widget = gtk.Label(fvalue)
widget.set_line_wrap(True)
self.form.attach(widget, leftattach, rightattach, linecounter, linecounter+1)
if commonlabel and field.label is not None:
label = gtk.Label(field.label)
label.set_justify(gtk.JUSTIFY_RIGHT)
self.attach(label, 0, 1, linecounter, linecounter+1)
elif ftype == 'jid-multi':
widget = gtk.Label('jid-multi field')
if commonwidget:
assert widget is not None
self.attach(widget, 1, 2, linecounter, linecounter+1)
widget.show_all()
elif ftype == 'jid-single':
widget = gtk.Label('jid-single field')
if commondesc and field.description is not None:
# TODO: with smaller font
label = gtk.Label(field.description)
label.set_line_wrap(True)
self.attach(label, 2, 3, linecounter, linecounter+1)
elif ftype == 'list-multi':
widget = gtk.Label('list-multi field')
linecounter+=1
if self.get_property('visible'):
self.show_all()
elif ftype == 'list-single':
# TODO: When more than few choices, make a list
widget = gtk.VBox()
first_radio = None
right_value = False
for label, value in foptions:
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)
def show(self):
# simulate that we are one widget
self.show_all()
elif ftype == 'text-multi':
widget = gtk.Label('text-multi field')
def on_boolean_checkbutton_toggled(self, widget, field):
field.value = widget.get_active()
elif ftype == 'text-private':
widget = gtk.Label('text-private field')
def on_list_single_radiobutton_toggled(self, widget, field, value):
field.value = value
elif ftype == '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)
else:
widget = gtk.Label('Unhandled widget type!')
def on_text_single_entry_changed(self, widget, field):
field.value = widget.get_text()
if commonlabel and flabel is not None:
label = gtk.Label(flabel)
label.set_justify(gtk.JUSTIFY_RIGHT)
self.form.attach(label, 0, 1, linecounter, linecounter+1)
if commonwidget:
self.form.attach(widget, 1, 2, linecounter, linecounter+1)
if commondesc and fdesc is not None:
label = gtk.Label(fdesc)
label.set_line_wrap(True)
self.form.attach(label, 2, 3, linecounter, linecounter+1)
linecounter += 1
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 on_boolean_checkbutton_toggled(self, widget, field):
if widget.get_active():
field.setValue('true')
else:
field.setValue('false')
def on_list_single_radiobutton_toggled(self, widget, field, value):
field.setValue(value)
def on_text_single_entry_changed(self, widget, field):
# TODO: check for encoding?
field.setValue(widget.get_text())
class MultipleForm(gtk.Alignment, object):
def __init__(self, dataform):
assert dataform.mode==dataforms.DATAFORM_MULTIPLE