Invoking commands works. It will be a long way to finish it, though...

As long as the responder uses only text-single, list-single and
boolean fields, it is possible to invoke and complete a command.
Other field types are not implemented now in dataforms.py.
This commit is contained in:
Tomasz Melcer 2006-06-25 17:03:59 +00:00
parent ce9cbe6f21
commit ecc0403981
3 changed files with 351 additions and 134 deletions

View file

@ -251,14 +251,13 @@
</child> </child>
<child> <child>
<widget class="GtkVBox" id="form_vbox"> <widget class="GtkTable" id="form_table">
<property name="visible">True</property> <property name="visible">True</property>
<property name="n_rows">1</property>
<property name="n_columns">3</property>
<property name="homogeneous">False</property> <property name="homogeneous">False</property>
<property name="spacing">0</property> <property name="row_spacing">0</property>
<property name="column_spacing">0</property>
<child>
<placeholder/>
</child>
</widget> </widget>
<packing> <packing>
<property name="padding">0</property> <property name="padding">0</property>

View file

@ -30,6 +30,7 @@ import common.xmpp as xmpp
import common.gajim as gajim import common.gajim as gajim
import gtkgui_helpers import gtkgui_helpers
import dataforms
class CommandWindow: class CommandWindow:
'''Class for a window for single ad-hoc commands session. Note, that '''Class for a window for single ad-hoc commands session. Note, that
@ -49,6 +50,11 @@ class CommandWindow:
self.pulse_id=None # to satisfy self.setup_pulsing() self.pulse_id=None # to satisfy self.setup_pulsing()
self.commandlist=None # a list of (commandname, commanddescription) self.commandlist=None # a list of (commandname, commanddescription)
# command's data
self.commandnode = None
self.sessionid = None
self.dataform = None
# retrieving widgets from xml # retrieving widgets from xml
self.xml = gtkgui_helpers.get_glade('adhoc_commands_window.glade') self.xml = gtkgui_helpers.get_glade('adhoc_commands_window.glade')
self.window = self.xml.get_widget('adhoc_commands_window') self.window = self.xml.get_widget('adhoc_commands_window')
@ -56,10 +62,15 @@ class CommandWindow:
'execute_button','stages_notebook', 'execute_button','stages_notebook',
'retrieving_commands_stage_vbox', 'retrieving_commands_stage_vbox',
'command_list_stage_vbox','command_list_vbox', 'command_list_stage_vbox','command_list_vbox',
'sending_form_stage_vbox','no_commands_stage_vbox', 'sending_form_stage_vbox','sending_form_progressbar',
'error_stage_vbox', 'error_description_label'): 'no_commands_stage_vbox','error_stage_vbox',
'error_description_label'):
self.__dict__[name] = self.xml.get_widget(name) self.__dict__[name] = self.xml.get_widget(name)
# creating data forms widget
self.data_form_widget = dataforms.DataFormWidget()
self.sending_form_stage_vbox.pack_start(self.data_form_widget)
# setting initial stage # setting initial stage
self.stage1() self.stage1()
@ -74,12 +85,32 @@ class CommandWindow:
# these functions are set up by appropriate stageX methods # these functions are set up by appropriate stageX methods
def stage_finish(self, *anything): pass def stage_finish(self, *anything): pass
def on_cancel_button_clicked(self, *anything): pass def stage_cancel_button_clicked(self, *anything): assert False
def on_back_button_clicked(self, *anything): pass def stage_back_button_clicked(self, *anything): assert False
def on_forward_button_clicked(self, *anything): pass def stage_forward_button_clicked(self, *anything): assert False
def on_execute_button_clicked(self, *anything): pass def stage_execute_button_clicked(self, *anything): assert False
def on_adhoc_commands_window_destroy(self, *anything): pass def stage_adhoc_commands_window_destroy(self, *anything): assert False
def do_nothing(self, *anything): pass def stage_adhoc_commands_window_delete_event(self, *anything): assert False
def do_nothing(self, *anything): return False
# widget callbacks
def on_cancel_button_clicked(self, *anything):
return self.stage_cancel_button_clicked(*anything)
def on_back_button_clicked(self, *anything):
return self.stage_back_button_clicked(*anything)
def on_forward_button_clicked(self, *anything):
return self.stage_forward_button_clicked(*anything)
def on_execute_button_clicked(self, *anything):
return self.stage_execute_button_clicked(*anything)
def on_adhoc_commands_window_destroy(self, *anything):
return self.stage_adhoc_commands_window_destroy(*anything)
def on_adhoc_commands_window_delete_event(self, *anything):
return self.stage_adhoc_commands_window_delete_event(self, *anything)
# stage 1: waiting for command list # stage 1: waiting for command list
def stage1(self): def stage1(self):
@ -106,17 +137,23 @@ class CommandWindow:
# setup the callbacks # setup the callbacks
self.stage_finish = self.stage1_finish self.stage_finish = self.stage1_finish
self.on_cancel_button_clicked = self.stage1_on_cancel_button_clicked self.stage_cancel_button_clicked = self.stage1_cancel_button_clicked
self.stage_adhoc_commands_window_delete_event = self.stage1_adhoc_commands_window_delete_event
self.stage_adhoc_commands_window_destroy = self.do_nothing
def stage1_finish(self): def stage1_finish(self):
self.remove_pulsing() self.remove_pulsing()
def stage1_on_cancel_button_clicked(self, widget): def stage1_cancel_button_clicked(self, widget):
# cancelling in this stage is not critical, so we don't # cancelling in this stage is not critical, so we don't
# show any popups to user # show any popups to user
self.stage1_finish() self.stage1_finish()
self.window.destroy() self.window.destroy()
def stage1_adhoc_commands_window_delete_event(self, widget):
self.stage1_finish()
return True
# stage 2: choosing the command to execute # stage 2: choosing the command to execute
def stage2(self): def stage2(self):
'''Populate the command list vbox with radiobuttons '''Populate the command list vbox with radiobuttons
@ -140,30 +177,115 @@ class CommandWindow:
first_radio = None first_radio = None
for (commandnode, commandname) in self.commandlist: for (commandnode, commandname) in self.commandlist:
radio = gtk.RadioButton(first_radio, label=commandname) radio = gtk.RadioButton(first_radio, label=commandname)
if first_radio is None: first_radio = radio radio.connect("toggled", self.on_command_radiobutton_toggled, commandnode)
if first_radio is None:
first_radio = radio
self.commandnode = commandnode
self.command_list_vbox.pack_end(radio, expand=False) self.command_list_vbox.pack_end(radio, expand=False)
self.command_list_vbox.show_all() self.command_list_vbox.show_all()
self.stage_finish = self.stage2_finish self.stage_finish = self.stage2_finish
self.on_cancel_button_clicked = self.stage2_on_cancel_button_clicked self.stage_cancel_button_clicked = self.stage2_cancel_button_clicked
self.on_forward_button_clicked = self.stage2_on_forward_button_clicked self.stage_forward_button_clicked = self.stage2_forward_button_clicked
self.stage_adhoc_commands_window_destroy = self.do_nothing
self.stage_adhoc_commands_window_delete_event = self.do_nothing
def stage2_finish(self): def stage2_finish(self):
'''Remove widgets we created.''' '''Remove widgets we created. Not needed when the window is destroyed.'''
def remove_widget(widget): def remove_widget(widget):
self.command_list_vbox.remove(widget) self.command_list_vbox.remove(widget)
self.command_list_vbox.foreach(remove_widget) self.command_list_vbox.foreach(remove_widget)
def stage2_on_cancel_button_clicked(self): def stage2_cancel_button_clicked(self, widget):
self.stage_finish() self.stage_finish()
self.window.destroy() self.window.destroy()
def stage2_on_forward_button_clicked(self): def stage2_forward_button_clicked(self, widget):
pass self.stage3()
def on_command_radiobutton_toggled(self, widget, commandnode):
self.commandnode = commandnode
def on_check_commands_1_button_clicked(self, widget): def on_check_commands_1_button_clicked(self, widget):
self.stage1() self.stage1()
# stage 3: command invocation
def stage3(self):
# close old stage
self.stage_finish()
assert isinstance(self.commandnode, unicode)
self.stages_notebook.set_current_page(
self.stages_notebook.page_num(
self.sending_form_stage_vbox))
self.cancel_button.set_sensitive(True)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(False)
self.stage3_submit_form()
self.stage_finish = self.stage3_finish
self.stage_cancel_button_clicked = self.stage3_cancel_button_clicked
self.stage_back_button_clicked = self.stage3_back_button_clicked
self.stage_forward_button_clicked = self.stage3_forward_button_clicked
self.stage_execute_button_clicked = self.stage3_execute_button_clicked
self.stage_adhoc_commands_window_destroy = self.do_nothing
self.stage_adhoc_commands_window_delete_event = self.do_nothing
def stage3_finish(self):
pass
def stage3_cancel_button_clicked(self, widget):
pass
def stage3_back_button_clicked(self, widget):
self.stage3_submit_form('prev')
def stage3_forward_button_clicked(self, widget):
self.stage3_submit_form('next')
def stage3_execute_button_clicked(self, widget):
self.stage3_submit_form('execute')
def stage3_submit_form(self, action='execute'):
self.data_form_widget.set_sensitive(False)
if self.data_form_widget.get_data_form() is None:
self.data_form_widget.hide()
self.sending_form_progressbar.show()
self.setup_pulsing(self.sending_form_progressbar)
self.send_command(action)
def stage3_next_form(self, command):
assert isinstance(command, xmpp.Node)
self.remove_pulsing()
self.sending_form_progressbar.hide()
self.sessionid = command.getAttr('sessionid')
self.dataform = xmpp.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)
action = command.getTag('action')
if action is None:
# no action tag? that's last stage...
self.cancel_button.set_sensitive(False)
self.back_button.set_sensitive(False)
self.forward_button.set_sensitive(False)
self.execute_button.set_sensitive(True)
else:
# actions, actions, actions...
self.cancel_button.set_sensitive(False)
self.back_button.set_sensitive(action.getTag('prev') is not None)
self.forward_button.set_sensitive(action.getTag('next') is not None)
self.execute_button.set_sensitive(True)
# stage 4: no commands are exposed # stage 4: no commands are exposed
def stage4(self): def stage4(self):
'''Display the message. Wait for user to close the window''' '''Display the message. Wait for user to close the window'''
@ -180,9 +302,11 @@ class CommandWindow:
self.execute_button.set_sensitive(False) self.execute_button.set_sensitive(False)
self.stage_finish = self.do_nothing self.stage_finish = self.do_nothing
self.on_cancel_button_clicked = self.stage4_on_cancel_button_clicked self.stage_cancel_button_clicked = self.stage4_cancel_button_clicked
self.stage_adhoc_commands_window_destroy = self.do_nothing
self.stage_adhoc_commands_window_delete_event = self.do_nothing
def stage4_on_cancel_button_clicked(self): def stage4_cancel_button_clicked(self, widget):
self.window.destroy() self.window.destroy()
def on_check_commands_2_button_clicked(self, widget): def on_check_commands_2_button_clicked(self, widget):
@ -208,9 +332,11 @@ class CommandWindow:
self.error_description_label.set_text(error) self.error_description_label.set_text(error)
self.stage_finish = self.do_nothing self.stage_finish = self.do_nothing
self.on_cancel_button_clicked = self.stage5_on_cancel_button_clicked self.stage_cancel_button_clicked = self.stage5_cancel_button_clicked
self.stage_adhoc_commands_window_destroy = self.do_nothing
self.stage_adhoc_commands_window_delete_event = self.do_nothing
def stage5_on_cancel_button_clicked(self): def stage5_cancel_button_clicked(self, widget):
self.window.destroy() self.window.destroy()
# helpers to handle pulsing in progressbar # helpers to handle pulsing in progressbar
@ -261,3 +387,30 @@ class CommandWindow:
self.stage2() self.stage2()
self.account.connection.SendAndCallForResponse(query, callback) self.account.connection.SendAndCallForResponse(query, callback)
def send_command(self, action='execute'):
'''Send the command with data form. Wait for reply.'''
# create the stanza
assert isinstance(self.commandnode, unicode)
assert action in ('execute', 'prev', 'next', 'complete')
stanza = xmpp.Iq(typ='set', to=self.jid)
cmdnode = stanza.addChild('command', attrs={
'xmlns':xmpp.NS_COMMANDS,
'node':self.commandnode,
'action':action
})
if self.sessionid is not None:
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())
def callback(response):
# TODO: error handling
self.stage3_next_form(response.getTag('command'))
print stanza
self.account.connection.SendAndCallForResponse(stanza, callback)

View file

@ -17,6 +17,9 @@
""" This module contains widget that can display data form (JEP-0004). """ """ This module contains widget that can display data form (JEP-0004). """
import gtk import gtk
import gtkgui_helpers
import common.xmpp as xmpp
class DataFormWidget(gtk.Alignment): class DataFormWidget(gtk.Alignment):
# "public" interface # "public" interface
@ -25,17 +28,21 @@ class DataFormWidget(gtk.Alignment):
""" Create a widget. """ """ Create a widget. """
gtk.Alignment.__init__(self) gtk.Alignment.__init__(self)
self._data_form = None
self.xml=gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_scrolledwindow') self.xml=gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_scrolledwindow')
self.instructions = self.xml.get_widget('form_instructions_label') self.instructions = self.xml.get_widget('form_instructions_label')
self.form = self.xml.get_widget('form_vbox') self.form = self.xml.get_widget('form_table')
self.add(self.xml.get_widget('data_form_scrolledwindow') self.add(self.xml.get_widget('data_form_scrolledwindow'))
self.set_data_form(dataform) self.set_data_form(dataformnode)
def set_data_form(self, dataform=None): def set_data_form(self, dataform=None):
""" 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. """ Set to None to erase the form. """
assert (isinstance(dataform, xmpp.Node) or (dataform is None))
if self._data_form is not None: self._cleanWidgets() if self._data_form is not None: self._cleanWidgets()
self._data_form = dataform self._data_form = dataform
if self._data_form is not None: self._buildWidgets() if self._data_form is not None: self._buildWidgets()
@ -56,6 +63,39 @@ class DataFormWidget(gtk.Alignment):
""" Treat 'us' as one widget. """ """ Treat 'us' as one widget. """
self.show_all() 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)
form = xmpp.Node('x', {'xmlns':xmpp.NS_DATA, 'type':'submit'})
for field in self._data_form.kids:
if not isinstance(field, xmpp.DataField): continue
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 in ('fixed',):
continue
newfield = xmpp.Node('field', {'var': field.getVar()})
if ftype in ('jid-multi', 'list-multi', 'text-multi'):
for value in field.getValues():
newvalue = xmpp.Node('value', {}, [value])
newfield.addChild(node=newvalue)
else:
newvalue = xmpp.Node('value', {}, [field.getValue()])
newfield.addChild(node=newvalue)
form.addChild(node=newfield)
return form
data_form = property(get_data_form, set_data_form, None, "Data form presented in a widget") 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") title = property(get_title, None, None, "Data form title")
@ -64,99 +104,131 @@ class DataFormWidget(gtk.Alignment):
""" Create all sub-widgets according to self._data_form and """ Create all sub-widgets according to self._data_form and
JEP-0004. """ JEP-0004. """
assert self._data_form is not None assert self._data_form is not None
assert length(self.form.get_children())==0 assert len(self.form.get_children())==0
# it is *very* often used here # it is *very* often used here
df = self._data_form df = self._data_form
if df.has_key('instructions'): instructions = df.getInstructions()
self.instructions.set_text(df['instructions']) if instructions is not None:
self.instructions.set_text(instructions)
i = -1 linecounter = 0
while df.has_key(i+1):
i += 1
if not df[i].has_key['type']:
continue
ctype = df[i]['type']
if ctype = 'hidden':
continue
hbox = gtk.HBox(spacing = 5) for field in df.kids:
label = gtk.Label('') if not isinstance(field, xmpp.DataField): continue
label.set_line_wrap(True)
label.set_alignment(0.0, 0.5) # TODO: rewrite that when xmpp.DataField will be rewritten
label.set_property('width_request', 150) ftype = field.getType()
hbox.pack_start(label, False) if ftype not in ('boolean', 'fixed', 'hidden', 'jid-multi',
if df[i].has_key('label'): 'jid-single', 'list-multi', 'list-single',
label.set_text(df[i]['label']) 'text-multi', 'text-private', 'text-single'):
if ctype == 'boolean': ftype = 'text-single'
desc = None
if df[i].has_key('desc'): if ftype == 'hidden': continue
desc = df[i]['desc']
widget = gtk.CheckButton(desc, False) # field label
activ = False flabel = field.getAttr('label')
if df[i].has_key('values'): if flabel is None:
activ = df[i]['values'][0] flabel = field.getVar()
widget.set_active(activ)
widget.connect('toggled', self.on_checkbutton_toggled, i) # field description
elif ctype == 'fixed': fdesc = field.getDesc()
widget = gtk.Label('\n'.join(df[i]['values']))
# field value (if one)
fvalue = field.getValue()
# field values (if one or more)
fvalues = field.getValues()
# field options
foptions = field.getOptions()
commonlabel = True
commondesc = True
commonwidget = True
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')
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) widget.set_line_wrap(True)
widget.set_alignment(0.0, 0.5) self.form.attach(widget, leftattach, rightattach, linecounter, linecounter+1)
elif ctype == 'jid-multi':
#FIXME elif ftype == 'jid-multi':
widget = gtk.Label('') widget = gtk.Label('jid-multi field')
elif ctype == 'jid-single':
#FIXME elif ftype == 'jid-single':
widget = gtk.Label('') widget = gtk.Label('jid-single field')
elif ctype == 'list-multi':
j = 0 elif ftype == 'list-multi':
widget = gtk.Table(1, 1) widget = gtk.Label('list-multi field')
while df[i]['options'].has_key(j):
widget.resize(j + 1, 1) elif ftype == 'list-single':
child = gtk.CheckButton(df[i]['options'][j]['label'], # TODO: When more than few choices, make a list
False) widget = gtk.VBox()
if df[i]['options'][j]['values'][0] in \ first_radio = None
df[i]['values']: right_value = False
child.set_active(True) for label, value in foptions:
child.connect('toggled', self.on_checkbutton_toggled2, i, j) radio = gtk.RadioButton(first_radio, label=label)
widget.attach(child, 0, 1, j, j+1) radio.connect('toggled', self.on_list_single_radiobutton_toggled,
j += 1 field, value)
elif ctype == 'list-single': if first_radio is None:
widget = gtk.combo_box_new_text() first_radio = radio
widget.connect('changed', self.on_combobox_changed, i) first_value = value
index = 0 if value == fvalue:
j = 0 right_value = True
while df[i]['options'].has_key(j): widget.pack_end(radio, expand=False)
if df[i]['options'][j]['values'][0] == \ if not right_value:
df[i]['values'][0]: field.setValue(first_value)
index = j
widget.append_text(df[i]['options'][j]['label']) elif ftype == 'text-multi':
j += 1 widget = gtk.Label('text-multi field')
widget.set_active(index)
elif ctype == 'text-multi': elif ftype == 'text-private':
widget = gtk.TextView() widget = gtk.Label('text-private field')
widget.set_size_request(100, -1)
widget.get_buffer().connect('changed', self.on_textbuffer_changed, \ elif ftype == 'text-single':
i)
widget.get_buffer().set_text('\n'.join(df[i]['values']))
elif ctype == 'text-private':
widget = gtk.Entry() widget = gtk.Entry()
widget.connect('changed', self.on_entry_changed, i) widget.connect('changed', self.on_text_single_entry_changed, field)
if not df[i].has_key('values'): if fvalue is None:
df[i]['values'] = [''] field.setValue('')
widget.set_text(df[i]['values'][0]) fvalue = ''
widget.set_visibility(False) widget.set_text(fvalue)
elif ctype == 'text-single':
widget = gtk.Entry() else:
widget.connect('changed', self.on_entry_changed, i) widget = gtk.Label('Unhandled widget type!')
if not df[i].has_key('values'):
df[i]['values'] = [''] if commonlabel and flabel is not None:
widget.set_text(df[i]['values'][0]) label = gtk.Label(flabel)
hbox.pack_start(widget, False) label.set_justify(gtk.JUSTIFY_RIGHT)
hbox.pack_start(gtk.Label('')) # So that widhet doesn't take all space self.form.attach(label, 0, 1, linecounter, linecounter+1)
self.form.pack_start(hbox, False)
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() self.form.show_all()
def _cleanWidgets(self): def _cleanWidgets(self):
@ -165,24 +237,17 @@ class DataFormWidget(gtk.Alignment):
self.form.remove(widget) self.form.remove(widget)
self.form.foreach(remove) self.form.foreach(remove)
self.instructions.set_text(u"")
def on_checkbutton_toggled(self, widget, index): def on_boolean_checkbutton_toggled(self, widget, field):
self.config[index]['values'][0] = widget.get_active() if widget.get_active():
field.setValue('true')
else:
field.setValue('false')
def on_checkbutton_toggled2(self, widget, index1, index2): def on_list_single_radiobutton_toggled(self, widget, field, value):
val = self._data_form[index1]['options'][index2]['values'][0] field.setValue(value)
if widget.get_active() and val not in self._data_form[index1]['values']:
self._data_form[index1]['values'].append(val)
elif not widget.get_active() and val in self._data_form[index1]['values']:
self._data_form[index1]['values'].remove(val)
def on_combobox_changed(self, widget, index): def on_text_single_entry_changed(self, widget, field):
self._data_form[index]['values'][0] = self.config[index]['options'][ \ # TODO: check for encoding?
widget.get_active()]['values'][0] field.setValue(widget.get_text())
def on_entry_changed(self, widget, index):
self._data_form[index]['values'][0] = widget.get_text().decode('utf-8')
def on_textbuffer_changed(self, widget, index):
begin, end = widget.get_bounds()
self._data_form[index]['values'][0] = widget.get_text(begin, end)