diff --git a/data/gajim-remote.1 b/data/gajim-remote.1 index d08b5765e..af5631723 100644 --- a/data/gajim-remote.1 +++ b/data/gajim-remote.1 @@ -19,8 +19,6 @@ by D-Bus. .El .Ss account_info Aq account Gets detailed info on a account -.Ss add_contact Ao jid Ac Bq account -Adds contact to roster .Ss change_avatar Ao picture Ac Bq account Change the avatar .Ss change_status Bo status Bc Bo message Bc Bq account @@ -35,18 +33,12 @@ Returns current status (the global one unless account is specified) Returns current status message (the global one unless account is specified) .Ss get_unread_msgs_number Returns number of unread messages -.Ss handle_uri Ao uri Ac Bo account Bc Bq message -Handle a xmpp:/ uri .Ss help Bq command Shows a help on specific command -.Ss join_room Ao room Ac Bo nick Bc Bo password Bc Bq account -Join a MUC room .Ss list_accounts Prints a list of registered accounts .Ss list_contacts Bq account Prints a list of all contacts in the roster. Each contact appears on a separate line -.Ss open_chat Ao jid Ac Bo account Bc Bq message -Shows the chat dialog so that you can send messages to a contact .Ss prefs_del Aq key Deletes a preference item .Ss prefs_list @@ -71,8 +63,6 @@ Sends custom XML Changes the priority of account or accounts .Ss show_next_pending_event Pops up a window with the next pending event -.Ss start_chat Aq account -Opens 'Start Chat' dialog .Ss toggle_ipython Shows or hides the ipython window .Ss toggle_roster_appearance diff --git a/data/gajim-remote.desktop.in b/data/gajim-remote.desktop.in index dcb8f8a4e..71d164539 100644 --- a/data/gajim-remote.desktop.in +++ b/data/gajim-remote.desktop.in @@ -9,5 +9,4 @@ TryExec=gajim-remote StartupNotify=false Terminal=false Type=Application -MimeType=x-scheme-handler/xmpp; NoDisplay=true diff --git a/data/org.gajim.Gajim.desktop.in b/data/org.gajim.Gajim.desktop.in index 16f8fb99d..521e84334 100644 --- a/data/org.gajim.Gajim.desktop.in +++ b/data/org.gajim.Gajim.desktop.in @@ -13,3 +13,4 @@ StartupNotify=true StartupWMClass=Gajim Terminal=false Type=Application +MimeType=x-scheme-handler/xmpp; diff --git a/gajim/app_actions.py b/gajim/app_actions.py index 8b7ae5cb3..d32989993 100644 --- a/gajim/app_actions.py +++ b/gajim/app_actions.py @@ -18,14 +18,15 @@ ## along with Gajim. If not, see . ## +import sys +import os + +from gi.repository import Gtk + from gajim.common import app from gajim.common import helpers from gajim.common.app import interface from gajim.common.exceptions import GajimGeneralException -from gi.repository import Gtk -import sys -import os - from gajim import config from gajim import dialogs from gajim import features_window @@ -43,6 +44,11 @@ class AppActions(): def __init__(self, application: Gtk.Application): self.application = application + # General Actions + + def on_add_contact_jid(self, action, param): + dialogs.AddNewContactWindow(None, param.get_string()) + # Application Menu Actions def on_preferences(self, action, param): @@ -74,6 +80,12 @@ class AppActions(): def on_quit(self, action, param): interface.roster.on_quit_request() + def on_new_chat(self, action, param): + if 'start_chat' in app.interface.instances: + app.interface.instances['start_chat'].present() + else: + app.interface.instances['start_chat'] = dialogs.StartChatDialog() + # Accounts Actions def on_profile(self, action, param): @@ -116,20 +128,14 @@ class AppActions(): 'You cannot join a group chat while you are invisible')) return if 'join_gc' in interface.instances[account]: - interface.instances[account]['join_gc'].window.present() + interface.instances[account]['join_gc'].present() else: - try: - interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account) - except GajimGeneralException: - pass + interface.instances[account]['join_gc'] = \ + dialogs.JoinGroupchatWindow(account, None) def on_add_contact(self, action, param): dialogs.AddNewContactWindow(param.get_string()) - def on_new_chat(self, action, param): - dialogs.NewChatDialog(param.get_string()) - def on_single_message(self, action, param): dialogs.SingleMessageWindow(param.get_string(), action='send') diff --git a/gajim/chat_control.py b/gajim/chat_control.py index 9442e19a2..a677fc5cf 100644 --- a/gajim/chat_control.py +++ b/gajim/chat_control.py @@ -1601,15 +1601,13 @@ class ChatControl(ChatControlBase): self._add_info_bar_message(markup, [b], file_props, Gtk.MessageType.ERROR) def _on_accept_gc_invitation(self, widget, event): - try: - if event.is_continued: - app.interface.join_gc_room(self.account, event.room_jid, - app.nicks[self.account], event.password, - is_continued=True) - else: - dialogs.JoinGroupchatWindow(self.account, event.room_jid) - except GajimGeneralException: - pass + if event.is_continued: + app.interface.join_gc_room(self.account, event.room_jid, + app.nicks[self.account], event.password, + is_continued=True) + else: + app.interface.join_gc_minimal(self.account, event.room_jid) + app.events.remove_events(self.account, self.contact.jid, event=event) def _on_cancel_gc_invitation(self, widget, event): diff --git a/gajim/command_system/implementation/standard.py b/gajim/command_system/implementation/standard.py index 0d96cc30f..059be9fae 100644 --- a/gajim/command_system/implementation/standard.py +++ b/gajim/command_system/implementation/standard.py @@ -296,21 +296,12 @@ class StandardGroupChatCommands(CommandContainer): 'room_jid': self.room_jid} @command(raw=True, empty=True) - @doc(_("Join a group chat given by a jid, optionally using given nickname")) - def join(self, jid, nick): - if not nick: - nick = self.nick - + @doc(_("Join a group chat given by a jid")) + def join(self, jid): if '@' not in jid: jid = jid + '@' + app.get_server_from_jid(self.room_jid) - try: - app.interface.instances[self.account]['join_gc'].window.present() - except KeyError: - try: - dialogs.JoinGroupchatWindow(account=self.account, room_jid=jid, nick=nick) - except GajimGeneralException: - pass + app.interface.join_gc_minimal(self.account, room_jid=jid) @command('part', 'close', raw=True, empty=True) @doc(_("Leave the groupchat, optionally giving a reason, and close tab or window")) diff --git a/gajim/common/app.py b/gajim/common/app.py index 010117a36..0a2e60632 100644 --- a/gajim/common/app.py +++ b/gajim/common/app.py @@ -33,6 +33,7 @@ import logging import locale import uuid from distutils.version import LooseVersion as V +from collections import namedtuple import gi import nbxmpp import hashlib @@ -81,6 +82,8 @@ PLUGINS_DIRS = [gajimpaths['PLUGINS_BASE'], PLUGINS_CONFIG_DIR = gajimpaths['PLUGINS_CONFIG_DIR'] MY_CERT_DIR = gajimpaths['MY_CERT'] +RecentGroupchat = namedtuple('RecentGroupchat', ['room', 'server', 'nickname']) + try: LANG = locale.getdefaultlocale()[0] # en_US, fr_FR, el_GR etc.. except (ValueError, locale.Error): @@ -373,6 +376,16 @@ def get_number_of_connected_accounts(accounts_list = None): connected_accounts = connected_accounts + 1 return connected_accounts +def get_connected_accounts(): + """ + Returns a list of CONNECTED accounts + """ + account_list = [] + for account in connections: + if account_is_connected(account): + account_list.append(account) + return account_list + def account_is_connected(account): if account not in connections: return False @@ -388,6 +401,11 @@ def zeroconf_is_connected(): return account_is_connected(ZEROCONF_ACC_NAME) and \ config.get_per('accounts', ZEROCONF_ACC_NAME, 'is_zeroconf') +def in_groupchat(account, room_jid): + if room_jid not in gc_connected[account]: + return False + return gc_connected[account][room_jid] + def get_number_of_securely_connected_accounts(): """ Return the number of the accounts that are SSL/TLS connected @@ -495,6 +513,34 @@ def get_name_from_jid(account, jid): actor = jid return actor +def get_muc_domain(account): + return connections[account].muc_jid.get('jabber', None) + +def get_recent_groupchats(account): + recent_groupchats = config.get_per( + 'accounts', account, 'recent_groupchats').split() + + recent_list = [] + for groupchat in recent_groupchats: + jid = nbxmpp.JID(groupchat) + recent = RecentGroupchat( + jid.getNode(), jid.getDomain(), jid.getResource()) + recent_list.append(recent) + return recent_list + +def add_recent_groupchat(account, room_jid, nickname): + recent = config.get_per( + 'accounts', account, 'recent_groupchats').split() + full_jid = room_jid + '/' + nickname + if full_jid in recent: + recent.remove(full_jid) + recent.insert(0, full_jid) + if len(recent) > 10: + recent = recent[0:9] + config_value = ' '.join(recent) + config.set_per( + 'accounts', account, 'recent_groupchats', config_value) + def get_priority(account, show): """ Return the priority an account must have diff --git a/gajim/common/caps_cache.py b/gajim/common/caps_cache.py index 957633f7d..b0975067c 100644 --- a/gajim/common/caps_cache.py +++ b/gajim/common/caps_cache.py @@ -472,6 +472,10 @@ class MucCapsCache: if child.getNamespace() == nbxmpp.NS_DATA: data.append(nbxmpp.DataForm(node=child)) + if nbxmpp.NS_MUC not in features: + # Not a MUC, dont cache info + return + self.cache[jid] = self.DiscoInfo(identities, features, data) def is_cached(self, jid): diff --git a/gajim/common/config.py b/gajim/common/config.py index 258df8afe..e2148448c 100644 --- a/gajim/common/config.py +++ b/gajim/common/config.py @@ -174,7 +174,6 @@ class Config: 'history_window_x-position': [ opt_int, 0 ], 'history_window_y-position': [ opt_int, 0 ], 'latest_disco_addresses': [ opt_str, '' ], - 'recently_groupchat': [ opt_str, '' ], 'time_stamp': [ opt_str, '[%X] ', _('This option let you customize timestamp that is printed in conversation. For exemple "[%H:%M] " will show "[hour:minute] ". See python doc on strftime for full documentation: http://docs.python.org/lib/module-time.html') ], 'before_nickname': [ opt_str, '', _('Characters that are printed before the nickname in conversations') ], 'after_nickname': [ opt_str, ':', _('Characters that are printed after the nickname in conversations') ], @@ -409,6 +408,7 @@ class Config: 'oauth2_client_id': [ opt_str, '0000000044077801', _('client_id for OAuth 2.0 authentication.')], 'oauth2_redirect_url': [ opt_str, 'https%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for OAuth 2.0 authentication.')], 'opened_chat_controls': [opt_str, '', _('Space separated list of JIDs for which we want to re-open a chat window on next startup.')], + 'recent_groupchats': [ opt_str, '' ], }, {}), 'statusmsg': ({ 'message': [ opt_str, '' ], diff --git a/gajim/common/connection_handlers.py b/gajim/common/connection_handlers.py index 01180fc98..5ac99fc48 100644 --- a/gajim/common/connection_handlers.py +++ b/gajim/common/connection_handlers.py @@ -98,6 +98,9 @@ class ConnectionDisco: self.disco_info_ids.append(id_) def discoverMUC(self, jid, callback): + if muc_caps_cache.is_cached(jid): + callback() + return disco_info = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_INFO) self.connection.SendAndCallForResponse( disco_info, self.received_muc_info, {'callback': callback}) diff --git a/gajim/common/helpers.py b/gajim/common/helpers.py index 06142f5fd..51eca6afa 100644 --- a/gajim/common/helpers.py +++ b/gajim/common/helpers.py @@ -411,6 +411,16 @@ def get_uf_show(show, use_mnemonic = False): uf_show = Q_('?contact has status:Has errors') return uf_show +def get_css_show_color(show): + if show in ('online', 'chat', 'invisible'): + return 'status-online' + elif show in ('offline', 'not in roster', 'requested'): + return None + elif show in ('xa', 'dnd'): + return 'status-dnd' + elif show in ('away'): + return 'status-away' + def get_uf_sub(sub): if sub == 'none': uf_sub = Q_('?Subscription we already have:None') diff --git a/gajim/conversation_textview.py b/gajim/conversation_textview.py index 721306d93..f1968f88d 100644 --- a/gajim/conversation_textview.py +++ b/gajim/conversation_textview.py @@ -689,15 +689,9 @@ class ConversationTextview(GObject.GObject): app.interface.new_chat_from_jid(self.account, jid) def on_join_group_chat_menuitem_activate(self, widget, room_jid): - if 'join_gc' in app.interface.instances[self.account]: - instance = app.interface.instances[self.account]['join_gc'] - instance.xml.get_object('room_jid_entry').set_text(room_jid) - app.interface.instances[self.account]['join_gc'].window.present() - else: - try: - dialogs.JoinGroupchatWindow(account=self.account, room_jid=room_jid) - except GajimGeneralException: - pass + # Remove ?join + room_jid = room_jid.split('?')[0] + app.interface.join_gc_minimal(self.account, room_jid) def on_add_to_roster_activate(self, widget, jid): dialogs.AddNewContactWindow(self.account, jid) @@ -805,7 +799,7 @@ class ConversationTextview(GObject.GObject): if '?' in word: (jid, action) = word.split('?') if action == 'join': - self.on_join_group_chat_menuitem_activate(None, jid) + app.interface.join_gc_minimal(None, jid) else: self.on_start_chat_activate(None, jid) else: diff --git a/gajim/data/gui/account_context_menu.ui b/gajim/data/gui/account_context_menu.ui index 665f54d80..333804447 100644 --- a/gajim/data/gui/account_context_menu.ui +++ b/gajim/data/gui/account_context_menu.ui @@ -28,15 +28,6 @@ GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - _Start Chat... - True - - True diff --git a/gajim/data/gui/application_menu.ui b/gajim/data/gui/application_menu.ui index 2be3e5eed..6611c813a 100644 --- a/gajim/data/gui/application_menu.ui +++ b/gajim/data/gui/application_menu.ui @@ -72,6 +72,11 @@ app.accounts <Primary><Shift>A + + Start Chat + app.start-chat + <Primary>N + Bookmarks app.bookmarks diff --git a/gajim/data/gui/join_groupchat_window.ui b/gajim/data/gui/join_groupchat_window.ui index 9ba120367..9aff6a87b 100644 --- a/gajim/data/gui/join_groupchat_window.ui +++ b/gajim/data/gui/join_groupchat_window.ui @@ -1,302 +1,340 @@ - + - - + + True False - gtk-find - - - True - False - gtk-apply - - - False - 6 - Join Group Chat - False - dialog - - + 18 + 18 + 18 + 18 + 6 + 12 - + + False + True + center + 12 + + + + 0 + 0 + 3 + + + + + True + True + Bookmark this Groupchat + start + center + 6 + True + + + + 1 + 7 + 2 + + + + + True + True + Join this Groupchat everytime Gajim is started + start + center + True + + + 1 + 8 + 2 + + + + True False - vertical - 12 + start + center + 6 + Bookmark + + + 0 + 7 + + + + + True + False + start + center + Autojoin + + + 0 + 8 + + + + + False + True + center + + + + 1 + 1 + 2 + + + + + True + True + center + + + 1 + 3 + 2 + + + + + False + True + start + Account + + + 0 + 1 + + + + + True + False + start + Nickname + + + 0 + 3 + + + + + False + True + start + Password + + + 0 + 6 + + + + + True + True + password + + + 1 + 6 + 2 + + + + + False + True + 18 + 6 + end - + + gtk-cancel True - False - 6 - 12 - - - True - True - True - - - - 1 - 3 - - - - - False - True - Account - 0 - - - 0 - 0 - - - - - False - True - - - - 1 - 0 - - - - - True - False - Recently: - 0 - - - 0 - 1 - - - - - True - False - - - - 1 - 1 - - - - - True - False - Nickname: - 0 - - - 0 - 2 - - - - - True - True - True - - - - 1 - 2 - - - - - True - False - Room: - 0 - - - 0 - 3 - - - - - True - False - Server: - 0 - - - 0 - 4 - - - - - True - False - Password: - 0 - - - 0 - 5 - - - - - True - True - False - True - - - 1 - 5 - - - - - _Bookmark this room - True - True - False - True - 0 - True - - - - 0 - 6 - 2 - - - - - Join this room _automatically when I connect - True - False - True - False - True - 0 - True - - - 0 - 7 - 2 - - - - - True - False - 6 - - - True - False - True - - - True - - - - - - True - True - 0 - - - - - Bro_wse Rooms - True - True - True - image1 - True - - - - False - True - end - 1 - - - - - 1 - 4 - - + True + True + True + - False + True True 0 - + + Join True - False - 12 - end - - - gtk-cancel - True - True - True - False - True - - - - False - False - 0 - - - - - _Join - True - True - True - True - False - image2 - True - - - - False - False - 1 - - + True + True + True + + - False + True True 1 + + 1 + 9 + 2 + + + + + False + True + start + Server + + + 0 + 5 + + + + + False + True + start + Room + + + 0 + 4 + + + + + False + True + start + Recently + + + 0 + 2 + + + + + True + True + + + 1 + 4 + 2 + + + + + False + True + + + + 1 + 2 + 2 + + + + + False + True + True + + + True + False + + + + + 1 + 5 + + + + + True + True + True + Search the rooms on this server + + + + True + False + system-search-symbolic + + + + + 2 + 5 + + + + + + + + True + False + Join Groupchat + + + gtk-cancel + True + True + True + True + + + + + + Join + True + True + True + + + + + end + 1 + diff --git a/gajim/data/gui/start_chat_dialog.ui b/gajim/data/gui/start_chat_dialog.ui new file mode 100644 index 000000000..9857be9b4 --- /dev/null +++ b/gajim/data/gui/start_chat_dialog.ui @@ -0,0 +1,56 @@ + + + + + + True + False + 18 + vertical + 6 + + + True + True + True + True + edit-find-symbolic + False + False + + + False + True + 0 + + + + + True + True + never + in + + + True + False + + + StartChatListBox + True + False + browse + False + + + + + + + True + True + 1 + + + + diff --git a/gajim/data/style/gajim.css b/gajim/data/style/gajim.css index 88a8335cd..696442aa4 100644 --- a/gajim/data/style/gajim.css +++ b/gajim/data/style/gajim.css @@ -86,4 +86,16 @@ popover#EmoticonPopover flowboxchild { padding-top: 5px; padding-bottom: 5px; } background-color: @theme_unfocused_bg_color; color: @theme_text_color; } +/* StartChatListBox */ +#StartChatListBox > row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; } +#StartChatListBox > row:last-child { border-bottom: 0px} +#StartChatListBox > row.activatable:active { box-shadow: none; } +#StartChatListBox > row { padding: 10px 20px 10px 10px; } +#StartChatListBox > row:not(.activatable) label { color: @insensitive_fg_color } +/* Text style */ + +.bold16 { font-size: 16px; font-weight: bold; } +.status-away { color: #ff8533;} +.status-dnd { color: #e62e00;} +.status-online { color: #66bf10;} diff --git a/gajim/dialogs.py b/gajim/dialogs.py index 6f6968389..8b0675ea4 100644 --- a/gajim/dialogs.py +++ b/gajim/dialogs.py @@ -38,6 +38,7 @@ from gi.repository import GLib import os import nbxmpp import time +import locale from gajim import gtkgui_helpers from gajim import vcard @@ -61,6 +62,8 @@ from gajim.common import app from gajim.common import helpers from gajim.common import i18n from gajim.common import dataforms +from gajim.common.const import AvatarSize +from gajim.common.caps_cache import muc_caps_cache from gajim.common.exceptions import GajimGeneralException from gajim.common.connection_handlers_events import MessageOutgoingEvent @@ -2350,199 +2353,233 @@ class SubscriptionRequestWindow: gtkgui_helpers.popup_emoticons_under_button(menu, widget, self.window.get_window()) +class JoinGroupchatWindow(Gtk.ApplicationWindow): + def __init__(self, account, room_jid, password=None, automatic=None): + Gtk.ApplicationWindow.__init__(self) + self.set_name('JoinGroupchat') + self.set_application(app.app) + self.set_show_menubar(False) + self.set_resizable(False) + self.set_position(Gtk.WindowPosition.CENTER) + self.set_title(_('Join Groupchat')) -class JoinGroupchatWindow: - def __init__(self, account=None, room_jid='', nick='', password='', - automatic=False): - """ - Automatic is a dict like {'invities': []}. If automatic is not empty, - this means room must be automaticaly configured and when done, invities - must be automatically invited - """ - self.window_account = None - if account: - if room_jid != '' and room_jid in app.gc_connected[account] and \ - app.gc_connected[account][room_jid]: - ErrorDialog(_('You are already in group chat %s') % room_jid) - raise GajimGeneralException('You are already in this group chat') - if nick == '': - nick = app.nicks[account] - if app.connections[account].connected < 2: - ErrorDialog(_('You are not connected to the server'), - _('You can not join a group chat unless you are connected.')) - raise GajimGeneralException('You must be connected to join a groupchat') - self.window_account = account - - self.xml = gtkgui_helpers.get_gtk_builder('join_groupchat_window.ui') - - account_label = self.xml.get_object('account_label') - account_combobox = self.xml.get_object('account_combobox') - account_label.set_no_show_all(False) - account_combobox.set_no_show_all(False) - liststore = Gtk.ListStore(str) - account_combobox.set_model(liststore) - cell = Gtk.CellRendererText() - account_combobox.pack_start(cell, True) - account_combobox.add_attribute(cell, 'text', 0) - account_combobox.set_active(-1) - - # Add accounts, set current as active if it matches 'account' - for acct in [a for a in app.connections if \ - app.account_is_connected(a)]: - if app.connections[acct].is_zeroconf: - continue - liststore.append([acct]) - if account and account == acct: - account_combobox.set_active(liststore.iter_n_children(None)-1) - - self.account = account self.automatic = automatic - self._empty_required_widgets = [] - - self.window = self.xml.get_object('join_groupchat_window') - self.window.set_transient_for(app.interface.roster.window) - self._room_jid_entry = self.xml.get_object('room_jid_entry') - self._nickname_entry = self.xml.get_object('nickname_entry') - self._password_entry = self.xml.get_object('password_entry') - self.server_comboboxtext = self.xml.get_object('server_comboboxtext') - - self._nickname_entry.set_text(nick) - if password: - self._password_entry.set_text(password) - self.xml.connect_signals(self) - title = None - if account: - # now add us to open windows - app.interface.instances[account]['join_gc'] = self - if len(app.connections) > 1: - title = _('Join Group Chat with account %s') % account - if title is None: - title = _('Join Group Chat') - self.window.set_title(title) - - self.browse_button = self.xml.get_object('browse_rooms_button') - self.browse_button.set_sensitive(False) - - self.recently_combobox = self.xml.get_object('recently_combobox') - liststore = Gtk.ListStore(str, str) - self.recently_combobox.set_model(liststore) - cell = Gtk.CellRendererText() - self.recently_combobox.pack_start(cell, True) - self.recently_combobox.add_attribute(cell, 'text', 0) - self.recently_groupchat = app.config.get('recently_groupchat').split() - - server_list = [] - # get the muc server of our server - if 'jabber' in app.connections[account].muc_jid: - server_list.append(app.connections[account].muc_jid['jabber']) - for g in self.recently_groupchat: - r_jid = app.get_jid_without_resource(g) - nick = app.get_resource_from_jid(g) - if nick: - show = '%(nick)s on %(room_jid)s' % {'nick': nick, - 'room_jid': r_jid} - else: - show = r_jid - liststore.append([show, g]) - server = app.get_server_from_jid(r_jid) - if server not in server_list and not server.startswith('irc'): - server_list.append(server) - - for s in server_list: - self.server_comboboxtext.append_text(s) - - - self._set_room_jid(room_jid) - - if len(self.recently_groupchat) == 0: - self.recently_combobox.set_sensitive(False) - elif room_jid == '': - self.recently_combobox.set_active(0) - self._room_jid_entry.select_region(0, -1) - elif room_jid != '': - self.xml.get_object('join_button').grab_focus() - - if not self._room_jid_entry.get_text(): - self._empty_required_widgets.append(self._room_jid_entry) - if not self._nickname_entry.get_text(): - self._empty_required_widgets.append(self._nickname_entry) - if len(self._empty_required_widgets): - self.xml.get_object('join_button').set_sensitive(False) - - if account and not app.connections[account].private_storage_supported: - self.xml.get_object('bookmark_checkbutton').set_sensitive(False) - + self.password = password self.requested_jid = None - app.ged.register_event_handler('agent-info-received', ged.GUI1, - self._nec_agent_info_received) - app.ged.register_event_handler('agent-info-error-received', ged.GUI1, - self._nec_agent_info_error_received) + self.room_jid = room_jid + self.account = account + self.minimal_mode = room_jid is not None - self.window.show_all() + glade_objects = ['grid', 'nick_entry', 'account_combo', 'jid_label', + 'bookmark_switch', 'autojoin_switch', 'headerbar', + 'account_label', 'password_entry', 'password_label', + 'join_button', 'button_box', 'server_label', + 'server_combo', 'recent_label', 'recent_combo', + 'room_label', 'room_entry', 'search_button'] - def on_join_groupchat_window_destroy(self, widget): - """ - Close window - """ - app.ged.remove_event_handler('agent-info-received', ged.GUI1, - self._nec_agent_info_received) - app.ged.register_event_handler('agent-info-error-received', ged.GUI1, - self._nec_agent_info_error_received) - if self.window_account and 'join_gc' in app.interface.instances[ - self.window_account]: - # remove us from open windows - del app.interface.instances[self.window_account]['join_gc'] + minimal_widgets = ['jid_label'] - def on_join_groupchat_window_key_press_event(self, widget, event): - if event.keyval == Gdk.KEY_Escape: # ESCAPE - widget.destroy() + extended_widgets = ['server_label', 'server_combo', 'recent_label', + 'recent_combo', 'room_label', 'room_entry', + 'search_button'] + self.builder = gtkgui_helpers.get_gtk_builder( + 'join_groupchat_window.ui') + for obj in glade_objects: + setattr(self, obj, self.builder.get_object(obj)) - def on_required_entry_changed(self, widget): - if not widget.get_text(): - self._empty_required_widgets.append(widget) - self.xml.get_object('join_button').set_sensitive(False) + self.add(self.grid) + + if os.environ.get('GTK_CSD', '1') == '1': + self.set_titlebar(self.headerbar) else: - if widget in self._empty_required_widgets: - self._empty_required_widgets.remove(widget) - if not self._empty_required_widgets and self.account: - self.xml.get_object('join_button').set_sensitive(True) - text = self._room_jid_entry.get_text() - if widget == self._room_jid_entry and text.startswith('xmpp:'): - text = text[5:] - self._room_jid_entry.set_text(text) - if widget == self._room_jid_entry and '@' in text: - # Don't allow @ char in room entry - room_jid, server = text.split('@', 1) - self._room_jid_entry.set_text(room_jid) - if server: - if '?' in server: - server = server.split('?')[0] - self.server_comboboxtext.get_child().set_text(server) - self.server_comboboxtext.grab_focus() + self.button_box.show() - def on_account_combobox_changed(self, widget): - model = widget.get_model() - iter_ = widget.get_active_iter() - self.account = model[iter_][0] - self.on_required_entry_changed(self._nickname_entry) + # Show widgets depending on the mode the window is in + if self.minimal_mode: + for widget in minimal_widgets: + getattr(self, widget).show() + self.jid_label.set_text(room_jid) + else: + for widget in extended_widgets: + getattr(self, widget).show() + self._fill_recent_and_servers(account) - def _set_room_jid(self, full_jid): - room_jid, nick = app.get_room_and_nick_from_fjid(full_jid) + if account is None: + connected_accounts = app.get_connected_accounts() + for acc in connected_accounts: + self.account_combo.append_text(acc) + else: + connected_accounts = [account] + self.account_combo.append_text(account) + + self.builder.connect_signals(self) + self.connect('key-press-event', self._on_key_press_event) + self.connect('destroy', self._on_destroy) + + if not self.minimal_mode: + app.ged.register_event_handler('agent-info-received', ged.GUI1, + self._nec_agent_info_received) + app.ged.register_event_handler('agent-info-error-received', ged.GUI1, + self._nec_agent_info_error_received) + + # Show account combobox if there is more than one account + if len(connected_accounts) > 1: + self.account_combo.show() + self.account_label.show() + + # Select first account + self.account_combo.set_active(0) + if not self.minimal_mode: + self.recent_combo.set_active(0) + + if self.password is not None: + self.password_entry.set_text(self.password) + + # Set bookmark switch sensitive if server supports bookmarks + acc = self.account_combo.get_active_text() + if not app.connections[acc].private_storage_supported: + self.bookmark_switch.set_sensitive(False) + self.autojoin_switch.set_sensitive(False) + + # Show password field if we are in extended mode or + # The MUC is passwordprotected + if not self.minimal_mode or muc_caps_cache.supports( + room_jid, 'muc_passwordprotected'): + self.password_entry.show() + self.password_label.show() + + self.show_all() + + def set_room(self, room_jid): room, server = app.get_name_and_server_from_jid(room_jid) - self._room_jid_entry.set_text(room) - model = self.server_comboboxtext.get_model() - self.server_comboboxtext.get_child().set_text(server) - if nick: - self._nickname_entry.set_text(nick) + self.room_entry.set_text(room) + self.server_combo.get_child().set_text(server) - def on_recently_combobox_changed(self, widget): - model = widget.get_model() - iter_ = widget.get_active_iter() - full_jid = model[iter_][1] - self._set_room_jid(full_jid) + def _fill_recent_and_servers(self, account): + recent = app.get_recent_groupchats(account) + servers = [] + for groupchat in recent: + text = '%s on %s@%s' % (groupchat.nickname, + groupchat.room, + groupchat.server) + self.recent_combo.append_text(text) + servers.append(groupchat.server) - def on_browse_rooms_button_clicked(self, widget): - server = self.server_comboboxtext.get_child().get_text() + for server in set(servers): + self.server_combo.append_text(server) + + # Add own Server to ComboBox + muc_domain = app.get_muc_domain(account) + if muc_domain is not None: + self.server_combo.insert_text(0, muc_domain) + + def _on_recent_changed(self, combo): + text = combo.get_active_text() + if text is None: + self.server_combo.set_active(0) + return + nickname, _, room_jid = text.split() + room, server = app.get_name_and_server_from_jid(room_jid) + self.room_entry.set_text(room) + self.nick_entry.set_text(nickname) + self.server_combo.get_child().set_text(server) + + def _on_account_combo_changed(self, combo): + account = combo.get_active_text() + self.nick_entry.set_text(app.nicks[account]) + + def _on_key_press_event(self, widget, event): + if event.keyval == Gdk.KEY_Escape: + self.destroy() + if event.keyval == Gdk.KEY_Return: + self._on_join_clicked() + return True + + def _on_join_clicked(self, *args): + account = self.account_combo.get_active_text() + nickname = self.nick_entry.get_text() + + if not self.minimal_mode: + server = self.server_combo.get_active_text() + room = self.room_entry.get_text() + if room == '': + ErrorDialog(_('Invalid Room'), + _('Please choose a room'), transient_for=self) + return + self.room_jid = '%s@%s' % (room, server) + + if app.in_groupchat(account, self.room_jid): + # If we already in the groupchat, join_gc_room will bring + # it to front + app.interface.join_gc_room(account, self.room_jid, nickname, '') + self.destroy() + return + + if nickname == '': + ErrorDialog(_('Invalid Nickname'), + _('Please choose a nickname'), transient_for=self) + return + + try: + helpers.parse_resource(nickname) + except helpers.InvalidFormat as error: + ErrorDialog(_('Invalid Nickname'), str(error), transient_for=self) + return + + try: + helpers.parse_jid(self.room_jid) + except helpers.InvalidFormat as error: + ErrorDialog(_('Invalid JID'), str(error), transient_for=self) + return + + if not app.account_is_connected(account): + ErrorDialog( + _('You are not connected to the server'), + _('You can not join a group chat unless you are connected.'), + transient_for=self) + return + + password = self.password_entry.get_text() + self._add_bookmark(account, nickname, password) + app.add_recent_groupchat(account, self.room_jid, nickname) + + if self.automatic: + app.automatic_rooms[self.account][self.room_jid] = self.automatic + + app.interface.join_gc_room(account, self.room_jid, nickname, password) + self.destroy() + + def _on_cancel_clicked(self, *args): + self.destroy() + + def _on_bookmark_activate(self, switch, param): + self.autojoin_switch.set_sensitive(switch.get_active()) + + def _add_bookmark(self, account, nickname, password): + if not app.connections[account].private_storage_supported: + return + + add_bookmark = self.bookmark_switch.get_active() + if not add_bookmark: + return + + autojoin = int(self.autojoin_switch.get_active()) + + # Add as bookmark, with autojoin and not minimized + name = app.get_nick_from_jid(self.room_jid) + app.interface.add_gc_bookmark( + account, name, self.room_jid, autojoin, 0, password, nickname) + + def _on_destroy(self, *args): + if not self.minimal_mode: + del app.interface.instances[self.account]['join_gc'] + app.ged.remove_event_handler('agent-info-received', ged.GUI1, + self._nec_agent_info_received) + app.ged.remove_event_handler('agent-info-error-received', ged.GUI1, + self._nec_agent_info_error_received) + + def _on_search_clicked(self, widget): + server = self.server_combo.get_active_text() self.requested_jid = server app.connections[self.account].discoverInfo(server) @@ -2552,9 +2589,9 @@ class JoinGroupchatWindow: if obj.jid != self.requested_jid: return self.requested_jid = None - window = app.interface.instances[self.account]['join_gc'].window - ErrorDialog(_('Wrong server'), _('%s is not a groupchat server') % \ - obj.jid, transient_for=window) + ErrorDialog(_('Wrong server'), + _('%s is not a groupchat server') % obj.jid, + transient_for=self) def _nec_agent_info_received(self, obj): if obj.conn.name != self.account: @@ -2563,9 +2600,9 @@ class JoinGroupchatWindow: return self.requested_jid = None if nbxmpp.NS_MUC not in obj.features: - window = app.interface.instances[self.account]['join_gc'].window - ErrorDialog(_('Wrong server'), _('%s is not a groupchat server') % \ - obj.jid, transient_for=window) + ErrorDialog(_('Wrong server'), + _('%s is not a groupchat server') % obj.jid, + transient_for=self) return if obj.jid in app.interface.instances[self.account]['disco']: app.interface.instances[self.account]['disco'][obj.jid].window.\ @@ -2573,99 +2610,14 @@ class JoinGroupchatWindow: else: try: # Object will add itself to the window dict - import disco - disco.ServiceDiscoveryWindow(self.account, obj.jid, + from gajim.disco import ServiceDiscoveryWindow + ServiceDiscoveryWindow( + self.account, obj.jid, initial_identities=[{'category': 'conference', - 'type': 'text'}]) + 'type': 'text'}]) except GajimGeneralException: pass - def on_server_entry_changed(self, widget): - if not widget.get_text(): - self.browse_button.set_sensitive(False) - else: - self.browse_button.set_sensitive(True) - - def on_cancel_button_clicked(self, widget): - """ - When Cancel button is clicked - """ - self.window.destroy() - - def on_bookmark_checkbutton_toggled(self, widget): - auto_join_checkbutton = self.xml.get_object('auto_join_checkbutton') - if widget.get_active(): - auto_join_checkbutton.set_sensitive(True) - else: - auto_join_checkbutton.set_sensitive(False) - - def on_join_button_clicked(self, widget): - """ - When Join button is clicked - """ - if not self.account: - ErrorDialog(_('Invalid Account'), - _('You have to choose an account from which you want to join the ' - 'groupchat.')) - return - nickname = self._nickname_entry.get_text() - server = self.server_comboboxtext.get_child().get_text() - room = self._room_jid_entry.get_text().strip() - room_jid = room + '@' + server - password = self._password_entry.get_text() - try: - nickname = helpers.parse_resource(nickname) - except Exception: - ErrorDialog(_('Invalid Nickname'), - _('The nickname contains invalid characters.')) - return - user, server, resource = helpers.decompose_jid(room_jid) - if not user or not server or resource: - ErrorDialog(_('Invalid group chat JID'), - _('Please enter the group chat JID as room@server.')) - return - try: - room_jid = helpers.parse_jid(room_jid) - except Exception: - ErrorDialog(_('Invalid group chat JID'), - _('The group chat JID contains invalid characters.')) - return - - if app.contacts.get_contact(self.account, room_jid) and \ - not app.contacts.get_contact(self.account, room_jid).is_groupchat(): - ErrorDialog(_('This is not a group chat'), - _('%(room_jid)s is already in your roster. Please check if ' - '%(room_jid)s is a correct group chat name. If it is, delete ' - 'it from your roster and try joining the group chat again.') % \ - {'room_jid': room_jid, 'room_jid': room_jid}) - return - - full_jid = room_jid + '/' + nickname - if full_jid in self.recently_groupchat: - self.recently_groupchat.remove(full_jid) - self.recently_groupchat.insert(0, full_jid) - if len(self.recently_groupchat) > 10: - self.recently_groupchat = self.recently_groupchat[0:10] - app.config.set('recently_groupchat', - ' '.join(self.recently_groupchat)) - - if self.xml.get_object('bookmark_checkbutton').get_active(): - if self.xml.get_object('auto_join_checkbutton').get_active(): - autojoin = '1' - else: - autojoin = '0' - # Add as bookmark, with autojoin and not minimized - name = app.get_nick_from_jid(room_jid) - app.interface.add_gc_bookmark(self.account, name, room_jid, - autojoin, autojoin, password, nickname) - - if self.automatic: - app.automatic_rooms[self.account][room_jid] = self.automatic - - app.interface.join_gc_room(self.account, room_jid, nickname, password) - - self.window.destroy() - class SynchroniseSelectAccountDialog: def __init__(self, account): # 'account' can be None if we are about to create our first one @@ -2807,59 +2759,323 @@ class SynchroniseSelectContactsDialog: iter_ = model.iter_next(iter_) self.dialog.destroy() -class NewChatDialog(InputDialog): - def __init__(self, account): - self.account = account +class StartChatDialog(Gtk.ApplicationWindow): + def __init__(self): + # Must be before ApplicationWindow.__init__ + # or we get our own window + active_window = app.app.get_active_window() - if len(app.connections) > 1: - title = _('Start Chat with account %s') % account + Gtk.ApplicationWindow.__init__(self) + self.set_name('StartChatDialog') + self.set_application(app.app) + mode = app.config.get('one_message_window') != 'always_with_roster' + if active_window == app.interface.roster.window and mode: + self.set_position(Gtk.WindowPosition.CENTER) else: - title = _('Start Chat') - prompt_text = _('Fill in the nickname or the JID of the contact you ' - 'would like\nto send a chat message to:') - InputDialog.__init__(self, title, prompt_text, is_modal=False) - self.input_entry.set_placeholder_text(_('Nickname / JID')) + self.set_transient_for(active_window) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + self.set_show_menubar(False) + self.set_title(_('Start new Conversation')) + self.set_default_size(-1, 400) - self.completion_dict = {} - liststore = gtkgui_helpers.get_completion_liststore(self.input_entry) - self.completion_dict = helpers.get_contact_dict_for_account(account) - # add all contacts to the model - keys = sorted(self.completion_dict.keys()) - for jid in keys: - contact = self.completion_dict[jid] - img = app.interface.jabber_state_images['16'][contact.show] - liststore.append((img.get_pixbuf(), jid)) + self.builder = gtkgui_helpers.get_gtk_builder( + 'start_chat_dialog.ui') + self.listbox = self.builder.get_object('listbox') + self.search_entry = self.builder.get_object('search_entry') + self.box = self.builder.get_object('box') - self.ok_handler = self.new_chat_response - okbutton = self.xml.get_object('okbutton') - okbutton.connect('clicked', self.on_okbutton_clicked) - cancelbutton = self.xml.get_object('cancelbutton') - cancelbutton.connect('clicked', self.on_cancelbutton_clicked) - self.dialog.set_transient_for(app.interface.roster.window) - self.dialog.show_all() + self.add(self.box) - def new_chat_response(self, jid): - """ - Called when ok button is clicked - """ - if app.connections[self.account].connected <= 1: - #if offline or connecting - ErrorDialog(_('Connection not available'), - _('Please make sure you are connected with "%s".') % self.account) - return + self.new_contact_row_visible = False + self.new_contact_rows = {} + self.new_groupchat_rows = {} + self.accounts = app.connections.keys() + self.add_contacts() + self.add_groupchats() - if jid in self.completion_dict: - jid = self.completion_dict[jid].jid + self.search_entry.connect('search-changed', + self._on_search_changed) + self.search_entry.connect('next-match', + self._select_new_match, 'next') + self.search_entry.connect('previous-match', + self._select_new_match, 'prev') + self.search_entry.connect('stop-search', + lambda *args: self.search_entry.set_text('')) + + self.listbox.set_filter_func(self._filter_func, None) + self.listbox.set_sort_func(self._sort_func, None) + self.listbox.connect('row-activated', self._on_row_activated) + + self.connect('key-press-event', self._on_key_press) + self.connect('destroy', self._destroy) + + self.select_first_row() + self.show_all() + + def add_contacts(self): + show_account = len(self.accounts) > 1 + for account in self.accounts: + self.new_contact_rows[account] = None + for jid in app.contacts.get_jid_list(account): + contact = app.contacts.get_contact_with_highest_priority( + account, jid) + if contact.is_groupchat(): + continue + row = ContactRow(account, contact, jid, + contact.get_shown_name(), show_account) + self.listbox.add(row) + + def add_groupchats(self): + show_account = len(self.accounts) > 1 + for account in self.accounts: + self.new_groupchat_rows[account] = None + bookmarks = app.connections[account].bookmarks + groupchats = {} + for bookmark in bookmarks: + groupchats[bookmark['jid']] = bookmark['name'] + + for jid in app.contacts.get_gc_list(account): + if jid in groupchats: + continue + groupchats[jid] = None + + for jid in groupchats: + name = groupchats[jid] + if name is None: + name = app.get_nick_from_jid(groupchats[jid]) + row = ContactRow(account, None, jid, name, + show_account, True) + self.listbox.add(row) + + def _on_row_activated(self, listbox, row): + row = row.get_child() + self._start_new_chat(row) + + def _on_key_press(self, widget, event): + if event.keyval in (Gdk.KEY_Down, Gdk.KEY_Tab): + self.search_entry.emit('next-match') + return True + elif (event.state == Gdk.ModifierType.SHIFT_MASK and + event.keyval == Gdk.KEY_ISO_Left_Tab): + self.search_entry.emit('previous-match') + return True + elif event.keyval == Gdk.KEY_Up: + self.search_entry.emit('previous-match') + return True + elif event.keyval == Gdk.KEY_Escape: + if self.search_entry.get_text() != '': + self.search_entry.emit('stop-search') + else: + self.destroy() + return True + elif event.keyval == Gdk.KEY_Return: + row = self.listbox.get_selected_row() + if row is not None: + row.emit('activate') + return True else: + self.search_entry.grab_focus_without_selecting() + + def _start_new_chat(self, row): + if row.new: + if not app.account_is_connected(row.account): + ErrorDialog( + _('You are not connected to the server'), + _('You can not start a new conversation' + ' unless you are connected.'), + transient_for=self) + return try: - jid = helpers.parse_jid(jid) + helpers.parse_jid(row.jid) except helpers.InvalidFormat as e: - ErrorDialog(_('Invalid JID'), str(e)) + ErrorDialog(_('Invalid JID'), str(e), transient_for=self) return - except: - ErrorDialog(_('Invalid JID'), _('Unable to parse "%s".') % jid) + + if row.groupchat: + app.interface.join_gc_minimal(row.account, row.jid) + else: + app.interface.new_chat_from_jid(row.account, row.jid) + + self.destroy() + + def _on_search_changed(self, entry): + search_text = entry.get_text() + if '@' in search_text: + self._add_new_jid_row() + self._update_new_jid_rows(search_text) + else: + self._remove_new_jid_row() + self.listbox.invalidate_filter() + + def _add_new_jid_row(self): + if self.new_contact_row_visible: + return + for account in self.new_contact_rows: + show_account = len(self.accounts) > 1 + row = ContactRow(account, None, '', None, show_account) + self.new_contact_rows[account] = row + group_row = ContactRow(account, None, '', None, show_account, True) + self.new_groupchat_rows[account] = group_row + self.listbox.add(row) + self.listbox.add(group_row) + row.get_parent().show_all() + self.new_contact_row_visible = True + + def _remove_new_jid_row(self): + if not self.new_contact_row_visible: + return + for account in self.new_contact_rows: + self.listbox.remove(self.new_contact_rows[account].get_parent()) + self.listbox.remove(self.new_groupchat_rows[account].get_parent()) + self.new_contact_row_visible = False + + def _update_new_jid_rows(self, search_text): + for account in self.new_contact_rows: + 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): + selected_row = self.listbox.get_selected_row() + index = selected_row.get_index() + + if direction == 'next': + index += 1 + else: + index -= 1 + + while True: + new_selected_row = self.listbox.get_row_at_index(index) + if new_selected_row is None: return - app.interface.new_chat_from_jid(self.account, jid) + if new_selected_row.get_child_visible(): + self.listbox.select_row(new_selected_row) + new_selected_row.grab_focus() + return + if direction == 'next': + index += 1 + else: + index -= 1 + + def select_first_row(self): + first_row = self.listbox.get_row_at_y(0) + self.listbox.select_row(first_row) + + def _filter_func(self, row, user_data): + search_text = self.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 + GLib.timeout_add(50, self.select_first_row) + return True + + @staticmethod + 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 + account2 = row2.get_child().account + is_groupchat1 = row1.get_child().groupchat + is_groupchat2 = row2.get_child().groupchat + new1 = row1.get_child().new + new2 = row2.get_child().new + + result = locale.strcoll(account1.lower(), account2.lower()) + if result != 0: + return result + + if new1 != new2: + return 1 if new1 else -1 + + if is_groupchat1 != is_groupchat2: + return 1 if is_groupchat1 else -1 + + return locale.strcoll(name1.lower(), name2.lower()) + + @staticmethod + def _destroy(*args): + del app.interface.instances['start_chat'] + +class ContactRow(Gtk.Grid): + def __init__(self, account, contact, jid, name, show_account, + groupchat=False): + Gtk.Grid.__init__(self) + self.set_column_spacing(12) + self.set_size_request(260, -1) + self.account = account + self.account_label = app.config.get_per( + 'accounts', account, 'account_label') or account + self.show_account = show_account + self.jid = jid + self.contact = contact + self.name = name + self.groupchat = groupchat + self.new = jid == '' + + if self.groupchat: + if self.new: + muc_image = app.interface.jabber_state_images['32']['muc_inactive'] + else: + muc_image = app.interface.jabber_state_images['32']['muc_active'] + image = Gtk.Image.new_from_pixbuf(muc_image.get_pixbuf()) + else: + avatar = app.contacts.get_avatar(account, jid, AvatarSize.ROSTER) + if avatar is None: + image = Gtk.Image.new_from_icon_name( + 'avatar-default', Gtk.IconSize.DND) + else: + image = Gtk.Image.new_from_pixbuf(avatar) + self.add(image) + + middle_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0) + middle_box.set_hexpand(True) + + if self.name is None: + if self.groupchat: + self.name = _('New Groupchat') + else: + self.name = _('New Contact') + + self.name_label = Gtk.Label(self.name) + self.name_label.set_halign(Gtk.Align.START) + self.name_label.get_style_context().add_class('bold16') + + status = contact.show if contact else 'offline' + css_class = helpers.get_css_show_color(status) + if css_class is not None: + self.name_label.get_style_context().add_class(css_class) + middle_box.add(self.name_label) + + self.jid_label = Gtk.Label(jid) + self.jid_label.set_halign(Gtk.Align.START) + middle_box.add(self.jid_label) + + self.add(middle_box) + + if show_account: + account_label = Gtk.Label(self.account_label) + account_label.set_halign(Gtk.Align.START) + account_label.set_valign(Gtk.Align.START) + + right_box = Gtk.Box() + right_box.set_vexpand(True) + right_box.add(account_label) + self.add(right_box) + + self.show_all() + + def update_jid(self, jid): + self.jid = jid + self.jid_label.set_text(jid) + + def get_search_text(self): + if self.contact is None and not self.groupchat: + return self.jid + if self.show_account: + return '%s %s %s' % (self.name, self.jid, self.account_label) + return '%s %s' % (self.name, self.jid) class ChangePasswordDialog: def __init__(self, account, on_response, transient_for=None): @@ -4476,16 +4692,13 @@ class InvitationReceivedDialog: sectext += '\n\n' + _('Do you want to accept the invitation?') def on_yes(checked, text): - try: - if self.is_continued: - app.interface.join_gc_room(self.account, self.room_jid, - app.nicks[self.account], self.password, - is_continued=True) - else: - JoinGroupchatWindow(self.account, self.room_jid, - password=self.password) - except GajimGeneralException: - pass + if self.is_continued: + app.interface.join_gc_room(self.account, self.room_jid, + app.nicks[self.account], self.password, + is_continued=True) + else: + app.interface.join_gc_minimal( + self.account, self.room_jid, password=self.password) def on_no(text): app.connections[account].decline_invitation(self.room_jid, @@ -4828,7 +5041,7 @@ class TransformChatToMUC: if 'jabber' in app.connections[account].muc_jid: server_list.append(app.connections[account].muc_jid['jabber']) # add servers or recently joined groupchats - recently_groupchat = app.config.get('recently_groupchat').split() + recently_groupchat = app.config.get_per('accounts', account, 'recent_groupchats').split() for g in recently_groupchat: server = app.get_server_from_jid(g) if server not in server_list and not server.startswith('irc'): diff --git a/gajim/disco.py b/gajim/disco.py index 536e6ee74..4d43f1d2b 100644 --- a/gajim/disco.py +++ b/gajim/disco.py @@ -1400,13 +1400,7 @@ class ToplevelAgentBrowser(AgentBrowser): if not iter_: return service = model[iter_][0] - if 'join_gc' not in app.interface.instances[self.account]: - try: - dialogs.JoinGroupchatWindow(self.account, service) - except GajimGeneralException: - pass - else: - app.interface.instances[self.account]['join_gc'].window.present() + app.interface.join_gc_minimal(self.account, service) def update_actions(self): if self.execute_button: @@ -1810,14 +1804,11 @@ class MucBrowser(AgentBrowser): return service = model[iter_][0] if 'join_gc' not in app.interface.instances[self.account]: - try: - dialogs.JoinGroupchatWindow(self.account, service) - except GajimGeneralException: - pass + app.interface.join_gc_minimal(self.account, service) else: - app.interface.instances[self.account]['join_gc']._set_room_jid( - service) - app.interface.instances[self.account]['join_gc'].window.present() + app.interface.instances[self.account]['join_gc'].set_room(service) + app.interface.instances[self.account]['join_gc'].present() + self.window.destroy() def update_actions(self): sens = self.window.services_treeview.get_selection().count_selected_rows() diff --git a/gajim/gajim.py b/gajim/gajim.py index 9801d66a5..17cc41d81 100644 --- a/gajim/gajim.py +++ b/gajim/gajim.py @@ -60,7 +60,8 @@ class GajimApplication(Gtk.Application): '''Main class handling activation and command line.''' def __init__(self): - Gtk.Application.__init__(self, application_id='org.gajim.Gajim') + Gtk.Application.__init__(self, application_id='org.gajim.Gajim', + flags=Gio.ApplicationFlags.HANDLES_OPEN) self.add_main_option('version', ord('V'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, @@ -89,13 +90,11 @@ class GajimApplication(Gtk.Application): self.add_main_option('warnings', ord('w'), GLib.OptionFlags.NONE, GLib.OptionArg.NONE, _('Show all warnings')) - self.add_main_option(GLib.OPTION_REMAINING, 0, GLib.OptionFlags.HIDDEN, - GLib.OptionArg.STRING_ARRAY, - "") self.connect('handle-local-options', self._handle_local_options) self.connect('startup', self._startup) self.connect('activate', self._activate) + self.connect('open', self._open) self.profile = '' self.config_path = None @@ -235,6 +234,28 @@ class GajimApplication(Gtk.Application): from gajim import gui_menu_builder gui_menu_builder.build_accounts_menu() + def _open(self, application, file, hint, *args): + for arg in file: + uri = arg.get_uri() + # remove xmpp:/// + uri = uri[8:] + jid, cmd = uri.split('?') + if cmd == 'join': + self.interface.join_gc_minimal(None, jid) + elif cmd == 'roster': + self.activate_action('add-contact', GLib.Variant('s', jid)) + elif cmd == 'message': + from gajim.common import app + accounts = list(app.connections.keys()) + if not accounts: + continue + if len(accounts) == 1: + app.interface.new_chat_from_jid(accounts[0], jid) + else: + self.activate_action('start-chat') + start_chat_window = app.interface.instances['start_chat'] + start_chat_window.search_entry.set_text(jid) + def do_shutdown(self, *args): Gtk.Application.do_shutdown(self) # Shutdown GUI and save config @@ -274,10 +295,6 @@ class GajimApplication(Gtk.Application): logging_helpers.set_loglevels(loglevel) if options.contains('warnings'): self.show_warnings() - if options.contains(GLib.OPTION_REMAINING): - unhandled = options.lookup_value(GLib.OPTION_REMAINING).get_strv() - print('Error: Unhandled arguments: %s' % unhandled) - return 0 return -1 def show_warnings(self): @@ -301,7 +318,6 @@ class GajimApplication(Gtk.Application): self.account_actions = [ ('-start-single-chat', action.on_single_message, 'online', 's'), - ('-start-chat', action.on_new_chat, 'online', 's'), ('-join-groupchat', action.on_join_gc, 'online', 's'), ('-add-contact', action.on_add_contact, 'online', 's'), ('-services', action.on_service_disco, 'online', 's'), @@ -342,6 +358,7 @@ class GajimApplication(Gtk.Application): ('accounts', action.on_accounts), ('add-account', action.on_add_account), ('manage-proxies', action.on_manage_proxies), + ('start-chat', action.on_new_chat), ('bookmarks', action.on_manage_bookmarks), ('history-manager', action.on_history_manager), ('preferences', action.on_preferences), @@ -355,6 +372,10 @@ class GajimApplication(Gtk.Application): ('faq', action.on_faq), ] + act = Gio.SimpleAction.new('add-contact', GLib.VariantType.new('s')) + act.connect("activate", action.on_add_contact_jid) + self.add_action(act) + for action in self.general_actions: action_name, func = action act = Gio.SimpleAction.new(action_name, None) diff --git a/gajim/gajim_remote.py b/gajim/gajim_remote.py index eb94ad65d..dbe3bb6b9 100644 --- a/gajim/gajim_remote.py +++ b/gajim/gajim_remote.py @@ -125,18 +125,6 @@ class GajimRemote: ' "sync with global status" option set'), False) ] ], - 'open_chat': [ - _('Shows the chat dialog so that you can send messages to a contact'), - [ - ('jid', _('JID of the contact that you want to chat with'), - True), - (Q_('?CLI:account'), _('if specified, contact is taken from the ' - 'contact list of this account'), False), - (Q_('?CLI:message'), - _('message content. The account must be specified or ""'), - False) - ] - ], 'send_chat_message': [ _('Sends new chat message to a contact in the roster. Both OpenPGP key ' 'and account are optional. If you want to set only \'account\', ' @@ -225,13 +213,6 @@ class GajimRemote: ] ], - 'add_contact': [ - _('Adds contact to roster'), - [ - (_('jid'), _('JID of the contact'), True), - (Q_('?CLI:account'), _('Adds new contact to this account'), False) - ] - ], 'get_status': [ _('Returns current status (the global one unless account is specified)'), @@ -251,12 +232,7 @@ class GajimRemote: _('Returns number of unread messages'), [ ] ], - 'start_chat': [ - _('Opens \'Start Chat\' dialog'), - [ - (Q_('?CLI:account'), _('Starts chat, using this account'), True) - ] - ], + 'send_xml': [ _('Sends custom XML'), [ @@ -275,25 +251,7 @@ class GajimRemote: False) ] ], - 'handle_uri': [ - _('Handle a xmpp:/ URI'), - [ - (Q_('?CLI:uri'), _('URI to handle'), True), - (Q_('?CLI:account'), _('Account in which you want to handle it'), - False), - (Q_('?CLI:message'), _('Message content'), False) - ] - ], - 'join_room': [ - _('Join a MUC room'), - [ - (Q_('?CLI:room'), _('Room JID'), True), - (Q_('?CLI:nick'), _('Nickname to use'), False), - (Q_('?CLI:password'), _('Password to enter the room'), False), - (Q_('?CLI:account'), _('Account from which you want to enter the ' - 'room'), False) - ] - ], + 'check_gajim_running': [ _('Check if Gajim is running'), [] @@ -317,8 +275,6 @@ class GajimRemote: else: print(self.compose_help().encode(PREFERRED_ENCODING)) sys.exit(0) - if self.command == 'handle_uri': - self.handle_uri() if self.command == 'check_gajim_running': print(self.check_gajim_running()) sys.exit(0) @@ -342,10 +298,8 @@ class GajimRemote: Print retrieved result to the output """ if res is not None: - if self.command in ('open_chat', 'send_chat_message', - 'send_single_message', 'start_chat'): - if self.command in ('send_message', 'send_single_message'): - self.argv_len -= 2 + if self.command in ('send_chat_message', 'send_single_message'): + self.argv_len -= 2 if res is False: if self.argv_len < 4: @@ -533,66 +487,6 @@ class GajimRemote: # add empty string for missing args self.arguments += ['']*(len(args)-i) - def handle_uri(self): - if len(sys.argv) < 3: - send_error(_('No URI given')) - if not sys.argv[2].startswith('xmpp:'): - send_error(_('Wrong URI')) - sys.argv[2] = sys.argv[2][5:] - uri = sys.argv[2] - if not '?' in uri: - self.command = sys.argv[1] = 'open_chat' - return - jid, args = uri.split('?', 1) - try: - jid = urllib.parse.unquote(jid) - except UnicodeDecodeError: - pass - args = args.split(';') - action = None - options = {} - if args: - action = args[0] - for arg in args[1:]: - opt = arg.split('=', 1) - if len(opt) != 2: - continue - options[opt[0]] = opt[1] - - if action == 'message': - self.command = sys.argv[1] = 'open_chat' - sys.argv[2] = jid - if 'body' in options: - # Open chat window and paste the text in the input message - # dialog - message = options['body'] - try: - message = urllib.parse.unquote(message) - except UnicodeDecodeError: - pass - if len(sys.argv) == 4: - # jid in the sys.argv - sys.argv.append(message) - else: - sys.argv.append('') - sys.argv.append(message) - sys.argv[3] = '' - sys.argv[4] = message - return - sys.argv[2] = jid - if action == 'join': - self.command = sys.argv[1] = 'join_room' - # Move account parameter from position 3 to 5 - sys.argv.append('') - sys.argv.append(sys.argv[3]) - sys.argv[3] = '' - return - if action == 'roster': - # Add contact to roster - self.command = sys.argv[1] = 'add_contact' - return - sys.exit(0) - def call_remote_method(self): """ Calls self.method with arguments from sys.argv[2:] diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index 458a23ac5..2ad82a292 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -101,6 +101,7 @@ from gajim import profile_window from gajim import config from threading import Thread from gajim.common import ged +from gajim.common.caps_cache import muc_caps_cache from gajim.common.configpaths import gajimpaths config_filename = gajimpaths['CONFIG_FILE'] @@ -1757,6 +1758,49 @@ class Interface: tv = ctrl.conv_textview tv.scroll_to_end_iter() + def join_gc_minimal(self, account, room_jid, password=None): + if account is not None: + if app.in_groupchat(account, room_jid): + # If we already in the groupchat, join_gc_room will bring + # it to front + app.interface.join_gc_room(account, room_jid, '', '') + return + + for bookmark in app.connections[account].bookmarks: + if bookmark['jid'] != room_jid: + continue + app.interface.join_gc_room( + account, room_jid, bookmark['nick'], bookmark['password']) + return + + try: + room_jid = helpers.parse_jid(room_jid) + except helpers.InvalidFormat: + dialogs.ErrorDialog('Invalid JID', + transient_for=app.app.get_active_window()) + return + + connected_accounts = app.get_connected_accounts() + if account is not None and account not in connected_accounts: + connected_accounts = None + if not connected_accounts: + dialogs.ErrorDialog( + _('You are not connected to the server'), + _('You can not join a group chat unless you are connected.'), + transient_for=app.app.get_active_window()) + return + + def _on_discover_result(): + if not muc_caps_cache.is_cached(room_jid): + dialogs.ErrorDialog(_('JID is not a Groupchat'), + transient_for=app.app.get_active_window()) + return + dialogs.JoinGroupchatWindow(account, room_jid, password=password) + + disco_account = connected_accounts[0] if account is None else account + app.connections[disco_account].discoverMUC( + room_jid, _on_discover_result) + ################################################################################ ### Methods dealing with emoticons ################################################################################ @@ -1953,8 +1997,6 @@ class Interface: win.set_active_tab(gc_ctrl) else: self.roster.on_groupchat_maximized(None, room_jid, account) - dialogs.ErrorDialog(_('You are already in group chat %s') % \ - room_jid) return invisible_show = app.SHOW_LIST.index('invisible') @@ -2130,6 +2172,7 @@ class Interface: ### Other Methods ################################################################################ + @staticmethod def change_awn_icon_status(status): if not dbus_support.supported: @@ -2403,8 +2446,8 @@ class Interface: pixbuf = None try: if size is not None: - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( - path, size, size) + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( + path, size, size, False) else: pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) except GLib.GError as error: @@ -2454,7 +2497,7 @@ class Interface: self.roster.add_groupchat(jid, account) def add_gc_bookmark(self, account, name, jid, autojoin, minimize, password, - nick): + nick): """ Add a bookmark for this account, sorted in bookmark list """ @@ -2471,10 +2514,6 @@ class Interface: # check for duplicate entry and respect alpha order for bookmark in app.connections[account].bookmarks: if bookmark['jid'] == bm['jid']: - dialogs.ErrorDialog( - _('Bookmark already set'), - _('Group Chat "%s" is already in your bookmarks.') % \ - bm['jid']) return if bookmark['name'] > bm['name']: place_found = True @@ -2486,10 +2525,6 @@ class Interface: app.connections[account].bookmarks.append(bm) app.connections[account].store_bookmarks() gui_menu_builder.build_bookmark_menu(account) - dialogs.InformationDialog( - _('Bookmark has been added successfully'), - _('You can manage your bookmarks via Actions menu in your roster.')) - # does JID exist only within a groupchat? def is_pm_contact(self, fjid, account): diff --git a/gajim/gui_menu_builder.py b/gajim/gui_menu_builder.py index bb51dcb5f..b70460fe4 100644 --- a/gajim/gui_menu_builder.py +++ b/gajim/gui_menu_builder.py @@ -722,7 +722,6 @@ def get_account_menu(account): ('-join-groupchat', _('Join Group Chat')), ('-profile', _('Profile')), ('-services', _('Discover Services')), - ('-start-chat', _('Start Chat...')), ('-start-single-chat', _('Send Single Message...')), ('Advanced', [ ('-archive', _('Archiving Preferences')), diff --git a/gajim/htmltextview.py b/gajim/htmltextview.py index a5eeb2ff4..d05d9a4a9 100644 --- a/gajim/htmltextview.py +++ b/gajim/htmltextview.py @@ -915,10 +915,7 @@ class HtmlTextView(Gtk.TextView): # app.interface.new_chat_from_jid(self.account, jid) def on_join_group_chat_menuitem_activate(self, widget, room_jid): - try: - dialogs.JoinGroupchatWindow(room_jid=room_jid) - except GajimGeneralException: - pass + dialogs.JoinGroupchatWindow(None, room_jid) def on_add_to_roster_activate(self, widget, jid): dialogs.AddNewContactWindow(self.account, jid) diff --git a/gajim/remote_control.py b/gajim/remote_control.py index 6ca4651aa..b1eff7350 100644 --- a/gajim/remote_control.py +++ b/gajim/remote_control.py @@ -35,7 +35,7 @@ import mimetypes from gajim.common import app from gajim.common import helpers from time import time -from gajim.dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow +from gajim.dialogs import AddNewContactWindow, JoinGroupchatWindow from gajim.common import ged from gajim.common.connection_handlers_events import MessageOutgoingEvent from gajim.common.connection_handlers_events import GcMessageOutgoingEvent @@ -849,7 +849,7 @@ class SignalObject(dbus.service.Object): if not account: # error is shown in gajim-remote check_arguments(..) return DBUS_BOOLEAN(False) - NewChatDialog(account) + app.app.activate_action('start-chat') return DBUS_BOOLEAN(True) @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') @@ -908,8 +908,6 @@ class SignalObject(dbus.service.Object): return if not nick: - nick = '' - app.interface.instances[account]['join_gc'] = \ - JoinGroupchatWindow(account, room_jid, nick) + app.interface.join_gc_minimal(account, room_jid) else: app.interface.join_gc_room(account, room_jid, nick, password) diff --git a/gajim/roster_window.py b/gajim/roster_window.py index 4cf496d4a..807066892 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -3091,15 +3091,11 @@ class RosterWindow: if app.connections[account].muc_jid[type_]: # create the room on this muc server if 'join_gc' in app.interface.instances[account]: - app.interface.instances[account]['join_gc'].window.\ - destroy() - try: + app.interface.instances[account]['join_gc'].destroy() + else: app.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account, - app.connections[account].muc_jid[type_], - automatic = {'invities': jid_list}) - except GajimGeneralException: - continue + dialogs.JoinGroupchatWindow( + account, None, automatic={'invities': jid_list}) break def on_invite_to_room(self, widget, list_, room_jid, room_account, @@ -3676,16 +3672,10 @@ class RosterWindow: 'invisible')) return if 'join_gc' in app.interface.instances[account]: - app.interface.instances[account]['join_gc'].window.present() + app.interface.instances[account]['join_gc'].present() else: - try: - app.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account) - except GajimGeneralException: - pass - - def on_new_chat_menuitem_activate(self, widget, account): - dialogs.NewChatDialog(account) + app.interface.instances[account]['join_gc'] = \ + dialogs.JoinGroupchatWindow(account, None) def on_show_transports_action(self, action, param): app.config.set('show_transports_group', param.get_boolean()) @@ -4934,7 +4924,6 @@ class RosterWindow: account_context_menu = xml.get_object('account_context_menu') status_menuitem = xml.get_object('status_menuitem') - start_chat_menuitem = xml.get_object('start_chat_menuitem') join_group_chat_menuitem = xml.get_object( 'join_group_chat_menuitem') add_contact_menuitem = xml.get_object('add_contact_menuitem') @@ -5022,9 +5011,6 @@ class RosterWindow: execute_command_menuitem.connect('activate', self.on_execute_command, contact, account) - start_chat_menuitem.connect('activate', - self.on_new_chat_menuitem_activate, account) - gc_sub_menu = Gtk.Menu() # gc is always a submenu join_group_chat_menuitem.set_submenu(gc_sub_menu) self.add_bookmarks_list(gc_sub_menu, account) @@ -5033,7 +5019,7 @@ class RosterWindow: if app.connections[account].connected < 2: for widget in (add_contact_menuitem, service_discovery_menuitem, join_group_chat_menuitem, execute_command_menuitem, - pep_menuitem, start_chat_menuitem): + pep_menuitem): widget.set_sensitive(False) else: xml = gtkgui_helpers.get_gtk_builder('zeroconf_context_menu.ui') diff --git a/gajim/statusicon.py b/gajim/statusicon.py index 74e98473e..a321c1f3f 100644 --- a/gajim/statusicon.py +++ b/gajim/statusicon.py @@ -185,7 +185,7 @@ class StatusIcon: dialogs.SingleMessageWindow(account, action='send') def on_new_chat(self, widget, account): - dialogs.NewChatDialog(account) + app.app.activate_action('start-chat') def make_menu(self, event_button, event_time): """ diff --git a/plugins/dbus_plugin/plugin.py b/plugins/dbus_plugin/plugin.py index 7e1ffd353..5c86cfb38 100644 --- a/plugins/dbus_plugin/plugin.py +++ b/plugins/dbus_plugin/plugin.py @@ -628,7 +628,7 @@ if dbus_support.supported: if not account: # error is shown in gajim-remote check_arguments(..) return DBUS_BOOLEAN(False) - NewChatDialog(account) + app.app.activate_action('start-chat') return DBUS_BOOLEAN(True) @dbus.service.method(INTERFACE, in_signature='ss', out_signature='') @@ -651,16 +651,14 @@ if dbus_support.supported: if not account: return if not nick: - nick = '' - gajim.interface.instances[account]['join_gc'] = \ - JoinGroupchatWindow(account, room_jid, nick) + gajim.interface.join_gc_minimal(account, room_jid) else: gajim.interface.join_gc_room(account, room_jid, nick, password) from gajim.common import app from gajim.common import helpers from time import time -from gajim.dialogs import AddNewContactWindow, NewChatDialog, JoinGroupchatWindow +from gajim.dialogs import AddNewContactWindow, JoinGroupchatWindow from gajim.plugins import GajimPlugin from gajim.plugins.helpers import log_calls, log