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
-
-
- 0
- 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)