Skip to content
Snippets Groups Projects
groupchat_control.py 103 KiB
Newer Older
roidelapluie's avatar
roidelapluie committed
# -*- coding:utf-8 -*-
roidelapluie's avatar
roidelapluie committed
## src/groupchat_control.py
## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
roidelapluie's avatar
roidelapluie committed
## 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>
roidelapluie's avatar
roidelapluie committed
## 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>
## 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
roidelapluie's avatar
roidelapluie committed
## 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
roidelapluie's avatar
roidelapluie committed
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
nicfit's avatar
nicfit committed
import os
nicfit's avatar
nicfit committed
import time
import gtk
import pango
import gobject
import gtkgui_helpers
nicfit's avatar
nicfit committed
import tooltips
import dialogs
nicfit's avatar
nicfit committed
import vcard
import cell_renderer_image
nicfit's avatar
nicfit committed

from common import gajim
from common import helpers
nicfit's avatar
nicfit committed
from chat_control import ChatControl
from chat_control import ChatControlBase
from common.exceptions import GajimGeneralException
from command_system.implementation.hosts import PrivateChatCommands
from command_system.implementation.hosts import GroupChatCommands
import logging
log = logging.getLogger('gajim.groupchat_control')

#(status_image, type, nick, shown_nick)
(
C_IMG, # image to show state (online, new message etc)
C_NICK, # contact nickame or ROLE name
C_TYPE, # type of the row ('contact' or 'role')
C_TEXT, # text shown in the cellrenderer
Brendan Taylor's avatar
Brendan Taylor committed

Yann Leboulanger's avatar
Yann Leboulanger committed
def set_renderer_color(treeview, renderer, set_background=True):
    """
    Set style for group row, using PRELIGHT system color
    """
    if set_background:
        bgcolor = treeview.style.bg[gtk.STATE_PRELIGHT]
        renderer.set_property('cell-background-gdk', bgcolor)
    else:
        fgcolor = treeview.style.fg[gtk.STATE_PRELIGHT]
        renderer.set_property('foreground-gdk', fgcolor)
def tree_cell_data_func(column, renderer, model, iter_, tv=None):
    # cell data func is global, because we don't want it to keep
    # reference to GroupchatControl instance (self)
    theme = gajim.config.get('roster_theme')
    # allocate space for avatar only if needed
    parent_iter = model.iter_parent(iter_)
    if isinstance(renderer, gtk.CellRendererPixbuf):
        avatar_position = gajim.config.get('avatar_position_in_roster')
        if avatar_position == 'right':
            renderer.set_property('xalign', 1) # align pixbuf to the right
        else:
            renderer.set_property('xalign', 0.5)
        if parent_iter and (model[iter_][C_AVATAR] or avatar_position == \
        'left'):
            renderer.set_property('visible', True)
            renderer.set_property('width', gajim.config.get(
                'roster_avatar_width'))
        else:
            renderer.set_property('visible', False)
    if parent_iter:
        bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor')
        if bgcolor:
            renderer.set_property('cell-background', bgcolor)
        else:
            renderer.set_property('cell-background', None)
        if isinstance(renderer, gtk.CellRendererText):
            # foreground property is only with CellRendererText
            color = gajim.config.get_per('themes', theme, 'contacttextcolor')
            if color:
                renderer.set_property('foreground', color)
            else:
                renderer.set_property('foreground', None)
            renderer.set_property('font',
                gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
    else: # it is root (eg. group)
        bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor')
        if bgcolor:
            renderer.set_property('cell-background', bgcolor)
        else:
            set_renderer_color(tv, renderer)
        if isinstance(renderer, gtk.CellRendererText):
            # foreground property is only with CellRendererText
            color = gajim.config.get_per('themes', theme, 'grouptextcolor')
            if color:
                renderer.set_property('foreground', color)
            else:
                set_renderer_color(tv, renderer, False)
            renderer.set_property('font',
                gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
nicfit's avatar
nicfit committed
class PrivateChatControl(ChatControl):
    TYPE_ID = message_control.TYPE_PM

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

    def __init__(self, parent_win, gc_contact, contact, account, session):
        room_jid = gc_contact.room_jid
        room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
            account)
        if room_jid in gajim.interface.minimized_controls[account]:
            room_ctrl = gajim.interface.minimized_controls[account][room_jid]
        if room_ctrl:
            self.room_name = room_ctrl.name
        else:
            self.room_name = room_jid
        self.gc_contact = gc_contact
        ChatControl.__init__(self, parent_win, contact, account, session)
        self.TYPE_ID = 'pm'

    def send_message(self, message, xhtml=None, process_commands=True):
        """
        Call this method to send the message
        """
        message = helpers.remove_invalid_xml_chars(message)
        if not message:
            return

        # We need to make sure that we can still send through the room and that
        # the recipient did not go away
        contact = gajim.contacts.get_first_contact_from_jid(self.account,
                self.contact.jid)
        if not contact:
            # contact was from pm in MUC
            room, nick = gajim.get_room_and_nick_from_fjid(self.contact.jid)
            gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick)
            if not gc_contact:
                dialogs.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': room, 'nick': nick})
                return

        ChatControl.send_message(self, message, xhtml=xhtml,
            process_commands=process_commands)

    def update_ui(self):
        if self.contact.show == 'offline':
            self.got_disconnected()
        else:
            self.got_connected()
        ChatControl.update_ui(self)

    def update_contact(self):
        self.contact = self.gc_contact.as_contact()

    def begin_e2e_negotiation(self):
        self.no_autonegotiation = True

        if not self.session:
            fjid = self.gc_contact.get_full_jid()
            new_sess = gajim.connections[self.account].make_new_session(fjid,
                type_=self.type_id)
            self.set_session(new_sess)

        self.session.negotiate_e2e(False)
nicfit's avatar
nicfit committed

class GroupchatControl(ChatControlBase):
    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

    def __init__(self, parent_win, contact, acct, is_continued=False):
        ChatControlBase.__init__(self, self.TYPE_ID, parent_win,
            'groupchat_control', contact, acct)
        self.is_continued = is_continued
        self.is_anonymous = True

        # Controls the state of autorejoin.
        # None - autorejoin is neutral.
        # False - autorejoin is to be prevented (gets reset to initial state in
        #         got_connected()).
        # int - autorejoin is being active and working (gets reset to initial
        #       state in got_connected()).
        self.autorejoin = None

        # Keep error dialog instance to be sure to have only once at a time
        self.error_dialog = None

        self.actions_button = self.xml.get_object('muc_window_actions_button')
        id_ = self.actions_button.connect('clicked',
            self.on_actions_button_clicked)
        self.handlers[id_] = self.actions_button

        widget = self.xml.get_object('change_nick_button')
        id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate)
        self.handlers[id_] = widget

        widget = self.xml.get_object('change_subject_button')
        id_ = widget.connect('clicked',
            self._on_change_subject_menuitem_activate)
        self.handlers[id_] = widget

        widget = self.xml.get_object('bookmark_button')
        for bm in gajim.connections[self.account].bookmarks:
            if bm['jid'] == self.contact.jid:
                widget.hide()
                break
        else:
            id_ = widget.connect('clicked',
                self._on_bookmark_room_menuitem_activate)
            self.handlers[id_] = widget
            widget.show()

        widget = self.xml.get_object('list_treeview')
        id_ = widget.connect('row_expanded', self.on_list_treeview_row_expanded)
        self.handlers[id_] = widget

        id_ = widget.connect('row_collapsed',
            self.on_list_treeview_row_collapsed)
        self.handlers[id_] = widget

        id_ = widget.connect('row_activated',
            self.on_list_treeview_row_activated)
        self.handlers[id_] = widget

        id_ = widget.connect('button_press_event',
            self.on_list_treeview_button_press_event)
        self.handlers[id_] = widget

        id_ = widget.connect('key_press_event',
            self.on_list_treeview_key_press_event)
        self.handlers[id_] = widget

        id_ = widget.connect('motion_notify_event',
            self.on_list_treeview_motion_notify_event)
        self.handlers[id_] = widget

        id_ = widget.connect('leave_notify_event',
            self.on_list_treeview_leave_notify_event)
        self.handlers[id_] = widget

        self.room_jid = self.contact.jid
        self.nick = contact.name.decode('utf-8')
        self.new_nick = ''
        self.name = ''
        for bm in gajim.connections[self.account].bookmarks:
            if bm['jid'] == self.room_jid:
                self.name = bm['name']
                break
        if not self.name:
            self.name = self.room_jid.split('@')[0]

        compact_view = gajim.config.get('compact_view')
        self.chat_buttons_set_visible(compact_view)
        self.widget_set_visible(self.xml.get_object('banner_eventbox'),
            gajim.config.get('hide_groupchat_banner'))
        self.widget_set_visible(self.xml.get_object('list_scrolledwindow'),
            gajim.config.get('hide_groupchat_occupants_list'))

        self._last_selected_contact = None # None or holds jid, account tuple

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

        # sorted list of nicks who mentioned us (last at the end)
        self.attention_list = []
        self.room_creation = int(time.time()) # Use int to reduce mem usage
        self.nick_hits = []
        self.last_key_tabs = False

        self.subject = ''

        self.tooltip = tooltips.GCTooltip()

        # nickname coloring
        self.gc_count_nicknames_colors = 0
        self.gc_custom_colors = {}
        self.number_of_colors = len(gajim.config.get('gc_nicknames_colors').\
            split(':'))

        self.name_label = self.xml.get_object('banner_name_label')
        self.event_box = self.xml.get_object('banner_eventbox')

        # set the position of the current hpaned
        hpaned_position = gajim.config.get('gc-hpaned-position')
        self.hpaned = self.xml.get_object('hpaned')
        self.hpaned.set_position(hpaned_position)

        self.list_treeview = self.xml.get_object('list_treeview')
        selection = self.list_treeview.get_selection()
        id_ = selection.connect('changed',
            self.on_list_treeview_selection_changed)
        self.handlers[id_] = selection
        id_ = self.list_treeview.connect('style-set',
            self.on_list_treeview_style_set)
        self.handlers[id_] = self.list_treeview
        self.resize_from_another_muc = False
        # we want to know when the the widget resizes, because that is
        # an indication that the hpaned has moved...
        # FIXME: Find a better indicator that the hpaned has moved.
        id_ = self.list_treeview.connect('size-allocate',
            self.on_treeview_size_allocate)
        self.handlers[id_] = self.list_treeview
        #status_image, shown_nick, type, nickname, avatar
        store = gtk.TreeStore(gtk.Image, str, str, str, gtk.gdk.Pixbuf)
        store.set_sort_func(C_NICK, self.tree_compare_iters)
        store.set_sort_column_id(C_NICK, gtk.SORT_ASCENDING)
        self.list_treeview.set_model(store)

        # columns

        # this col has 3 cells:
        # first one img, second one text, third is sec pixbuf
        column = gtk.TreeViewColumn()

        def add_avatar_renderer():
            renderer_pixbuf = gtk.CellRendererPixbuf() # avatar image
            column.pack_start(renderer_pixbuf, expand=False)
            column.add_attribute(renderer_pixbuf, 'pixbuf', C_AVATAR)
            column.set_cell_data_func(renderer_pixbuf, tree_cell_data_func,
                self.list_treeview)

        if gajim.config.get('avatar_position_in_roster') == 'left':
            add_avatar_renderer()

        # status img
        renderer_image = cell_renderer_image.CellRendererImage(0, 0)
        renderer_image.set_property('width', 26)
        column.pack_start(renderer_image, expand=False)
        column.add_attribute(renderer_image, 'image', C_IMG)
        column.set_cell_data_func(renderer_image, tree_cell_data_func,
            self.list_treeview)

        renderer_text = gtk.CellRendererText() # nickname
        column.pack_start(renderer_text, expand=True)
        column.add_attribute(renderer_text, 'markup', C_TEXT)
        renderer_text.set_property("ellipsize", pango.ELLIPSIZE_END)
        column.set_cell_data_func(renderer_text, tree_cell_data_func,
            self.list_treeview)

        if gajim.config.get('avatar_position_in_roster') == 'right':
            add_avatar_renderer()

        self.list_treeview.append_column(column)

        # workaround to avoid gtk arrows to be shown
        column = gtk.TreeViewColumn() # 2nd COLUMN
        renderer = gtk.CellRendererPixbuf()
        column.pack_start(renderer, expand=False)
        self.list_treeview.append_column(column)
        column.set_visible(False)
        self.list_treeview.set_expander_column(column)

        self.setup_seclabel(self.xml.get_object('label_selector'))

        gajim.gc_connected[self.account][self.room_jid] = False
        # disable win, we are not connected yet
        ChatControlBase.got_disconnected(self)

        self.update_ui()
        self.conv_textview.tv.grab_focus()
        self.widget.show_all()

    def tree_compare_iters(self, model, iter1, iter2):
        """
        Compare two iters to sort them
        """
        type1 = model[iter1][C_TYPE]
        type2 = model[iter2][C_TYPE]
        if not type1 or not type2:
            return 0
        nick1 = model[iter1][C_NICK]
        nick2 = model[iter2][C_NICK]
        if not nick1 or not nick2:
            return 0
        nick1 = nick1.decode('utf-8')
        nick2 = nick2.decode('utf-8')
        if type1 == 'role':
            return locale.strcoll(nick1, nick2)
        if type1 == 'contact':
            gc_contact1 = gajim.contacts.get_gc_contact(self.account,
                    self.room_jid, nick1)
            if not gc_contact1:
                return 0
        if type2 == 'contact':
            gc_contact2 = gajim.contacts.get_gc_contact(self.account,
                    self.room_jid, nick2)
            if not gc_contact2:
                return 0
        if type1 == 'contact' and type2 == 'contact' and \
        gajim.config.get('sort_by_show_in_muc'):
            cshow = {'chat':0, 'online': 1, 'away': 2, 'xa': 3, 'dnd': 4,
                'invisible': 5, 'offline': 6, 'error': 7}
            show1 = cshow[gc_contact1.show]
            show2 = cshow[gc_contact2.show]
            if show1 < show2:
                return -1
            elif show1 > show2:
                return 1
        # We compare names
        name1 = gc_contact1.get_shown_name()
        name2 = gc_contact2.get_shown_name()
        return locale.strcoll(name1.lower(), name2.lower())

    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)
        item = gtk.SeparatorMenuItem()
        menu.prepend(item)

        item = gtk.MenuItem(_('Insert Nickname'))
        menu.prepend(item)
        submenu = gtk.Menu()
        item.set_submenu(submenu)

        for nick in sorted(gajim.contacts.get_nick_list(self.account,
        self.room_jid)):
            item = gtk.MenuItem(nick, use_underline=False)
            submenu.append(item)
            id_ = item.connect('activate', self.append_nick_in_msg_textview,
                nick)
            self.handlers[id_] = item

        menu.show_all()

    def resize_occupant_treeview(self, position):
        self.resize_from_another_muc = True
        self.hpaned.set_position(position)
        def reset_flag():
            self.resize_from_another_muc = False
        # Reset the flag when everything will be redrawn, and in particular when
        # on_treeview_size_allocate will have been called.
        gobject.idle_add(reset_flag)

    def on_treeview_size_allocate(self, widget, allocation):
        """
        The MUC treeview has resized. Move the hpaned in all tabs to match
        """
        if self.resize_from_another_muc:
            # Don't send the event to other MUC
            return
        hpaned_position = self.hpaned.get_position()
        for account in gajim.gc_connected:
            for room_jid in [i for i in gajim.gc_connected[account] if \
            gajim.gc_connected[account][i] and i != self.room_jid]:
                ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
                    account)
                if not ctrl and room_jid in \
                gajim.interface.minimized_controls[account]:
                    ctrl = gajim.interface.minimized_controls[account][room_jid]
                if ctrl:
                    ctrl.resize_occupant_treeview(hpaned_position)

    def iter_contact_rows(self):
        """
        Iterate over all contact rows in the tree model
        """
        model = self.list_treeview.get_model()
        role_iter = model.get_iter_root()
        while role_iter:
            contact_iter = model.iter_children(role_iter)
            while contact_iter:
                yield model[contact_iter]
                contact_iter = model.iter_next(contact_iter)
            role_iter = model.iter_next(role_iter)

    def on_list_treeview_style_set(self, treeview, style):
        """
        When style (theme) changes, redraw all contacts
        """
        # Get the room_jid from treeview
        for contact in self.iter_contact_rows():
            nick = contact[C_NICK].decode('utf-8')
            self.draw_contact(nick)

    def on_list_treeview_selection_changed(self, selection):
        model, selected_iter = selection.get_selected()
        self.draw_contact(self.nick)
        if self._last_selected_contact is not None:
            self.draw_contact(self._last_selected_contact)
        if selected_iter is None:
            self._last_selected_contact = None
            return
        contact = model[selected_iter]
        nick = contact[C_NICK].decode('utf-8')
        self._last_selected_contact = nick
        if contact[C_TYPE] != 'contact':
            return
        self.draw_contact(nick, selected=True, focus=True)

    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_name = None
        color = None
        theme = gajim.config.get('roster_theme')
        if chatstate == 'attention' and (not has_focus or not current_tab):
            self.attention_flag = True
            color_name = gajim.config.get_per('themes', theme,
                                            'state_muc_directed_msg_color')
        elif chatstate:
            if chatstate == 'active' or (current_tab and has_focus):
                self.attention_flag = False
                # get active color from gtk
                color = self.parent_win.notebook.style.fg[gtk.STATE_ACTIVE]
            elif chatstate == 'newmsg' and (not has_focus or not current_tab) \
            and not self.attention_flag:
                color_name = gajim.config.get_per('themes', theme,
                        'state_muc_msg_color')
        if color_name:
            color = gtk.gdk.colormap_get_system().alloc_color(color_name)

        if self.is_continued:
            # if this is a continued conversation
            label_str = self.get_continued_conversation_name()
        else:
            label_str = self.name

        # count waiting highlighted messages
        unread = ''
        num_unread = self.get_nb_unread()
        if num_unread == 1:
            unread = '*'
        elif num_unread > 1:
            unread = '[' + unicode(num_unread) + ']'
        label_str = unread + label_str
        return (label_str, color)

    def get_tab_image(self, count_unread=True):
        # Set tab image (always 16x16)
        tab_image = None
        if gajim.gc_connected[self.account][self.room_jid]:
            tab_image = gtkgui_helpers.load_icon('muc_active')
        else:
            tab_image = gtkgui_helpers.load_icon('muc_inactive')
        return tab_image

    def update_ui(self):
        ChatControlBase.update_ui(self)
        for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
            self.draw_contact(nick)

    def _change_style(self, model, path, iter_):
        model[iter_][C_NICK] = model[iter_][C_NICK]

    def change_roster_style(self):
        model = self.list_treeview.get_model()
        model.foreach(self._change_style)

    def repaint_themed_widgets(self):
        ChatControlBase.repaint_themed_widgets(self)
        self.change_roster_style()

    def _update_banner_state_image(self):
        banner_status_img = self.xml.get_object('gc_banner_status_image')
        images = gajim.interface.jabber_state_images
        if self.room_jid in gajim.gc_connected[self.account] and \
        gajim.gc_connected[self.account][self.room_jid]:
            image = 'muc_active'
        else:
            image = 'muc_inactive'
        if '32' in images and image in images['32']:
            muc_icon = images['32'][image]
            if muc_icon.get_storage_type() != gtk.IMAGE_EMPTY:
                pix = muc_icon.get_pixbuf()
                banner_status_img.set_from_pixbuf(pix)
                return
        # we need to scale 16x16 to 32x32
        muc_icon = images['16'][image]
        pix = muc_icon.get_pixbuf()
        scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR)
        banner_status_img.set_from_pixbuf(scaled_pix)

    def get_continued_conversation_name(self):
        """
        Get the name of a continued conversation.  Will return Continued
        Conversation if there isn't any other contact in the room
        """
        nicks = []
        for nick in gajim.contacts.get_nick_list(self.account,
        self.room_jid):
            if nick != self.nick:
                nicks.append(nick)
        if nicks != []:
            title = ', '
            title = _('Conversation with ') + title.join(nicks)
        else:
            title = _('Continued conversation')
        return title

    def draw_banner_text(self):
        """
        Draw the text in the fat line at the top of the window that houses the
        room jid, subject
        """
        self.name_label.set_ellipsize(pango.ELLIPSIZE_END)
        self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END)
        font_attrs, font_attrs_small = self.get_font_attrs()
        if self.is_continued:
            name = self.get_continued_conversation_name()
        else:
            name = self.room_jid
        text = '<span %s>%s</span>' % (font_attrs, name)
        self.name_label.set_markup(text)

        if self.subject:
            subject = helpers.reduce_chars_newlines(self.subject, max_lines=2)
            subject = gobject.markup_escape_text(subject)
            subject_text = self.urlfinder.sub(self.make_href, subject)
            subject_text = '<span %s>%s</span>' % (font_attrs_small,
                subject_text)

            # tooltip must always hold ALL the subject
            self.event_box.set_tooltip_text(self.subject)
            self.banner_status_label.set_no_show_all(False)
            self.banner_status_label.show()
        else:
            subject_text = ''
            self.event_box.set_has_tooltip(False)
            self.banner_status_label.hide()
            self.banner_status_label.set_no_show_all(True)

        self.banner_status_label.set_markup(subject_text)

    def prepare_context_menu(self, hide_buttonbar_items=False):
        """
        Set sensitivity state for configure_room
        """
        xml = gtkgui_helpers.get_gtk_builder('gc_control_popup_menu.ui')
        menu = xml.get_object('gc_control_popup_menu')

        bookmark_room_menuitem = xml.get_object('bookmark_room_menuitem')
        change_nick_menuitem = xml.get_object('change_nick_menuitem')
        configure_room_menuitem = xml.get_object('configure_room_menuitem')
        destroy_room_menuitem = xml.get_object('destroy_room_menuitem')
        change_subject_menuitem = xml.get_object('change_subject_menuitem')
        history_menuitem = xml.get_object('history_menuitem')
        minimize_menuitem = xml.get_object('minimize_menuitem')
        bookmark_separator = xml.get_object('bookmark_separator')
        separatormenuitem2 = xml.get_object('separatormenuitem2')

        if hide_buttonbar_items:
            change_nick_menuitem.hide()
            change_subject_menuitem.hide()
            bookmark_room_menuitem.hide()
            history_menuitem.hide()
            bookmark_separator.hide()
            separatormenuitem2.hide()
        else:
            change_nick_menuitem.show()
            change_subject_menuitem.show()
            bookmark_room_menuitem.show()
            history_menuitem.show()
            bookmark_separator.show()
            separatormenuitem2.show()
            for bm in gajim.connections[self.account].bookmarks:
                if bm['jid'] == self.room_jid:
                    bookmark_room_menuitem.hide()
                    bookmark_separator.hide()
                    break

        ag = gtk.accel_groups_from_object(self.parent_win.window)[0]
        change_nick_menuitem.add_accelerator('activate', ag, gtk.keysyms.n,
            gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK, gtk.ACCEL_VISIBLE)
        change_subject_menuitem.add_accelerator('activate', ag,
            gtk.keysyms.t, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE)
        bookmark_room_menuitem.add_accelerator('activate', ag, gtk.keysyms.b,
            gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
        history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h,
            gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)

        if self.contact.jid in gajim.config.get_per('accounts', self.account,
        'minimized_gc').split(' '):
            minimize_menuitem.set_active(True)
        if not gajim.connections[self.account].private_storage_supported:
            bookmark_room_menuitem.set_sensitive(False)
        if gajim.gc_connected[self.account][self.room_jid]:
            c = gajim.contacts.get_gc_contact(self.account, self.room_jid,
            if c.affiliation not in ('owner', 'admin'):
                configure_room_menuitem.set_sensitive(False)
            else:
                configure_room_menuitem.set_sensitive(True)
            if c.affiliation != 'owner':
                destroy_room_menuitem.set_sensitive(False)
            else:
                destroy_room_menuitem.set_sensitive(True)
            change_subject_menuitem.set_sensitive(True)
            change_nick_menuitem.set_sensitive(True)
        else:
            # We are not connected to this groupchat, disable unusable menuitems
            configure_room_menuitem.set_sensitive(False)
            destroy_room_menuitem.set_sensitive(False)
            change_subject_menuitem.set_sensitive(False)
            change_nick_menuitem.set_sensitive(False)

        # connect the menuitems to their respective functions
        id_ = bookmark_room_menuitem.connect('activate',
            self._on_bookmark_room_menuitem_activate)
        self.handlers[id_] = bookmark_room_menuitem

        id_ = change_nick_menuitem.connect('activate',
            self._on_change_nick_menuitem_activate)
        self.handlers[id_] = change_nick_menuitem

        id_ = configure_room_menuitem.connect('activate',
            self._on_configure_room_menuitem_activate)
        self.handlers[id_] = configure_room_menuitem

        id_ = destroy_room_menuitem.connect('activate',
            self._on_destroy_room_menuitem_activate)
        self.handlers[id_] = destroy_room_menuitem

        id_ = change_subject_menuitem.connect('activate',
            self._on_change_subject_menuitem_activate)
        self.handlers[id_] = change_subject_menuitem

        id_ = history_menuitem.connect('activate',
            self._on_history_menuitem_activate)
        self.handlers[id_] = history_menuitem

        id_ = minimize_menuitem.connect('toggled',
            self.on_minimize_menuitem_toggled)
        self.handlers[id_] = minimize_menuitem

        menu.connect('selection-done', self.destroy_menu,
            change_nick_menuitem, change_subject_menuitem,
            bookmark_room_menuitem, history_menuitem)
        return menu

    def destroy_menu(self, menu, change_nick_menuitem, change_subject_menuitem,
    bookmark_room_menuitem, history_menuitem):
        # destroy accelerators
        ag = gtk.accel_groups_from_object(self.parent_win.window)[0]
        change_nick_menuitem.remove_accelerator(ag, gtk.keysyms.n,
            gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK)
        change_subject_menuitem.remove_accelerator(ag, gtk.keysyms.t,
            gtk.gdk.MOD1_MASK)
        bookmark_room_menuitem.remove_accelerator(ag, gtk.keysyms.b,
            gtk.gdk.CONTROL_MASK)
        history_menuitem.remove_accelerator(ag, gtk.keysyms.h,
            gtk.gdk.CONTROL_MASK)
        # destroy menu
        menu.destroy()

    def on_message(self, nick, msg, tim, has_timestamp=False, xhtml=None,
    status_code=[], displaymarking=None, captcha=None):
        if captcha:
            dataform = dataforms.ExtendForm(node=captcha)
            self.form_widget = dataforms_widget.DataFormWidget(dataform)
            self.form_widget.show_all()
            vbox = self.xml.get_object('gc_textviews_vbox')
            vbox.pack_start(self.form_widget, expand=False, fill=False)

            def on_send_dataform_clicked(widget):
                if not self.form_widget:
                    return
                form_node = self.form_widget.data_form.get_purged()
                form_node.type = 'submit'
                gajim.connections[self.account].send_captcha(self.room_jid,
                    form_node)
                self.form_widget.hide()
                self.form_widget.destroy()
                self.btn_box.destroy()
                del self.form_widget
                del self.btn_box

            valid_button = gtk.Button(stock=gtk.STOCK_OK)
            valid_button.connect('clicked', on_send_dataform_clicked)
            self.btn_box = gtk.HButtonBox()
            self.btn_box.set_layout(gtk.BUTTONBOX_END)
            self.btn_box.pack_start(valid_button)
            self.btn_box.show_all()
            vbox.pack_start(self.btn_box, expand=False, fill=False)
            if self.parent_win:
                self.parent_win.redraw_tab(self, 'attention')
            else:
                self.attention_flag = True
            helpers.play_sound('muc_message_received')
        if '100' in status_code:
            # Room is not anonymous
            self.is_anonymous = False
        if not nick:
            # message from server
            self.print_conversation(msg, tim=tim, xhtml=xhtml, displaymarking=displaymarking)
        else:
            # message from someone
            if has_timestamp:
                # don't print xhtml if it's an old message.
                # Like that xhtml messages are grayed too.
                self.print_old_conversation(msg, nick, tim, None, displaymarking=displaymarking)
                self.print_conversation(msg, nick, tim, xhtml, displaymarking=displaymarking)

    def on_private_message(self, nick, msg, tim, xhtml, session, msg_id=None,
    encrypted=False, displaymarking=None):
        # Do we have a queue?
        fjid = self.room_jid + '/' + nick
        no_queue = len(gajim.events.get_events(self.account, fjid)) == 0

        event = gajim.events.create_event('pm', (msg, '', 'incoming', tim,
            encrypted, '', msg_id, xhtml, session, displaymarking))
        gajim.events.add_event(self.account, fjid, event)

        autopopup = gajim.config.get('autopopup')
        autopopupaway = gajim.config.get('autopopupaway')
        iter_ = self.get_contact_iter(nick)
        path = self.list_treeview.get_model().get_path(iter_)
        if not autopopup or (not autopopupaway and \
        gajim.connections[self.account].connected > 2):
            if no_queue: # We didn't have a queue: we change icons
                model = self.list_treeview.get_model()
                state_images = \
                    gajim.interface.roster.get_appropriate_state_images(
                    self.room_jid, icon_name='event')
                image = state_images['event']
                model[iter_][C_IMG] = image
            if self.parent_win:
                self.parent_win.show_title()
                self.parent_win.redraw_tab(self)
        else:
            self._start_private_message(nick)
        # Scroll to line
        self.list_treeview.expand_row(path[0:1], False)
        self.list_treeview.scroll_to_cell(path)
        self.list_treeview.set_cursor(path)
        contact = gajim.contacts.get_contact_with_highest_priority(
            self.account, self.room_jid)
        if contact:
            gajim.interface.roster.draw_contact(self.room_jid, self.account)

    def get_contact_iter(self, nick):
        model = self.list_treeview.get_model()
        role_iter = model.get_iter_root()
        while role_iter:
            user_iter = model.iter_children(role_iter)
            while user_iter:
                if nick == model[user_iter][C_NICK].decode('utf-8'):
                    return user_iter
                else:
                    user_iter = model.iter_next(user_iter)
            role_iter = model.iter_next(role_iter)
        return None

    def print_old_conversation(self, text, contact='', tim=None, xhtml = None,
        displaymarking=None):
        if isinstance(text, str):
            text = unicode(text, 'utf-8')
        if contact:
            if contact == self.nick: # it's us
                kind = 'outgoing'
            else:
                kind = 'incoming'
        else:
            kind = 'status'
        if gajim.config.get('restored_messages_small'):
            small_attr = ['small']
        else:
            small_attr = []
        ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
            small_attr, small_attr + ['restored_message'],
            small_attr + ['restored_message'], count_as_new=False, xhtml=xhtml,
            displaymarking=displaymarking)

    def print_conversation(self, text, contact='', tim=None, xhtml=None,
        """
        Print a line in the conversation

        If contact is set: it's a message from someone or an info message
        (contact = 'info' in such a case).
        If contact is not set: it's a message from the server or help.
        """
        if isinstance(text, str):
            text = unicode(text, 'utf-8')
        other_tags_for_name = []
        other_tags_for_text = []
        if contact:
            if contact == self.nick: # it's us
                kind = 'outgoing'
            elif contact == 'info':
                kind = 'info'
                contact = None
            else:
                kind = 'incoming'
                # muc-specific chatstate
                if self.parent_win:
                    self.parent_win.redraw_tab(self, 'newmsg')
        else:
            kind = 'status'

        if kind == 'incoming': # it's a message NOT from us
            # highlighting and sounds
            (highlight, sound) = self.highlighting_for_message(text, tim)
            if contact in self.gc_custom_colors:
                other_tags_for_name.append('gc_nickname_color_' + \
                    str(self.gc_custom_colors[contact]))
            else:
                self.gc_count_nicknames_colors += 1
                if self.gc_count_nicknames_colors == self.number_of_colors:
                    self.gc_count_nicknames_colors = 0
                self.gc_custom_colors[contact] = \
                    self.gc_count_nicknames_colors
                other_tags_for_name.append('gc_nickname_color_' + \
                    str(self.gc_count_nicknames_colors))
            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')

                if contact in self.attention_list:
                    self.attention_list.remove(contact)
                elif len(self.attention_list) > 6:
                    self.attention_list.pop(0) # remove older
                self.attention_list.append(contact)

            if sound == 'received':
                helpers.play_sound('muc_message_received')
            elif sound == 'highlight':
                helpers.play_sound('muc_message_highlight')
            if text.startswith('/me ') or text.startswith('/me\n'):
                other_tags_for_text.append('gc_nickname_color_' + \
                    str(self.gc_custom_colors[contact]))

            self.check_and_possibly_add_focus_out_line()

        ChatControlBase.print_conversation_line(self, text, kind, contact, tim,
            other_tags_for_name, [], other_tags_for_text, xhtml=xhtml,
            graphics=graphics, displaymarking=displaymarking)

    def get_nb_unread(self):
        type_events = ['printed_marked_gc_msg']
        if gajim.config.get('notify_on_all_muc_messages'):
            type_events.append('printed_gc_msg')
        nb = len(gajim.events.get_events(self.account, self.room_jid,
            type_events))
        nb += self.get_nb_unread_pm()
        return nb

    def get_nb_unread_pm(self):
        nb = 0
        for nick in gajim.contacts.get_nick_list(self.account, self.room_jid):
            nb += len(gajim.events.get_events(self.account, self.room_jid + \
                '/' + nick, ['pm']))
        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
        """