Refactor MUC Presence

- Use nbxmpp properties
- Split into multiple events
- Add new options in the group chat menu for print settings
This commit is contained in:
Philipp Hörist 2018-12-30 01:42:59 +01:00
parent ed4a81fead
commit 0ad369dc97
19 changed files with 970 additions and 753 deletions

View file

@ -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:

View file

@ -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?')

View file

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

View file

@ -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:

View file

@ -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.')],

View file

@ -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'

View file

@ -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]

View file

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

View file

@ -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

View file

@ -702,42 +702,10 @@
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="tooltip_text" translatable="yes">These status messages are displayed when a room occupant changes status (this includes entering/leaving the room)</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Displa_y status messages in group chats</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">print_status_in_muc_combobox</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
<placeholder/>
</child>
<child>
<object class="GtkComboBox" id="print_status_in_muc_combobox">
<property name="width_request">200</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="model">print_status_in_muc_liststore</property>
<signal name="changed" handler="print_status_in_muc_combobox_changed" swapped="no"/>
<child>
<object class="GtkCellRendererText"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
<placeholder/>
</child>
</object>
<packing>

File diff suppressed because it is too large Load diff

View file

@ -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')

View file

@ -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}

View file

@ -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'),
_('<b>%s</b> 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 <b>%s</b>.') % \
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 <b>%s</b> 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 <b>%s</b> 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 <b>%s</b>.')\
% 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

View file

@ -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')),

View file

@ -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]

View file

@ -24,6 +24,8 @@
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
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)

View file

@ -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,

View file

@ -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:<b>Role:</b>"))
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(_("<b>Affiliation:</b>"))
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)