Philipp Hörist pushed to branch master at gajim / gajim
Commits: 25e31bd5 by Philipp Hörist at 2019-07-10T16:36:18Z StartChat: Use JID arg for start-chat action
- - - - - 799c284e by Philipp Hörist at 2019-07-10T16:53:43Z MUC: Fix joining with bookmark
- - - - - 5586a209 by Philipp Hörist at 2019-07-10T17:34:07Z CmdLine: Use new Join Group Chat dialog
If there is only one account active we use the GroupchatJoin, else the jid is passed to StartChat
- - - - - b6c26e57 by Philipp Hörist at 2019-07-11T21:29:49Z StartChat: Integrate Groupchat info
- - - - - 7f33ecfb by Philipp Hörist at 2019-07-11T21:31:33Z StartChat: Fix some pylint errors
- - - - -
8 changed files:
- gajim/application.py - gajim/data/gui/start_chat_dialog.ui - gajim/data/style/gajim.css - gajim/dialog_messages.py - gajim/gtk/groupchat_info.py - gajim/gtk/start_chat.py - gajim/gui_interface.py - gajim/remote_control.py
Changes:
===================================== gajim/application.py ===================================== @@ -39,6 +39,8 @@ from pathlib import Path from urllib.parse import unquote
import nbxmpp +from nbxmpp import JID +from nbxmpp.protocol import InvalidJid from gi.repository import Gio from gi.repository import GLib from gi.repository import Gtk @@ -236,6 +238,10 @@ class GajimApplication(Gtk.Application): self._on_feature_discovered)
def _open_uris(self, uris): + accounts = list(app.connections.keys()) + if not accounts: + return + for uri in uris: app.log('uri_handler').info('open %s', uri) if not uri.startswith('xmpp:'): @@ -247,10 +253,30 @@ class GajimApplication(Gtk.Application): except ValueError: # No query argument jid, cmd = uri, 'message' + + try: + jid = JID(jid) + except InvalidJid as error: + app.log('uri_handler').warning('Invalid JID %s: %s', uri, error) + continue + + if cmd == 'join' and jid.getResource(): + app.log('uri_handler').warning('Invalid MUC JID %s', uri) + continue + + jid = str(jid) + if cmd == 'join': - self.interface.join_gc_minimal(None, jid) + if len(accounts) == 1: + self.activate_action( + 'groupchat-join', + GLib.Variant('as', [accounts[0], jid])) + else: + self.activate_action('start-chat', GLib.Variant('s', jid)) + elif cmd == 'roster': self.activate_action('add-contact', GLib.Variant('s', jid)) + elif cmd.startswith('message'): attributes = cmd.split(';') message = None @@ -261,15 +287,11 @@ class GajimApplication(Gtk.Application): message = unquote(key.split('=')[1]) except Exception: app.log('uri_handler').error('Invalid URI: %s', cmd) - accounts = list(app.connections.keys()) - if not accounts: - continue + if len(accounts) == 1: app.interface.new_chat_from_jid(accounts[0], jid, message) else: - self.activate_action('start-chat', GLib.Variant('s', '')) - start_chat = app.get_app_window('StartChatDialog') - start_chat.set_search_text(jid) + self.activate_action('start-chat', GLib.Variant('s', jid))
def do_shutdown(self, *args): Gtk.Application.do_shutdown(self)
===================================== gajim/data/gui/start_chat_dialog.ui ===================================== @@ -2,57 +2,6 @@ <!-- Generated with glade 3.22.1 --> <interface> <requires lib="gtk+" version="3.20"/> - <object class="GtkBox" id="box"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="border_width">18</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkSearchEntry" id="search_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="has_focus">True</property> - <property name="is_focus">True</property> - <property name="primary_icon_name">edit-find-symbolic</property> - <property name="primary_icon_activatable">False</property> - <property name="primary_icon_sensitive">False</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow" id="scrolledwindow"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="shadow_type">in</property> - <child> - <object class="GtkViewport"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <child> - <object class="GtkListBox" id="listbox"> - <property name="name">StartChatListBox</property> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="selection_mode">browse</property> - <property name="activate_on_single_click">False</property> - </object> - </child> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> <object class="GtkGrid" id="search_result_grid"> <property name="visible">True</property> <property name="can_focus">False</property> @@ -128,6 +77,234 @@ </packing> </child> </object> + <object class="GtkStack" id="stack"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkBox" id="box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="border_width">18</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkSearchEntry" id="search_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="is_focus">True</property> + <property name="primary_icon_name">edit-find-symbolic</property> + <property name="primary_icon_activatable">False</property> + <property name="primary_icon_sensitive">False</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">never</property> + <property name="shadow_type">in</property> + <child> + <object class="GtkViewport"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <child> + <object class="GtkListBox" id="listbox"> + <property name="name">StartChatListBox</property> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="selection_mode">browse</property> + <property name="activate_on_single_click">False</property> + </object> + </child> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="name">search</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkSpinner" id="spinner"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="vexpand">True</property> + <property name="active">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + </object> + <packing> + <property name="name">progress</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <property name="homogeneous">True</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">end</property> + <property name="icon_name">dialog-error</property> + <property name="icon_size">6</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="error_label"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="valign">start</property> + <property name="justify">center</property> + <property name="wrap">True</property> + <style> + <class name="bold16"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">_Back</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">start</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="_on_back_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + <style> + <class name="margin-18"/> + </style> + </object> + <packing> + <property name="name">error</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkBox" id="info_box"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <placeholder/> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="homogeneous">True</property> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">_Back</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">start</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="_on_back_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">_Join</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="halign">end</property> + <property name="use_underline">True</property> + <signal name="clicked" handler="_on_join_clicked" swapped="no"/> + <style> + <class name="suggested-action"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack_type">end</property> + <property name="position">1</property> + </packing> + </child> + <style> + <class name="margin-18"/> + </style> + </object> + <packing> + <property name="name">info</property> + <property name="position">3</property> + </packing> + </child> + </object> <object class="GtkGrid" id="tooltip"> <property name="visible">True</property> <property name="can_focus">False</property>
===================================== gajim/data/style/gajim.css ===================================== @@ -227,6 +227,7 @@ list.settings > row > box { /* Padding/Margins */ .margin-top6 { margin-top: 6px; } .margin-12 { margin: 12px; } +.margin-18 { margin: 18px; }
/* Treeview */ treeview.space { padding: 6px; }
===================================== gajim/dialog_messages.py ===================================== @@ -27,11 +27,6 @@ from gajim.gtk.dialogs import InformationDialog Message = namedtuple('Message', ['title', 'text', 'dialog'])
messages = { - 'start-chat-not-connected': Message( - _('You are not connected to the server'), - _('You can not start a new conversation unless you are connected.'), - ErrorDialog), - 'invalid-jid-with-error': Message( _('Invalid JID'), '%s',
===================================== gajim/gtk/groupchat_info.py ===================================== @@ -93,9 +93,10 @@ MUC_FEATURES = {
class GroupChatInfoScrolled(Gtk.ScrolledWindow): - def __init__(self, account): + def __init__(self, account=None): Gtk.ScrolledWindow.__init__(self) self.set_size_request(400, -1) + self.set_vexpand(True) self.set_min_content_height(400) self.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) @@ -105,6 +106,9 @@ class GroupChatInfoScrolled(Gtk.ScrolledWindow): self._ui.connect_signals(self) self.show_all()
+ def set_account(self, account): + self._account = account + def set_author(self, author, epoch_timestamp=None): if not author: return @@ -181,6 +185,11 @@ class GroupChatInfoScrolled(Gtk.ScrolledWindow):
def _add_features(self, features): grid = self._ui.info_grid + for row in range(30, 9, -1): + # Remove everything from row 30 to 10 + # We probably will never have 30 rows and + # there is no method to count grid rows + grid.remove_row(row) features = list(features)
has_mam = NS_MAM_2 in features or NS_MAM_1 in features
===================================== gajim/gtk/start_chat.py ===================================== @@ -20,6 +20,8 @@ from gi.repository import Gtk from gi.repository import GLib from gi.repository import Pango
+from nbxmpp.protocol import InvalidJid +from nbxmpp.protocol import JID from nbxmpp.util import is_error_result from nbxmpp.const import AnonymityMode
@@ -27,7 +29,9 @@ from gajim.common import app from gajim.common import helpers from gajim.common.i18n import _ from gajim.common.const import AvatarSize +from gajim.common.const import MUC_DISCO_ERRORS
+from gajim.gtk.groupchat_info import GroupChatInfoScrolled from gajim.gtk.util import get_icon_name from gajim.gtk.util import get_builder from gajim.gtk.util import ensure_not_destroyed @@ -56,7 +60,7 @@ class StartChatDialog(Gtk.ApplicationWindow): self._search_stopped = False
self._ui = get_builder('start_chat_dialog.ui') - self.add(self._ui.box) + self.add(self._ui.stack)
self.new_contact_row_visible = False self.new_contact_rows = {} @@ -81,10 +85,14 @@ class StartChatDialog(Gtk.ApplicationWindow): self._muc_search_listbox = MUCSearch() self._current_listbox = self._ui.listbox
+ self._muc_info_box = GroupChatInfoScrolled() + self._ui.info_box.add(self._muc_info_box) + self.connect('key-press-event', self._on_key_press) self.connect('destroy', self._destroy)
self.select_first_row() + self._ui.connect_signals(self) self.show_all()
def set_search_text(self, text): @@ -126,64 +134,131 @@ class StartChatDialog(Gtk.ApplicationWindow): show_account, True) self._ui.listbox.add(row)
- def _on_row_activated(self, listbox, row): + def _on_row_activated(self, _listbox, row): row = row.get_child() self._start_new_chat(row)
- def _on_key_press(self, widget, event): + def _on_key_press(self, _widget, event): + is_search = self._ui.stack.get_visible_child_name() == 'search' if event.keyval in (Gdk.KEY_Down, Gdk.KEY_Tab): - self._ui.search_entry.emit('next-match') - return True + if is_search: + self._ui.search_entry.emit('next-match') + return Gdk.EVENT_STOP + return Gdk.EVENT_PROPAGATE
if (event.state == Gdk.ModifierType.SHIFT_MASK and event.keyval == Gdk.KEY_ISO_Left_Tab): - self._ui.search_entry.emit('previous-match') - return True + if is_search: + self._ui.search_entry.emit('previous-match') + return Gdk.EVENT_STOP + return Gdk.EVENT_PROPAGATE
if event.keyval == Gdk.KEY_Up: - self._ui.search_entry.emit('previous-match') - return True + if is_search: + self._ui.search_entry.emit('previous-match') + return Gdk.EVENT_STOP + return Gdk.EVENT_PROPAGATE
if event.keyval == Gdk.KEY_Escape: + if self._ui.stack.get_visible_child_name() == 'progress': + self.destroy() + return Gdk.EVENT_STOP + + if self._ui.stack.get_visible_child_name() in ('error', 'info'): + self._ui.stack.set_visible_child_name('search') + return Gdk.EVENT_STOP + self._search_stopped = True self._muc_search_listbox.remove_all() if self._ui.search_entry.get_text() != '': self._ui.search_entry.emit('stop-search') else: self.destroy() - return True + return Gdk.EVENT_STOP
if event.keyval == Gdk.KEY_Return: + if self._ui.stack.get_visible_child_name() == 'progress': + return Gdk.EVENT_STOP + + if self._ui.stack.get_visible_child_name() == 'error': + self._ui.stack.set_visible_child_name('search') + return Gdk.EVENT_STOP + + if self._ui.stack.get_visible_child_name() == 'info': + self._on_join_clicked() + return Gdk.EVENT_STOP + if self._current_listbox_is(Search.MUC): self._muc_search_listbox.remove_all() self._start_search() - return True + return Gdk.EVENT_STOP
row = self._ui.listbox.get_selected_row() if row is not None: row.emit('activate') - return True + return Gdk.EVENT_STOP
- self._ui.search_entry.grab_focus_without_selecting() + if is_search: + self._ui.search_entry.grab_focus_without_selecting() + return Gdk.EVENT_PROPAGATE
def _start_new_chat(self, row): if row.new: - if not app.account_is_connected(row.account): - app.interface.raise_dialog('start-chat-not-connected') - return try: - helpers.parse_jid(row.jid) - except helpers.InvalidFormat as e: - app.interface.raise_dialog('invalid-jid-with-error', str(e)) + JID(row.jid) + except InvalidJid as error: + self._show_error_page(str(error)) return
if row.groupchat: - app.interface.join_gc_minimal(row.account, row.jid) + if not app.account_is_connected(row.account): + self._show_error_page(_('You can not join a group chat ' + 'unless you are connected.')) + return + self._disco_muc(row.account, row.jid) else: app.interface.new_chat_from_jid(row.account, row.jid)
self.ready_to_destroy = True
+ def _disco_muc(self, account, jid): + self._ui.stack.set_visible_child_name('progress') + con = app.connections[account] + con.get_module('Discovery').disco_info( + jid, callback=self._disco_info_received, user_data=account) + + @ensure_not_destroyed + def _disco_info_received(self, result, account): + if is_error_result(result): + self._set_error(result) + + elif result.is_muc: + self._muc_info_box.set_account(account) + self._muc_info_box.set_from_disco_info(result) + self._ui.stack.set_visible_child_name('info') + + else: + self._set_error_from_code('not-muc-service') + + def _set_error(self, error): + text = MUC_DISCO_ERRORS.get(error.type, str(error)) + self._show_error_page(text) + + def _set_error_from_code(self, error_code): + self._show_error_page(MUC_DISCO_ERRORS[error_code]) + + def _show_error_page(self, text): + self._ui.error_label.set_text(text) + self._ui.stack.set_visible_child_name('error') + + def _on_join_clicked(self, _button=None): + row = self._ui.listbox.get_selected_row().get_child() + app.interface.join_gc_minimal(row.account, row.jid) + self.ready_to_destroy = True + + def _on_back_clicked(self, _button): + self._ui.stack.set_visible_child_name('search') + def _set_listbox(self, listbox): if self._current_listbox == listbox: return @@ -241,7 +316,7 @@ class StartChatDialog(Gtk.ApplicationWindow): self.new_contact_rows[account].update_jid(search_text) self.new_groupchat_rows[account].update_jid(search_text)
- def _select_new_match(self, entry, direction): + def _select_new_match(self, _entry, direction): selected_row = self._ui.listbox.get_selected_row() if selected_row is None: return @@ -270,19 +345,19 @@ class StartChatDialog(Gtk.ApplicationWindow): first_row = self._ui.listbox.get_row_at_y(0) self._ui.listbox.select_row(first_row)
- def _filter_func(self, row, user_data): + def _filter_func(self, row, _user_data): search_text = self._ui.search_entry.get_text().lower() search_text_list = search_text.split() row_text = row.get_child().get_search_text().lower() for text in search_text_list: if text not in row_text: GLib.timeout_add(50, self.select_first_row) - return + return None GLib.timeout_add(50, self.select_first_row) return True
@staticmethod - def _sort_func(row1, row2, user_data): + def _sort_func(row1, row2, _user_data): name1 = row1.get_child().get_search_text() name2 = row2.get_child().get_search_text() account1 = row1.get_child().account @@ -539,7 +614,8 @@ class MUCSearch(Gtk.ListBox): self._ui.tooltip_name.hide() self._ui.tooltip_name_label.hide()
- def _on_row_activated(self, _listbox, row): + @staticmethod + def _on_row_activated(_listbox, row): app.interface.join_gc_minimal(None, row.item.jid)
===================================== gajim/gui_interface.py ===================================== @@ -1441,7 +1441,7 @@ class Interface: bookmark = con.get_module('Bookmarks').get_bookmark_from_jid(room_jid) if bookmark is not None: app.interface.join_gc_room( - account, bookmark.jid, bookmark.nick, bookmark.password) + account, str(bookmark.jid), bookmark.nick, bookmark.password) return
try:
===================================== gajim/remote_control.py ===================================== @@ -221,7 +221,7 @@ class GajimRemote(Server): <method name='show_next_pending_event' /> <method name='show_roster' /> <method name='start_chat'> - <arg name='account' type='s' /> + <arg name='jid' type='s' /> <arg direction='out' type='b' /> </method> <method name='toggle_ipython' /> @@ -884,11 +884,8 @@ class GajimRemote(Server): unread -= 1 return str(unread)
- def start_chat(self, account): - if not account: - # error is shown in gajim-remote check_arguments(..) - return False - app.app.activate_action('start-chat', GLib.Variant('s', '')) + def start_chat(self, jid=''): + app.app.activate_action('start-chat', GLib.Variant('s', jid)) return True
def send_xml(self, xml, account):
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/58cfd1e44fe99b31f0bdf4d928c268e1bd...