diff --git a/gajim/gtk/discovery.py b/gajim/gtk/discovery.py index fb4339a0fd4810ce7a7047a7ad0823629dcfa0c1..64077815fbdedcf697c7d32b89e5854fd1911be9 100644 --- a/gajim/gtk/discovery.py +++ b/gajim/gtk/discovery.py @@ -59,7 +59,7 @@ from gajim.common.const import StyleAttr from .dialogs import ErrorDialog -from .search import Search +from .search import DirectorySearch from .util import icon_exists from .builder import get_builder from .util import open_window @@ -1310,7 +1310,7 @@ def on_search_button_clicked(self, widget=None): if not iter_: return service = model[iter_][0] - Search(self.account, service, self.window.window) + DirectorySearch(self.account, service, self.window.window) def cleanup(self): AgentBrowser.cleanup(self) diff --git a/gajim/gtk/search.py b/gajim/gtk/search.py index 170509f8395b50e9f717705ebcb888b882c794aa..28f26467cd409d1d8c603f68fae350a470b35b5e 100644 --- a/gajim/gtk/search.py +++ b/gajim/gtk/search.py @@ -14,252 +14,219 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see <http://www.gnu.org/licenses/>. +from typing import cast +from typing import Any +from typing import Optional +from typing import Union +from typing import Type + import logging import itertools -from enum import IntEnum +from gi.repository import Gdk from gi.repository import Gtk +from nbxmpp.simplexml import Node from nbxmpp.modules import dataforms from gajim.common import app from gajim.common import ged +from gajim.common.events import SearchFormReceivedEvent +from gajim.common.events import SearchResultReceivedEvent from gajim.common.i18n import _ -from .menus import SearchMenu +from .assistant import Assistant +from .assistant import ErrorPage +from .assistant import Page +from .assistant import ProgressPage from .dataform import DataFormWidget +from .menus import SearchMenu from .util import ensure_not_destroyed -from .util import find_widget from .util import EventHelper log = logging.getLogger('gajim.gui.search') -class Page(IntEnum): - REQUEST_FORM = 0 - FORM = 1 - REQUEST_RESULT = 2 - COMPLETED = 3 - ERROR = 4 - - -class Search(Gtk.Assistant, EventHelper): - def __init__(self, account, jid, transient_for=None): - Gtk.Assistant.__init__(self) +class DirectorySearch(Assistant, EventHelper): + def __init__(self, + account: str, + jid: str, + transient_for: Optional[Gtk.Window] = None + ) -> None: + Assistant.__init__(self, + transient_for=transient_for, + width=700, + height=500) EventHelper.__init__(self) - self._con = app.connections[account] - self._account = account + self._client = app.get_client(account) + self.account = account self._jid = jid self._destroyed = False - self.set_application(app.app) - self.set_resizable(True) - self.set_position(Gtk.WindowPosition.CENTER) - if transient_for is not None: - self.set_transient_for(transient_for) + self.add_button('search', _('Search'), 'suggested-action') + self.add_button('new-search', _('New Search')) + self.add_button('close', _('Close')) - self.set_size_request(500, 400) - self.get_style_context().add_class('dialog-margin') + self.add_pages({ + 'prepare': RequestForm(), + 'form': SearchForm(), + 'result': Result(), + 'error': Error() + }) - self._add_page(RequestForm()) - self._add_page(Form()) - self._add_page(RequestResult()) - self._add_page(Completed()) - self._add_page(Error()) + progress = cast(ProgressPage, self.add_default_page('progress')) + progress.set_title(_('Searching')) + progress.set_text(_('Searching…')) - self.connect('prepare', self._on_page_change) - self.connect('cancel', self._on_cancel) - self.connect('close', self._on_cancel) + self.connect('button-clicked', self._on_button_clicked) self.connect('destroy', self._on_destroy) - self._remove_sidebar() - - self._buttons = {} - self._add_custom_buttons() - - self.show() self.register_events([ ('search-form-received', ged.GUI1, self._search_form_received), ('search-result-received', ged.GUI1, self._search_result_received), ]) - self._request_search_fields() - - 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) - - search = Gtk.Button(label=_('Search')) - search.connect('clicked', self._execute_search) - search.get_style_context().add_class('suggested-action') - self._buttons['search'] = search - self.add_action_widget(search) - - new_search = Gtk.Button(label=_('New Search')) - new_search.get_style_context().add_class('suggested-action') - new_search.connect('clicked', - lambda *args: self.set_current_page(Page.FORM)) - self._buttons['new-search'] = new_search - self.add_action_widget(new_search) - - def _set_button_visibility(self, page): - for button in self._buttons.values(): - button.hide() + self._client.get_module('Search').request_search_fields(self._jid) - if page == Page.FORM: - self._buttons['search'].show() - - elif page in (Page.ERROR, Page.COMPLETED): - self._buttons['new-search'].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) + self.show_all() - def set_stage_complete(self, is_valid): - self._buttons['search'].set_sensitive(is_valid) + def _on_button_clicked(self, + _assistant: Assistant, + button_name: str + ) -> None: + if button_name == 'search': + self.show_page('progress', Gtk.StackTransitionType.SLIDE_LEFT) + form = cast(SearchForm, self.get_page('form')).get_submit_form() + self._client.get_module('Search').send_search_form( + self._jid, form, True) + return - def _request_search_fields(self): - self._con.get_module('Search').request_search_fields(self._jid) + if button_name == 'new-search': + self.show_page('form', Gtk.StackTransitionType.SLIDE_RIGHT) + return - def _execute_search(self, *args): - self.set_current_page(Page.REQUEST_RESULT) - form = self.get_nth_page(Page.FORM).get_submit_form() - self._con.get_module('Search').send_search_form(self._jid, form, True) + if button_name == 'close': + self.destroy() @ensure_not_destroyed - def _search_form_received(self, event): + def _search_form_received(self, event: SearchFormReceivedEvent) -> None: if not event.is_dataform: - self.set_current_page(Page.ERROR) + error_page = cast(Error, self.get_page('error')) + error_page.set_text(_('Error while retrieving search form.')) + self.show_page('error') return - self.get_nth_page(Page.FORM).process_search_form(event.data) - self.set_current_page(Page.FORM) + form_page = cast(SearchForm, self.get_page('form')) + form_page.process_search_form(event.data) + self.show_page('form') @ensure_not_destroyed - def _search_result_received(self, event): + def _search_result_received(self, + event: SearchResultReceivedEvent + ) -> None: if event.data is None: - self._on_error('') + error_page = cast(Error, self.get_page('error')) + error_page.set_text(_('Error while receiving search results.')) + self.show_page('error') return - self.get_nth_page(Page.COMPLETED).process_result(event.data) - self.set_current_page(Page.COMPLETED) - def _remove_sidebar(self): - main_box = self.get_children()[0] - sidebar = main_box.get_children()[0] - main_box.remove(sidebar) + result_page = cast(Result, self.get_page('result')) + result_page.process_result(event.data) + self.show_page('result') - 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): + def _on_destroy(self, *args: Any) -> None: self._destroyed = True -class RequestForm(Gtk.Box): - - type_ = Gtk.AssistantPageType.CUSTOM - title = _('Request Search Form') - complete = False - +class RequestForm(ProgressPage): 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() + ProgressPage.__init__(self) + self.set_title(_('Request Search Form')) + self.set_text(_('Requesting search form from server')) + def get_visible_buttons(self) -> list[str]: + return ['close'] -class Form(Gtk.Box): - type_ = Gtk.AssistantPageType.CUSTOM - title = _('Search') - complete = True +class SearchForm(Page): + def __init__(self) -> None: + Page.__init__(self) + self.title = _('Search') + + self.complete = False - def __init__(self): - super().__init__(orientation=Gtk.Orientation.VERTICAL) - self.set_spacing(18) self._dataform_widget = None + self.show_all() @property - def search_form(self): + def search_form(self) -> dataforms.SimpleDataForm: return self._dataform_widget.get_submit_form() - def clear(self): + def clear(self) -> None: self._show_form(None) - def process_search_form(self, form): + def process_search_form(self, form: Node) -> None: self._show_form(form) - def _show_form(self, form): + def _show_form(self, form: Optional[Node]) -> None: if self._dataform_widget is not None: self.remove(self._dataform_widget) self._dataform_widget.destroy() if form is None: return - options = {'form-width': 350} + options = {'form-width': 350, + 'entry-activates-default': True + } form = dataforms.extend_form(node=form) self._dataform_widget = DataFormWidget(form, options=options) + self._dataform_widget.set_propagate_natural_height(True) 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 _on_is_valid(self, _widget, is_valid): - self.get_toplevel().set_stage_complete(is_valid) + def _on_is_valid(self, _widget: DataFormWidget, is_valid: bool) -> None: + self.complete = True + self.update_page_complete() - def get_submit_form(self): + def get_submit_form(self) -> dataforms.SimpleDataForm: return self._dataform_widget.get_submit_form() + def get_visible_buttons(self) -> list[str]: + return ['close', 'search'] -class RequestResult(RequestForm): - - type_ = Gtk.AssistantPageType.CUSTOM - title = _('Search…') - complete = False - + def get_default_button(self) -> str: + return 'search' -class Completed(Gtk.Box): - type_ = Gtk.AssistantPageType.CUSTOM - title = _('Search Result') - complete = True +class Result(Page): + def __init__(self) -> None: + Page.__init__(self) + self.title = _('Search Result') - def __init__(self): - super().__init__(orientation=Gtk.Orientation.VERTICAL) - self.set_spacing(12) - self.show_all() self._label = Gtk.Label(label=_('No results found')) self._label.get_style_context().add_class('bold16') self._label.set_no_show_all(True) self._label.set_halign(Gtk.Align.CENTER) + self._scrolled = Gtk.ScrolledWindow() + self._scrolled.set_propagate_natural_height(True) self._scrolled.get_style_context().add_class('search-scrolled') self._scrolled.set_no_show_all(True) - self._treeview = None - self._menu = None + self.add(self._label) self.add(self._scrolled) + + self._treeview: Optional[Gtk.TreeView] = None + self._menu: Optional[SearchMenu] = None + self.show_all() - def process_result(self, form): + def process_result(self, form: Optional[Node]) -> None: if self._treeview is not None: self._scrolled.remove(self._treeview) self._treeview.destroy() @@ -274,8 +241,8 @@ def process_result(self, form): form = dataforms.extend_form(node=form) - fieldtypes = [] - fieldvars = [] + fieldtypes: list[Union[Type[bool], Type[str]]] = [] + fieldvars: list[Any] = [] for field in form.reported.iter_fields(): if field.type_ == 'boolean': fieldtypes.append(bool) @@ -315,42 +282,41 @@ def process_result(self, form): self._scrolled.add(self._treeview) self._scrolled.show() - def _on_button_press(self, treeview, event): - if event.button != 3: - return - path, _column, _x, _y = treeview.get_path_at_pos(event.x, event.y) + def _on_button_press(self, + treeview: Gtk.TreeView, + event: Gdk.EventButton + ) -> bool: + if event.button != 3: # Right click + return False + + path = treeview.get_path_at_pos(int(event.x), int(event.y)) if path is None: - return + return False + + path, _column, _x, _y = path store = treeview.get_model() + assert store is not None + assert path is not None iter_ = store.get_iter(path) - column_values = store[iter_] + column_values = str(store[iter_]) text = ' '.join(column_values) + assert self._menu is not None self._menu.set_copy_text(text) self._menu.popup_at_pointer() + return True + def get_visible_buttons(self) -> list[str]: + return ['close', 'new-search'] -class Error(Gtk.Box): + def get_default_button(self) -> str: + return 'close' - type_ = Gtk.AssistantPageType.CUSTOM - title = _('Error') - 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() +class Error(ErrorPage): + def __init__(self) -> None: + ErrorPage.__init__(self) + self.set_title(_('Error')) + self.set_heading(_('An error occurred')) - def set_text(self, text): - self._label.set_text(text) + def get_visible_buttons(self) -> list[str]: + return ['close']