diff --git a/gajim/chat_control.py b/gajim/chat_control.py index 6fdfcad0e..209349bf4 100644 --- a/gajim/chat_control.py +++ b/gajim/chat_control.py @@ -1182,12 +1182,8 @@ class ChatControl(ChatControlBase): return scale = self.parent_win.window.get_scale_factor() - if self.TYPE_ID == message_control.TYPE_CHAT: - surface = app.contacts.get_avatar( - self.account, self.contact.jid, AvatarSize.CHAT, scale) - else: - surface = app.interface.get_avatar( - self.gc_contact.avatar_sha, AvatarSize.CHAT, scale) + surface = app.contacts.get_avatar( + self.account, self.contact.jid, AvatarSize.CHAT, scale) image = self.xml.get_object('avatar_image') if surface is None: diff --git a/gajim/chat_control_base.py b/gajim/chat_control_base.py index f2b7d1afc..aad86c0f6 100644 --- a/gajim/chat_control_base.py +++ b/gajim/chat_control_base.py @@ -512,10 +512,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): super(ChatControlBase, self).shutdown() # PluginSystem: removing GUI extension points connected with ChatControlBase # instance object - app.plugin_manager.remove_gui_extension_point('chat_control_base', - self) + app.plugin_manager.remove_gui_extension_point('chat_control_base', self) app.plugin_manager.remove_gui_extension_point( 'chat_control_base_draw_banner', self) + app.plugin_manager.remove_gui_extension_point( + 'chat_control_base_update_toolbar', self) app.ged.remove_event_handler('our-show', ged.GUI1, self._nec_our_status) app.ged.remove_event_handler('sec-catalog-received', ged.GUI1, @@ -1049,8 +1050,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): gc_contact.room_jid, self.account) self_contact = app.contacts.get_gc_contact(self.account, gc_control.room_jid, gc_control.nick) - if gc_control.is_anonymous and gc_contact.affiliation not in ['admin', - 'owner'] and self_contact.affiliation in ['admin', 'owner']: + if gc_control.is_anonymous and gc_contact.affiliation.value not in ['admin', + 'owner'] and self_contact.affiliation.value in ['admin', 'owner']: contact = app.contacts.get_contact(self.account, gc_contact.jid) if not contact or contact.sub not in ('both', 'to'): prim_text = _('Really send file?') diff --git a/gajim/command_system/implementation/standard.py b/gajim/command_system/implementation/standard.py index 863486ca8..a82932210 100644 --- a/gajim/command_system/implementation/standard.py +++ b/gajim/command_system/implementation/standard.py @@ -379,8 +379,8 @@ class StandardGroupChatCommands(CommandContainer): for nick in nicks: contact = get_contact(nick) - role = helpers.get_uf_role(contact.role) - affiliation = helpers.get_uf_affiliation(contact.affiliation) + role = helpers.get_uf_role(contact.role.value) + affiliation = helpers.get_uf_affiliation(contact.affiliation.value) self.echo("%s - %s - %s" % (nick, role, affiliation)) @command('ignore', raw=True) diff --git a/gajim/common/caps_cache.py b/gajim/common/caps_cache.py index 6a6ea9a07..db2d9b4ba 100644 --- a/gajim/common/caps_cache.py +++ b/gajim/common/caps_cache.py @@ -35,6 +35,7 @@ import logging log = logging.getLogger('gajim.c.caps_cache') import nbxmpp +from nbxmpp.const import Affiliation from nbxmpp import (NS_XHTML_IM, NS_ESESSION, NS_CHATSTATES, NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_FILE_TRANSFER_5) @@ -485,7 +486,7 @@ class MucCapsCache: def is_subject_change_allowed(self, jid, affiliation): allowed = True - if affiliation in ('owner', 'admin'): + if affiliation in (Affiliation.OWNER, Affiliation.ADMIN): return allowed if jid in self.cache: diff --git a/gajim/common/config.py b/gajim/common/config.py index c43a5d944..ed629bc73 100644 --- a/gajim/common/config.py +++ b/gajim/common/config.py @@ -235,7 +235,6 @@ class Config: 'show_location_in_roster': [opt_bool, True, '', True], 'avatar_position_in_roster': [opt_str, 'right', _('Define the position of the avatar in roster. Can be left or right'), True], 'print_status_in_chats': [opt_bool, False, _('If False, Gajim will no longer print status line in chats when a contact changes their status and/or their status message.')], - 'print_status_in_muc': [opt_str, 'none', _('Can be "none", "all" or "in_and_out". If "none", Gajim will no longer print status line in groupchats when a member changes their status and/or their status message. If "all" Gajim will print all status messages. If "in_and_out", Gajim will only print FOO enters/leaves group chat.')], 'log_contact_status_changes': [opt_bool, False], 'log_xhtml_messages': [opt_bool, False, _('Log XHTML messages instead of plain text messages.')], 'restored_messages_small': [opt_bool, True, _('If true, restored messages will use a smaller font than the default one.')], @@ -440,6 +439,8 @@ class Config: 'muc_restore_lines': [opt_int, -2, _('How many lines to request from server when entering a groupchat. -1 means no limit, -2 means global value')], 'muc_restore_timeout': [opt_int, -2, _('Minutes of backlog to request when entering a groupchat. -1 means no limit, -2 means global value')], 'notify_on_all_messages': [opt_bool, False, _('State whether a notification is created for every message in this room')], + 'print_status': [opt_bool, False, _('Show a status message for all status (away, dnd, etc.) changes of users in a group chat')], + 'print_join_left': [opt_bool, False, _('Show a status message for every join or leave in a group chat')], }, {}), 'plugins': ({ 'active': [opt_bool, False, _('State whether plugins should be activated on startup (this is saved on Gajim exit). This option SHOULD NOT be used to (de)activate plug-ins. Use GUI instead.')], diff --git a/gajim/common/connection_handlers_events.py b/gajim/common/connection_handlers_events.py index cefc82148..93b54eaec 100644 --- a/gajim/common/connection_handlers_events.py +++ b/gajim/common/connection_handlers_events.py @@ -209,8 +209,6 @@ PresenceHelperEvent): jid_list = app.contacts.get_jid_list(self.conn.name) self.timestamp = None self.get_id() - self.is_gc = False # is it a GC presence ? - sig_tag = None self.avatar_sha = None # XEP-0172 User Nickname self.user_nick = self.stanza.getTagData('nick') or '' @@ -225,13 +223,7 @@ PresenceHelperEvent): # XEP-0319 self.idle_time = parse_idle(self.stanza) - xtags = self.stanza.getTags('x') - for x in xtags: - namespace = x.getNamespace() - if namespace in (nbxmpp.NS_MUC_USER, nbxmpp.NS_MUC): - self.is_gc = True - elif namespace == nbxmpp.NS_SIGNED: - sig_tag = x + sig_tag = self.stanza.getTag('x', namespace=nbxmpp.NS_SIGNED) self.status = self.stanza.getStatus() or '' self._generate_show() @@ -241,13 +233,6 @@ PresenceHelperEvent): self.errcode = self.stanza.getErrorCode() self.errmsg = self.stanza.getErrorMsg() - if self.is_gc: - app.nec.push_incoming_event( - GcPresenceReceivedEvent( - None, conn=self.conn, stanza=self.stanza, - presence_obj=self)) - return - if self.ptype == 'error': return @@ -278,7 +263,6 @@ class ZeroconfPresenceReceivedEvent(nec.NetworkIncomingEvent): self.ptype = 'unavailable' else: self.ptype = None - self.is_gc = False self.user_nick = '' self.transport_auto_auth = False self.errcode = None @@ -286,86 +270,6 @@ class ZeroconfPresenceReceivedEvent(nec.NetworkIncomingEvent): self.popup = False # Do we want to open chat window ? return True -class GcPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): - name = 'gc-presence-received' - - def generate(self): - self.ptype = self.presence_obj.ptype - self.fjid = self.presence_obj.fjid - self.jid = self.presence_obj.jid - self.room_jid = self.presence_obj.jid - self.nick = self.presence_obj.resource - self.show = self.presence_obj.show - self.status = self.presence_obj.status - self.avatar_sha = self.presence_obj.avatar_sha - self.errcode = self.presence_obj.errcode - self.errmsg = self.presence_obj.errmsg - self.errcon = self.stanza.getError() - self.get_gc_control() - self.gc_contact = app.contacts.get_gc_contact(self.conn.name, - self.room_jid, self.nick) - - if self.ptype == 'error': - return True - - if self.ptype and self.ptype != 'unavailable': - return - if app.config.get('log_contact_status_changes') and \ - app.config.should_log(self.conn.name, self.room_jid): - if self.gc_contact: - jid = self.gc_contact.jid - else: - jid = self.stanza.getJid() - st = self.status - if jid: - # we know real jid, save it in db - st += ' (%s)' % jid - show = app.logger.convert_show_values_to_db_api_values(self.show) - if show is not None: - fjid = nbxmpp.JID(self.fjid) - app.logger.insert_into_logs(self.conn.name, - fjid.getStripped(), - time_time(), - KindConstant.GCSTATUS, - contact_name=fjid.getResource(), - message=st, - show=show) - - - # NOTE: if it's a gc presence, don't ask vcard here. - # We may ask it to real jid in gui part. - self.status_code = [] - ns_muc_user_x = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER) - if ns_muc_user_x: - destroy = ns_muc_user_x.getTag('destroy') - else: - destroy = None - if ns_muc_user_x and destroy: - # Room has been destroyed. see - # http://www.xmpp.org/extensions/xep-0045.html#destroyroom - self.reason = _('Room has been destroyed') - r = destroy.getTagData('reason') - if r: - self.reason += ' (%s)' % r - if destroy.getAttr('jid'): - try: - jid = helpers.parse_jid(destroy.getAttr('jid')) - self.reason += '\n' + \ - _('You can join this room instead: %s') % jid - except helpers.InvalidFormat: - pass - self.status_code = ['destroyed'] - else: - self.reason = self.stanza.getReason() - self.status_code = self.stanza.getStatusCode() - - self.role = self.stanza.getRole() - self.affiliation = self.stanza.getAffiliation() - self.real_jid = self.stanza.getJid() - self.actor = self.stanza.getActor() - self.new_nick = self.stanza.getNewNick() - return True - class OurShowEvent(nec.NetworkIncomingEvent): name = 'our-show' diff --git a/gajim/common/contacts.py b/gajim/common/contacts.py index da084ae32..5fcde5f00 100644 --- a/gajim/common/contacts.py +++ b/gajim/common/contacts.py @@ -46,12 +46,13 @@ class XMPPEntity: class CommonContact(XMPPEntity): - def __init__(self, jid, account, resource, show, status, name, + def __init__(self, jid, account, resource, show, presence, status, name, chatstate, client_caps=None): XMPPEntity.__init__(self, jid, account, resource) - self.show = show + self._show = show + self._presence = presence self.status = status self.name = name @@ -68,10 +69,22 @@ class CommonContact(XMPPEntity): @show.setter def show(self, value): - if not isinstance(value, str): - raise TypeError('show must be a string') + if isinstance(value, str) and isinstance(self, GC_Contact): + breakpoint() self._show = value + @property + def presence(self): + return self._presence + + @presence.setter + def presence(self, value): + self._presence = value + + @property + def is_available(self): + return self._presence.is_available + @property def chatstate_enum(self): return self._chatstate @@ -130,7 +143,8 @@ class Contact(CommonContact): if groups is None: groups = [] - CommonContact.__init__(self, jid, account, resource, show, status, name, + CommonContact.__init__(self, jid, account, resource, show, + None, status, name, chatstate, client_caps=client_caps) self.contact_name = '' # nick choosen by contact @@ -199,6 +213,14 @@ class Contact(CommonContact): def is_groupchat(self): return self._is_groupchat + @property + def is_connected(self): + from gajim.common import app + try: + return app.gc_connected[self.account.name][self.jid] + except Exception as error: + return False + def is_transport(self): # if not '@' or '@' starts the jid then contact is transport return self.jid.find('@') <= 0 @@ -209,11 +231,12 @@ class GC_Contact(CommonContact): Information concerning each groupchat contact """ - def __init__(self, room_jid, account, name='', show='', status='', role='', - affiliation='', jid='', resource='', chatstate=None, avatar_sha=None): + def __init__(self, room_jid, account, name='', show='', presence=None, + status='', role='', affiliation='', jid='', resource='', + chatstate=None, avatar_sha=None): - CommonContact.__init__(self, jid, account, resource, show, status, name, - chatstate) + CommonContact.__init__(self, jid, account, resource, show, + presence, status, name, chatstate) self.room_jid = room_jid self.role = role @@ -443,11 +466,12 @@ class LegacyContactsAPI: return getattr(self._metacontact_manager, attr_name) raise AttributeError(attr_name) - def create_gc_contact(self, room_jid, account, name='', show='', status='', - role='', affiliation='', jid='', resource='', avatar_sha=None): + def create_gc_contact(self, room_jid, account, name='', show='', + presence=None, status='', role='', + affiliation='', jid='', resource='', avatar_sha=None): account = self._accounts.get(account, account) # Use Account object if available - return GC_Contact(room_jid, account, name, show, status, role, affiliation, jid, - resource, avatar_sha=avatar_sha) + return GC_Contact(room_jid, account, name, show, presence, status, + role, affiliation, jid, resource, avatar_sha=avatar_sha) def add_gc_contact(self, account, gc_contact): return self._accounts[account].gc_contacts.add_gc_contact(gc_contact) @@ -464,6 +488,9 @@ class LegacyContactsAPI: def get_nick_list(self, account, room_jid): return self._accounts[account].gc_contacts.get_nick_list(room_jid) + def get_gc_contact_list(self, account, room_jid): + return self._accounts[account].gc_contacts.get_gc_contact_list(room_jid) + def get_gc_contact(self, account, room_jid, nick): return self._accounts[account].gc_contacts.get_gc_contact(room_jid, nick) @@ -682,6 +709,12 @@ class GC_Contacts(): return [] return list(self._rooms[room_jid].keys()) + def get_gc_contact_list(self, room_jid): + try: + return list(self._rooms[room_jid].values()) + except Exception: + return [] + def get_gc_contact(self, room_jid, nick): try: return self._rooms[room_jid][nick] diff --git a/gajim/common/modules/chatstates.py b/gajim/common/modules/chatstates.py index 2d1ad4455..c740de663 100644 --- a/gajim/common/modules/chatstates.py +++ b/gajim/common/modules/chatstates.py @@ -220,8 +220,9 @@ class Chatstate: 'chatstate': str(State.ACTIVE)} if contact.is_groupchat(): - app.nec.push_outgoing_event( - GcMessageOutgoingEvent(None, **event_attrs)) + if contact.is_connected: + app.nec.push_outgoing_event( + GcMessageOutgoingEvent(None, **event_attrs)) else: app.nec.push_outgoing_event( MessageOutgoingEvent(None, **event_attrs)) @@ -265,8 +266,9 @@ class Chatstate: 'chatstate': str(state)} if contact.is_groupchat(): - app.nec.push_outgoing_event( - GcMessageOutgoingEvent(None, **event_attrs)) + if contact.is_connected: + app.nec.push_outgoing_event( + GcMessageOutgoingEvent(None, **event_attrs)) else: app.nec.push_outgoing_event( MessageOutgoingEvent(None, **event_attrs)) diff --git a/gajim/common/modules/muc.py b/gajim/common/modules/muc.py index 58f14c849..d33aa5fea 100644 --- a/gajim/common/modules/muc.py +++ b/gajim/common/modules/muc.py @@ -20,11 +20,14 @@ import logging import nbxmpp from nbxmpp.const import InviteType +from nbxmpp.const import PresenceType from nbxmpp.structs import StanzaHandler from gajim.common import i18n from gajim.common import app from gajim.common import helpers +from gajim.common.const import KindConstant +from gajim.common.helpers import AdditionalDataDict from gajim.common.caps_cache import muc_caps_cache from gajim.common.nec import NetworkEvent from gajim.common.modules.bits_of_binary import store_bob_data @@ -38,6 +41,14 @@ class MUC: self._account = con.name self.handlers = [ + StanzaHandler(name='presence', + callback=self._on_muc_user_presence, + ns=nbxmpp.NS_MUC_USER, + priority=49), + StanzaHandler(name='presence', + callback=self._on_muc_presence, + ns=nbxmpp.NS_MUC, + priority=49), StanzaHandler(name='message', callback=self._on_subject_change, typ='groupchat', @@ -79,13 +90,14 @@ class MUC: ] def __getattr__(self, key): - if key in self._nbmxpp_methods: - if not app.account_is_connected(self._account): - log.warning('Account %s not connected, cant use %s', - self._account, key) - return - module = self._con.connection.get_module('MUC') - return getattr(module, key) + if key not in self._nbmxpp_methods: + raise AttributeError + if not app.account_is_connected(self._account): + log.warning('Account %s not connected, cant use %s', + self._account, key) + return + module = self._con.connection.get_module('MUC') + return getattr(module, key) def pass_disco(self, from_, identities, features, _data, _node): for identity in identities: @@ -149,6 +161,163 @@ class MUC: if tags: muc_x.setTag('history', tags) + def _on_muc_presence(self, _con, _stanza, properties): + if properties.type == PresenceType.ERROR: + self._raise_muc_event('muc-presence-error', properties) + raise nbxmpp.NodeProcessed + + def _on_muc_user_presence(self, _con, _stanza, properties): + if properties.type == PresenceType.ERROR: + return + + if properties.is_muc_destroyed: + for contact in app.contacts.get_gc_contact_list( + self._account, properties.jid.getBare()): + contact.presence = PresenceType.UNAVAILABLE + log.info('MUC destroyed: %s', properties.jid.getBare()) + self._raise_muc_event('muc-destroyed', properties) + raise nbxmpp.NodeProcessed + + contact = app.contacts.get_gc_contact(self._account, + properties.jid.getBare(), + properties.muc_nickname) + + if properties.is_nickname_changed: + app.contacts.remove_gc_contact(self._account, contact) + contact.name = properties.muc_user.nick + app.contacts.add_gc_contact(self._account, contact) + log.info('Nickname changed: %s to %s', + properties.jid, + properties.muc_user.nick) + self._raise_muc_event('muc-nickname-changed', properties) + raise nbxmpp.NodeProcessed + + if contact is None and properties.type.is_available: + self._add_new_muc_contact(properties) + if properties.is_muc_self_presence: + log.info('Self presence: %s', properties.jid) + self._raise_muc_event('muc-self-presence', properties) + else: + log.info('User joined: %s', properties.jid) + self._raise_muc_event('muc-user-joined', properties) + raise nbxmpp.NodeProcessed + + if properties.is_muc_self_presence and properties.is_kicked: + self._raise_muc_event('muc-self-kicked', properties) + raise nbxmpp.NodeProcessed + + if properties.is_muc_self_presence and properties.type.is_unavailable: + # Its not a kick, so this is the reflection of our own + # unavailable presence, because we left the MUC + raise nbxmpp.NodeProcessed + + if properties.type.is_unavailable: + for _event in app.events.get_events(self._account, + jid=str(properties.jid), + types=['pm']): + contact.show = properties.show + contact.presence = properties.type + contact.status = properties.status + contact.affiliation = properties.affiliation + app.interface.handle_event(self._account, + str(properties.jid), + 'pm') + # Handle only the first pm event, the rest will be + # handled by the opened ChatControl + break + + # We remove the contact from the MUC, but there could be + # a PrivateChatControl open, so we update the contacts presence + contact.presence = properties.type + app.contacts.remove_gc_contact(self._account, contact) + log.info('User %s left', properties.jid) + self._raise_muc_event('muc-user-left', properties) + raise nbxmpp.NodeProcessed + + if contact.affiliation != properties.affiliation: + contact.affiliation = properties.affiliation + log.info('Affiliation changed: %s %s', + properties.jid, + properties.affiliation) + self._raise_muc_event('muc-user-affiliation-changed', properties) + + if contact.role != properties.role: + contact.role = properties.role + log.info('Role changed: %s %s', + properties.jid, + properties.role) + self._raise_muc_event('muc-user-role-changed', properties) + + if (contact.status != properties.status or + contact.show != properties.show): + contact.status = properties.status + contact.show = properties.show + log.info('Show/Status changed: %s %s %s', + properties.jid, + properties.status, + properties.show) + self._raise_muc_event('muc-user-status-show-changed', properties) + + raise nbxmpp.NodeProcessed + + def _raise_muc_event(self, event_name, properties): + app.nec.push_incoming_event( + NetworkEvent(event_name, + account=self._account, + room_jid=properties.jid.getBare(), + properties=properties)) + self._log_muc_event(event_name, properties) + + def _log_muc_event(self, event_name, properties): + if event_name not in ['muc-user-joined', + 'muc-user-left', + 'muc-user-status-show-changed']: + return + + if (not app.config.get('log_contact_status_changes') or + not app.config.should_log(self._account, properties.jid)): + return + + additional_data = AdditionalDataDict() + if properties.muc_user is not None: + if properties.muc_user.jid is not None: + additional_data.set_value( + 'gajim', 'real_jid', str(properties.muc_user.jid)) + + # TODO: Refactor + if properties.type == PresenceType.UNAVAILABLE: + show = 'offline' + else: + show = properties.show.value + show = app.logger.convert_show_values_to_db_api_values(show) + + app.logger.insert_into_logs( + self._account, + properties.jid.getBare(), + properties.timestamp, + KindConstant.GCSTATUS, + contact_name=properties.muc_nickname, + message=properties.status or None, + show=show, + additional_data=additional_data) + + def _add_new_muc_contact(self, properties): + real_jid = None + if properties.muc_user.jid is not None: + real_jid = str(properties.muc_user.jid) + contact = app.contacts.create_gc_contact( + room_jid=properties.jid.getBare(), + account=self._account, + name=properties.muc_nickname, + show=properties.show, + status=properties.status, + presence=properties.type, + role=properties.role, + affiliation=properties.affiliation, + jid=real_jid, + avatar_sha=properties.avatar_sha) + app.contacts.add_gc_contact(self._account, contact) + def _on_subject_change(self, _con, _stanza, properties): if not properties.is_muc_subject: return diff --git a/gajim/data/gui/preferences_window.ui b/gajim/data/gui/preferences_window.ui index c68ace7ef..f6c82c63b 100644 --- a/gajim/data/gui/preferences_window.ui +++ b/gajim/data/gui/preferences_window.ui @@ -702,42 +702,10 @@ 6 12 - - True - False - These status messages are displayed when a room occupant changes status (this includes entering/leaving the room) - end - Displa_y status messages in group chats - True - print_status_in_muc_combobox - - - - 0 - 0 - + - - 200 - True - False - start - print_status_in_muc_liststore - - - - - 0 - - - - - 1 - 0 - + diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index 5f01c1bc9..62b4ee541 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -34,6 +34,10 @@ from enum import IntEnum, unique import nbxmpp from nbxmpp.const import StatusCode +from nbxmpp.const import Affiliation +from nbxmpp.const import Role +from nbxmpp.const import Error +from nbxmpp.const import PresenceType from gi.repository import Gtk from gi.repository import Gdk @@ -54,7 +58,6 @@ from gajim.common import helpers from gajim.common.helpers import launch_browser_mailer from gajim.common.helpers import AdditionalDataDict from gajim.common import ged -from gajim.common import i18n from gajim.common.i18n import _ from gajim.common import contacts from gajim.common.const import StyleAttr @@ -309,32 +312,34 @@ class GroupchatControl(ChatControlBase): settings_menu = self.xml.get_object('settings_menu') settings_menu.set_menu_model(self.control_menu) - app.ged.register_event_handler('gc-presence-received', ged.GUI1, - self._nec_gc_presence_received) - app.ged.register_event_handler('gc-message-received', ged.GUI1, - self._nec_gc_message_received) - app.ged.register_event_handler('mam-decrypted-message-received', - ged.GUI1, self._nec_mam_decrypted_message_received) - app.ged.register_event_handler('vcard-published', ged.GUI1, - self._nec_vcard_published) - app.ged.register_event_handler('update-gc-avatar', ged.GUI1, - self._nec_update_avatar) - app.ged.register_event_handler('update-room-avatar', ged.GUI1, - self._nec_update_room_avatar) - app.ged.register_event_handler('gc-subject-received', ged.GUI1, - self._nec_gc_subject_received) - app.ged.register_event_handler('gc-config-changed-received', ged.GUI1, - self._nec_gc_config_changed_received) - app.ged.register_event_handler('signed-in', ged.GUI1, - self._nec_signed_in) - app.ged.register_event_handler('decrypted-message-received', ged.GUI2, - self._nec_decrypted_message_received) - app.ged.register_event_handler('gc-stanza-message-outgoing', ged.OUT_POSTCORE, - self._message_sent) - app.ged.register_event_handler('captcha-challenge', ged.GUI1, - self._on_captcha_challenge) - app.ged.register_event_handler('voice-approval', ged.GUI1, - self._on_voice_approval) + self._event_handlers = [ + ('muc-user-joined', ged.GUI1, self._on_user_joined), + ('muc-user-left', ged.GUI1, self._on_user_left), + ('muc-nickname-changed', ged.GUI1, self._on_nickname_changed), + ('muc-self-presence', ged.GUI1, self._on_self_presence), + ('muc-self-kicked', ged.GUI1, self._on_self_kicked), + ('muc-user-affiliation-changed', ged.GUI1, self._on_affiliation_changed), + ('muc-user-status-show-changed', ged.GUI1, self._on_status_show_changed), + ('muc-user-role-changed', ged.GUI1, self._on_role_changed), + ('muc-destroyed', ged.GUI1, self._on_muc_destroyed), + ('muc-presence-error', ged.GUI1, self._on_muc_presence_error), + ('gc-message-received', ged.GUI1, self._nec_gc_message_received), + ('mam-decrypted-message-received', ged.GUI1, self._nec_mam_decrypted_message_received), + ('vcard-published', ged.GUI1, self._nec_vcard_published), + ('update-gc-avatar', ged.GUI1, self._nec_update_avatar), + ('update-room-avatar', ged.GUI1, self._nec_update_room_avatar), + ('gc-subject-received', ged.GUI1, self._nec_gc_subject_received), + ('gc-config-changed-received', ged.GUI1, self._nec_gc_config_changed_received), + ('signed-in', ged.GUI1, self._nec_signed_in), + ('decrypted-message-received', ged.GUI2, self._nec_decrypted_message_received), + ('gc-stanza-message-outgoing', ged.OUT_POSTCORE, self._message_sent), + ('captcha-challenge', ged.GUI1, self._on_captcha_challenge), + ('voice-approval', ged.GUI1, self._on_voice_approval), + ] + + for handler in self._event_handlers: + app.ged.register_event_handler(*handler) + self.is_connected = False # disable win, we are not connected yet ChatControlBase.got_disconnected(self) @@ -392,6 +397,22 @@ class GroupchatControl(ChatControlBase): act.connect('change-state', self._on_notify_on_all_messages) self.parent_win.window.add_action(act) + value = app.config.get_per('rooms', self.contact.jid, 'print_status') + + act = Gio.SimpleAction.new_stateful( + 'print-status-' + self.control_id, + None, GLib.Variant.new_boolean(value)) + act.connect('change-state', self._on_print_status) + self.parent_win.window.add_action(act) + + value = app.config.get_per('rooms', self.contact.jid, 'print_join_left') + + act = Gio.SimpleAction.new_stateful( + 'print-join-left-' + self.control_id, + None, GLib.Variant.new_boolean(value)) + act.connect('change-state', self._on_print_join_left) + self.parent_win.window.add_action(act) + archive_info = app.logger.get_archive_infos(self.contact.jid) threshold = helpers.get_sync_threshold(self.contact.jid, archive_info) @@ -413,11 +434,12 @@ class GroupchatControl(ChatControlBase): # Destroy Room win.lookup_action('destroy-' + self.control_id).set_enabled( - self.is_connected and contact.affiliation == 'owner') + self.is_connected and contact.affiliation.is_owner) # Configure Room win.lookup_action('configure-' + self.control_id).set_enabled( - self.is_connected and contact.affiliation in ('admin', 'owner')) + self.is_connected and contact.affiliation in (Affiliation.ADMIN, + Affiliation.OWNER)) # Bookmarks con = app.connections[self.account] @@ -428,7 +450,7 @@ class GroupchatControl(ChatControlBase): # Request Voice role = self.get_role(self.nick) win.lookup_action('request-voice-' + self.control_id).set_enabled( - self.is_connected and role == 'visitor') + self.is_connected and role.is_visitor) # Change Subject subject = False @@ -463,7 +485,7 @@ class GroupchatControl(ChatControlBase): # Upload Avatar vcard_support = muc_caps_cache.supports(self.room_jid, nbxmpp.NS_VCARD) win.lookup_action('upload-avatar-' + self.control_id).set_enabled( - self.is_connected and vcard_support and contact.affiliation == 'owner') + self.is_connected and vcard_support and contact.affiliation.is_owner) # Sync Threshold has_mam = muc_caps_cache.has_mam(self.room_jid) @@ -609,10 +631,10 @@ class GroupchatControl(ChatControlBase): def _on_configure_room(self, _action, _param): contact = app.contacts.get_gc_contact( self.account, self.room_jid, self.nick) - if contact.affiliation == 'owner': + if contact.affiliation.is_owner: con = app.connections[self.account] con.get_module('MUC').request_config(self.room_jid) - elif contact.affiliation == 'admin': + elif contact.affiliation.is_admin: win = app.get_app_window( 'GroupchatConfig', self.account, self.room_jid) if win is not None: @@ -620,7 +642,17 @@ class GroupchatControl(ChatControlBase): else: GroupchatConfig(self.account, self.room_jid, - contact.affiliation) + contact.affiliation.value) + + def _on_print_join_left(self, action, param): + action.set_state(param) + app.config.set_per('rooms', self.contact.jid, + 'print_join_left', param.get_boolean()) + + def _on_print_status(self, action, param): + action.set_state(param) + app.config.set_per('rooms', self.contact.jid, + 'print_status', param.get_boolean()) def _on_bookmark_room(self, action, param): """ @@ -791,10 +823,9 @@ class GroupchatControl(ChatControlBase): return 0 if type1 == 'contact' and type2 == 'contact' and \ app.config.get('sort_by_show_in_muc'): - cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4, - 'invisible': 5, 'offline': 6, 'error': 7} - show1 = cshow[gc_contact1.show] - show2 = cshow[gc_contact2.show] + cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4} + show1 = cshow[gc_contact1.show.value] + show2 = cshow[gc_contact2.show.value] if show1 < show2: return -1 if show1 > show2: @@ -1521,6 +1552,21 @@ class GroupchatControl(ChatControlBase): def is_connected(self, value: bool) -> None: app.gc_connected[self.account][self.room_jid] = value + def _disable_roster_sort(self): + self.model.set_sort_column_id(Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, + Gtk.SortType.ASCENDING) + self.list_treeview.set_model(None) + + def _enable_roster_sort(self): + self.model.set_sort_column_id(Column.NICK, Gtk.SortType.ASCENDING) + self.list_treeview.set_model(self.model) + self.list_treeview.expand_all() + + def _reset_roster(self): + self._contact_refs = {} + self._role_refs = {} + self.model.clear() + def got_connected(self): self.join_time = time.time() # Make autorejoin stop. @@ -1534,14 +1580,12 @@ class GroupchatControl(ChatControlBase): con.get_module('MAM').request_archive_on_muc_join( self.room_jid) - self.is_connected = True ChatControlBase.got_connected(self) # Sort model and assign it to treeview - self.model.set_sort_column_id(Column.NICK, Gtk.SortType.ASCENDING) - self.list_treeview.set_model(self.model) - self.list_treeview.expand_all() + self._enable_roster_sort() + # We don't redraw the whole banner here, because only icon change self._update_banner_state_image() if self.parent_win: @@ -1556,30 +1600,27 @@ class GroupchatControl(ChatControlBase): formattings_button = self.xml.get_object('formattings_button') formattings_button.set_sensitive(False) - self.model.set_sort_column_id(Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, - Gtk.SortType.ASCENDING) - self.list_treeview.set_model(None) - self._contact_refs = {} - self._role_refs = {} - self.model.clear() - nick_list = app.contacts.get_nick_list(self.account, self.room_jid) - for nick in nick_list: - # Update pm chat window - fjid = self.room_jid + '/' + nick - gc_contact = app.contacts.get_gc_contact(self.account, - self.room_jid, nick) + self._reset_roster() + self._disable_roster_sort() - ctrl = app.interface.msg_win_mgr.get_control(fjid, self.account) + for contact in app.contacts.get_gc_contact_list( + self.account, self.room_jid): + contact.presence = PresenceType.UNAVAILABLE + ctrl = app.interface.msg_win_mgr.get_control(contact.get_full_jid, + self.account) if ctrl: - gc_contact.show = 'offline' - gc_contact.status = '' - ctrl.update_ui() - if ctrl.parent_win: - ctrl.parent_win.redraw_tab(ctrl) + ctrl.got_disconnected() + + app.contacts.remove_gc_contact(self.account, contact) - app.contacts.remove_gc_contact(self.account, gc_contact) self.is_connected = False ChatControlBase.got_disconnected(self) + + contact = app.contacts.get_groupchat_contact(self.account, + self.room_jid) + if contact is not None: + app.interface.roster.draw_contact(self.room_jid, self.account) + # We don't redraw the whole banner here, because only icon change self._update_banner_state_image() if self.parent_win: @@ -1604,12 +1645,13 @@ class GroupchatControl(ChatControlBase): return True def draw_roster(self): - self.model.clear() + self._reset_roster() + self._disable_roster_sort() + for nick in app.contacts.get_nick_list(self.account, self.room_jid): - gc_contact = app.contacts.get_gc_contact(self.account, - self.room_jid, nick) - self.add_contact_to_roster(nick, gc_contact.show, gc_contact.role, - gc_contact.affiliation, gc_contact.status, gc_contact.jid) + self.add_contact_to_roster(nick) + + self._enable_roster_sort() self.draw_all_roles() # Recalculate column width for ellipsizin self.list_treeview.columns_autosize() @@ -1631,13 +1673,14 @@ class GroupchatControl(ChatControlBase): iter_ = self.get_contact_iter(nick) if not iter_: return + gc_contact = app.contacts.get_gc_contact( self.account, self.room_jid, nick) if app.events.get_events(self.account, self.room_jid + '/' + nick): icon_name = get_icon_name('event') else: - icon_name = get_icon_name(gc_contact.show) + icon_name = get_icon_name(gc_contact.show.value) name = GLib.markup_escape_text(gc_contact.name) @@ -1656,9 +1699,9 @@ class GroupchatControl(ChatControlBase): name += ('\n' '{}'.format(GLib.markup_escape_text(status))) - if (gc_contact.affiliation != 'none' and + if (not gc_contact.affiliation.is_none and app.config.get('show_affiliation_in_groupchat')): - icon_name += ':%s' % gc_contact.affiliation + icon_name += ':%s' % gc_contact.affiliation.value self.model[iter_][Column.IMG] = icon_name self.model[iter_][Column.TEXT] = name @@ -1694,338 +1737,449 @@ class GroupchatControl(ChatControlBase): self.nick = new_nick self._nick_completion.change_nick(new_nick) - def _nec_gc_presence_received(self, obj): - if obj.room_jid != self.room_jid or obj.conn.name != self.account: + def _on_self_presence(self, event): + if event.account != self.account: return - if obj.ptype == 'error': + if event.room_jid != self.room_jid: return - role = obj.role - if not role: - role = 'visitor' + nick = event.properties.muc_nickname + affiliation = event.properties.affiliation + jid = str(event.properties.jid) + status_codes = event.properties.muc_status_codes or [] - affiliation = obj.affiliation - if not affiliation: - affiliation = 'none' + if not self.is_connected: + # We just joined the room + self.print_conversation(_('You (%s) joined the room') % nick, + 'info', graphics=False) + self.add_contact_to_roster(nick) + self.got_connected() - newly_created = False - nick = i18n.direction_mark + obj.nick - nick_jid = nick + i18n.direction_mark - - # Set to true if role or affiliation have changed - right_changed = False - - if obj.real_jid: - # delete resource - simple_jid = app.get_jid_without_resource(obj.real_jid) - nick_jid += ' (%s)' % simple_jid - - con = app.connections[self.account] - bookmarks = con.get_module('Bookmarks').bookmarks - bookmark = bookmarks.get(self.room_jid, None) - if bookmark is None or not bookmark['print_status']: - print_status = app.config.get('print_status_in_muc') - else: - print_status = bookmark['print_status'] - - # status_code - # http://www.xmpp.org/extensions/xep-0045.html#registrar-statuscodes-\ - # init - if obj.status_code and obj.nick == self.nick: - if '110' in obj.status_code: - if not self.is_connected: - # We just join the room - self.print_conversation( - _('You (%s) joined the room') % self.nick, - 'info', graphics=False) - if self.room_jid in app.automatic_rooms[self.account] and \ - app.automatic_rooms[self.account][self.room_jid]['invities']: - if self.room_jid not in app.interface.instances[ - self.account]['gc_config']: - if obj.affiliation == 'owner': - # We need to configure the room if it's a new one. - # We cannot know it's a new one. Status 201 is not - # sent by all servers. - con = app.connections[self.account] - con.get_module('MUC').request_config(self.room_jid) - elif 'continue_tag' in app.automatic_rooms[ - self.account][self.room_jid]: - # We just need to invite contacts - for jid in app.automatic_rooms[self.account][ - self.room_jid]['invities']: - obj.conn.get_module('MUC').invite(self.room_jid, jid) - self.print_conversation(_('%(jid)s has been ' - 'invited in this room') % {'jid': jid}, - graphics=False) - if '100' in obj.status_code: - # Can be a message (see handle_event_gc_config_change in - # app.py) - self.print_conversation( - _('Any occupant is allowed to see your full JID')) - self.is_anonymous = False - if '170' in obj.status_code: - # Can be a message (see handle_event_gc_config_change in - # app.py) - self.print_conversation(_('Room logging is enabled')) - if '201' in obj.status_code: - app.connections[self.account].get_module('Discovery').disco_muc( - self.room_jid, self._on_room_created, update=True) - self.print_conversation(_('A new room has been created')) - if '210' in obj.status_code: - self.print_conversation(\ - _('The server has assigned or modified your roomnick')) - - if obj.show in ('offline', 'error'): - if obj.status_code: - if '333' in obj.status_code: - # Handle 333 before 307, some MUCs add both - if print_status != 'none': - if obj.nick == self.nick: - s = _('%s kicked us due to an error' % self.room_jid) - else: - s = _('%s has left due to an error' % nick) - if obj.reason: - s += ' (%s)' % obj.reason - self.print_conversation(s, 'info', graphics=False) - elif '307' in obj.status_code: - if obj.actor is None: # do not print 'kicked by None' - s = _('%(nick)s has been kicked: %(reason)s') % { - 'nick': nick, 'reason': obj.reason} - else: - s = _('%(nick)s has been kicked by %(who)s: ' - '%(reason)s') % {'nick': nick, 'who': obj.actor, - 'reason': obj.reason} - self.print_conversation(s, 'info', graphics=False) - if obj.nick == self.nick and not app.config.get( - 'muc_autorejoin_on_kick'): - self.autorejoin = False - elif '301' in obj.status_code: - if obj.actor is None: # do not print 'banned by None' - s = _('%(nick)s has been banned: %(reason)s') % { - 'nick': nick, 'reason': obj.reason} - else: - s = _('%(nick)s has been banned by %(who)s: ' - '%(reason)s') % {'nick': nick, 'who': obj.actor, - 'reason': obj.reason} - self.print_conversation(s, 'info', graphics=False) - if obj.nick == self.nick: - self.autorejoin = False - elif '303' in obj.status_code: # Someone changed their nick - if obj.new_nick == self.new_nick or obj.nick == self.nick: - # We changed our nick - self._change_nick(obj.new_nick) - self.new_nick = '' - s = _('You are now known as %s') % self.nick - else: - s = _('%(nick)s is now known as %(new_nick)s') % { - 'nick': nick, 'new_nick': obj.new_nick} - tv = self.conv_textview - if obj.nick in tv.last_received_message_id: - tv.last_received_message_id[obj.new_nick] = \ - tv.last_received_message_id[obj.nick] - del tv.last_received_message_id[obj.nick] - # We add new nick to muc roster here, so we don't see - # that "new_nick has joined the room" when he just changed - # nick. - # add_contact_to_roster will be called a second time - # after that, but that doesn't hurt - self.add_contact_to_roster(obj.new_nick, obj.show, role, - affiliation, obj.status, obj.real_jid) - self._nick_completion.contact_renamed(nick, obj.new_nick) - # keep nickname color - if obj.nick in self.gc_custom_colors: - self.gc_custom_colors[obj.new_nick] = \ - self.gc_custom_colors[obj.nick] - self.print_conversation(s, 'info', graphics=False) - elif '321' in obj.status_code: - s = _('%(nick)s has been removed from the room ' - '(%(reason)s)') % {'nick': nick, - 'reason': _('affiliation changed')} - self.print_conversation(s, 'info', graphics=False) - elif '322' in obj.status_code: - s = _('%(nick)s has been removed from the room ' - '(%(reason)s)') % {'nick': nick, - 'reason': _('room configuration changed to ' - 'members-only')} - self.print_conversation(s, 'info', graphics=False) - elif '332' in obj.status_code: - s = _('%(nick)s has been removed from the room ' - '(%(reason)s)') % {'nick': nick, - 'reason': _('system shutdown')} - self.print_conversation(s, 'info', graphics=False) - # Room has been destroyed. - elif 'destroyed' in obj.status_code: - self.autorejoin = False - self.print_conversation(obj.reason, 'info', graphics=False) - - if not app.events.get_events( - self.account, jid=obj.fjid, types=['pm']): - self.remove_contact(obj.nick) - self.draw_all_roles() - else: - c = app.contacts.get_gc_contact(self.account, self.room_jid, - obj.nick) - c.show = obj.show - c.status = obj.status - if obj.nick == self.nick and (not obj.status_code or \ - '303' not in obj.status_code): # We became offline - self.got_disconnected() - contact = app.contacts.\ - get_contact_with_highest_priority(self.account, - self.room_jid) - if contact: - app.interface.roster.draw_contact(self.room_jid, - self.account) - if self.parent_win: - self.parent_win.redraw_tab(self) - else: - iter_ = self.get_contact_iter(obj.nick) - if not iter_: - if '210' in obj.status_code: - # Server changed our nick - self._change_nick(obj.nick) - s = _('You are now known as %s') % nick - self.print_conversation(s, 'info', graphics=False) - iter_ = self.add_contact_to_roster(obj.nick, obj.show, role, - affiliation, obj.status, obj.real_jid, obj.avatar_sha) - newly_created = True - self.draw_all_roles() - if obj.status_code and '201' in obj.status_code: - # We just created the room - con = app.connections[self.account] + if self.room_jid in app.automatic_rooms[self.account] and \ + app.automatic_rooms[self.account][self.room_jid]['invities']: + if self.room_jid not in app.interface.instances[self.account]['gc_config']: + con = app.connections[self.account] + if affiliation.is_owner: + # We need to configure the room if it's a new one. + # We cannot know it's a new one. Status 201 is not + # sent by all servers. con.get_module('MUC').request_config(self.room_jid) - else: - gc_c = app.contacts.get_gc_contact(self.account, - self.room_jid, obj.nick) - if not gc_c: - log.error('%s has an iter, but no gc_contact instance', - obj.nick) - return - actual_affiliation = gc_c.affiliation - if affiliation != actual_affiliation: - if obj.actor: - st = _('** Affiliation of %(nick)s has been set to ' - '%(affiliation)s by %(actor)s') % {'nick': nick_jid, - 'affiliation': affiliation, 'actor': obj.actor} - else: - st = _('** Affiliation of %(nick)s has been set to ' - '%(affiliation)s') % {'nick': nick_jid, - 'affiliation': affiliation} - if obj.reason: - st += ' (%s)' % obj.reason - self.print_conversation(st, graphics=False) - right_changed = True - actual_role = self.get_role(obj.nick) - if role != actual_role: - self.remove_contact(obj.nick) - self.add_contact_to_roster(obj.nick, obj.show, role, - affiliation, obj.status, obj.real_jid) - self.draw_role(actual_role) - self.draw_role(role) - if obj.actor: - st = _('** Role of %(nick)s has been set to %(role)s ' - 'by %(actor)s') % {'nick': nick_jid, 'role': role, - 'actor': obj.actor} - else: - st = _('** Role of %(nick)s has been set to ' - '%(role)s') % {'nick': nick_jid, 'role': role} - if obj.reason: - st += ' (%s)' % obj.reason - self.print_conversation(st, graphics=False) - right_changed = True - else: - if gc_c.show == obj.show and gc_c.status == obj.status and \ - gc_c.affiliation == affiliation: # no change - return - gc_c.show = obj.show - gc_c.affiliation = affiliation - gc_c.status = obj.status - self.draw_contact(obj.nick) - if self.is_connected and obj.nick != self.nick \ - and (not obj.status_code or '303' not in obj.status_code) and not \ - right_changed: - st = '' + elif 'continue_tag' in app.automatic_rooms[self.account][self.room_jid]: + # We just need to invite contacts + for jid in app.automatic_rooms[self.account][self.room_jid]['invities']: + con.get_module('MUC').invite(self.room_jid, jid) + self.print_conversation( + _('%(jid)s has been ' + 'invited in this room') % {'jid': jid}, + graphics=False) - if obj.show == 'offline' and print_status in ('all', 'in_and_out') \ - and (not obj.status_code or '307' not in obj.status_code): - st = _('%s has left') % nick_jid - if obj.reason: - st += ' [%s]' % obj.reason - else: - if newly_created and print_status in ('all', 'in_and_out'): - st = _('%s has joined the group chat') % nick_jid - elif print_status == 'all': - st = _('%(nick)s is now %(status)s') % {'nick': nick_jid, - 'status': helpers.get_uf_show(obj.show)} - if st: - if obj.status: - st += ' (' + obj.status + ')' - self.print_conversation(st, graphics=False) + if StatusCode.NON_ANONYMOUS in status_codes: + self.print_conversation( + _('Any occupant is allowed to see your full JID')) + self.is_anonymous = False + + if StatusCode.CONFIG_ROOM_LOGGING in status_codes: + self.print_conversation(_('Room logging is enabled')) + + if StatusCode.NICKNAME_MODIFIED in status_codes: + self.print_conversation(\ + _('The server has assigned or modified your roomnick')) + + if event.properties.is_new_room: + app.connections[self.account].get_module('Discovery').disco_muc( + self.room_jid, self._on_room_created, update=True) + self.print_conversation(_('A new room has been created')) + con = app.connections[self.account] + con.get_module('MUC').request_config(self.room_jid) # Update Actions - if obj.status_code: - if '110' in obj.status_code: - self.update_actions() + self.update_actions() - def add_contact_to_roster(self, nick, show, role, affiliation, status, - jid='', avatar_sha=None): - role_name = helpers.get_uf_role(role, plural=True) + def _on_nickname_changed(self, event): + if event.account != self.account: + return + if event.room_jid != self.room_jid: + return - resource = '' - if jid: - jids = jid.split('/', 1) - j = jids[0] - if len(jids) > 1: - resource = jids[1] + nick = event.properties.muc_nickname + new_nick = event.properties.muc_user.nick + if event.properties.is_muc_self_presence: + self._change_nick(new_nick) + message = _('You are now known as %s') % new_nick else: - j = '' + message = _('{nick} is now known ' + 'as {new_nick}').format(nick=nick, new_nick=new_nick) + self._nick_completion.contact_renamed(nick, new_nick) - name = nick + self.print_conversation(message, 'info', graphics=False) - # Add Contact - gc_contact = app.contacts.create_gc_contact( - room_jid=self.room_jid, account=self.account, - name=nick, show=show, status=status, role=role, - affiliation=affiliation, jid=j, resource=resource, - avatar_sha=avatar_sha) - app.contacts.add_gc_contact(self.account, gc_contact) + tv = self.conv_textview + if nick in tv.last_received_message_id: + tv.last_received_message_id[new_nick] = \ + tv.last_received_message_id[nick] + del tv.last_received_message_id[nick] + + # keep nickname color + if nick in self.gc_custom_colors: + self.gc_custom_colors[new_nick] = self.gc_custom_colors[nick] + + self.remove_contact(nick) + self.add_contact_to_roster(new_nick) + + def _on_status_show_changed(self, event): + if event.account != self.account: + return + if event.room_jid != self.room_jid: + return + + nick = event.properties.muc_nickname + status = event.properties.status + status = '' if status is None else ' (%s)' % status + show = helpers.get_uf_show(event.properties.show.value) + + if event.properties.is_muc_self_presence: + message = _('You are now {show}{status}').format(show=show, + status=status) + self.print_conversation(message, 'info', graphics=False) + + elif app.config.get_per('rooms', self.room_jid, 'print_status'): + message = _('{nick} is now {show}{status}').format(nick=nick, + show=show, + status=status) + self.print_conversation(message, 'info', graphics=False) + + self.draw_contact(nick) + + def _on_affiliation_changed(self, event): + if event.account != self.account: + return + if event.room_jid != self.room_jid: + return + + affiliation = helpers.get_uf_affiliation( + event.properties.affiliation.value) + nick = event.properties.muc_nickname + reason = event.properties.muc_user.reason + reason = '' if reason is None else ': {reason}'.format(reason=reason) + + actor = event.properties.muc_user.actor + #Group Chat: You have been kicked by Alice + actor = '' if actor is None else _(' by {actor}').format(actor=actor) + + if event.properties.is_muc_self_presence: + message = _('** Your Affiliation has been set to ' + '{affiliation}{actor}{reason}').format( + affiliation=affiliation, + actor=actor, + reason=reason) + else: + message = _('** Affiliation of {nick} has been set to ' + '{affiliation}{actor}{reason}').format( + nick=nick, + affiliation=affiliation, + actor=actor, + reason=reason) + + self.print_conversation(message, graphics=False) + self.draw_contact(nick) + + def _on_role_changed(self, event): + if event.account != self.account: + return + if event.room_jid != self.room_jid: + return + + role = helpers.get_uf_role(event.properties.role.value) + nick = event.properties.muc_nickname + reason = event.properties.muc_user.reason + reason = '' if reason is None else ': {reason}'.format(reason=reason) + + actor = event.properties.muc_user.actor + #Group Chat: You have been kicked by Alice + actor = '' if actor is None else _(' by {actor}').format(actor=actor) + + if event.properties.is_muc_self_presence: + message = _('** Your Role has been set to ' + '{role}{actor}{reason}').format(role=role, + actor=actor, + reason=reason) + else: + message = _('** Role of {nick} has been set to ' + '{role}{actor}{reason}').format(nick=nick, + role=role, + actor=actor, + reason=reason) + + self.print_conversation(message, graphics=False) + self.remove_contact(nick) + self.add_contact_to_roster(nick) + + def _on_self_kicked(self, event): + if event.account != self.account: + return + if event.room_jid != self.room_jid: + return + + self.autorejoin = False + + status_codes = event.properties.muc_status_codes or [] + + reason = event.properties.muc_user.reason + reason = '' if reason is None else ': {reason}'.format(reason=reason) + + actor = event.properties.muc_user.actor + #Group Chat: You have been kicked by Alice + actor = '' if actor is None else _(' by {actor}').format(actor=actor) + + #Group Chat: We have been removed from the room by Alice: reason + message = _('You have been removed from the room{actor}{reason}') + + if StatusCode.REMOVED_ERROR in status_codes: + # Handle 333 before 307, some MUCs add both + #Group Chat: Server kicked us because of an server error + message = _('You have left due ' + 'to an error{reason}').format(reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif StatusCode.REMOVED_KICKED in status_codes: + #Group Chat: We have been kicked by Alice: reason + message = _('You have been ' + 'kicked{actor}{reason}').format(actor=actor, + reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif StatusCode.REMOVED_BANNED in status_codes: + #Group Chat: We have been banned by Alice: reason + message = _('You have been ' + 'banned{actor}{reason}').format(actor=actor, + reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes: + #Group Chat: We were removed because of an affiliation change + reason = _(': Affiliation changed') + message = message.format(actor=actor, reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes: + #Group Chat: Room configuration changed + reason = _(': Room configuration changed to members-only') + message = message.format(actor=actor, reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif StatusCode.REMOVED_SERVICE_SHUTDOWN in status_codes: + #Group Chat: Kicked because of server shutdown + reason = ': System shutdown' + message = message.format(actor=actor, reason=reason) + self.print_conversation(message, 'info', graphics=False) + self.autorejoin = True + + self.got_disconnected() + + # Update Actions + self.update_actions() + + def _on_user_left(self, event): + if event.account != self.account: + return + if event.room_jid != self.room_jid: + return + + status_codes = event.properties.muc_status_codes or [] + nick = event.properties.muc_nickname + + reason = event.properties.muc_user.reason + reason = '' if reason is None else ': {reason}'.format(reason=reason) + + actor = event.properties.muc_user.actor + #Group Chat: You have been kicked by Alice + actor = '' if actor is None else _(' by {actor}').format(actor=actor) + + #Group Chat: We have been removed from the room + message = _('{nick} has been removed from the room{by}{reason}') + + print_join_left = app.config.get_per( + 'rooms', self.room_jid, 'print_join_left') + + if StatusCode.REMOVED_ERROR in status_codes: + # Handle 333 before 307, some MUCs add both + if print_join_left: + #Group Chat: User was kicked because of an server error: reason + message = _('{nick} has left due to ' + 'an error{reason}').format(nick=nick, reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif StatusCode.REMOVED_KICKED in status_codes: + #Group Chat: User was kicked by Alice: reason + message = _('{nick} has been ' + 'kicked{actor}{reason}').format(nick=nick, + actor=actor, + reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif StatusCode.REMOVED_BANNED in status_codes: + #Group Chat: User was banned by Alice: reason + message = _('{nick} has been ' + 'banned{actor}{reason}').format(nick=nick, + actor=actor, + reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif StatusCode.REMOVED_AFFILIATION_CHANGE in status_codes: + reason = _(': Affiliation changed') + message = message.format(nick=nick, actor=actor, reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif StatusCode.REMOVED_NONMEMBER_IN_MEMBERS_ONLY in status_codes: + reason = _(': Room configuration changed to members-only') + message = message.format(nick=nick, actor=actor, reason=reason) + self.print_conversation(message, 'info', graphics=False) + + elif print_join_left: + message = _('{nick} has left{reason}').format(nick=nick, + reason=reason) + self.print_conversation(message, 'info', graphics=False) + + self.remove_contact(nick) + self.draw_all_roles() + + def _on_user_joined(self, event): + if event.account != self.account: + return + if event.room_jid != self.room_jid: + return + + nick = event.properties.muc_nickname + print_join_left = app.config.get_per( + 'rooms', self.room_jid, 'print_join_left') + + self.add_contact_to_roster(nick) + + if self.is_connected and print_join_left: + self.print_conversation(_('%s has joined the group chat') % nick, + graphics=False) + + def _on_muc_presence_error(self, event): + if event.account != self.account: + return + if event.room_jid != self.room_jid: + return + + nick = event.properties.muc_nickname + error_type = event.properties.error.type + error_message = event.properties.error.message + + if error_type == Error.NOT_AUTHORIZED: + app.interface.handle_gc_password_required( + self.account, self.room_jid, nick) + + elif error_type == Error.FORBIDDEN: + # we are banned + ErrorDialog( + _('Unable to join group chat'), + _('You are banned from group chat %s.') % self.room_jid) + + elif error_type == Error.REMOTE_SERVER_NOT_FOUND: + ErrorDialog( + _('Unable to join group chat'), + _('Remote server %s does not exist.') % self.room_jid) + + elif error_type == Error.ITEM_NOT_FOUND: + ErrorDialog( + _('Unable to join group chat'), + _('Group chat %s does not exist.') % self.room_jid) + + elif error_type == Error.NOT_ALLOWED: + ErrorDialog( + _('Unable to join group chat'), + _('Group chat creation is not permitted.')) + + elif error_type == Error.NOT_ACCEPTABLE: + ErrorDialog( + _('Unable to join groupchat'), + _('You must use your registered ' + 'nickname in %s.') % self.room_jid) + + elif error_type == Error.REGISTRATION_REQUIRED: + ErrorDialog( + _('Unable to join group chat'), + _('You are not in the members ' + 'list in groupchat %s.') % self.room_jid) + + elif error_type == Error.CONFLICT: + win = None if self.parent_win is None else self.parent_win.window + app.interface.handle_ask_new_nick( + self.account, self.room_jid, win) + + else: + self.print_conversation( + 'Error %s: %s' % (error_type.value, error_message)) + + self.autorejoin = False + + def add_contact_to_roster(self, nick): + contact = app.contacts.get_gc_contact(self.account, + self.room_jid, + nick) + role_name = helpers.get_uf_role(contact.role.value, plural=True) # Create Role - role_iter = self.get_role_iter(role) + role_iter = self.get_role_iter(contact.role.value) if not role_iter: icon_name = get_icon_name('closed') ext_columns = [None] * self.nb_ext_renderers - row = [icon_name, role, 'role', role_name, None] + ext_columns + row = [icon_name, contact.role.value, + 'role', role_name, None] + ext_columns role_iter = self.model.append(None, row) - self._role_refs[role] = Gtk.TreeRowReference( + self._role_refs[contact.role.value] = Gtk.TreeRowReference( self.model, self.model.get_path(role_iter)) - self.draw_all_roles() # Avatar image = None if app.config.get('show_avatars_in_roster'): surface = app.interface.get_avatar( - avatar_sha, AvatarSize.ROSTER, self.scale_factor) + contact.avatar_sha, AvatarSize.ROSTER, self.scale_factor) image = Gtk.Image.new_from_surface(surface) # Add to model ext_columns = [None] * self.nb_ext_renderers - row = [None, nick, 'contact', name, image] + ext_columns + row = [None, nick, 'contact', nick, image] + ext_columns iter_ = self.model.append(role_iter, row) self._contact_refs[nick] = Gtk.TreeRowReference( self.model, self.model.get_path(iter_)) + self.draw_all_roles() self.draw_contact(nick) - if nick == self.nick: # we became online - self.got_connected() if self.list_treeview.get_model(): self.list_treeview.expand_row( (self.model.get_path(role_iter)), False) if self.is_continued: self.draw_banner_text() - return iter_ + + def _on_muc_destroyed(self, event): + if event.account != self.account: + return + if event.room_jid != self.room_jid: + return + + destroyed = event.properties.muc_destroyed + + reason = destroyed.reason + reason = '' if reason is None else ': %s' % reason + + message = _('Room has been destroyed') + self.print_conversation(message, 'info', graphics=False) + + alternate = destroyed.alternate + if alternate is not None: + join_message = _('You can join this room ' + 'instead: xmpp:%s?join') % alternate + self.print_conversation(join_message, 'info', graphics=False) + + self.autorejoin = False + self.got_disconnected() def get_role_iter(self, role: str) -> Optional[Gtk.TreeIter]: try: @@ -2046,10 +2200,6 @@ class GroupchatControl(ChatControlBase): iter_ = self.get_contact_iter(nick) if not iter_: return - gc_contact = app.contacts.get_gc_contact( - self.account, self.room_jid, nick) - if gc_contact: - app.contacts.remove_gc_contact(self.account, gc_contact) parent_iter = self.model.iter_parent(iter_) if parent_iter is None: @@ -2124,7 +2274,7 @@ class GroupchatControl(ChatControlBase): self.account, self.room_jid, nick) if gc_contact: return gc_contact.role - return 'visitor' + return Role.VISITOR def minimizable(self): if self.force_non_minimizable: @@ -2166,36 +2316,14 @@ class GroupchatControl(ChatControlBase): # Preventing autorejoin from being activated self.autorejoin = False - app.ged.remove_event_handler('gc-presence-received', ged.GUI1, - self._nec_gc_presence_received) - app.ged.remove_event_handler('gc-message-received', ged.GUI1, - self._nec_gc_message_received) - app.ged.remove_event_handler('vcard-published', ged.GUI1, - self._nec_vcard_published) - app.ged.remove_event_handler('update-gc-avatar', ged.GUI1, - self._nec_update_avatar) - app.ged.remove_event_handler('update-room-avatar', ged.GUI1, - self._nec_update_room_avatar) - app.ged.remove_event_handler('gc-subject-received', ged.GUI1, - self._nec_gc_subject_received) - app.ged.remove_event_handler('gc-config-changed-received', ged.GUI1, - self._nec_gc_config_changed_received) - app.ged.remove_event_handler('signed-in', ged.GUI1, - self._nec_signed_in) - app.ged.remove_event_handler('decrypted-message-received', ged.GUI2, - self._nec_decrypted_message_received) - app.ged.remove_event_handler('mam-decrypted-message-received', - ged.GUI1, self._nec_mam_decrypted_message_received) - app.ged.remove_event_handler('gc-stanza-message-outgoing', ged.OUT_POSTCORE, - self._message_sent) - app.ged.remove_event_handler('captcha-challenge', ged.GUI1, - self._on_captcha_challenge) - app.ged.remove_event_handler('voice-approval', ged.GUI1, - self._on_voice_approval) + # Unregister handlers + for handler in self._event_handlers: + app.ged.remove_event_handler(*handler) if self.is_connected: app.connections[self.account].send_gc_status(self.nick, self.room_jid, show='offline', status=status) + nick_list = app.contacts.get_nick_list(self.account, self.room_jid) for nick in nick_list: # Update pm chat window @@ -2209,10 +2337,12 @@ class GroupchatControl(ChatControlBase): contact.status = '' ctrl.update_ui() ctrl.parent_win.redraw_tab(ctrl) + # They can already be removed by the destroy function if self.room_jid in app.contacts.get_gc_list(self.account): app.contacts.remove_room(self.account, self.room_jid) del app.gc_connected[self.account][self.room_jid] + # Save hpaned position app.config.set('gc-hpaned-position', self.hpaned.get_position()) # remove all register handlers on wigets, created by self.xml @@ -2504,64 +2634,62 @@ class GroupchatControl(ChatControlBase): # these conditions were taken from JEP 0045 item = xml.get_object('kick_menuitem') - if user_role != 'moderator' or \ - (user_affiliation == 'admin' and target_affiliation == 'owner') or \ - (user_affiliation == 'member' and target_affiliation in ('admin', - 'owner')) or (user_affiliation == 'none' and target_affiliation != \ - 'none'): + if not user_role.is_moderator or \ + (user_affiliation.is_admin and target_affiliation.is_owner) or \ + (user_affiliation.is_member and target_affiliation in (Affiliation.ADMIN, + Affiliation.OWNER)) or (user_affiliation.is_none and not target_affiliation.is_none): item.set_sensitive(False) id_ = item.connect('activate', self.kick, nick) self.handlers[id_] = item item = xml.get_object('voice_checkmenuitem') - item.set_active(target_role != 'visitor') - if user_role != 'moderator' or \ - user_affiliation == 'none' or \ - (user_affiliation == 'member' and target_affiliation != 'none') or \ - target_affiliation in ('admin', 'owner'): + item.set_active(not target_role.is_visitor) + if not user_role.is_moderator or \ + user_affiliation.is_none or \ + (user_affiliation.is_member and not target_affiliation.is_none) or \ + target_affiliation in (Affiliation.ADMIN, Affiliation.OWNER): item.set_sensitive(False) id_ = item.connect('activate', self.on_voice_checkmenuitem_activate, nick) self.handlers[id_] = item item = xml.get_object('moderator_checkmenuitem') - item.set_active(target_role == 'moderator') - if not user_affiliation in ('admin', 'owner') or \ - target_affiliation in ('admin', 'owner'): + item.set_active(target_role.is_moderator) + if not user_affiliation in (Affiliation.ADMIN, Affiliation.OWNER) or \ + target_affiliation in (Affiliation.ADMIN, Affiliation.OWNER): item.set_sensitive(False) id_ = item.connect('activate', self.on_moderator_checkmenuitem_activate, nick) self.handlers[id_] = item item = xml.get_object('ban_menuitem') - if not user_affiliation in ('admin', 'owner') or \ - (target_affiliation in ('admin', 'owner') and\ - user_affiliation != 'owner'): + if not user_affiliation in (Affiliation.ADMIN, Affiliation.OWNER) or \ + (target_affiliation in (Affiliation.ADMIN, Affiliation.OWNER) and\ + not user_affiliation.is_owner): item.set_sensitive(False) id_ = item.connect('activate', self.ban, jid) self.handlers[id_] = item item = xml.get_object('member_checkmenuitem') - item.set_active(target_affiliation != 'none') - if not user_affiliation in ('admin', 'owner') or \ - (user_affiliation != 'owner' and target_affiliation in ('admin', - 'owner')): + item.set_active(not target_affiliation.is_none) + if not user_affiliation in (Affiliation.ADMIN, Affiliation.OWNER) or \ + (not user_affiliation.is_owner and target_affiliation in (Affiliation.ADMIN, Affiliation.OWNER)): item.set_sensitive(False) id_ = item.connect('activate', self.on_member_checkmenuitem_activate, jid) self.handlers[id_] = item item = xml.get_object('admin_checkmenuitem') - item.set_active(target_affiliation in ('admin', 'owner')) - if not user_affiliation == 'owner': + item.set_active(target_affiliation in (Affiliation.ADMIN, Affiliation.OWNER)) + if not user_affiliation.is_owner: item.set_sensitive(False) id_ = item.connect('activate', self.on_admin_checkmenuitem_activate, jid) self.handlers[id_] = item item = xml.get_object('owner_checkmenuitem') - item.set_active(target_affiliation == 'owner') - if not user_affiliation == 'owner': + item.set_active(target_affiliation.is_owner) + if not user_affiliation.is_owner: item.set_sensitive(False) id_ = item.connect('activate', self.on_owner_checkmenuitem_activate, jid) diff --git a/gajim/gtk/preferences.py b/gajim/gtk/preferences.py index 4f177b654..7849ffdd8 100644 --- a/gajim/gtk/preferences.py +++ b/gajim/gtk/preferences.py @@ -147,15 +147,6 @@ class Preferences(Gtk.ApplicationWindow): st = app.config.get('show_subject_on_join') self._ui.subject_on_join_checkbutton.set_active(st) - # Print status in MUC - st = app.config.get('print_status_in_muc') - if st == 'none': - self._ui.print_status_in_muc_combobox.set_active(0) - elif st == 'all': - self._ui.print_status_in_muc_combobox.set_active(1) - else: # in_and_out - self._ui.print_status_in_muc_combobox.set_active(2) - # Displayed chat state notifications st = app.config.get('show_chatstate_in_tabs') self._ui.show_chatstate_in_tabs.set_active(st) @@ -604,15 +595,6 @@ class Preferences(Gtk.ApplicationWindow): def on_subject_on_join_checkbutton_toggled(self, widget): self.on_checkbutton_toggled(widget, 'show_subject_on_join') - def print_status_in_muc_combobox_changed(self, widget): - active = widget.get_active() - if active == 0: # none - app.config.set('print_status_in_muc', 'none') - elif active == 1: # all - app.config.set('print_status_in_muc', 'all') - else: # in_and_out - app.config.set('print_status_in_muc', 'in_and_out') - def on_show_chatstate_in_tabs_toggled(self, widget): self.on_checkbutton_toggled(widget, 'show_chatstate_in_tabs') diff --git a/gajim/gtk/tooltips.py b/gajim/gtk/tooltips.py index 87e3d42fc..1fbaee11f 100644 --- a/gajim/gtk/tooltips.py +++ b/gajim/gtk/tooltips.py @@ -203,18 +203,18 @@ class GCTooltip(): self._ui.status.show() # Status - show = helpers.get_uf_show(contact.show) + show = helpers.get_uf_show(contact.show.value) self._ui.user_show.set_markup(colorize_status(show)) self._ui.user_show.show() # JID - if contact.jid.strip(): - self._ui.jid.set_text(contact.jid) + if contact.jid is not None: + self._ui.jid.set_text(str(contact.jid)) self._ui.jid.show() # Affiliation - if contact.affiliation != 'none': - uf_affiliation = helpers.get_uf_affiliation(contact.affiliation) + if not contact.affiliation.is_none: + uf_affiliation = helpers.get_uf_affiliation(contact.affiliation.value) uf_affiliation = \ _('%(owner_or_admin_or_member)s of this group chat') \ % {'owner_or_admin_or_member': uf_affiliation} diff --git a/gajim/gui_interface.py b/gajim/gui_interface.py index f43def857..66091a96d 100644 --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -329,57 +329,6 @@ class Interface: cancel_handler=on_cancel) gc_control.error_dialog.input_entry.set_visibility(False) - def handle_event_gc_presence(self, obj): - gc_control = obj.gc_control - parent_win = None - if gc_control and gc_control.parent_win: - parent_win = gc_control.parent_win.window - if obj.ptype == 'error': - if obj.errcode == '503': - # maximum user number reached - self.handle_gc_error(gc_control, - _('Unable to join group chat'), - _('%s is full')\ - % obj.room_jid) - elif (obj.errcode == '401') or (obj.errcon == 'not-authorized'): - # password required to join - self.handle_gc_password_required(obj.conn.name, obj.room_jid, - obj.nick) - elif (obj.errcode == '403') or (obj.errcon == 'forbidden'): - # we are banned - self.handle_gc_error(gc_control, _('Unable to join group chat'), - _('You are banned from group chat %s.') % \ - obj.room_jid) - elif (obj.errcode == '404') or (obj.errcon in ('item-not-found', - 'remote-server-not-found')): - # remote server does not exist - if obj.errcon == 'remote-server-not-found': - self.handle_gc_error(gc_control, _('Unable to join group chat'), - _('Remote server %s does not exist.') % obj.room_jid) - # group chat does not exist - else: - self.handle_gc_error(gc_control, _('Unable to join group chat'), - _('Group chat %s does not exist.') % obj.room_jid) - elif (obj.errcode == '405') or (obj.errcon == 'not-allowed'): - self.handle_gc_error(gc_control, _('Unable to join group chat'), - _('Group chat creation is not permitted.')) - elif (obj.errcode == '406') or (obj.errcon == 'not-acceptable'): - self.handle_gc_error(gc_control, _('Unable to join groupchat'), - _('You must use your registered nickname in %s.')\ - % obj.room_jid) - elif (obj.errcode == '407') or (obj.errcon == \ - 'registration-required'): - self.handle_gc_error(gc_control, _('Unable to join group chat'), - _('You are not in the members list in groupchat %s.') % \ - obj.room_jid) - elif (obj.errcode == '409') or (obj.errcon == 'conflict'): - self.handle_ask_new_nick(obj.conn.name, obj.room_jid, parent_win) - elif gc_control: - gc_control.print_conversation('Error %s: %s' % (obj.errcode, - obj.errmsg)) - if gc_control and gc_control.autorejoin: - gc_control.autorejoin = False - def handle_event_presence(self, obj): # 'NOTIFY' (account, (jid, status, status message, resource, # priority, # keyID, timestamp, contact_nickname)) @@ -1445,7 +1394,6 @@ class Interface: 'file-request-received': [self.handle_event_file_request], 'gc-invitation-received': [self.handle_event_gc_invitation], 'gc-decline-received': [self.handle_event_gc_decline], - 'gc-presence-received': [self.handle_event_gc_presence], 'gpg-password-required': [self.handle_event_gpg_password_required], 'gpg-trust-key': [self.handle_event_gpg_trust_key], 'http-auth-received': [self.handle_event_http_auth], @@ -1597,14 +1545,6 @@ class Interface: nick = resource gc_contact = app.contacts.get_gc_contact( account, room_jid, nick) - if gc_contact: - show = gc_contact.show - else: - show = 'offline' - gc_contact = app.contacts.create_gc_contact( - room_jid=room_jid, account=account, name=nick, - show=show) - ctrl = self.new_private_chat(gc_contact, account) w = ctrl.parent_win diff --git a/gajim/gui_menu_builder.py b/gajim/gui_menu_builder.py index 71f408162..c924cda0a 100644 --- a/gajim/gui_menu_builder.py +++ b/gajim/gui_menu_builder.py @@ -659,12 +659,16 @@ def get_groupchat_menu(control_id, account, jid): ('win.upload-avatar-', _('Upload Avatar…')), ('win.destroy-', _('Destroy Room')), ]), + (_('Chat Settings'), [ + ('win.print-join-left-', _('Show join/leave')), + ('win.print-status-', _('Show status changes')), + ('win.notify-on-message-', _('Notify on all messages')), + ('win.minimize-', _('Minimize on close')), + ]), (_('Sync Threshold'), []), ('win.change-nick-', _('Change Nick')), ('win.bookmark-', _('Bookmark Room')), ('win.request-voice-', _('Request Voice')), - ('win.notify-on-message-', _('Notify on all messages')), - ('win.minimize-', _('Minimize on close')), ('win.execute-command-', _('Execute command')), ('app.browse-history', _('History')), ('win.disconnect-', _('Disconnect')), diff --git a/gajim/message_window.py b/gajim/message_window.py index 12a30df3a..c259a2346 100644 --- a/gajim/message_window.py +++ b/gajim/message_window.py @@ -630,6 +630,7 @@ class MessageWindow: tab = self.notebook.get_tab_label(ctrl.widget) if not tab: return + hbox = tab.get_children()[0] status_img = hbox.get_children()[0] nick_label = hbox.get_children()[1] diff --git a/gajim/privatechat_control.py b/gajim/privatechat_control.py index 4a831009a..98228ab13 100644 --- a/gajim/privatechat_control.py +++ b/gajim/privatechat_control.py @@ -24,6 +24,8 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . +from gi.repository import Gtk + from gajim import message_control from gajim.common import app @@ -36,6 +38,7 @@ from gajim.chat_control import ChatControl from gajim.command_system.implementation.hosts import PrivateChatCommands from gajim.gtk.dialogs import ErrorDialog +from gajim.gtk.util import get_icon_name class PrivateChatControl(ChatControl): @@ -58,24 +61,38 @@ class PrivateChatControl(ChatControl): self.gc_contact = gc_contact ChatControl.__init__(self, parent_win, contact, account, session) self.TYPE_ID = 'pm' - app.ged.register_event_handler('update-gc-avatar', ged.GUI1, - self._nec_update_avatar) - app.ged.register_event_handler('caps-update', ged.GUI1, - self._nec_caps_received_pm) - app.ged.register_event_handler('gc-presence-received', ged.GUI1, - self._nec_gc_presence_received) + + self.__event_handlers = [ + ('update-gc-avatar', ged.GUI1, self._nec_update_avatar), + ('caps-update', ged.GUI1, self._nec_caps_received_pm), + ('muc-user-joined', ged.GUI1, self._on_user_joined), + ('muc-user-left', ged.GUI1, self._on_user_left), + ('muc-nickname-changed', ged.GUI1, self._on_nickname_changed), + ('muc-self-presence', ged.GUI1, self._on_self_presence), + ('muc-self-kicked', ged.GUI1, self._on_diconnected), + ('muc-user-status-show-changed', ged.GUI1, self._on_status_show_changed), + ('muc-destroyed', ged.GUI1, self._on_diconnected), + ] + + for handler in self.__event_handlers: + app.ged.register_event_handler(*handler) + + @property + def contact(self): + return self.gc_contact.as_contact() + + @contact.setter + def contact(self, _value): + # TODO: remove all code that sets the contact here + return def get_our_nick(self): return self.room_ctrl.nick def shutdown(self): super(PrivateChatControl, self).shutdown() - app.ged.remove_event_handler('update-gc-avatar', ged.GUI1, - self._nec_update_avatar) - app.ged.remove_event_handler('caps-update', ged.GUI1, - self._nec_caps_received_pm) - app.ged.remove_event_handler('gc-presence-received', ged.GUI1, - self._nec_gc_presence_received) + for handler in self.__event_handlers: + app.ged.remove_event_handler(*handler) def _nec_caps_received_pm(self, obj): if obj.conn.name != self.account or \ @@ -83,43 +100,90 @@ class PrivateChatControl(ChatControl): return self.update_contact() - def _nec_gc_presence_received(self, obj): - if obj.conn.name != self.account: + def _on_nickname_changed(self, event): + if event.account != self.account: return - if obj.fjid != self.full_jid: + if event.properties.new_jid != self.gc_contact.get_full_jid(): return - if '303' in obj.status_code: - self.print_conversation( - _('%(nick)s is now known as %(new_nick)s') % { - 'nick': obj.nick, 'new_nick': obj.new_nick}, - 'status') - gc_c = app.contacts.get_gc_contact(obj.conn.name, obj.room_jid, - obj.new_nick) - c = gc_c.as_contact() - self.gc_contact = gc_c - self.contact = c - self.draw_banner() - old_jid = obj.room_jid + '/' + obj.nick - new_jid = obj.room_jid + '/' + obj.new_nick - app.interface.msg_win_mgr.change_key( - old_jid, new_jid, obj.conn.name) + + nick = event.properties.muc_nickname + new_nick = event.properties.muc_user.nick + if event.properties.is_muc_self_presence: + message = _('You are now known as %s') % new_nick else: - self.contact.show = obj.show - self.contact.status = obj.status - self.gc_contact.show = obj.show - self.gc_contact.status = obj.status - uf_show = helpers.get_uf_show(obj.show) - self.print_conversation( - _('%(nick)s is now %(status)s') % {'nick': obj.nick, - 'status': uf_show}, - 'status') - if obj.status: - self.print_conversation(' (', 'status', simple=True) - self.print_conversation( - '%s' % (obj.status), 'status', simple=True) - self.print_conversation(')', 'status', simple=True) - self.parent_win.redraw_tab(self) - self.update_ui() + message = _('{nick} is now known ' + 'as {new_nick}').format(nick=nick, new_nick=new_nick) + + self.print_conversation(message, 'info') + + self.draw_banner() + app.interface.msg_win_mgr.change_key(str(event.properties.jid), + str(event.properties.new_jid), + self.account) + + self.parent_win.redraw_tab(self) + self.update_ui() + + def _on_status_show_changed(self, event): + if event.account != self.account: + return + if event.properties.jid != self.gc_contact.get_full_jid(): + return + + nick = event.properties.muc_nickname + status = event.properties.status + status = '' if status is None else ' (%s)' % status + show = helpers.get_uf_show(event.properties.show.value) + + if event.properties.is_muc_self_presence: + message = _('You are now {show}{status}').format(show=show, + status=status) + self.print_conversation(message, 'info') + + elif app.config.get_per('rooms', self.room_name, 'print_status'): + message = _('{nick} is now {show}{status}').format(nick=nick, + show=show, + status=status) + self.print_conversation(message, 'info') + + self.parent_win.redraw_tab(self) + self.update_ui() + + def _on_diconnected(self, event): + if event.account != self.account: + return + if event.properties.jid != self.gc_contact.get_full_jid(): + return + + self.got_disconnected() + + def _on_user_left(self, event): + if event.account != self.account: + return + if event.properties.jid != self.gc_contact.get_full_jid(): + return + + self.got_disconnected() + + def _on_user_joined(self, event): + if event.account != self.account: + return + if event.properties.jid != self.gc_contact.get_full_jid(): + return + + self.gc_contact = app.contacts.get_gc_contact( + self.account, self.gc_contact.room_jid, self.gc_contact.name) + self.parent_win.redraw_tab(self) + self.got_connected() + + def _on_self_presence(self, event): + if event.account != self.account: + return + if event.properties.jid != self.gc_contact.get_full_jid(): + return + + self.parent_win.redraw_tab(self) + self.got_connected() def send_message(self, message, xhtml=None, process_commands=True, attention=False): @@ -132,21 +196,15 @@ class PrivateChatControl(ChatControl): # We need to make sure that we can still send through the room and that # the recipient did not go away - contact = app.contacts.get_first_contact_from_jid( - self.account, self.contact.jid) - if not contact: - # contact was from pm in MUC - room, nick = app.get_room_and_nick_from_fjid(self.contact.jid) - gc_contact = app.contacts.get_gc_contact(self.account, room, nick) - if not gc_contact: - ErrorDialog( - _('Sending private message failed'), - #in second %s code replaces with nickname - _('You are no longer in group chat "%(room)s" or ' - '"%(nick)s" has left.') % { - 'room': '\u200E' + room, 'nick': nick}, - transient_for=self.parent_win.window) - return + if self.gc_contact.presence.is_unavailable: + ErrorDialog( + _('Sending private message failed'), + #in second %s code replaces with nickname + _('You are no longer in group chat "%(room)s" or ' + '"%(nick)s" has left.') % { + 'room': self.room_name, 'nick': self.gc_contact.name}, + transient_for=self.parent_win.window) + return ChatControl.send_message(self, message, xhtml=xhtml, @@ -154,11 +212,10 @@ class PrivateChatControl(ChatControl): attention=attention) def update_ui(self): - if self.contact.show == 'offline': + if self.gc_contact.presence.is_unavailable: self.got_disconnected() else: self.got_connected() - ChatControl.update_ui(self) def _nec_update_avatar(self, obj): if obj.contact != self.gc_contact: @@ -173,10 +230,59 @@ class PrivateChatControl(ChatControl): surface = app.interface.get_avatar( self.gc_contact.avatar_sha, AvatarSize.CHAT, scale) image = self.xml.get_object('avatar_image') - image.set_from_surface(surface) + if surface is None: + image.set_from_icon_name('avatar-default', Gtk.IconSize.DIALOG) + else: + image.set_from_surface(surface) + + def _update_banner_state_image(self): + # Set banner image + if self.gc_contact.presence.is_unavailable: + icon = get_icon_name('offline') + else: + icon = get_icon_name(self.gc_contact.show.value) + banner_status_img = self.xml.get_object('banner_status_image') + banner_status_img.set_from_icon_name(icon, Gtk.IconSize.DND) + + def get_tab_image(self, count_unread=True): + jid = self.gc_contact.get_full_jid() + if app.config.get('show_avatar_in_tabs'): + scale = self.parent_win.window.get_scale_factor() + surface = app.contacts.get_avatar( + self.account, jid, AvatarSize.TAB, scale) + if surface is not None: + return surface + + if count_unread: + num_unread = len(app.events.get_events( + self.account, jid, ['printed_' + self.type_id, self.type_id])) + else: + num_unread = 0 + + transport = None + if app.jid_is_transport(jid): + transport = app.get_transport_name_from_jid(jid) + + if self.gc_contact.presence.is_unavailable: + show = 'offline' + else: + show = self.gc_contact.show.value + + if num_unread and app.config.get('show_unread_tab_icon'): + icon_name = get_icon_name('event', transport=transport) + else: + icon_name = get_icon_name(show, transport=transport) + + return icon_name def update_contact(self): self.contact = self.gc_contact.as_contact() def got_disconnected(self): ChatControl.got_disconnected(self) + self.parent_win.redraw_tab(self) + ChatControl.update_ui(self) + + def got_connected(self): + ChatControl.got_connected(self) + ChatControl.update_ui(self) diff --git a/gajim/roster_window.py b/gajim/roster_window.py index eb8d6c284..871f7cbd5 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -2591,21 +2591,6 @@ class RosterWindow: if app.events.get_events(account, obj.jid): ctrl.read_queue() - def _nec_gc_presence_received(self, obj): - account = obj.conn.name - if obj.room_jid in app.interface.minimized_controls[account]: - gc_ctrl = app.interface.minimized_controls[account][obj.room_jid] - else: - return - - if obj.nick == gc_ctrl.nick: - contact = app.contacts.get_contact_with_highest_priority( - account, obj.room_jid) - if contact: - contact.show = obj.show - self.draw_contact(obj.room_jid, account) - self.draw_group(_('Groupchats'), account) - def _nec_roster_received(self, obj): if obj.received_from_server: self.fill_contacts_and_groups_dicts(obj.roster, obj.conn.name) @@ -5860,10 +5845,6 @@ class RosterWindow: app.ged.register_event_handler('presence-received', ged.GUI1, self._nec_presence_received) - # presence has to be fully handled so that contact is added to occupant - # list before roster can be correctly updated - app.ged.register_event_handler('gc-presence-received', ged.GUI2, - self._nec_gc_presence_received) app.ged.register_event_handler('roster-received', ged.GUI1, self._nec_roster_received) app.ged.register_event_handler('anonymous-auth', ged.GUI1, diff --git a/gajim/vcard.py b/gajim/vcard.py index cbed78e4a..634104bb5 100644 --- a/gajim/vcard.py +++ b/gajim/vcard.py @@ -372,11 +372,11 @@ class VcardWindow: ask_label = self.xml.get_object('ask_label') if self.gc_contact: self.xml.get_object('subscription_title_label').set_markup(Q_("?Role in Group Chat:Role:")) - uf_role = helpers.get_uf_role(self.gc_contact.role) + uf_role = helpers.get_uf_role(self.gc_contact.role.value) subscription_label.set_text(uf_role) self.xml.get_object('ask_title_label').set_markup(_("Affiliation:")) - uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation) + uf_affiliation = helpers.get_uf_affiliation(self.gc_contact.affiliation.value) ask_label.set_text(uf_affiliation) else: uf_sub = helpers.get_uf_sub(self.contact.sub)