# 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 . import logging from gi.repository import Gtk from gi.repository import GObject from nbxmpp.const import AdHocAction from nbxmpp.modules import dataforms from nbxmpp.errors import StanzaError from nbxmpp.errors import MalformedStanzaError from gajim.common import app from gajim.common.i18n import _ from gajim.common.helpers import to_user_string from .assistant import Assistant from .assistant import Page from .assistant import ErrorPage from .assistant import ProgressPage from .dataform import DataFormWidget from .util import MultiLineLabel from .util import ensure_not_destroyed log = logging.getLogger('gajim.gui.adhoc') class AdHocCommand(Assistant): def __init__(self, account, jid=None): Assistant.__init__(self, width=600, height=500) self._destroyed = False self._client = app.get_client(account) self._account = account self._jid = jid self.add_button('complete', _('Complete'), complete=True, css_class='suggested-action') self.add_button('next', _('Next'), complete=True, css_class='suggested-action') self.add_button('prev', _('Previous')) self.add_button('cancel', _('Cancel'), css_class='destructive-action') self.add_button('commands', _('Commands'), css_class='suggested-action') self.add_button('execute', _('Execute'), css_class='suggested-action') self.add_pages({ 'request': RequestCommandList(), 'commands': Commands(), 'stage': Stage(), 'completed': Completed(), 'error': Error(), 'executing': Executing(), }) self.get_page('commands').connect('execute', self._on_execute) self.connect('button-clicked', self._on_button_clicked) self.connect('destroy', self._on_destroy) self._client.get_module('AdHocCommands').request_command_list( jid, callback=self._received_command_list) self.show_all() @ensure_not_destroyed def _received_command_list(self, task): try: commands = task.finish() except (StanzaError, MalformedStanzaError) as error: self._set_error(to_user_string(error), False) return if not commands: self._set_error(_('No commands available'), False) return self.get_page('commands').add_commands(commands) self.show_page('commands') @ensure_not_destroyed def _received_stage(self, task): try: stage = task.finish() except (StanzaError, MalformedStanzaError) as error: self._set_error(to_user_string(error), True) return page_name = 'stage' if stage.is_completed: page_name = 'completed' page = self.get_page(page_name) page.process_stage(stage) self.show_page(page_name) def _set_error(self, text, show_command_button): self.get_page('error').set_show_commands_button(show_command_button) self.get_page('error').set_text(text) self.show_page('error') def _on_destroy(self, *args): self._destroyed = True def _on_button_clicked(self, _assistant, button_name): if button_name == 'commands': self.show_page('commands') elif button_name == 'execute': self._on_execute() elif button_name in ('prev', 'next', 'complete'): self._on_stage_action(AdHocAction(button_name)) elif button_name == 'cancel': self._on_cancel() else: raise ValueError('Invalid button name: %s' % button_name) def _on_stage_action(self, action): command, dataform = self.get_page('stage').stage_data if action == AdHocAction.PREV: dataform = None self._client.get_module('AdHocCommands').execute_command( command, action=action, dataform=dataform, callback=self._received_stage) self.show_page('executing') self.get_page('stage').clear() def _on_execute(self, *args): command = self.get_page('commands').get_selected_command() if command is None: return self._client.get_module('AdHocCommands').execute_command( command, action=AdHocAction.EXECUTE, callback=self._received_stage) self.show_page('executing') def _on_cancel(self): command, _ = self.get_page('stage').stage_data self._client.get_module('AdHocCommands').execute_command( command, AdHocAction.CANCEL) self.show_page('commands') class Commands(Page): __gsignals__ = { 'execute': (GObject.SignalFlags.RUN_LAST, None, ()), } def __init__(self): Page.__init__(self) self.set_valign(Gtk.Align.FILL) self.complete = True self.title = _('Command List') 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() @staticmethod def _search_func(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.emit('execute') 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() if treeiter is None: return None key = model[treeiter][1] return self._commands[key] def get_visible_buttons(self): return ['execute'] class Stage(Page): def __init__(self): Page.__init__(self) self.set_valign(Gtk.Align.FILL) self.complete = False self.title = _('Stage') self._dataform_widget = None self._notes = [] self._last_stage_data = None self.default = 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) self.default = stage_data.default 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) options = {'entry-activates-default': True} self._dataform_widget = DataFormWidget(form, options) 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.complete = is_valid self.update_page_complete() def get_visible_buttons(self): actions = list(map(lambda action: action.value, self._last_stage_data.actions)) return actions def get_default_button(self): return self._last_stage_data.default.value class Completed(Page): def __init__(self): Page.__init__(self) self.set_valign(Gtk.Align.FILL) self.complete = True self.title = _('Completed') 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='Completed') 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) def get_visible_buttons(self): return ['commands'] class Error(ErrorPage): def __init__(self): ErrorPage.__init__(self) self._show_commands_button = False self.set_heading(_('An error occurred')) def set_show_commands_button(self, value): self._show_commands_button = value def get_visible_buttons(self): if self._show_commands_button: return ['commands'] return None class Executing(ProgressPage): def __init__(self): ProgressPage.__init__(self) self.set_title(_('Executing…')) self.set_text(_('Executing…')) class RequestCommandList(ProgressPage): def __init__(self): ProgressPage.__init__(self) self.set_title(_('Requesting Command List')) self.set_text(_('Requesting Command List'))