diff --git a/gajim/data/gui/assistant.ui b/gajim/data/gui/assistant.ui new file mode 100644 index 0000000000000000000000000000000000000000..9844e5bddebccc9538304c05a8b472c4d47a2c3e --- /dev/null +++ b/gajim/data/gui/assistant.ui @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.22.1 --> +<interface> + <requires lib="gtk+" version="3.20"/> + <object class="GtkGrid" id="main_grid"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="row_spacing">6</property> + <child> + <object class="GtkBox" id="content_area"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <signal name="notify::visible-child-name" handler="_on_visible_child_name" swapped="no"/> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="action_area"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="spacing">12</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="left_attach">0</property> + <property name="top_attach">1</property> + </packing> + </child> + <style> + <class name="assistant-grid"/> + </style> + </object> +</interface> diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css index ebe47f77933fd1cf58b65bc4f013beb1e2fbfef2..8d7b2864e627e0e5e70f6af3f92750c630171ad6 100644 --- a/gajim/data/style/gajim.css +++ b/gajim/data/style/gajim.css @@ -273,4 +273,7 @@ .gajim-treeview check { padding: 0px; } .gajim-scrolled { border: 1px solid; border-color:@unfocused_borders; } -#GroupchatJoin > box {padding: 18px; } \ No newline at end of file +#GroupchatJoin > box {padding: 18px; } + +.assistant-grid { padding: 18px; margin: 6px;} +.assistant-grid > box stack { padding: 18px; } diff --git a/gajim/gtk/assistant.py b/gajim/gtk/assistant.py new file mode 100644 index 0000000000000000000000000000000000000000..43b579bfd535816bb8433755951c31a67667e2ec --- /dev/null +++ b/gajim/gtk/assistant.py @@ -0,0 +1,197 @@ +# 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/>. + +from gi.repository import Gdk +from gi.repository import Gtk +from gi.repository import Gio +from gi.repository import GObject + +from gajim.gtk.util import get_builder +from gajim.gtk.util import EventHelper + + +class Assistant(Gtk.ApplicationWindow, EventHelper): + + __gsignals__ = dict( + button_clicked=( + GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, + None, + (str, ) + ), + page_changed=( + GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, + None, + (str, ) + )) + + def __init__(self, transient_for=None, + width=550, + height=400, + transition_duration=200): + Gtk.ApplicationWindow.__init__(self) + EventHelper.__init__(self) + self.set_application(Gio.Application.get_default()) + self.set_position(Gtk.WindowPosition.CENTER) + self.set_show_menubar(False) + self.set_name('Assistant') + self.set_default_size(width, height) + self.set_resizable(True) + self.set_transient_for(transient_for) + + self._pages = {} + self._buttons = {} + self._button_visible_func = None + + self._ui = get_builder('assistant.ui') + self.add(self._ui.main_grid) + + self._ui.stack.set_transition_duration(transition_duration) + + self.connect('key-press-event', self._on_key_press_event) + self.connect('destroy', self.__on_destroy) + self._ui.connect_signals(self) + + def show_all(self): + page_name = self._ui.stack.get_visible_child_name() + buttons = self._button_visible_func(self, page_name) + self._set_buttons_visible(buttons) + self.emit('page-changed', page_name) + Gtk.ApplicationWindow.show_all(self) + + def _on_key_press_event(self, _widget, event): + if event.keyval == Gdk.KEY_Escape: + self.destroy() + + def set_button_visible_func(self, func): + self._button_visible_func = func + + def set_default_button(self, button_name): + self._buttons[button_name].grab_default() + + def add_button(self, name, label, css_class=None): + button = Gtk.Button(label=label, + can_default=True, + no_show_all=True) + button.connect('clicked', self.__on_button_clicked) + if css_class is not None: + button.get_style_context().add_class(css_class) + self._buttons[name] = button + self._ui.action_area.pack_end(button, False, False, 0) + + def add_pages(self, pages): + self._pages = pages + for name, widget in pages.items(): + self._ui.stack.add_named(widget, name) + + def add_default_page(self, name): + if name == 'success': + page = SuccessPage() + elif name == 'error': + page = ErrorPage() + else: + raise ValueError('Unknown page: %s' % name) + + self._pages[name] = page + self._ui.stack.add_named(page, name) + + def get_current_page(self): + return self._ui.stack.get_visible_child_name() + + def show_page(self, name, transition=Gtk.StackTransitionType.NONE): + buttons = self._button_visible_func(self, name) + self._set_buttons_visible(buttons) + self._ui.stack.set_visible_child_full(name, transition) + + def get_error_page(self): + return self._pages['error'] + + def get_success_page(self): + return self._pages['success'] + + def _set_buttons_visible(self, buttons): + for button in self._buttons.values(): + button.hide() + + if buttons is None: + return + + for button_name in buttons: + self._buttons[button_name].show() + + def _on_visible_child_name(self, stack, _param): + self.set_title(stack.get_visible_child().title) + self.emit('page-changed', stack.get_visible_child_name()) + + def __on_button_clicked(self, button): + for button_name, button_ in self._buttons.items(): + if button_ == button: + self.emit('button-clicked', button_name) + return + + def __on_destroy(self, *args): + self._pages.clear() + self._buttons.clear() + + +class Page(Gtk.Box): + def __init__(self, icon_name, icon_css_class): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(18) + self.set_valign(Gtk.Align.CENTER) + + self.title = '' + + self._heading = Gtk.Label() + self._heading.get_style_context().add_class('large-header') + self._heading.set_max_width_chars(30) + self._heading.set_line_wrap(True) + self._heading.set_halign(Gtk.Align.CENTER) + self._heading.set_justify(Gtk.Justification.CENTER) + + icon = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DIALOG) + icon.get_style_context().add_class(icon_css_class) + + self._label = Gtk.Label() + self._label.set_max_width_chars(50) + self._label.set_line_wrap(True) + self._label.set_halign(Gtk.Align.CENTER) + self._label.set_justify(Gtk.Justification.CENTER) + + self.pack_start(self._heading, False, True, 0) + self.pack_start(icon, False, True, 0) + self.pack_start(self._label, False, True, 0) + self.show_all() + + def set_heading(self, heading): + self._heading.set_text(heading) + + def set_text(self, text): + self._label.set_text(text) + + def set_title(self, title): + self.title = title + + +class ErrorPage(Page): + def __init__(self): + Page.__init__(self, + icon_name='dialog-error-symbolic', + icon_css_class='error-color') + + +class SuccessPage(Page): + def __init__(self): + Page.__init__(self, + icon_name='object-select-symbolic', + icon_css_class='success-color') diff --git a/test/gtk/assistant.py b/test/gtk/assistant.py new file mode 100644 index 0000000000000000000000000000000000000000..8a049a72c859ff5f39042b131fb6cafcf9269385 --- /dev/null +++ b/test/gtk/assistant.py @@ -0,0 +1,151 @@ +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +from gajim.common.const import CSSPriority + +from gajim.gtk.assistant import Assistant + +from test.gtk import util +util.load_style('gajim.css', CSSPriority.APPLICATION) + + +class TestAssistant(Assistant): + def __init__(self): + Assistant.__init__(self) + + self.add_pages({'start': Start(), + 'progress': Progress()}) + + self.add_default_page('error') + self.add_default_page('success') + + self.add_button('forward', 'Forward', 'suggested-action') + self.add_button('close', 'Close', 'destructive-action') + self.add_button('back', 'Back') + + self.set_button_visible_func(self._visible_func) + + self.connect('button-clicked', self._on_button_clicked) + self.connect('page-changed', self._on_page_changed) + + self.show_all() + + @staticmethod + def _visible_func(_assistant, page_name): + if page_name == 'start': + return ['forward'] + + if page_name == 'progress': + return ['forward', 'back'] + + if page_name == 'success': + return ['forward', 'back'] + + if page_name == 'error': + return ['back', 'close'] + raise ValueError('page %s unknown' % page_name) + + def _on_button_clicked(self, _assistant, button_name): + page = self.get_current_page() + if button_name == 'forward': + if page == 'start': + self.show_page('progress', Gtk.StackTransitionType.SLIDE_LEFT) + elif page == 'progress': + self.show_page('success', Gtk.StackTransitionType.SLIDE_LEFT) + elif page == 'success': + self.show_page('error', Gtk.StackTransitionType.SLIDE_LEFT) + return + + if button_name == 'back': + if page == 'progress': + self.show_page('start') + if page == 'success': + self.show_page('progress') + if page == 'error': + self.show_page('success') + return + + if button_name == 'close': + self.destroy() + + def _on_page_changed(self, _assistant, page_name): + if page_name == 'start': + self.set_default_button('forward') + + elif page_name == 'progress': + self.set_default_button('forward') + + elif page_name == 'success': + success_page = self.get_success_page() + success_page.set_title('Success') + success_page.set_heading('Success Heading') + success_page.set_text('This is the success text') + self.set_default_button('forward') + + elif page_name == 'error': + error_page = self.get_error_page() + error_page.set_title('Error') + error_page.set_heading('Error Heading') + error_page.set_text('This is the error text') + self.set_default_button('back') + + +class Progress(Gtk.Box): + + title = 'Progress' + + def __init__(self): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(18) + self.set_valign(Gtk.Align.CENTER) + label = Gtk.Label(label='Test label...') + label.set_max_width_chars(50) + label.set_line_wrap(True) + label.set_halign(Gtk.Align.CENTER) + label.set_justify(Gtk.Justification.CENTER) + spinner = Gtk.Spinner() + self.pack_start(label, False, True, 0) + self.pack_start(spinner, False, True, 0) + spinner.start() + self.show_all() + + +class Start(Gtk.Box): + + title = 'Start' + + def __init__(self): + super().__init__(orientation=Gtk.Orientation.VERTICAL) + self.set_spacing(18) + self.set_valign(Gtk.Align.CENTER) + heading = Gtk.Label(label='Test Assistant') + heading.get_style_context().add_class('large-header') + + label1 = Gtk.Label(label='This is label 1 with some text') + label1.set_max_width_chars(50) + label1.set_line_wrap(True) + label1.set_halign(Gtk.Align.CENTER) + label1.set_justify(Gtk.Justification.CENTER) + label1.set_margin_bottom(24) + + label2 = Gtk.Label(label='This is label 2 with some more text') + label2.set_max_width_chars(50) + label2.set_line_wrap(True) + label2.set_halign(Gtk.Align.CENTER) + label2.set_justify(Gtk.Justification.CENTER) + + self._server = Gtk.CheckButton.new_with_mnemonic('A fancy checkbox') + self._server.set_halign(Gtk.Align.CENTER) + + self.pack_start(heading, False, True, 0) + self.pack_start(label1, False, True, 0) + self.pack_start(label2, False, True, 0) + self.pack_start(self._server, False, True, 0) + self.show_all() + + +win = TestAssistant() +win.connect('destroy', Gtk.main_quit) +win.show_all() +Gtk.main()