diff --git a/gajim/common/modules/adhoc_commands.py b/gajim/common/modules/adhoc_commands.py index 93e856c97..2650f4549 100644 --- a/gajim/common/modules/adhoc_commands.py +++ b/gajim/common/modules/adhoc_commands.py @@ -296,6 +296,13 @@ class LeaveGroupchatsCommand(AdHocCommand): class AdHocCommands(BaseModule): + + _nbxmpp_extends = 'AdHoc' + _nbxmpp_methods = [ + 'request_command_list', + 'execute_command', + ] + def __init__(self, con): BaseModule.__init__(self, con) @@ -469,38 +476,6 @@ class AdHocCommands(BaseModule): raise nbxmpp.NodeProcessed - def request_command_list(self, jid): - """ - Request the command list. - """ - self._log.info('Request Command List: %s', jid) - query = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_ITEMS) - query.setQuerynode(nbxmpp.NS_COMMANDS) - - self._con.connection.SendAndCallForResponse( - query, self._command_list_received) - - def _command_list_received(self, stanza): - if not nbxmpp.isResultNode(stanza): - self._log.info('Error: %s', stanza.getError()) - - app.nec.push_incoming_event( - AdHocCommandError(None, conn=self._con, - error=stanza.getError())) - return - - items = stanza.getQueryPayload() - commandlist = [] - if items: - commandlist = [ - (t.getAttr('node'), t.getAttr('name')) for t in items - ] - - self._log.info('Received: %s', commandlist) - app.nec.push_incoming_event( - AdHocCommandListReceived( - None, conn=self._con, commandlist=commandlist)) - def send_command(self, jid, node, session_id, form, action='execute'): """ @@ -563,10 +538,6 @@ class AdHocCommandError(NetworkIncomingEvent): name = 'adhoc-command-error' -class AdHocCommandListReceived(NetworkIncomingEvent): - name = 'adhoc-command-list' - - class AdHocCommandActionResponse(NetworkIncomingEvent): name = 'adhoc-command-action-response' diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css index fdc9caee3..4cb9356eb 100644 --- a/gajim/data/style/gajim.css +++ b/gajim/data/style/gajim.css @@ -244,10 +244,15 @@ button.flat.link { padding: 0; border: 0; } /*Dataforms*/ .field-fixed { font-size: 16px; font-weight: bold; padding-top:5px;} .data-form-title { font-size: 16px; font-weight: bold; } -.data-form-widget grid { margin: 18px; } +.data-form-widget grid { margin: 0px 18px 18px 18px; } .data-form-widget treeview { padding: 5px; } .data-form-widget scrolledwindow { border: 1px solid; border-color:@unfocused_borders; } .data-form-widget textview { padding: 5px; } /*Image Preview*/ .preview-image { box-shadow: 0px 0px 3px 0px alpha(@theme_text_color, 0.2); } + + +/* Treeview */ +.adhoc-treeview { padding: 5px; } +.adhoc-scrolled { border: 1px solid; border-color:@unfocused_borders; } diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index a0bdb0808..740633cc9 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -79,7 +79,7 @@ from gajim.gtk.filechoosers import AvatarChooserDialog from gajim.gtk.add_contact import AddNewContactWindow from gajim.gtk.tooltips import GCTooltip from gajim.gtk.groupchat_config import GroupchatConfig -from gajim.gtk.adhoc_commands import CommandWindow +from gajim.gtk.adhoc import AdHocCommand from gajim.gtk.dataform import DataFormWidget from gajim.gtk.util import NickCompletionGenerator from gajim.gtk.util import get_icon_name @@ -735,7 +735,7 @@ class GroupchatControl(ChatControlBase): """ Execute AdHoc commands on the current room """ - CommandWindow(self.account, self.room_jid) + AdHocCommand(self.account, self.room_jid) def _on_upload_avatar(self, action, param): def _on_accept(filename): @@ -2815,7 +2815,7 @@ class GroupchatControl(ChatControlBase): def _on_execute_command_occupant(self, widget, nick): jid = self.room_jid + '/' + nick - CommandWindow(self.account, jid) + AdHocCommand(self.account, jid) def on_row_activated(self, widget, path): """ diff --git a/gajim/gtk/adhoc.py b/gajim/gtk/adhoc.py new file mode 100644 index 000000000..208767153 --- /dev/null +++ b/gajim/gtk/adhoc.py @@ -0,0 +1,461 @@ +# Copyright (C) 2019 Philipp Hörist +# +# This file is part of the OpenPGP Gajim Plugin. +# +# OpenPGP Gajim Plugin is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# OpenPGP Gajim Plugin is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with OpenPGP Gajim Plugin. If not, see . + +import logging +from enum import IntEnum + +from gi.repository import Gtk +from nbxmpp.util import is_error_result +from nbxmpp.const import AdHocAction + +from gajim.common import app +from gajim.common.i18n import _ + +from gajim.common.modules import dataforms + +from gajim.gtk.dataform import DataFormWidget +from gajim.gtk.util import ensure_not_destroyed +from gajim.gtk.util import find_widget +from gajim.gtk.util import MultiLineLabel + +log = logging.getLogger('gajim.gtk.adhoc') + + +class Page(IntEnum): + REQUEST = 0 + EXECUTE = 1 + COMMANDS = 2 + STAGE = 3 + COMPLETED = 4 + ERROR = 5 + + +class AdHocCommand(Gtk.Assistant): + def __init__(self, account, jid=None): + Gtk.Assistant.__init__(self) + + self._con = app.connections[account] + self._account = account + self._destroyed = False + + self.set_application(app.app) + self.set_resizable(True) + self.set_position(Gtk.WindowPosition.CENTER) + + self.set_default_size(600, 400) + self.get_style_context().add_class('dialog-margin') + + self._add_page(Request()) + self._add_page(ExecuteCommand()) + self._add_page(Commands()) + self._add_page(Stage()) + self._add_page(Completed()) + self._add_page(Error()) + + self.connect('prepare', self._on_page_change) + self.connect('cancel', self._on_cancel) + self.connect('close', self._on_cancel) + self.connect('destroy', self._on_destroy) + + self._remove_sidebar() + + self._buttons = {} + self._add_custom_buttons() + + self.show() + self._con.get_module('AdHocCommands').request_command_list( + jid, callback=self._received_command_list) + + def _add_custom_buttons(self): + action_area = find_widget('action_area', self) + for button in list(action_area.get_children()): + self.remove_action_widget(button) + + cancel = Gtk.Button(label=_('Cancel')) + cancel.connect('clicked', self._abort) + cancel.get_style_context().add_class('destructive-action') + self._buttons['cancel'] = cancel + self.add_action_widget(cancel) + + complete = Gtk.Button(label=_('Finish')) + complete.action = AdHocAction.COMPLETE + complete.connect('clicked', self._execute_action) + self._buttons['complete'] = complete + self.add_action_widget(complete) + + commands = Gtk.Button(label=_('Commands')) + commands.connect('clicked', + lambda *args: self.set_current_page(Page.COMMANDS)) + self._buttons['commands'] = commands + self.add_action_widget(commands) + + next_ = Gtk.Button(label=_('Next')) + next_.action = AdHocAction.NEXT + next_.connect('clicked', self._execute_action) + self._buttons['next'] = next_ + self.add_action_widget(next_) + + prev = Gtk.Button(label=_('Previous')) + prev.action = AdHocAction.PREV + prev.connect('clicked', self._execute_action) + self._buttons['prev'] = prev + self.add_action_widget(prev) + + execute = Gtk.Button(label=_('Execute')) + execute.action = AdHocAction.EXECUTE + execute.get_style_context().add_class('suggested-action') + execute.connect('clicked', self._execute_action) + self._buttons['execute'] = execute + self.add_action_widget(execute) + + def _set_button_visibility(self, page): + for button in self._buttons.values(): + button.hide() + + if page == Page.COMMANDS: + self._buttons['execute'].show() + + elif page == Page.STAGE: + self._buttons['cancel'].show() + stage_page = self.get_nth_page(page) + if not stage_page.actions: + self._buttons['complete'].show() + else: + for action in stage_page.actions: + button = self._buttons.get(action.value) + if button is not None: + button.show() + + elif page in (Page.ERROR, Page.COMPLETED): + self._buttons['commands'].show() + + def _add_page(self, page): + self.append_page(page) + self.set_page_type(page, page.type_) + self.set_page_title(page, page.title) + self.set_page_complete(page, page.complete) + + def execute_action(self): + self._execute_action(self._buttons['execute']) + + def _execute_action(self, button): + action = button.action + current_page = self.get_current_page() + dataform = None + if action == AdHocAction.EXECUTE: + command = self.get_nth_page(current_page).get_selected_command() + else: + command, dataform = self.get_nth_page(current_page).stage_data + if action == AdHocAction.PREV: + dataform = None + + self.set_current_page(Page.EXECUTE) + if current_page == Page.STAGE: + self.get_nth_page(current_page).clear() + self._con.get_module('AdHocCommands').execute_command( + command, + action=action, + dataform=dataform, + callback=self._received_stage) + + def _abort(self, *args): + if self.get_current_page() == Page.STAGE: + command = self.get_nth_page(Page.STAGE).stage_data[0] + self._con.get_module('AdHocCommands').execute_command( + command, AdHocAction.CANCEL) + self.set_current_page(Page.COMMANDS) + + @ensure_not_destroyed + def _received_command_list(self, commands): + if is_error_result(commands): + self.set_current_page(Page.ERROR) + return + self.get_nth_page(Page.COMMANDS).add_commands(commands) + self.set_current_page(Page.COMMANDS) + + @ensure_not_destroyed + def _received_stage(self, stage): + if is_error_result(stage): + self.get_nth_page(Page.ERROR).set_text(str(stage)) + self.set_current_page(Page.ERROR) + return + + page = Page.STAGE + if stage.is_completed: + page = Page.COMPLETED + + stage_page = self.get_nth_page(page) + stage_page.process_stage(stage) + self.set_current_page(page) + + def set_stage_complete(self, is_valid): + self._buttons['next'].set_sensitive(is_valid) + self._buttons['complete'].set_sensitive(is_valid) + + def _remove_sidebar(self): + main_box = self.get_children()[0] + sidebar = main_box.get_children()[0] + main_box.remove(sidebar) + + def _on_page_change(self, _assistant, _page): + self._set_button_visibility(self.get_current_page()) + + def _on_error(self, error_text): + log.info('Show Error page') + page = self.get_nth_page(Page.ERROR) + page.set_text(error_text) + self.set_current_page(Page.ERROR) + + def _on_cancel(self, _widget): + self.destroy() + + def _on_destroy(self, *args): + self._destroyed = True + + +class Request(Gtk.Box): + + type_ = Gtk.AssistantPageType.CUSTOM + title = _('Request Command List') + complete = False + + def __init__(self): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(18) + spinner = Gtk.Spinner() + self.pack_start(spinner, True, True, 0) + spinner.start() + self.show_all() + + +class ExecuteCommand(Request): + + type_ = Gtk.AssistantPageType.CUSTOM + title = _('Executing…') + complete = False + + +class Commands(Gtk.Box): + + type_ = Gtk.AssistantPageType.CUSTOM + title = _('Command List') + complete = True + + def __init__(self): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(18) + self._commands = {} + self._scrolled = Gtk.ScrolledWindow() + self._scrolled.get_style_context().add_class('adhoc-scrolled') + self._scrolled.set_max_content_height(400) + self._scrolled.set_max_content_width(400) + self._scrolled.set_policy(Gtk.PolicyType.NEVER, + Gtk.PolicyType.AUTOMATIC) + self._treeview = Gtk.TreeView() + self._treeview.get_style_context().add_class('adhoc-treeview') + self._store = Gtk.ListStore(str, str) + self._treeview.set_model(self._store) + column = Gtk.TreeViewColumn(_('Commands')) + column.set_expand(True) + self._treeview.append_column(column) + renderer = Gtk.CellRendererText() + column.pack_start(renderer, True) + column.add_attribute(renderer, 'text', 0) + + self._treeview.connect('row-activated', self._on_row_activate) + self._treeview.set_search_equal_func(self._search_func) + + self._scrolled.add(self._treeview) + self.pack_start(self._scrolled, True, True, 0) + self.show_all() + + def _search_func(self, model, column, search_text, iter_): + return search_text.lower() not in model[iter_][0].lower() + + def _on_row_activate(self, tree_view, path, column): + self.get_toplevel().execute_action() + + def add_commands(self, commands): + self._store.clear() + self._commands = {} + for command in commands: + key = '%s:%s' % (command.jid, command.node) + self._commands[key] = command + self._store.append((command.name, key)) + + def get_selected_command(self): + model, treeiter = self._treeview.get_selection().get_selected() + key = model[treeiter][1] + return self._commands[key] + + +class Stage(Gtk.Box): + + type_ = Gtk.AssistantPageType.CUSTOM + title = _('Settings') + complete = True + + def __init__(self): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(18) + self._dataform_widget = None + self._notes = [] + self._last_stage_data = None + self.show_all() + + @property + def stage_data(self): + return self._last_stage_data, self._dataform_widget.get_submit_form() + + @property + def actions(self): + return self._last_stage_data.actions + + def clear(self): + self._show_form(None) + self._show_notes(None) + self._last_stage_data = None + + def process_stage(self, stage_data): + self._last_stage_data = stage_data + self._show_notes(stage_data.notes) + self._show_form(stage_data.data) + + def _show_form(self, form): + if self._dataform_widget is not None: + self.remove(self._dataform_widget) + self._dataform_widget.destroy() + if form is None: + return + form = dataforms.extend_form(node=form) + self._dataform_widget = DataFormWidget(form) + self._dataform_widget.connect('is-valid', self._on_is_valid) + self._dataform_widget.validate() + self._dataform_widget.show_all() + self.add(self._dataform_widget) + + def _show_notes(self, notes): + for note in self._notes: + self.remove(note) + self._notes = [] + + if notes is None: + return + + for note in notes: + label = Gtk.Label(label=note.text) + label.show() + self._notes.append(label) + self.add(label) + + def _on_is_valid(self, _widget, is_valid): + self.get_toplevel().set_stage_complete(is_valid) + + +class Completed(Gtk.Box): + + type_ = Gtk.AssistantPageType.CUSTOM + title = _('Finished') + complete = True + + def __init__(self): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(12) + self._notes = [] + self._dataform_widget = None + + icon = Gtk.Image.new_from_icon_name('object-select-symbolic', + Gtk.IconSize.DIALOG) + icon.get_style_context().add_class('success-color') + icon.show() + + label = Gtk.Label(label='Finished') + label.show() + + self._icon_text = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self._icon_text.set_spacing(12) + self._icon_text.set_halign(Gtk.Align.CENTER) + self._icon_text.add(icon) + self._icon_text.add(label) + self.add(self._icon_text) + + self.show_all() + + def process_stage(self, stage_data): + self._show_notes(stage_data.notes) + self._show_form(stage_data.data) + self._show_icon_text(stage_data.data is None) + + def _show_icon_text(self, show): + if show: + self.set_valign(Gtk.Align.CENTER) + self._icon_text.show_all() + else: + self.set_valign(Gtk.Align.FILL) + self._icon_text.hide() + + def _show_form(self, form): + if self._dataform_widget is not None: + self.remove(self._dataform_widget) + self._dataform_widget.destroy() + if form is None: + return + + form = dataforms.extend_form(node=form) + + self._dataform_widget = DataFormWidget( + form, options={'read-only': True}) + self._dataform_widget.show_all() + self.add(self._dataform_widget) + + def _show_notes(self, notes): + for note in self._notes: + self.remove(note) + self._notes = [] + + for note in notes: + label = MultiLineLabel(label=note.text) + label.set_justify(Gtk.Justification.CENTER) + label.show() + self._notes.append(label) + self.add(label) + + +class Error(Gtk.Box): + + type_ = Gtk.AssistantPageType.CUSTOM + title = _('Execution failed') + complete = True + + def __init__(self): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(12) + self.set_homogeneous(True) + + icon = Gtk.Image.new_from_icon_name('dialog-error-symbolic', + Gtk.IconSize.DIALOG) + icon.get_style_context().add_class('error-color') + icon.set_valign(Gtk.Align.END) + self._label = Gtk.Label() + self._label.get_style_context().add_class('bold16') + self._label.set_valign(Gtk.Align.START) + + self.add(icon) + self.add(self._label) + self.show_all() + + def set_text(self, text): + self._label.set_text(text) diff --git a/gajim/gtk/adhoc_commands.py b/gajim/gtk/adhoc_commands.py deleted file mode 100644 index 1cc5e0d69..000000000 --- a/gajim/gtk/adhoc_commands.py +++ /dev/null @@ -1,557 +0,0 @@ -# Copyright (C) 2006 Nikos Kouremenos -# Copyright (C) 2006-2007 Tomasz Melcer -# Copyright (C) 2006-2014 Yann Leboulanger -# Copyright (C) 2008 Jonathan Schleifer -# Stephan Erb -# -# This file is part of Gajim. -# -# Gajim is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# Gajim is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Gajim. If not, see . - -# FIXME: think if we need caching command list. it may be wrong if there will -# be entities that often change the list, it may be slow to fetch it every time - -from gi.repository import Gtk -import nbxmpp - -from gajim.common import app -from gajim.common.i18n import _ -from gajim.common.modules import dataforms -from gajim.common import ged - -from gajim import dataforms_widget - -from gajim.gtk.dialogs import HigDialog -from gajim.gtk.util import get_builder - - -class CommandWindow: - """ - Class for a window for single ad-hoc commands session - - Note, that there might be more than one for one account/jid pair in one - moment. - - TODO: Maybe put this window into MessageWindow? consider this when it will - be possible to manage more than one window of one. - TODO: Account/jid pair in MessageWindowMgr. - TODO: GTK 2.10 has a special wizard-widget, consider using it... - """ - - def __init__(self, account, jid, commandnode=None): - """ - Create new window - """ - - # an account object - self._con = app.connections[account] - self.jid = jid - self.commandnode = commandnode - self.data_form_widget = None - self.stage_finish_cb = None - self.stage_back_button_cb = None - self.stage_forward_button_cb = None - self.stage_execute_button_cb = None - self.stage_finish_button_cb = None - self.stage_close_button_cb = None - self.stage_restart_button_cb = None - self.stage_window_delete_cb = None - - # retrieving widgets from xml - self.xml = get_builder('adhoc_commands_window.ui') - self.window = self.xml.get_object('adhoc_commands_window') - self.window.connect('delete-event', - self.on_adhoc_commands_window_delete_event) - for name in ('restart_button', 'back_button', 'forward_button', - 'execute_button', 'finish_button', 'close_button', 'stages_notebook', - 'retrieving_commands_stage_vbox', 'retrieving_commands_spinner', - 'command_list_stage_vbox', 'command_treeview', - 'sending_form_stage_vbox', 'sending_form_spinner', - 'notes_label', 'no_commands_stage_vbox', - 'error_stage_vbox', 'error_description_label'): - setattr(self, name, self.xml.get_object(name)) - - self.command_store = Gtk.ListStore(str, str) - self.command_treeview.set_model(self.command_store) - renderer = Gtk.CellRendererText() - column = Gtk.TreeViewColumn("Command", renderer, text=0) - self.command_treeview.append_column(column) - - app.ged.register_event_handler( - 'adhoc-command-error', ged.CORE, self._on_command_error) - app.ged.register_event_handler( - 'adhoc-command-list', ged.CORE, self._on_command_list) - app.ged.register_event_handler('adhoc-command-action-response', - ged.CORE, - self._on_action_response) - - self.initiate() - - def initiate(self): - - self.commandlist = None # a list of (commandname, commanddescription) - - # command's data - self.sessionid = None - self.dataform = None - self.allow_stage3_close = False - - # creating data forms widget - if self.data_form_widget: - self.sending_form_stage_vbox.remove(self.data_form_widget) - self.data_form_widget.destroy() - self.data_form_widget = dataforms_widget.DataFormWidget() - self.data_form_widget.show() - self.sending_form_stage_vbox.pack_start(self.data_form_widget, True, True, 0) - - if self.commandnode: - # Execute command - self.stage3() - else: - # setting initial stage - self.stage1() - - # displaying the window - self.window.set_title(_('Ad-hoc Commands - Gajim')) - self.xml.connect_signals(self) - self.window.show_all() - - self.restart_button.set_sensitive(False) - - # These functions are set up by appropriate stageX methods. - def stage_finish(self, *anything): - if self.stage_finish_cb: - self.stage_finish_cb(*anything) - - # Widget callbacks... - def on_back_button_clicked(self, *anything): - if self.stage_back_button_cb: - return self.stage_back_button_cb(*anything) - return False - - def on_forward_button_clicked(self, *anything): - if self.stage_forward_button_cb: - return self.stage_forward_button_cb(*anything) - return False - - def on_execute_button_clicked(self, *anything): - if self.stage_execute_button_cb: - return self.stage_execute_button_cb(*anything) - return False - - def on_finish_button_clicked(self, *anything): - if self.stage_finish_button_cb: - return self.stage_finish_button_cb(*anything) - return False - - def on_close_button_clicked(self, *anything): - if self.stage_close_button_cb: - return self.stage_close_button_cb(*anything) - return False - - def on_restart_button_clicked(self, *anything): - if self.stage_restart_button_cb: - return self.stage_restart_button_cb(*anything) - return False - - def on_adhoc_commands_window_destroy(self, *anything): - app.ged.remove_event_handler( - 'adhoc-command-error', ged.CORE, self._on_command_error) - app.ged.remove_event_handler( - 'adhoc-command-list', ged.CORE, self._on_command_list) - app.ged.remove_event_handler('adhoc-command-action-response', - ged.CORE, - self._on_action_response) - - def on_adhoc_commands_window_delete_event(self, *anything): - if self.stage_window_delete_cb: - return self.stage_window_delete_cb(self.window) - return False - - def __del__(self): - print('Object has been deleted.') - -# stage 1: waiting for command list - def stage1(self): - """ - Prepare the first stage. Request command list, set appropriate state of - widgets - """ - # close old stage... - self.stage_finish() - - # show the stage - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.retrieving_commands_stage_vbox)) - - # set widgets' state - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) - self.finish_button.set_sensitive(False) - - # request command list - self._con.get_module('AdHocCommands').request_command_list(self.jid) - self.retrieving_commands_spinner.start() - - # setup the callbacks - self.stage_finish_cb = self.stage1_finish - self.stage_close_button_cb = self.stage1_close_button_clicked - self.stage_restart_button_cb = self.stage1_restart_button_clicked - self.stage_window_delete_cb = \ - self.stage1_adhoc_commands_window_delete_event - - def stage1_finish(self): - self.retrieving_commands_spinner.stop() - - def stage1_close_button_clicked(self, widget): - # cancelling in this stage is not critical, so we don't - # show any popups to user - self.stage_finish() - self.window.destroy() - - def stage1_restart_button_clicked(self, widget): - self.stage_finish() - self.restart() - - def stage1_adhoc_commands_window_delete_event(self, widget): - self.stage1_finish() - return True - -# stage 2: choosing the command to execute - def stage2(self): - """ - Populate the command list - """ - # close old stage - self.stage_finish() - - assert self.commandlist - - self.stages_notebook.set_current_page( - self.stages_notebook.page_num(self.command_list_stage_vbox)) - - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(True) - self.execute_button.set_sensitive(False) - self.finish_button.set_sensitive(False) - - # build the commands list - self.command_store.clear() - for (commandnode, commandname) in self.commandlist: - self.command_store.append([commandname, commandnode]) - self.command_treeview.get_selection().select_iter(self.command_store.get_iter_first()) - - self.stage_finish_cb = self.stage2_finish - self.stage_close_button_cb = self.stage2_close_button_clicked - self.stage_restart_button_cb = self.stage2_restart_button_clicked - self.stage_forward_button_cb = self.stage2_forward_button_clicked - self.stage_window_delete_cb = None - - def stage2_finish(self): - """ - Save selected command to self.commandnode - """ - model, treeiter = self.command_treeview.get_selection().get_selected() - self.commandnode = model[treeiter][1] - - def stage2_close_button_clicked(self, widget): - self.stage_finish() - self.window.destroy() - - def stage2_restart_button_clicked(self, widget): - self.stage_finish() - self.restart() - - def stage2_forward_button_clicked(self, widget): - self.stage3() - - def on_check_commands_1_button_clicked(self, widget): - self.stage1() - -# stage 3: command invocation - def stage3(self): - # close old stage - self.stage_finish() - - self.form_status = None - - self.stages_notebook.set_current_page( - self.stages_notebook.page_num( - self.sending_form_stage_vbox)) - - self.restart_button.set_sensitive(True) - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) - self.finish_button.set_sensitive(False) - - self.stage3_submit_form() - - self.stage_finish_cb = None - self.stage_back_button_cb = self.stage3_back_button_clicked - self.stage_forward_button_cb = self.stage3_forward_button_clicked - self.stage_execute_button_cb = self.stage3_execute_button_clicked - self.stage_finish_button_cb = self.stage3_finish_button_clicked - self.stage_close_button_cb = self.stage3_close_button_clicked - self.stage_restart_button_cb = self.stage3_restart_button_clicked - self.stage_window_delete_cb = self.stage3_close_button_clicked - - def stage3_can_close(self, cb): - if self.form_status == 'completed': - cb() - return - - def on_yes(button): - self._con.get_module('AdHocCommands').send_cancel( - self.jid, self.commandnode, self.sessionid) - dialog.destroy() - cb() - - dialog = HigDialog(self.window, Gtk.MessageType.WARNING, - Gtk.ButtonsType.YES_NO, _('Cancel confirmation'), - _('You are in process of executing command. Do you really want to ' - 'cancel it?'), on_response_yes=on_yes) - dialog.popup() - - def stage3_close_button_clicked(self, widget): - """ - 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 - if self.allow_stage3_close: - return False - - def on_ok(): - self.allow_stage3_close = True - self.window.destroy() - - self.stage3_can_close(on_ok) - - return True # Block event, don't close window - - def stage3_restart_button_clicked(self, widget): - def on_ok(): - self.restart() - - self.stage3_can_close(on_ok) - - 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_finish_button_clicked(self, widget): - self.stage3_submit_form('complete') - - def stage3_submit_form(self, action='execute'): - self.data_form_widget.set_sensitive(False) - - if self.data_form_widget.get_data_form(): - df = self.data_form_widget.get_data_form() - if not df.is_valid(): - app.interface.raise_dialog( - 'invalid-form', transient_for=self.window) - self.data_form_widget.set_sensitive(True) - return - self.data_form_widget.data_form.type_ = 'submit' - else: - self.data_form_widget.hide() - - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) - self.finish_button.set_sensitive(False) - - self.sending_form_spinner.start() - self._con.get_module('AdHocCommands').send_command( - self.jid, self.commandnode, self.sessionid, - self.data_form_widget.data_form, action) - - def stage3_next_form(self, command): - if not isinstance(command, nbxmpp.Node): - self.stage5(error=_('Service sent malformed data'), senderror=True) - return - - self.sending_form_spinner.stop() - - if not self.sessionid: - self.sessionid = command.getAttr('sessionid') - elif self.sessionid != command.getAttr('sessionid'): - self.stage5(error=_('Service changed the session identifier.'), - senderror=True) - return - - self.form_status = command.getAttr('status') - - self.commandnode = command.getAttr('node') - if command.getTag('x'): - self.dataform = dataforms.extend_form(node=command.getTag('x')) - - self.data_form_widget.set_sensitive(True) - try: - self.data_form_widget.selectable = True - self.data_form_widget.data_form = self.dataform - except dataforms.Error: - self.stage5(error=_('Service sent malformed data'), - senderror=True) - return - self.data_form_widget.show() - if self.data_form_widget.title: - self.window.set_title(_('%s - Ad-hoc Commands - Gajim') % \ - self.data_form_widget.title) - else: - self.data_form_widget.hide() - - actions = command.getTag('actions') - if actions: - # actions, actions, actions... - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(actions.getTag('prev') is not None) - self.forward_button.set_sensitive( - actions.getTag('next') is not None) - self.execute_button.set_sensitive(True) - self.finish_button.set_sensitive(actions.getTag('complete') is not \ - None) - else: - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(True) - self.finish_button.set_sensitive(False) - - if self.form_status == 'completed': - self.close_button.set_sensitive(True) - self.back_button.hide() - self.forward_button.hide() - self.execute_button.hide() - self.finish_button.hide() - self.close_button.show() - self.stage_window_delete_cb = self.stage3_close_button_clicked - - note = command.getTag('note') - if note: - self.notes_label.set_text(note.getData()) - self.notes_label.set_no_show_all(False) - self.notes_label.show() - else: - self.notes_label.set_no_show_all(True) - self.notes_label.hide() - - def restart(self): - self.commandnode = None - self.initiate() - -# stage 4: no commands are exposed - def stage4(self): - """ - Display the message. Wait for user to close the window - """ - # close old stage - self.stage_finish() - - self.stages_notebook.set_current_page( - self.stages_notebook.page_num(self.no_commands_stage_vbox)) - - self.close_button.set_sensitive(True) - self.back_button.set_sensitive(False) - self.forward_button.set_sensitive(False) - self.execute_button.set_sensitive(False) - self.finish_button.set_sensitive(False) - - self.stage_finish_cb = None - self.stage_close_button_cb = self.stage4_close_button_clicked - self.stage_restart_button_cb = self.stage4_restart_button_clicked - self.stage_window_delete_cb = None - - def stage4_close_button_clicked(self, widget): - self.window.destroy() - - def stage4_restart_button_clicked(self, widget): - self.restart() - - def on_check_commands_2_button_clicked(self, widget): - self.stage1() - -# stage 5: an error has occurred - def stage5(self, error=None, errorid=None, senderror=False): - """ - Display the error message. Wait for user to close the window - """ - # FIXME: sending error to responder - # close old stage - self.stage_finish() - - assert errorid or error - - if errorid: - # we've got error code, display appropriate message - try: - errorname = nbxmpp.NS_STANZAS + ' ' + str(errorid) - errordesc = nbxmpp.ERRORS[errorname][2] - error = errordesc - del errorname, errordesc - except KeyError: # when stanza doesn't have error description - error = _('Service returned an error.') - elif error: - # we've got error message - pass - else: - # we don't know what's that, bailing out - assert False - - self.stages_notebook.set_current_page( - self.stages_notebook.page_num(self.error_stage_vbox)) - - self.close_button.set_sensitive(True) - self.back_button.hide() - self.forward_button.hide() - self.execute_button.hide() - self.finish_button.hide() - - self.error_description_label.set_text(error) - - self.stage_finish_cb = None - self.stage_close_button_cb = self.stage5_close_button_clicked - self.stage_restart_button_cb = self.stage5_restart_button_clicked - self.stage_window_delete_cb = None - - def stage5_close_button_clicked(self, widget): - self.window.destroy() - - def stage5_restart_button_clicked(self, widget): - self.restart() - - def _on_command_error(self, obj): - self.stage5(errorid=obj.error) - - def _on_command_list(self, obj): - self.commandlist = obj.commandlist - if not self.commandlist: - self.stage4() - else: - self.stage2() - - def _on_action_response(self, obj): - self.stage3_next_form(obj.command) diff --git a/gajim/gtk/discovery.py b/gajim/gtk/discovery.py index 7ae558d0e..aad2b3ff0 100644 --- a/gajim/gtk/discovery.py +++ b/gajim/gtk/discovery.py @@ -59,7 +59,7 @@ from gajim.gtk.dialogs import ErrorDialog from gajim.gtk.dialogs import InformationDialog from gajim.gtk.service_registration import ServiceRegistration from gajim.gtk.discovery_search import SearchWindow -from gajim.gtk.adhoc_commands import CommandWindow +from gajim.gtk.adhoc import AdHocCommand from gajim.gtk.util import icon_exists from gajim.gtk.util import get_builder @@ -1311,7 +1311,7 @@ class ToplevelAgentBrowser(AgentBrowser): return service = model[iter_][0] node = model[iter_][1] - CommandWindow(self.account, service, commandnode=node) + AdHocCommand(self.account, service) def on_register_button_clicked(self, widget=None): """ diff --git a/gajim/roster_window.py b/gajim/roster_window.py index c3e7edc8c..9561accd5 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -84,7 +84,7 @@ from gajim.gtk.service_registration import ServiceRegistration from gajim.gtk.discovery import ServiceDiscoveryWindow from gajim.gtk.accounts import AccountsWindow from gajim.gtk.tooltips import RosterTooltip -from gajim.gtk.adhoc_commands import CommandWindow +from gajim.gtk.adhoc import AdHocCommand from gajim.gtk.subscription_request import SubscriptionRequestWindow from gajim.gtk.util import get_icon_name from gajim.gtk.util import resize_window @@ -3635,7 +3635,7 @@ class RosterWindow: jid = contact.jid if resource is not None: jid = jid + '/' + resource - CommandWindow(account, jid) + AdHocCommand(account, jid) def on_roster_window_focus_in_event(self, widget, event): # roster received focus, so if we had urgency REMOVE IT