gajim3/gajim/gtk/adhoc.py

405 lines
12 KiB
Python

# 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 <http://www.gnu.org/licenses/>.
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'))