Skip to content
Snippets Groups Projects
groupchat_control.py 121 KiB
Newer Older
roidelapluie's avatar
roidelapluie committed
# -*- coding:utf-8 -*-
roidelapluie's avatar
roidelapluie committed
## src/groupchat_control.py
Yann Leboulanger's avatar
Yann Leboulanger committed
## Copyright (C) 2003-2013 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
from common import ged
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
from common.connection_handlers_events import GcMessageOutgoingEvent
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
        self.room_ctrl = gajim.interface.msg_win_mgr.get_gc_control(room_jid,
        if room_jid in gajim.interface.minimized_controls[account]:
            self.room_ctrl = gajim.interface.minimized_controls[account][room_jid]
        if self.room_ctrl:
            self.room_name = self.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'
        gajim.ged.register_event_handler('caps-received', ged.GUI1,
        gajim.ged.register_event_handler('gc-presence-received', ged.GUI1,
            self._nec_gc_presence_received)
    def get_our_nick(self):
        return self.room_ctrl.nick

    def shutdown(self):
        super(PrivateChatControl, self).shutdown()
        gajim.ged.remove_event_handler('caps-received', ged.GUI1,
        gajim.ged.remove_event_handler('gc-presence-received', ged.GUI1,
            self._nec_gc_presence_received)
    def _nec_caps_received_pm(self, obj):
        if obj.conn.name != self.account or \
        obj.fjid != self.gc_contact.get_full_jid():
            return
        self.update_contact()

    def _nec_gc_presence_received(self, obj):
        if obj.conn.name != self.account:
            return
        if obj.fjid != self.full_jid:
            return
        if '303' in obj.status_code:
            self.print_conversation(_('%(nick)s is now known as '
                '%(new_nick)s') % {'nick': obj.nick, 'new_nick': obj.new_nick},
                'status')
            gc_c = gajim.contacts.get_gc_contact(obj.conn.name, obj.room_jid,
                obj.new_nick)
            c = gc_c.as_contact()
            self.gc_contact = gc_c
            self.contact = c
            if self.session:
                # stop e2e
                if self.session.enable_encryption:
                    thread_id = self.session.thread_id
                    self.session.terminate_e2e()
                    obj.conn.delete_session(obj.fjid, thread_id)
                    self.no_autonegotiation = False
            self.draw_banner()
            old_jid = obj.room_jid + '/' + obj.nick
            new_jid = obj.room_jid + '/' + obj.new_nick
            gajim.interface.msg_win_mgr.change_key(old_jid, new_jid,
                obj.conn.name)
        else:
            self.contact.show = obj.show
            self.contact.status = obj.status
            self.gc_contact.show = obj.show
            self.gc_contact.status = obj.status
            uf_show = helpers.get_uf_show(obj.show)
            self.print_conversation(_('%(nick)s is now %(status)s') % {
                    'nick': obj.nick, 'status': uf_show}, 'status')
            if obj.status:
                self.print_conversation(' (', 'status', simple=True)
                self.print_conversation('%s' % (obj.status), 'status',
                    simple=True)
                self.print_conversation(')', 'status', simple=True)
            self.parent_win.redraw_tab(self)
            self.update_ui()

    def send_message(self, message, xhtml=None, process_commands=True,
    attention=False):
        """
        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': u'\u200E' + room,
                    'nick': nick})
                return

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

    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

    def prepare_context_menu(self, hide_buttonbar_items=False):
        """
        Set compact view menuitem active state sets active and sensitivity state
        for toggle_gpg_menuitem sets sensitivity for history_menuitem (False for
        tranasports) and file_transfer_menuitem and hide()/show() for
        add_to_roster_menuitem
        """
        menu = gui_menu_builder.get_contact_menu(self.contact, self.account,
            use_multiple_contacts=False, show_start_chat=False,
            show_encryption=True, control=self,
            show_buttonbar_items=not hide_buttonbar_items,
            gc_contact=self.gc_contact,
            is_anonymous=self.room_ctrl.is_anonymous)
    def got_disconnected(self):
        ChatControl.got_disconnected(self)

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
        send_button = self.xml.get_object('send_button')
        send_button.set_sensitive(False)
        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')
        widget.set_sensitive(False)
        id_ = widget.connect('clicked', self._on_change_nick_menuitem_activate)
        self.handlers[id_] = widget

        widget = self.xml.get_object('change_subject_button')
        widget.set_sensitive(False)
        id_ = widget.connect('clicked',
            self._on_change_subject_menuitem_activate)
        formattings_button = self.xml.get_object('formattings_button')
        formattings_button.set_sensitive(False)

        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
            if gtkgui_helpers.gtk_icon_theme.has_icon('bookmark-new'):
                img = self.xml.get_object('image7')
                img.set_from_icon_name('bookmark-new', gtk.ICON_SIZE_MENU)
            widget.set_sensitive(
                gajim.connections[self.account].private_storage_supported or \
                (gajim.connections[self.account].pubsub_supported and \
                gajim.connections[self.account].pubsub_publish_options_supported))
            widget.show()
        if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
Dicson's avatar
Dicson committed
            img = self.xml.get_object('image8')
            img.set_from_icon_name('document-open-recent', gtk.ICON_SIZE_MENU)
        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 = -1
        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')

        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...
        self.hpaned = self.xml.get_object('hpaned')
        id_ = self.hpaned.connect('notify', self.on_hpaned_notify)
        self.handlers[id_] = self.hpaned

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

        #status_image, shown_nick, type, nickname, avatar
        self.columns = [gtk.Image, str, str, str, gtk.gdk.Pixbuf]
        self.model = gtk.TreeStore(*self.columns)
        self.model.set_sort_func(C_NICK, self.tree_compare_iters)
        self.model.set_sort_column_id(C_NICK, gtk.SORT_ASCENDING)

        # columns
        column = gtk.TreeViewColumn()
        # list of renderers with attributes / properties in the form:
        # (name, renderer_object, expand?, attribute_name, attribute_value,
        # cell_data_func, func_arg)
        self.renderers_list = []
        # Number of renderers plugins added
        self.nb_ext_renderers = 0
        self.renderers_propertys = {}
        renderer_image = cell_renderer_image.CellRendererImage(0, 0)
        self.renderers_propertys[renderer_image] = ('width', 26)
        renderer_text = gtk.CellRendererText()
        self.renderers_propertys[renderer_text] = ('ellipsize',
            pango.ELLIPSIZE_END)

        self.renderers_list += (
            # status img
            ('icon', renderer_image, False,
            'image', C_IMG, tree_cell_data_func, self.list_treeview),
            # contact name
            ('name', renderer_text, True,
            'markup', C_TEXT, tree_cell_data_func, self.list_treeview))

        # avatar img
        avater_renderer = ('avatar', gtk.CellRendererPixbuf(),
            False, 'pixbuf', C_AVATAR,
            tree_cell_data_func, self.list_treeview)

        if gajim.config.get('avatar_position_in_roster') == 'right':
            self.renderers_list.append(avater_renderer)
        else:
            self.renderers_list.insert(0, avater_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.ged.register_event_handler('gc-presence-received', ged.GUI1,
            self._nec_gc_presence_received)
        gajim.ged.register_event_handler('gc-message-received', ged.GUI1,
            self._nec_gc_message_received)
        gajim.ged.register_event_handler('vcard-published', ged.GUI1,
            self._nec_vcard_published)
        gajim.ged.register_event_handler('vcard-received', ged.GUI1,
            self._nec_vcard_received)
        gajim.ged.register_event_handler('gc-subject-received', ged.GUI1,
            self._nec_gc_subject_received)
        gajim.ged.register_event_handler('gc-config-changed-received', ged.GUI1,
            self._nec_gc_config_changed_received)
        gajim.ged.register_event_handler('signed-in', ged.GUI1,
            self._nec_signed_in)
        gajim.ged.register_event_handler('decrypted-message-received', ged.GUI2,
            self._nec_decrypted_message_received)
        gajim.gc_connected[self.account][self.room_jid] = False
        # disable win, we are not connected yet
        ChatControlBase.got_disconnected(self)

        self.update_ui()
        self.widget.show_all()

        # PluginSystem: adding GUI extension point for this GroupchatControl
        # instance object
        gajim.plugin_manager.gui_extension_point('groupchat_control', self)

    def fill_column(self, col):
        for rend in self.renderers_list:
            col.pack_start(rend[1], expand=rend[2])
            col.add_attribute(rend[1], rend[3], rend[4])
            col.set_cell_data_func(rend[1], rend[5], rend[6])
        # set renderers propertys
        for renderer in self.renderers_propertys.keys():
            renderer.set_property(self.renderers_propertys[renderer][0],
                self.renderers_propertys[renderer][1])

    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_hpaned_notify(self, pane, gparamspec):
        """
        The MUC treeview has resized. Move the hpaned in all tabs to match
        """
        if gparamspec.name != 'position':
            return
        if self.resize_from_another_muc:
            # Don't send the event to other MUC
            return
        hpaned_position = self.hpaned.get_position()
        gajim.config.set('gc-hpaned-position', hpaned_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 and gajim.config.get('one_message_window') != 'never':
                    ctrl.resize_occupant_treeview(hpaned_position)

    def iter_contact_rows(self):
        """
        Iterate over all contact rows in the tree model
        """
            contact_iter = self.model.iter_children(role_iter)
                yield self.model[contact_iter]
                contact_iter = self.model.iter_next(contact_iter)
            role_iter = self.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):

    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, u'\u200E' + 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')
        request_voice_menuitem = xml.get_object('request_voice_menuitem')
        bookmark_separator = xml.get_object('bookmark_separator')
        separatormenuitem2 = xml.get_object('separatormenuitem2')
        request_voice_separator = xml.get_object('request_voice_separator')
        if gtkgui_helpers.gtk_icon_theme.has_icon('bookmark-new'):
            img = gtk.Image()
            img.set_from_icon_name('bookmark-new', gtk.ICON_SIZE_MENU)
            bookmark_room_menuitem.set_image(img)
        if gtkgui_helpers.gtk_icon_theme.has_icon('document-open-recent'):
            img = gtk.Image()
            img.set_from_icon_name('document-open-recent', gtk.ICON_SIZE_MENU)
            history_menuitem.set_image(img)
        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)
        conn = gajim.connections[self.account]
        if not conn.private_storage_supported and (not conn.pubsub_supported or \
        not conn.pubsub_publish_options_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)
            if c.role == 'visitor':
                request_voice_menuitem.set_sensitive(True)
            else:
                request_voice_menuitem.set_sensitive(False)
        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)
            request_voice_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_ = request_voice_menuitem.connect('activate',
            self._on_request_voice_menuitem_activate)
        self.handlers[id_] = request_voice_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)
    def _nec_vcard_published(self, obj):
        if obj.conn.name != self.account:
            return
        show = gajim.SHOW_LIST[obj.conn.connected]
        status = obj.conn.status
        obj.conn.send_gc_status(self.nick, self.room_jid, show, status)

    def _nec_vcard_received(self, obj):
        if obj.conn.name != self.account:
            return
        if obj.jid != self.room_jid:
            return
        self.draw_avatar(obj.resource)

    def _nec_gc_message_received(self, obj):
        if obj.room_jid != self.room_jid or obj.conn.name != self.account:
            return
        if obj.captcha_form:
            if self.form_widget:
                self.form_widget.hide()
                self.form_widget.destroy()
                self.btn_box.destroy()
            dataform = dataforms.ExtendForm(node=obj.captcha_form)
            self.form_widget = dataforms_widget.DataFormWidget(dataform)

            def on_send_dataform_clicked(widget):
                if not self.form_widget:
                    return
                form_node = self.form_widget.data_form.get_purged()
Yann Leboulanger's avatar
Yann Leboulanger committed
                form_node.type_ = 'submit'
                obj.conn.send_captcha(self.room_jid, form_node)
                self.form_widget.hide()
                self.form_widget.destroy()
                self.btn_box.destroy()
            self.form_widget.connect('validated', on_send_dataform_clicked)
            self.form_widget.show_all()
            vbox = self.xml.get_object('gc_textviews_vbox')
            vbox.pack_start(self.form_widget, expand=False, fill=False)

            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: