groupchat_control.py 84.3 KB
Newer Older
Philipp Hörist's avatar
Philipp Hörist committed
1 2 3 4 5 6 7 8 9 10
# Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
# Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
# Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
#                    Alex Mauer <hawke AT hawkesnest.net>
# Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
#                         Travis Shirk <travis AT pobox.com>
# Copyright (C) 2007-2008 Julien Pivotto <roidelapluie AT gmail.com>
#                         Stephan Erb <steve-e AT h3c.de>
# Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
#                    Jonathan Schleifer <js-gajim AT webkeks.org>
11
# Copyright (C) 2018 Marcin Mielniczuk <marmistrz dot dev at zoho dot eu>
Philipp Hörist's avatar
Philipp Hörist committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25
#
# This file is part of Gajim.
#
# Gajim is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published
# by the Free Software Foundation; version 3 only.
#
# Gajim is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
nicfit's avatar
nicfit committed
26

nicfit's avatar
nicfit committed
27
import time
28
import base64
Philipp Hörist's avatar
Philipp Hörist committed
29
import logging
Philipp Hörist's avatar
Philipp Hörist committed
30

Philipp Hörist's avatar
Philipp Hörist committed
31
import nbxmpp
32 33
from nbxmpp.protocol import InvalidJid
from nbxmpp.protocol import validate_resourcepart
Philipp Hörist's avatar
Philipp Hörist committed
34
from nbxmpp.const import StatusCode
Philipp Hörist's avatar
Philipp Hörist committed
35 36
from nbxmpp.const import Affiliation
from nbxmpp.const import PresenceType
37
from nbxmpp.util import is_error_result
38

39
from gi.repository import Gtk
Dicson's avatar
Dicson committed
40
from gi.repository import Gdk
Yann Leboulanger's avatar
Yann Leboulanger committed
41
from gi.repository import GLib
Philipp Hörist's avatar
Philipp Hörist committed
42
from gi.repository import Gio
Philipp Hörist's avatar
Philipp Hörist committed
43

André's avatar
André committed
44 45 46 47
from gajim import gtkgui_helpers
from gajim import gui_menu_builder
from gajim import message_control
from gajim import vcard
48

André's avatar
André committed
49
from gajim.common import events
50
from gajim.common import app
51
from gajim.common import ged
André's avatar
André committed
52
from gajim.common import helpers
Philipp Hörist's avatar
Philipp Hörist committed
53
from gajim.common.helpers import event_filter
54
from gajim.common.helpers import to_user_string
55 56
from gajim.common.const import AvatarSize

57
from gajim.common.i18n import _
58
from gajim.common import contacts
59
from gajim.common.const import Chatstate
60
from gajim.common.const import MUCJoinedState
61

André's avatar
André committed
62
from gajim.chat_control_base import ChatControlBase
63

André's avatar
André committed
64 65
from gajim.command_system.implementation.hosts import GroupChatCommands
from gajim.common.connection_handlers_events import GcMessageOutgoingEvent
66

67 68
from gajim.gtk.dialogs import DialogButton
from gajim.gtk.dialogs import NewConfirmationCheckDialog
69
from gajim.gtk.dialogs import ErrorDialog
70
from gajim.gtk.dialogs import NewConfirmationDialog
71
from gajim.gtk.filechoosers import AvatarChooserDialog
72
from gajim.gtk.groupchat_config import GroupchatConfig
73
from gajim.gtk.adhoc import AdHocCommand
74
from gajim.gtk.dataform import DataFormWidget
Philipp Hörist's avatar
Philipp Hörist committed
75
from gajim.gtk.groupchat_info import GroupChatInfoScrolled
Philipp Hörist's avatar
Philipp Hörist committed
76
from gajim.gtk.groupchat_roster import GroupchatRoster
77
from gajim.gtk.util import NickCompletionGenerator
78
from gajim.gtk.util import get_icon_name
79

Philipp Hörist's avatar
Philipp Hörist committed
80

81 82
log = logging.getLogger('gajim.groupchat_control')

83

nicfit's avatar
nicfit committed
84
class GroupchatControl(ChatControlBase):
85 86 87 88 89 90
    TYPE_ID = message_control.TYPE_GC

    # Set a command host to bound to. Every command given through a group chat
    # will be processed with this command host.
    COMMAND_HOST = GroupChatCommands

91
    def __init__(self, parent_win, contact, muc_data, acct):
92
        ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
93
                                 'groupchat_control', contact, acct)
94
        self.force_non_minimizable = False
95 96
        self.is_anonymous = True

Philipp Hörist's avatar
Philipp Hörist committed
97 98
        self.toggle_emoticons()

Philipp Hörist's avatar
Philipp Hörist committed
99 100 101 102 103 104 105 106 107
        self.room_jid = self.contact.jid
        self._muc_data = muc_data

        # Stores nickname we want to kick
        self._kick_nick = None

        # Stores nickname we want to ban
        self._ban_jid = None

108 109 110 111 112 113
        # Last sent message text
        self.last_sent_txt = ''

        # Attribute, encryption plugins use to signal the message can be sent
        self.sendmessage = False

Philipp Hörist's avatar
Philipp Hörist committed
114 115 116 117
        self.roster = GroupchatRoster(self.account, self.room_jid, self)
        self.xml.roster_revealer.add(self.roster)
        self.roster.connect('row-activated', self._on_roster_row_activated)

118
        if parent_win is not None:
119
            # On AutoJoin with minimize Groupchats are created without parent
Philipp Hörist's avatar
Philipp Hörist committed
120
            # Tooltip Window and Actions have to be created with parent
Philipp Hörist's avatar
Philipp Hörist committed
121
            self.roster.enable_tooltips()
Philipp Hörist's avatar
Philipp Hörist committed
122
            self.add_actions()
123
            GLib.idle_add(self.update_actions)
124 125 126
            self.scale_factor = parent_win.window.get_scale_factor()
        else:
            self.scale_factor = app.interface.roster.scale_factor
lovetox's avatar
lovetox committed
127

128 129
        if not app.config.get('hide_groupchat_banner'):
            self.xml.banner_eventbox.set_no_show_all(False)
130 131 132 133 134

        # muc attention flag (when we are mentioned in a muc)
        # if True, the room has mentioned us
        self.attention_flag = False

Philipp Hörist's avatar
Philipp Hörist committed
135 136 137
        # True if we initiated room destruction
        self._wait_for_destruction = False

138 139 140
        # sorted list of nicks who mentioned us (last at the end)
        self.attention_list = []
        self.nick_hits = []
Philipp Hörist's avatar
Philipp Hörist committed
141
        self._nick_completion = NickCompletionGenerator(muc_data.nick)
142 143
        self.last_key_tabs = False

Philipp Hörist's avatar
Philipp Hörist committed
144
        self.setup_seclabel()
145

146
        # Send file
147 148
        self.xml.sendfile_button.set_action_name(
            'win.send-file-%s' % self.control_id)
149

150 151 152
        # Encryption
        self.set_lock_image()

153
        self.xml.encryption_menu.set_menu_model(
154
            gui_menu_builder.get_encryption_menu(self.control_id, self.type_id))
155
        self.set_encryption_menu_icon()
156

Philipp Hörist's avatar
Philipp Hörist committed
157 158
        # Banner
        self.hide_roster_button = Gtk.Button.new_from_icon_name(
159
            'go-next-symbolic', Gtk.IconSize.MENU)
160
        self.hide_roster_button.set_valign(Gtk.Align.CENTER)
Philipp Hörist's avatar
Philipp Hörist committed
161 162
        self.hide_roster_button.connect('clicked',
                                        lambda *args: self.show_roster())
163
        self.xml.banner_actionbar.pack_end(self.hide_roster_button)
Philipp Hörist's avatar
Philipp Hörist committed
164

Philipp Hörist's avatar
Philipp Hörist committed
165 166 167
        # Holds CaptchaRequest widget
        self._captcha_request = None

Philipp Hörist's avatar
Philipp Hörist committed
168 169 170 171 172
        # MUC Info
        self._subject_data = None
        self._muc_info_box = GroupChatInfoScrolled(self.account, {'width': 600})
        self.xml.info_grid.attach(self._muc_info_box, 0, 0, 1, 1)

173 174 175
        self.control_menu = gui_menu_builder.get_groupchat_menu(self.control_id,
                                                                self.account,
                                                                self.room_jid)
176 177

        self.xml.settings_menu.set_menu_model(self.control_menu)
Philipp Hörist's avatar
Philipp Hörist committed
178

179
        # pylint: disable=line-too-long
Philipp Hörist's avatar
Philipp Hörist committed
180
        self._event_handlers = [
Philipp Hörist's avatar
Philipp Hörist committed
181
            ('muc-creation-failed', ged.GUI1, self._on_muc_creation_failed),
182
            ('muc-joined', ged.GUI1, self._on_muc_joined),
183
            ('muc-join-failed', ged.GUI1, self._on_muc_join_failed),
Philipp Hörist's avatar
Philipp Hörist committed
184 185 186 187 188 189 190 191
            ('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),
Philipp Hörist's avatar
Philipp Hörist committed
192 193
            ('muc-destroyed', ged.GUI1, self._on_destroyed),
            ('muc-presence-error', ged.GUI1, self._on_presence_error),
194
            ('muc-password-required', ged.GUI1, self._on_password_required),
Philipp Hörist's avatar
Philipp Hörist committed
195 196 197
            ('muc-config-changed', ged.GUI1, self._on_config_changed),
            ('muc-subject', ged.GUI1, self._on_subject),
            ('muc-captcha-challenge', ged.GUI1, self._on_captcha_challenge),
198
            ('muc-captcha-error', ged.GUI1, self._on_captcha_error),
199
            ('muc-voice-request', ged.GUI1, self._on_voice_request),
200 201
            ('muc-disco-update', ged.GUI1, self._on_disco_update),
            ('muc-configuration-finished', ged.GUI1, self._on_configuration_finished),
Philipp Hörist's avatar
Philipp Hörist committed
202
            ('muc-configuration-failed', ged.GUI1, self._on_configuration_failed),
Philipp Hörist's avatar
Philipp Hörist committed
203 204 205 206 207 208
            ('gc-message-received', ged.GUI1, self._nec_gc_message_received),
            ('mam-decrypted-message-received', ged.GUI1, self._nec_mam_decrypted_message_received),
            ('update-room-avatar', ged.GUI1, self._nec_update_room_avatar),
            ('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),
209
            ('bookmarks-received', ged.GUI2, self._on_bookmarks_received),
Philipp Hörist's avatar
Philipp Hörist committed
210
        ]
211
        # pylint: enable=line-too-long
Philipp Hörist's avatar
Philipp Hörist committed
212 213 214 215

        for handler in self._event_handlers:
            app.ged.register_event_handler(*handler)

216
        self.is_connected = False
217 218 219
        # disable win, we are not connected yet
        ChatControlBase.got_disconnected(self)

220 221
        # Stack
        self.xml.stack.show_all()
222 223
        self.xml.stack.set_visible_child_name('progress')
        self.xml.progress_spinner.start()
224

225 226
        self.update_ui()
        self.widget.show_all()
227 228 229 230 231

        if app.config.get('hide_groupchat_occupants_list'):
            # Roster is shown by default, so toggle the roster button to hide it
            self.show_roster()

232 233
        # PluginSystem: adding GUI extension point for this GroupchatControl
        # instance object
234
        app.plugin_manager.gui_extension_point('groupchat_control', self)
235

Philipp Hörist's avatar
Philipp Hörist committed
236 237 238 239
    @property
    def nick(self):
        return self._muc_data.nick

240 241 242 243 244 245
    @property
    def subject(self):
        if self._subject_data is None:
            return ''
        return self._subject_data.subject

246 247 248 249
    @property
    def room_name(self):
        return self.contact.get_shown_name()

250 251 252 253
    @property
    def disco_info(self):
        return app.logger.get_last_disco_info(self.contact.jid)

Philipp Hörist's avatar
Philipp Hörist committed
254
    def add_actions(self):
255
        super().add_actions()
Philipp Hörist's avatar
Philipp Hörist committed
256
        actions = [
Philipp Hörist's avatar
Philipp Hörist committed
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272
            ('change-subject-', None, self._on_change_subject),
            ('change-nickname-', None, self._on_change_nick),
            ('disconnect-', None, self._on_disconnect),
            ('destroy-', None, self._on_destroy_room),
            ('configure-', None, self._on_configure_room),
            ('request-voice-', None, self._on_request_voice),
            ('upload-avatar-', None, self._on_upload_avatar),
            ('information-', None, self._on_information),
            ('contact-information-', 's', self._on_contact_information),
            ('execute-command-', 's', self._on_execute_command),
            ('block-', 's', self._on_block),
            ('unblock-', 's', self._on_unblock),
            ('ban-', 's', self._on_ban),
            ('kick-', 's', self._on_kick),
            ('change-role-', 'as', self._on_change_role),
            ('change-affiliation-', 'as', self._on_change_affiliation),
273
        ]
Philipp Hörist's avatar
Philipp Hörist committed
274 275

        for action in actions:
Philipp Hörist's avatar
Philipp Hörist committed
276 277 278 279
            action_name, variant, func = action
            if variant is not None:
                variant = GLib.VariantType.new(variant)
            act = Gio.SimpleAction.new(action_name + self.control_id, variant)
Philipp Hörist's avatar
Philipp Hörist committed
280 281 282
            act.connect("activate", func)
            self.parent_win.window.add_action(act)

283 284
        minimize = app.config.get_per(
            'rooms', self.contact.jid, 'minimize_on_close', True)
Philipp Hörist's avatar
Philipp Hörist committed
285 286

        act = Gio.SimpleAction.new_stateful(
287 288 289 290 291 292 293 294 295 296 297 298
            'minimize-on-close-' + self.control_id, None,
            GLib.Variant.new_boolean(minimize))
        act.connect('change-state', self._on_minimize_on_close)
        self.parent_win.window.add_action(act)

        minimize = app.config.get_per(
            'rooms', self.contact.jid, 'minimize_on_autojoin', True)

        act = Gio.SimpleAction.new_stateful(
            'minimize-on-autojoin-' + self.control_id, None,
            GLib.Variant.new_boolean(minimize))
        act.connect('change-state', self._on_minimize_on_autojoin)
Philipp Hörist's avatar
Philipp Hörist committed
299 300
        self.parent_win.window.add_action(act)

301
        default_muc_chatstate = app.config.get('send_chatstate_muc_default')
302
        chatstate = app.config.get_per(
Philipp Hörist's avatar
Philipp Hörist committed
303
            'rooms', self.contact.jid, 'send_chatstate', default_muc_chatstate)
304 305 306 307 308 309 310 311

        act = Gio.SimpleAction.new_stateful(
            'send-chatstate-' + self.control_id,
            GLib.VariantType.new("s"),
            GLib.Variant("s", chatstate))
        act.connect('change-state', self._on_send_chatstate)
        self.parent_win.window.add_action(act)

Philipp Hörist's avatar
Philipp Hörist committed
312
        # Enable notify on all for private rooms
313 314 315 316
        members_only = False
        if self.disco_info is not None:
            members_only = self.disco_info.muc_is_members_only

Philipp Hörist's avatar
Philipp Hörist committed
317
        value = app.config.get_per(
Philipp Hörist's avatar
Philipp Hörist committed
318
            'rooms', self.contact.jid, 'notify_on_all_messages', members_only)
Philipp Hörist's avatar
Philipp Hörist committed
319 320 321 322 323 324 325

        act = Gio.SimpleAction.new_stateful(
            'notify-on-message-' + self.control_id,
            None, GLib.Variant.new_boolean(value))
        act.connect('change-state', self._on_notify_on_all_messages)
        self.parent_win.window.add_action(act)

326
        status_default = app.config.get('print_status_muc_default')
327 328
        value = app.config.get_per('rooms', self.contact.jid,
                                   'print_status', status_default)
Philipp Hörist's avatar
Philipp Hörist committed
329 330 331 332 333 334 335

        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)

336
        join_default = app.config.get('print_join_left_default')
337 338
        value = app.config.get_per('rooms', self.contact.jid,
                                   'print_join_left', join_default)
Philipp Hörist's avatar
Philipp Hörist committed
339 340 341 342 343 344 345

        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)

346 347 348 349 350 351 352 353 354 355 356
        archive_info = app.logger.get_archive_infos(self.contact.jid)
        threshold = helpers.get_sync_threshold(self.contact.jid,
                                               archive_info)

        inital = GLib.Variant.new_string(str(threshold))
        act = Gio.SimpleAction.new_stateful(
            'choose-sync-' + self.control_id,
            inital.get_type(), inital)
        act.connect('change-state', self._on_sync_threshold)
        self.parent_win.window.add_action(act)

357 358 359 360 361 362 363 364 365 366
        value = app.config.get_per(
            'rooms', self.contact.jid, 'send_marker', False)

        act = Gio.SimpleAction.new_stateful(
            'send-marker-' + self.control_id,
            None,
            GLib.Variant.new_boolean(value))
        act.connect('change-state', self._on_send_marker)
        self.parent_win.window.add_action(act)

Philipp Hörist's avatar
Philipp Hörist committed
367 368 369
    def update_actions(self):
        if self.parent_win is None:
            return
Philipp Hörist's avatar
Philipp Hörist committed
370

Philipp Hörist's avatar
Philipp Hörist committed
371 372
        contact = app.contacts.get_gc_contact(
            self.account, self.room_jid, self.nick)
Philipp Hörist's avatar
Philipp Hörist committed
373
        con = app.connections[self.account]
Philipp Hörist's avatar
Philipp Hörist committed
374 375

        # Destroy Room
376 377
        self._get_action('destroy-').set_enabled(self.is_connected and
                                                 contact.affiliation.is_owner)
Philipp Hörist's avatar
Philipp Hörist committed
378 379

        # Configure Room
380
        self._get_action('configure-').set_enabled(
Philipp Hörist's avatar
Philipp Hörist committed
381 382
            self.is_connected and contact.affiliation in (Affiliation.ADMIN,
                                                          Affiliation.OWNER))
Philipp Hörist's avatar
Philipp Hörist committed
383

384
        self._get_action('request-voice-').set_enabled(self.is_connected and
Philipp Hörist's avatar
Philipp Hörist committed
385
                                                       contact.role.is_visitor)
Philipp Hörist's avatar
Philipp Hörist committed
386 387

        # Change Subject
388
        subject_change = self._is_subject_change_allowed()
389
        self._get_action('change-subject-').set_enabled(self.is_connected and
390
                                                        subject_change)
Philipp Hörist's avatar
Philipp Hörist committed
391 392

        # Change Nick
393
        self._get_action('change-nickname-').set_enabled(self.is_connected)
Philipp Hörist's avatar
Philipp Hörist committed
394

395
        # Execute command
396
        self._get_action('execute-command-').set_enabled(self.is_connected)
397

398
        # Send file (HTTP File Upload)
399 400 401 402 403
        httpupload = self._get_action(
            'send-file-httpupload-')
        httpupload.set_enabled(self.is_connected and
                               con.get_module('HTTPUpload').available)
        self._get_action('send-file-').set_enabled(httpupload.get_enabled())
404

405
        if self.is_connected and httpupload.get_enabled():
406
            tooltip_text = _('Send File…')
407 408 409 410
            max_file_size = con.get_module('HTTPUpload').max_file_size
            if max_file_size is not None:
                max_file_size = max_file_size / (1024 * 1024)
                tooltip_text = _('Send File (max. %s MiB)…') % max_file_size
411 412
        else:
            tooltip_text = _('No File Transfer available')
413
        self.xml.sendfile_button.set_tooltip_text(tooltip_text)
414

415
        # Upload Avatar
416 417 418
        vcard_support = False
        if self.disco_info is not None:
            vcard_support = self.disco_info.supports(nbxmpp.NS_VCARD)
419 420 421 422
        self._get_action('upload-avatar-').set_enabled(
            self.is_connected and
            vcard_support and
            contact.affiliation.is_owner)
423

424 425 426 427 428 429 430 431 432 433 434 435 436 437
        # Print join/left
        join_default = app.config.get('print_join_left_default')
        value = app.config.get_per('rooms', self.contact.jid,
                                   'print_join_left', join_default)
        self._get_action('print-join-left-').set_state(
            GLib.Variant.new_boolean(value))

        # Print join/left
        status_default = app.config.get('print_status_muc_default')
        value = app.config.get_per('rooms', self.contact.jid,
                                   'print_status', status_default)
        self._get_action('print-status-').set_state(
            GLib.Variant.new_boolean(value))

Philipp Hörist's avatar
Philipp Hörist committed
438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
        self._get_action('contact-information-').set_enabled(self.is_connected)

        self._get_action('execute-command-').set_enabled(self.is_connected)

        block_supported = con.get_module('PrivacyLists').supported
        self._get_action('block-').set_enabled(self.is_connected and
                                               block_supported)

        self._get_action('unblock-').set_enabled(self.is_connected and
                                                 block_supported)

        self._get_action('ban-').set_enabled(self.is_connected)

        self._get_action('kick-').set_enabled(self.is_connected)

453 454 455 456 457 458 459 460 461 462 463
    def _is_subject_change_allowed(self):
        contact = app.contacts.get_gc_contact(
            self.account, self.room_jid, self.nick)
        if contact is None:
            return False

        if contact.affiliation in (Affiliation.OWNER, Affiliation.ADMIN):
            return True

        if self.disco_info is None:
            return False
464
        return self.disco_info.muc_subjectmod or False
465

466 467 468
    def _get_action(self, name):
        win = self.parent_win.window
        return win.lookup_action(name + self.control_id)
469

470 471 472 473
    def _show_page(self, name):
        transition = Gtk.StackTransitionType.SLIDE_DOWN
        if name == 'groupchat':
            transition = Gtk.StackTransitionType.SLIDE_UP
474
            self.msg_textview.grab_focus()
475
        if name == 'muc-info':
476 477 478
            # Set focus on the close button, otherwise one of
            # the selectable labels of the GroupchatInfo box gets focus,
            # which means it is fully selected
479
            self.xml.info_close_button.grab_focus()
480 481
        self.xml.stack.set_visible_child_full(name, transition)

482 483 484
    def _get_current_page(self):
        return self.xml.stack.get_visible_child_name()

485 486
    @event_filter(['account', 'room_jid'])
    def _on_disco_update(self, _event):
487 488 489 490
        if self.parent_win is None:
            return
        win = self.parent_win.window
        self.update_actions()
491
        self.draw_banner_text()
492 493

        # After the room has been created, reevaluate threshold
494
        if self.disco_info.has_mam:
495 496 497 498 499 500
            archive_info = app.logger.get_archive_infos(self.contact.jid)
            threshold = helpers.get_sync_threshold(self.contact.jid,
                                                   archive_info)
            win.change_action_state('choose-sync-%s' % self.control_id,
                                    GLib.Variant('s', str(threshold)))

Philipp Hörist's avatar
Philipp Hörist committed
501 502
    # Actions

503
    def _on_disconnect(self, _action, _param):
504
        self.leave()
Philipp Hörist's avatar
Philipp Hörist committed
505

506
    def _on_information(self, _action, _param):
507
        self._muc_info_box.set_from_disco_info(self.disco_info)
Philipp Hörist's avatar
Philipp Hörist committed
508 509 510 511 512 513
        if self._subject_data is not None:
            self._muc_info_box.set_subject(self._subject_data.subject)
            self._muc_info_box.set_author(self._subject_data.nickname,
                                          self._subject_data.user_timestamp)
        self._show_page('muc-info')

514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543
    def _on_destroy_room(self, _action, _param):
        self.xml.destroy_reason_entry.grab_focus()
        self.xml.destroy_button.grab_default()
        self._show_page('destroy')

    def _on_destroy_alternate_changed(self, entry, _param):
        jid = entry.get_text()
        if jid:
            try:
                jid = helpers.validate_jid(jid)
            except Exception:
                icon = 'dialog-warning-symbolic'
                text = _('Invalid XMPP Address')
                self.xml.destroy_alternate_entry.set_icon_from_icon_name(
                    Gtk.EntryIconPosition.SECONDARY, icon)
                self.xml.destroy_alternate_entry.set_icon_tooltip_text(
                    Gtk.EntryIconPosition.SECONDARY, text)
                self.xml.destroy_button.set_sensitive(False)
                return
        self.xml.destroy_alternate_entry.set_icon_from_icon_name(
            Gtk.EntryIconPosition.SECONDARY, None)
        self.xml.destroy_button.set_sensitive(True)

    def _on_destroy_confirm(self, _button):
        reason = self.xml.destroy_reason_entry.get_text()
        jid = self.xml.destroy_alternate_entry.get_text()
        self._wait_for_destruction = True
        con = app.connections[self.account]
        con.get_module('MUC').destroy(self.room_jid, reason, jid)
        self._show_page('groupchat')
Philipp Hörist's avatar
Philipp Hörist committed
544

545
    def _on_configure_room(self, _action, _param):
546 547 548 549 550
        win = app.get_app_window('GroupchatConfig', self.account, self.room_jid)
        if win is not None:
            win.present()
            return

551
        contact = app.contacts.get_gc_contact(
Philipp Hörist's avatar
Philipp Hörist committed
552
            self.account, self.room_jid, self.nick)
Philipp Hörist's avatar
Philipp Hörist committed
553
        if contact.affiliation.is_owner:
554
            con = app.connections[self.account]
555 556
            con.get_module('MUC').request_config(
                self.room_jid, callback=self._on_configure_form_received)
Philipp Hörist's avatar
Philipp Hörist committed
557
        elif contact.affiliation.is_admin:
558 559 560 561 562 563
            GroupchatConfig(self.account,
                            self.room_jid,
                            contact.affiliation.value)

    def _on_configure_form_received(self, result):
        if is_error_result(result):
564
            log.info(result)
565 566
            return
        GroupchatConfig(self.account, result.jid, 'owner', result.form)
Philipp Hörist's avatar
Philipp Hörist committed
567 568 569 570 571 572 573 574 575 576

    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())
Philipp Hörist's avatar
Philipp Hörist committed
577

578
    def _on_request_voice(self, _action, _param):
Philipp Hörist's avatar
Philipp Hörist committed
579 580 581
        """
        Request voice in the current room
        """
Philipp Hörist's avatar
Philipp Hörist committed
582 583
        con = app.connections[self.account]
        con.get_module('MUC').request_voice(self.room_jid)
Philipp Hörist's avatar
Philipp Hörist committed
584

585
    def _on_minimize_on_close(self, action, param):
Philipp Hörist's avatar
Philipp Hörist committed
586
        action.set_state(param)
Philipp Hörist's avatar
Philipp Hörist committed
587 588
        app.config.set_per('rooms', self.contact.jid,
                           'minimize_on_close', param.get_boolean())
Philipp Hörist's avatar
Philipp Hörist committed
589

590 591
    def _on_minimize_on_autojoin(self, action, param):
        action.set_state(param)
Philipp Hörist's avatar
Philipp Hörist committed
592 593
        app.config.set_per('rooms', self.contact.jid,
                           'minimize_on_autojoin', param.get_boolean())
Philipp Hörist's avatar
Philipp Hörist committed
594

595 596 597 598 599
    def _on_send_chatstate(self, action, param):
        action.set_state(param)
        app.config.set_per('rooms', self.contact.jid,
                           'send_chatstate', param.get_string())

600 601 602 603 604
    def _on_send_marker(self, action, param):
        action.set_state(param)
        app.config.set_per('rooms', self.contact.jid,
                           'send_marker', param.get_boolean())

Philipp Hörist's avatar
Philipp Hörist committed
605 606 607 608 609
    def _on_notify_on_all_messages(self, action, param):
        action.set_state(param)
        app.config.set_per('rooms', self.contact.jid,
                           'notify_on_all_messages', param.get_boolean())

610 611 612 613 614
    def _on_sync_threshold(self, action, param):
        threshold = param.get_string()
        action.set_state(param)
        app.logger.set_archive_infos(self.contact.jid, sync_threshold=threshold)

Philipp Hörist's avatar
Philipp Hörist committed
615 616 617 618 619 620
    def _on_execute_command(self, _action, param):
        jid = self.room_jid
        nick = param.get_string()
        if nick:
            jid += '/' + nick
        AdHocCommand(self.account, jid)
621

622
    def _on_upload_avatar(self, _action, _param):
623
        def _on_accept(filename):
Philipp Hörist's avatar
Philipp Hörist committed
624 625
            data, sha = app.interface.avatar_storage.prepare_for_publish(
                filename)
626
            if sha is None:
627
                ErrorDialog(
628 629 630 631
                    _('Could not load image'),
                    transient_for=self.parent_win.window)
                return

Philipp Hörist's avatar
Philipp Hörist committed
632
            avatar = base64.b64encode(data).decode('utf-8')
633 634
            con = app.connections[self.account]
            con.get_module('VCardTemp').upload_room_avatar(
635 636 637 638 639 640
                self.room_jid, avatar)

        AvatarChooserDialog(_on_accept,
                            transient_for=self.parent_win.window,
                            modal=True)

Philipp Hörist's avatar
Philipp Hörist committed
641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
    def _on_contact_information(self, _action, param):
        nick = param.get_string()
        gc_contact = app.contacts.get_gc_contact(self.account,
                                                 self.room_jid,
                                                 nick)
        contact = gc_contact.as_contact()
        if contact.jid in app.interface.instances[self.account]['infos']:
            app.interface.instances[self.account]['infos'][contact.jid].\
                window.present()
        else:
            app.interface.instances[self.account]['infos'][contact.jid] = \
                vcard.VcardWindow(contact, self.account, gc_contact)

    def _on_block(self, _action, param):
        nick = param.get_string()
        fjid = self.room_jid + '/' + nick
        con = app.connections[self.account]
        con.get_module('PrivacyLists').block_gc_contact(fjid)
        self.roster.draw_contact(nick)

    def _on_unblock(self, _action, param):
        nick = param.get_string()
        fjid = self.room_jid + '/' + nick
        con = app.connections[self.account]
        con.get_module('PrivacyLists').unblock_gc_contact(fjid)
        self.roster.draw_contact(nick)

    def _on_kick(self, _action, param):
        nick = param.get_string()
        self._kick_nick = nick
        self.xml.kick_label.set_text(_('Kick %s' % nick))
        self.xml.kick_reason_entry.grab_focus()
        self.xml.kick_participant_button.grab_default()
        self._show_page('kick')

    def _on_ban(self, _action, param):
        jid = param.get_string()
        self._ban_jid = jid
        nick = app.get_nick_from_jid(jid)
        self.xml.ban_label.set_text(_('Ban %s' % nick))
        self.xml.ban_reason_entry.grab_focus()
        self.xml.ban_participant_button.grab_default()
        self._show_page('ban')

    def _on_change_role(self, _action, param):
        nick, role = param.get_strv()
        con = app.connections[self.account]
        con.get_module('MUC').set_role(self.room_jid, nick, role)

    def _on_change_affiliation(self, _action, param):
        jid, affiliation = param.get_strv()
        con = app.connections[self.account]
        con.get_module('MUC').set_affiliation(
            self.room_jid,
            {jid: {'affiliation': affiliation}})

Philipp Hörist's avatar
Philipp Hörist committed
697
    def show_roster(self):
Philipp Hörist's avatar
Philipp Hörist committed
698 699
        show = not self.xml.roster_revealer.get_reveal_child()
        icon = 'go-next-symbolic' if show else 'go-previous-symbolic'
Philipp Hörist's avatar
Philipp Hörist committed
700
        image = self.hide_roster_button.get_image()
Philipp Hörist's avatar
Philipp Hörist committed
701 702 703 704 705 706 707
        image.set_from_icon_name(icon, Gtk.IconSize.MENU)

        transition = Gtk.RevealerTransitionType.SLIDE_RIGHT
        if show:
            transition = Gtk.RevealerTransitionType.SLIDE_LEFT
        self.xml.roster_revealer.set_transition_type(transition)
        self.xml.roster_revealer.set_reveal_child(show)
Philipp Hörist's avatar
Philipp Hörist committed
708

709
    def on_groupchat_maximize(self):
Philipp Hörist's avatar
Philipp Hörist committed
710
        self.roster.enable_tooltips()
Philipp Hörist's avatar
Philipp Hörist committed
711 712
        self.add_actions()
        self.update_actions()
713
        self.set_lock_image()
714
        self.draw_banner_text()
715

Philipp Hörist's avatar
Philipp Hörist committed
716 717
    def _on_roster_row_activated(self, _roster, nick):
        self._start_private_message(nick)
718 719 720 721 722 723 724

    def on_msg_textview_populate_popup(self, textview, menu):
        """
        Override the default context menu and we prepend Clear
        and the ability to insert a nick
        """
        ChatControlBase.on_msg_textview_populate_popup(self, textview, menu)
Dicson's avatar
Dicson committed
725
        item = Gtk.SeparatorMenuItem.new()
726 727
        menu.prepend(item)

728
        item = Gtk.MenuItem.new_with_label(_('Insert Nickname'))
729
        menu.prepend(item)
730
        submenu = Gtk.Menu()
731 732
        item.set_submenu(submenu)

733 734 735
        nicks = app.contacts.get_nick_list(self.account, self.room_jid)
        nicks.sort()
        for nick in nicks:
736 737
            item = Gtk.MenuItem.new_with_label(nick)
            item.set_use_underline(False)
738
            submenu.append(item)
739 740
            id_ = item.connect('activate',
                               self.append_nick_in_msg_textview, nick)
741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
            self.handlers[id_] = item

        menu.show_all()

    def get_tab_label(self, chatstate):
        """
        Markup the label if necessary. Returns a tuple such as: (new_label_str,
        color) either of which can be None if chatstate is given that means we
        have HE SENT US a chatstate
        """

        has_focus = self.parent_win.window.get_property('has-toplevel-focus')
        current_tab = self.parent_win.get_active_control() == self
        color = None
        if chatstate == 'attention' and (not has_focus or not current_tab):
            self.attention_flag = True
Philipp Hörist's avatar
Philipp Hörist committed
757
            color = 'tab-muc-directed-msg'
758 759 760 761 762 763
        elif chatstate == 'active' or (current_tab and has_focus):
            self.attention_flag = False
            # get active color from gtk
            color = 'active'
        elif chatstate == 'newmsg' and (not has_focus or not current_tab) \
        and not self.attention_flag:
Philipp Hörist's avatar
Philipp Hörist committed
764
            color = 'tab-muc-msg'
765

766
        label_str = GLib.markup_escape_text(self.room_name)
767 768 769 770 771 772 773

        # count waiting highlighted messages
        unread = ''
        num_unread = self.get_nb_unread()
        if num_unread == 1:
            unread = '*'
        elif num_unread > 1:
Yann Leboulanger's avatar
Yann Leboulanger committed
774
            unread = '[' + str(num_unread) + ']'
775 776 777 778 779
        label_str = unread + label_str
        return (label_str, color)

    def get_tab_image(self, count_unread=True):
        tab_image = None
780
        if self.is_connected:
781
            tab_image = get_icon_name('muc-active')
782
        else:
783
            tab_image = get_icon_name('muc-inactive')
784 785 786
        return tab_image

    def _update_banner_state_image(self):
787 788 789 790 791 792 793
        surface = app.interface.avatar_storage.get_muc_surface(
            self.account,
            self.contact.jid,
            AvatarSize.CHAT,
            self.scale_factor)

        self.xml.gc_banner_status_image.set_from_surface(surface)
794 795 796 797

    def draw_banner_text(self):
        """
        Draw the text in the fat line at the top of the window that houses the
798
        room jid
799
        """
800
        self.xml.banner_name_label.set_text(self.room_name)
801

Philipp Hörist's avatar
Philipp Hörist committed
802 803 804 805 806
    def _nec_update_room_avatar(self, obj):
        if obj.jid != self.room_jid:
            return
        self._update_banner_state_image()

807 808 809 810 811 812 813
    @event_filter(['account'])
    def _on_bookmarks_received(self, _event):
        if self.parent_win is None:
            return
        self.parent_win.redraw_tab(self)
        self.draw_banner_text()

Philipp Hörist's avatar
Philipp Hörist committed
814
    @event_filter(['account', 'room_jid'])
815 816 817 818 819 820 821 822 823 824 825
    def _on_voice_request(self, event):
        def on_approve():
            con = app.connections[self.account]
            con.get_module('MUC').approve_voice_request(self.room_jid,
                                                        event.voice_request)
        NewConfirmationDialog(
            _('Voice Request'),
            _('Voice Request'),
            _('<b>%s</b> from <b>%s</b> requests voice') % (
                event.voice_request.nick, self.room_name),
            [DialogButton.make('Cancel'),
826 827
             DialogButton.make('Accept',
                               text=_('_Approve'),
828 829
                               callback=on_approve)],
            modal=False).show()
Philipp Hörist's avatar
Philipp Hörist committed
830

Philipp Hörist's avatar
Philipp Hörist committed
831
    @event_filter(['account'])
Philipp Hörist's avatar
Philipp Hörist committed
832 833 834
    def _nec_mam_decrypted_message_received(self, obj):
        if not obj.groupchat:
            return
Philipp Hörist's avatar
Philipp Hörist committed
835
        if obj.archive_jid != self.room_jid:
Philipp Hörist's avatar
Philipp Hörist committed
836
            return
837
        self.add_message(obj.msgtxt,
838
                         contact=obj.properties.muc_nickname,
839 840
                         tim=obj.properties.mam.timestamp,
                         correct_id=obj.correct_id,
841
                         message_id=obj.properties.id,
842
                         additional_data=obj.additional_data)
Philipp Hörist's avatar
Philipp Hörist committed
843

Philipp Hörist's avatar
Philipp Hörist committed
844
    @event_filter(['account', 'room_jid'])
845
    def _nec_gc_message_received(self, obj):
846
        if obj.properties.muc_nickname is None:
847
            # message from server
848 849 850 851
            self.add_message(obj.msgtxt,
                             tim=obj.properties.timestamp,
                             displaymarking=obj.displaymarking,
                             additional_data=obj.additional_data)
852
        else:
853
            if obj.properties.muc_nickname == self.nick:
854
                self.last_sent_txt = obj.msgtxt
855
            self.add_message(obj.msgtxt,
856
                             contact=obj.properties.muc_nickname,
857 858 859
                             tim=obj.properties.timestamp,
                             displaymarking=obj.displaymarking,
                             correct_id=obj.correct_id,
860
                             message_id=obj.properties.id,
861
                             additional_data=obj.additional_data)
862
        obj.needs_highlight = self.needs_visual_notification(obj.msgtxt)
863

Philipp Hörist's avatar
Philipp Hörist committed
864
    def on_private_message(self, nick, sent, msg, tim, session, additional_data,
865
                           message_id, msg_log_id=None, displaymarking=None):
866 867 868
        # Do we have a queue?
        fjid = self.room_jid + '/' + nick

869 870 871 872 873 874 875 876
        event = events.PmEvent(msg,
                               '',
                               'incoming',
                               tim,
                               '',
                               msg_log_id,
                               session=session,
                               displaymarking=displaymarking,
Philipp Hörist's avatar
Philipp Hörist committed
877
                               sent_forwarded=sent,
878 879 880
                               additional_data=additional_data,
                               message_id=message_id)

881
        app.events.add_event(self.account, fjid, event)
882

883 884
        autopopup = app.config.get('autopopup')
        autopopupaway = app.config.get('autopopupaway')
885
        if not autopopup or (not autopopupaway and \
886
        app.connections[self.account].connected > 2):
Philipp Hörist's avatar
Philipp Hörist committed
887
            self.roster.draw_contact(nick)
888 889 890 891 892
            if self.parent_win:
                self.parent_win.show_title()
                self.parent_win.redraw_tab(self)
        else:
            self._start_private_message(nick)
Philipp Hörist's avatar
Philipp Hörist committed
893

894
        contact = app.contacts.get_contact_with_highest_priority(
895
            self.account, self.room_jid)
896
        if contact:
897
            app.interface.roster.draw_contact(self.room_jid, self.account)
898

Philipp Hörist's avatar
Philipp Hörist committed
899
    def add_message(self, text, contact='', tim=None,
Philipp Hörist's avatar
Philipp Hörist committed
900
                    displaymarking=None, correct_id=None, message_id=None,
901
                    additional_data=None):
902
        """
Philipp Hörist's avatar
Philipp Hörist committed
903
        Add message to the ConversationsTextview
904

Philipp Hörist's avatar
Philipp Hörist committed
905
        If contact is set: it's a message from someone
906 907
        If contact is not set: it's a message from the server or help.
        """
Philipp Hörist's avatar
Philipp Hörist committed
908

909 910
        other_tags_for_name = []
        other_tags_for_text = []
911

912 913 914 915
        if not contact:
            # Message from the server
            kind = 'status'
        elif contact == self.nick: # it's us
916
            kind = 'outgoing'
917
        else:
918 919 920 921
            kind = 'incoming'
            # muc-specific chatstate
            if self.parent_win:
                self.parent_win.redraw_tab(self, 'newmsg')
922 923 924

        if kind == 'incoming': # it's a message NOT from us
            # highlighting and sounds
925
            highlight, _sound = self.highlighting_for_message(text, tim)
926
            other_tags_for_name.append('muc_nickname_color_%s' % contact)
927 928 929 930 931 932 933 934 935
            if highlight:
                # muc-specific chatstate
                if self.parent_win:
                    self.parent_win.redraw_tab(self, 'attention')
                else:
                    self.attention_flag = True
                other_tags_for_name.append('bold')
                other_tags_for_text.append('marked')

936
            self._nick_completion.record_message(contact, highlight)
937

938
            self.check_focus_out_line()
939

Philipp Hörist's avatar
Philipp Hörist committed
940 941 942 943 944 945 946 947 948 949 950 951
        ChatControlBase.add_message(self,
                                    text,
                                    kind,
                                    contact,
                                    tim,
                                    other_tags_for_name,
                                    [],
                                    other_tags_for_text,
                                    displaymarking=displaymarking,
                                    correct_id=correct_id,
                                    message_id=message_id,
                                    additional_data=additional_data)
952 953 954

    def get_nb_unread(self):
        type_events = ['printed_marked_gc_msg']
Philipp Hörist's avatar
Philipp Hörist committed
955
        if app.config.notify_for_muc(self.room_jid):
956
            type_events.append('printed_gc_msg')
957 958 959
        nb = len(app.events.get_events(self.account,
                                       self.room_jid,
                                       type_events))
960 961 962 963 964
        nb += self.get_nb_unread_pm()
        return nb

    def get_nb_unread_pm(self):
        nb = 0
965 966
        for nick in app.contacts.get_nick_list(self.account, self.room_jid):
            nb += len(app.events.get_events(self.account, self.room_jid + \
967
                '/' + nick, ['pm']))
968 969 970 971 972 973 974
        return nb

    def highlighting_for_message(self, text, tim):
        """
        Returns a 2-Tuple. The first says whether or not to highlight the text,
        the second, what sound to play
        """
Philipp Hörist's avatar
Philipp Hörist committed
975 976 977 978 979 980
        highlight, sound = None, None

        notify = app.config.notify_for_muc(self.room_jid)
        message_sound_enabled = app.config.get_per('soundevents',
                                                   'muc_message_received',
                                                   'enabled')
981 982 983 984

        # Are any of the defined highlighting words in the text?
        if self.needs_visual_notification(text):
            highlight = True
Philipp Hörist's avatar
Philipp Hörist committed
985 986 987
            if app.config.get_per('soundevents',
                                  'muc_message_highlight',
                                  'enabled'):
988 989 990
                sound = 'highlight'

        # Do we play a sound on every muc message?
Philipp Hörist's avatar
Philipp Hörist committed
991
        elif notify and message_sound_enabled:
992 993 994
            sound = 'received'

        # Is it a history message? Don't want sound-floods when we join.
995
        if tim is not None and time.mktime(time.localtime()) - tim > 1:
996 997
            sound = None

Philipp Hörist's avatar
Philipp Hörist committed
998
        return highlight, sound
999

1000
    def check_focus_out_line(self):
1001 1002 1003 1004 1005
        """
        Check and possibly add focus out line for room_jid if it needs it and
        does not already have it as last event. If it goes to add this line
        - remove previous line first
        """
1006
        win = app.interface.msg_win_mgr.get_window(self.room_jid, self.account)
1007 1008 1009 1010 1011 1012 1013
        if win and self.room_jid == win.get_active_jid() and\
        win.window.get_property('has-toplevel-focus') and\
        self.parent_win.get_active_control() == self:
            # it's the current room and it's the focused window.
            # we have full focus (we are reading it!)
            return

Philipp Hörist's avatar
Philipp Hörist committed
1014
        self.conv_textview.show_focus_out_line()
1015 1016 1017 1018 1019 1020

    def needs_visual_notification(self, text):
        """
        Check text to see whether any of the words in (muc_highlight_words and
        nick) appear
        """
1021
        special_words = app.config.get('muc_highlight_words').split(';')
1022
        special_words.append(self.nick)
1023 1024
        con = app.connections[self.account]
        special_words.append(con.get_own_jid().getStripped())
1025 1026 1027 1028 1029 1030 1031
        # Strip empties: ''.split(';') == [''] and would highlight everything.
        # Also lowercase everything for case insensitive compare.
        special_words = [word.lower() for word in special_words if word]
        text = text.lower()

        for special_word in special_words:
            found_here = text.find(special_word)
1032
            while found_here > -1:
1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043
                end_here = found_here + len(special_word)
                if (found_here == 0 or not text[found_here - 1].isalpha()) and \
                (end_here == len(text) or not text[end_here].isalpha()):
                    # It is beginning of text or char before is not alpha AND
                    # it is end of text or char after is not alpha
                    return True
                # continue searching
                start = found_here + 1
                found_here = text.find(special_word, start)
        return False

Philipp Hörist's avatar
Philipp Hörist committed
1044
    @event_filter(['account', 'room_jid'])
Philipp Hörist's avatar
Philipp Hörist committed
1045
    def _on_subject(self, event):
1046
        if self.subject == event.subject or event.is_fake:
1047 1048
            # Probably a rejoin, we already showed that subject
            return
1049 1050 1051

        self._subject_data = event

1052
        text = _('%(nick)s has set the subject to %(subject)s') % {
Philipp Hörist's avatar
Philipp Hörist committed
1053
            'nick': event.nickname, 'subject': event.subject}
1054

Philipp Hörist's avatar
Philipp Hörist committed
1055
        if event.user_timestamp:
1056
            date = time.strftime('%c', time.localtime(event.user_timestamp))
1057
            text = '%s - %s' % (text, date)
1058

1059
        if (app.config.get('show_subject_on_join') or
1060
                self._muc_data.state != MUCJoinedState.JOINING):
1061
            self.add_info_message(text)
1062

Philipp Hörist's avatar
Philipp Hörist committed
1063
    @event_filter(['account', 'room_jid'])
Philipp Hörist's avatar
Philipp Hörist committed
1064
    def _on_config_changed(self, event):
1065 1066
        # http://www.xmpp.org/extensions/xep-0045.html#roomconfig-notify
        changes = []
Philipp Hörist's avatar
Philipp Hörist committed
1067
        if StatusCode.SHOWING_UNAVAILABLE in event.status_codes:
1068
            changes.append(_('Group chat now shows unavailable members'))
Philipp Hörist's avatar
Philipp Hörist committed
1069

Philipp Hörist's avatar
Philipp Hörist committed
1070
        if StatusCode.NOT_SHOWING_UNAVAILABLE in event.status_codes:
1071 1072
            changes.append(_('Group chat now does not show '
                             'unavailable members'))
Philipp Hörist's avatar
Philipp Hörist committed
1073

Philipp Hörist's avatar
Philipp Hörist committed
1074
        if StatusCode.CONFIG_NON_PRIVACY_RELATED in event.status_codes:
1075
            changes.append(_('A setting not related to privacy has been '
1076
                             'changed'))