diff --git a/data/glade/adhoc_commands_window.glade b/data/glade/adhoc_commands_window.glade index 6d7a4b8b6..6a08e5dc7 100644 --- a/data/glade/adhoc_commands_window.glade +++ b/data/glade/adhoc_commands_window.glade @@ -28,6 +28,8 @@ + 400 + 400 True False False @@ -91,10 +93,56 @@ + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + True @@ -104,16 +152,16 @@ True - Choose command to execute: + <b>Choose command to execute:</b> False - False + True GTK_JUSTIFY_LEFT False False - 0.5 + 0.20000000298 0.5 0 - 0 + 6 PANGO_ELLIPSIZE_NONE -1 False @@ -128,6 +176,7 @@ + 12 True False 0 @@ -169,7 +218,7 @@ 0 - True + False True @@ -180,10 +229,56 @@ + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + True @@ -193,9 +288,9 @@ True - Fill in the form + <b>Fill in the form</b> False - False + True GTK_JUSTIFY_LEFT False False @@ -232,6 +327,7 @@ 0 False False + GTK_PACK_END @@ -241,10 +337,56 @@ + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + True @@ -310,8 +452,8 @@ 0 - True - True + False + False GTK_PACK_END @@ -322,10 +464,56 @@ + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + True @@ -335,9 +523,9 @@ True - An error has occured: + <b>An error has occured:</b> False - False + True GTK_JUSTIFY_LEFT False False @@ -388,9 +576,55 @@ + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + 0 diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py index 1282449cf..7519fee3b 100644 --- a/src/adhoc_commands.py +++ b/src/adhoc_commands.py @@ -29,6 +29,7 @@ import gtk from common import xmpp, gajim, dataforms import gtkgui_helpers +import dialogs import dataforms as dataformwidget class CommandWindow: @@ -80,18 +81,12 @@ class CommandWindow: self.xml.signal_autoconnect(self) self.window.show_all() - def on_adhoc_commands_window_destroy(self, window): - ''' The window dissappeared somehow... clean the environment, - Stop pulsing.''' - self.remove_pulsing() - # these functions are set up by appropriate stageX methods def stage_finish(self, *anything): pass def stage_cancel_button_clicked(self, *anything): assert False def stage_back_button_clicked(self, *anything): assert False def stage_forward_button_clicked(self, *anything): assert False def stage_execute_button_clicked(self, *anything): assert False - def stage_adhoc_commands_window_destroy(self, *anything): assert False def stage_adhoc_commands_window_delete_event(self, *anything): assert False def do_nothing(self, *anything): return False @@ -109,11 +104,15 @@ class CommandWindow: return self.stage_execute_button_clicked(*anything) def on_adhoc_commands_window_destroy(self, *anything): - return self.stage_adhoc_commands_window_destroy(*anything) + # do all actions that are needed to remove this object from memory... + self.remove_pulsing() def on_adhoc_commands_window_delete_event(self, *anything): return self.stage_adhoc_commands_window_delete_event(self, *anything) + def __del__(self): + print "Object has been deleted." + # stage 1: waiting for command list def stage1(self): '''Prepare the first stage. Request command list, @@ -141,7 +140,6 @@ class CommandWindow: self.stage_finish = self.stage1_finish 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): self.remove_pulsing() @@ -183,13 +181,12 @@ class CommandWindow: 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_start(radio, expand=False) self.command_list_vbox.show_all() self.stage_finish = self.stage2_finish self.stage_cancel_button_clicked = self.stage2_cancel_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): @@ -234,14 +231,29 @@ class CommandWindow: 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 + self.stage_adhoc_commands_window_delete_event = self.stage3_cancel_button_clicked def stage3_finish(self): pass - def stage3_cancel_button_clicked(self, widget): - pass + def stage3_cancel_button_clicked(self, widget, *anything): + ''' We are in the middle of executing command. Ask user if he really want to cancel + the process, then... cancel it. ''' + # this works also as a handler for window_delete_event, so we have to return appropriate + # values + # TODO: translate it + dialog = dialogs.HigDialog(self.window, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL, + gtk.BUTTONS_YES_NO, 'Cancel confirmation', + 'You are in process of executing command. Do you really want to cancel it?') + dialog.popup() + if dialog.get_response()==gtk.RESPONSE_YES: + self.send_cancel() + if widget==self.window: + return False + else: + self.window.destroy() + return False + return True def stage3_back_button_clicked(self, widget): self.stage3_submit_form('prev') @@ -256,6 +268,12 @@ class CommandWindow: self.data_form_widget.set_sensitive(False) if self.data_form_widget.get_data_form() is None: self.data_form_widget.hide() + + 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.sending_form_progressbar.show() self.setup_pulsing(self.sending_form_progressbar) self.send_command(action) @@ -272,19 +290,26 @@ class CommandWindow: self.dataform = dataforms.DataForm(node=command.getTag('x')) self.data_form_widget.set_sensitive(True) - self.data_form_widget.set_data_form(self.dataform) + try: + self.data_form_widget.data_form=self.dataform + except dataforms.BadDataFormNode: + # TODO: translate + self.stage5('Service sent malformed data', senderror=True) self.data_form_widget.show() action = command.getTag('action') if action is None: - # no action tag? that's last stage... - self.cancel_button.set_sensitive(False) + # no action tag? check if that's last stage... + if command.getAttr('status')=='completed': + self.cancel_button.set_sensitive(False) + else: + self.cancel_button.set_sensitive(True) 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.cancel_button.set_sensitive(True) 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) @@ -306,7 +331,6 @@ class CommandWindow: self.stage_finish = self.do_nothing 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_cancel_button_clicked(self, widget): @@ -316,12 +340,13 @@ class CommandWindow: self.stage1() # stage 5: an error has occured - def stage5(self, error): + def stage5(self, error, senderror=False): '''Display the error message. Wait for user to close the window''' + # TODO: sending error to responder # close old stage self.stage_finish() - assert isinstance(error, unicode) + assert isinstance(error, basestring) self.stages_notebook.set_current_page( self.stages_notebook.page_num( @@ -336,7 +361,6 @@ class CommandWindow: self.stage_finish = self.do_nothing 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_cancel_button_clicked(self, widget): @@ -419,3 +443,18 @@ class CommandWindow: print stanza self.account.connection.SendAndCallForResponse(stanza, callback) + + def send_cancel(self): + '''Send the command with action='cancel'. ''' + assert self.commandnode is not None + assert self.sessionid is not None + + stanza = xmpp.Iq(typ='set', to=self.jid) + stanza.addChild('command', attrs={ + 'xmlns':xmpp.NS_COMMANDS, + 'node':self.commandnode, + 'sessionid':self.sessionid, + 'action':'cancel' + }) + + self.account.connection.send(stanza) diff --git a/src/common/dataforms.py b/src/common/dataforms.py index 7113618ec..f1390bf05 100644 --- a/src/common/dataforms.py +++ b/src/common/dataforms.py @@ -78,9 +78,14 @@ class DataForm(xmpp.Node, object): if tofill is not None: self._mode = tofill.mode - self.fields = tofill.fields + self.fields = (field for field in tofill.fields if field.type!='fixed') self.records = tofill.records self.type = 'submit' + for field in self.iter_fields(): + field.required=False + 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: @@ -138,7 +143,7 @@ class DataForm(xmpp.Node, object): return self.getTagData('title') def set_title(self, title): - self.setTagData('title') + self.setTagData('title', title) def del_title(self): try: @@ -283,11 +288,16 @@ class DataForm(xmpp.Node, object): 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): + def __init__(self, typ=None,var=None, value=None, label=None, desc=None, + required=None, options=None, node=None): + assert typ in ('boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', 'list-multi', - 'list-single', 'text-multi', 'text-private', 'text-single', None) + 'list-single', 'text-multi', 'text-private', 'text-single',None) xmpp.Node.__init__(self, 'field', node=node) + if typ is not None: self.type = typ + if var is not None: self.var = var + if label is not None: self.label = label if desc is not None: self.description = desc if required is not None: self.required = required if value is not None: self.value = value @@ -309,7 +319,10 @@ class DataField(xmpp.Node, object): if typ!='text-single': self.setAttr('type', typ) else: - self.delAttr('type') + try: + self.delAttr('type') + except KeyError: + pass type = property(get_type, set_type, None, """ Field type. One of: 'boolean', 'fixed', 'hidden', 'jid-multi', 'jid-single', @@ -383,8 +396,8 @@ class DataField(xmpp.Node, object): elif self.type in ('jid-multi', 'list-multi'): return [value.getData() for value in self.getTags('value')] - - elif self.type in ('hidden', 'jid-single', 'list-single', 'text-single', 'text-private'): + + elif self.type in ('hidden', 'jid-single', 'list-single', 'text-single', 'text-private') or True: return self.getTagData('value') def set_value(self, value): @@ -399,8 +412,8 @@ class DataField(xmpp.Node, object): elif self.type in ('jid-multi', 'list-multi'): del_multiple_tag_value(self, 'value') - for item in self.value: - self.addChild('value', None, (item,)) + for item in value: + self.addChild('value', {}, (item,)) elif self.type in ('hidden', 'jid-single', 'list-single', 'text-single', 'text-private'): self.setTagData('value', value) @@ -417,6 +430,7 @@ class DataField(xmpp.Node, object): for element in self.getChildren(): if not isinstance(element, xmpp.Node): continue if not element.getName()=='option': continue + if element.getTag('value') is None: raise BadDataFormNode yield element.getAttr('label'), element.getTag('value').getData() def get_options(self): @@ -432,10 +446,10 @@ class DataField(xmpp.Node, object): assert option[0] is None or isinstance(option[0], unicode) assert isinstance(option[1], unicode) if option[0] is None: - attr=None + attr={} else: attr={'label': option[0].encode('utf-8')} - self.addChild('option', attr, (option[1].encode('utf-8'),)) + self.addChild('option', attr, (xmpp.Node('value', {}, (option[1].encode('utf-8'),)),)) def del_options(self): del_multiple_tag_value(self, 'option') diff --git a/src/dataforms.py b/src/dataforms.py index 3ae4c43ee..4d82a5885 100644 --- a/src/dataforms.py +++ b/src/dataforms.py @@ -52,7 +52,7 @@ class DataFormWidget(gtk.Alignment, object): else: self.form = self.__class__.MultipleForm(dataform) self.form.show() - self.container.pack_end(self.form) + self.container.pack_end(self.form, expand=True, fill=True) def get_data_form(self): """ Data form displayed in the widget or None if no form. """ @@ -81,38 +81,38 @@ class DataFormWidget(gtk.Alignment, object): """ Treat 'us' as one widget. """ self.show_all() - def filled_data_form(self): - """ 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 - - 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 +#? def filled_data_form(self): +#? """ 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 +#? +#? 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 # "private" methods @@ -164,8 +164,7 @@ class DataFormWidget(gtk.Alignment, object): widget.set_line_wrap(True) self.attach(widget, leftattach, rightattach, linecounter, linecounter+1) - elif field.type in ('jid-multi', 'jid-single', 'list-multi', 'text-multi', - 'text-private'): + elif field.type in ('jid-multi'): widget = gtk.Label(field.type) elif field.type == 'list-single': @@ -176,7 +175,7 @@ class DataFormWidget(gtk.Alignment, object): # TODO: We cannot deactivate them all... widget = gtk.VBox() first_radio = None - for label, value in field.iter_options(): + for value, label in field.iter_options(): radio = gtk.RadioButton(first_radio, label=label) radio.connect('toggled', self.on_list_single_radiobutton_toggled, field, value) @@ -188,6 +187,41 @@ class DataFormWidget(gtk.Alignment, object): radio.set_active(True) widget.pack_start(radio, expand=False) + elif field.type == 'list-multi': + # TODO: When more than few choices, make a list + widget = gtk.VBox() + for value, label in field.iter_options(): + check = gtk.CheckButton(label, use_underline=False) + check.set_active(value in field.value) + check.connect('toggled', self.on_list_multi_checkbutton_toggled, + field, value) + widget.pack_start(check, expand=False) + + elif field.type == 'jid-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 field.type == 'text-private': + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, field) + widget.set_visibility(False) + if field.value is None: + field.value = u'' + widget.set_text(field.value) + + elif field.type == 'text-multi': + # TODO: bigger text view + widget = gtk.TextView() + widget.set_wrap_mode(gtk.WRAP_WORD) + widget.get_buffer().connect('changed', self.on_text_multi_textbuffer_changed, + field) + if field.value is None: + field.value = u'' + widget.get_buffer().set_text(field.value) + 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 @@ -199,7 +233,7 @@ class DataFormWidget(gtk.Alignment, object): if commonlabel and field.label is not None: label = gtk.Label(field.label) - label.set_justify(gtk.JUSTIFY_RIGHT) + label.set_alignment(1.0, 0.5) self.attach(label, 0, 1, linecounter, linecounter+1) if commonwidget: @@ -208,8 +242,10 @@ class DataFormWidget(gtk.Alignment, object): widget.show_all() if commondesc and field.description is not None: - # TODO: with smaller font - label = gtk.Label(field.description) + label = gtk.Label() + label.set_markup(''+\ + gtkgui_helpers.escape_for_pango_markup(field.description)+\ + '') label.set_line_wrap(True) self.attach(label, 2, 3, linecounter, linecounter+1) @@ -227,9 +263,20 @@ class DataFormWidget(gtk.Alignment, object): def on_list_single_radiobutton_toggled(self, widget, field, value): field.value = value + def on_list_multi_checkbutton_toggled(self, widget, field, value): + if widget.get_active() and value not in field.value: + field.value.append(value) + elif not widget.get_active() and value in field.value: + field.value.remove(value) + def on_text_single_entry_changed(self, widget, field): field.value = widget.get_text() + def on_text_multi_textbuffer_changed(self, widget, field): + field.value = widget.get_text( + widget.get_start_iter(), + widget.get_end_iter()) + class MultipleForm(gtk.Alignment, object): def __init__(self, dataform): assert dataform.mode==dataforms.DATAFORM_MULTIPLE