Skip to content
Snippets Groups Projects
roster_window.py 276 KiB
Newer Older
# -*- coding: utf-8 -*-
roidelapluie's avatar
roidelapluie committed
## src/roster_window.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 Alex Mauer <hawke AT hawkesnest.net>
roidelapluie's avatar
roidelapluie committed
##                    Stéphan Kochen <stephan AT kochen.nl>
roidelapluie's avatar
roidelapluie committed
## Copyright (C) 2005-2006 Dimitur Kirov <dkirov AT gmail.com>
## Copyright (C) 2005-2007 Travis Shirk <travis AT pobox.com>
##                         Nikos Kouremenos <kourem AT gmail.com>
## Copyright (C) 2006 Stefan Bethge <stefan AT lanpartei.de>
## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
##                    James Newton <redshodan AT gmail.com>
##                    Tomasz Melcer <liori AT exroot.org>
##                    Julien Pivotto <roidelapluie AT gmail.com>
## Copyright (C) 2007-2008 Stephan Erb <steve-e AT h3c.de>
## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
##                    Jonathan Schleifer <js-gajim AT webkeks.org>
Yann Leboulanger's avatar
Yann Leboulanger committed
## 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
Yann Leboulanger's avatar
Yann Leboulanger committed
## by the Free Software Foundation; version 3 only.
Yann Leboulanger's avatar
Yann Leboulanger committed
## 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.
##
Yann Leboulanger's avatar
Yann Leboulanger committed
## 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/>.
Yann Leboulanger's avatar
Yann Leboulanger committed
##
import pango
import gobject
import os
Yann Leboulanger's avatar
Yann Leboulanger committed
import sys
import time
import history_window
import dialogs
nkour's avatar
nkour committed
import vcard
import config
dkirov's avatar
dkirov committed
import gtkgui_helpers
import cell_renderer_image
nicfit's avatar
nicfit committed
import message_control
import adhoc_commands
import features_window

from common import gajim
from common import helpers
from common.exceptions import GajimGeneralException
from common import pep
from common import location_listener
from message_window import MessageWindowMgr
from common import dbus_support
if dbus_support.supported:
from nbxmpp.protocol import NS_FILE, NS_ROSTERX
js's avatar
js committed
from common.pep import MOODS, ACTIVITIES
#(icon, name, type, jid, account, editable, second pixbuf)
    C_IMG, # image to show state (online, new message etc)
    C_NAME, # cellrenderer text that holds contact nickame
    C_TYPE, # account, group or contact?
    C_JID, # the jid of the row
    C_ACCOUNT, # cellrenderer text that holds account name
    C_MOOD_PIXBUF,
    C_ACTIVITY_PIXBUF,
    C_TUNE_PIXBUF,
    C_LOCATION_PIXBUF,
    C_AVATAR_PIXBUF, # avatar_pixbuf
    C_PADLOCK_PIXBUF, # use for account row only
class RosterWindow:
    """
    Class for main window of the GTK+ interface
    """

    def _get_account_iter(self, name, model=None):
        """
        Return the gtk.TreeIter of the given account or None if not found

        Keyword arguments:
        name -- the account name
        model -- the data model (default TreeFilterModel)
        """
        if model is None:
            model = self.modelfilter
            if model is None:
                return

            name = 'MERGED'
        it = self._iters[name]['account']

        if model == self.model or it is None:
            return it
        try:
            return self.modelfilter.convert_child_iter_to_iter(it)
        except RuntimeError:
            return None
    def _get_group_iter(self, name, account, model=None):
        """
        Return the gtk.TreeIter of the given group or None if not found

        Keyword arguments:
        name -- the group name
        account -- the account name
        model -- the data model (default TreeFilterModel)
        """
        if self.regroup:
            account = 'MERGED'

        if name not in self._iters[account]['groups']:
            return None

        it = self._iters[account]['groups'][name]
        if model == self.model or it is None:
            return it
        try:
            return self.modelfilter.convert_child_iter_to_iter(it)
        except RuntimeError:
            return None


    def _get_self_contact_iter(self, account, model=None):
        """
        Return the gtk.TreeIter of SelfContact or None if not found

        Keyword arguments:
        account -- the account of SelfContact
        model -- the data model (default TreeFilterModel)
        """
        jid = gajim.get_jid_from_account(account)
        its = self._get_contact_iter(jid, account, model=model)
        if its:
            return its[0]
        return None


    def _get_contact_iter(self, jid, account, contact=None, model=None):
        """
        Return a list of gtk.TreeIter of the given contact

        Keyword arguments:
        jid -- the jid without resource
        account -- the account
        contact -- the contact (default None)
        model -- the data model (default TreeFilterModel)
        """
            model = self.modelfilter
            # when closing Gajim model can be none (async pbs?)
            if model is None:
                return []

        if not contact:
            contact = gajim.contacts.get_first_contact_from_jid(account, jid)
            if not contact:
                # We don't know this contact
                return []

        if account not in self._iters:
            return []

        if jid not in self._iters[account]['contacts']:
            return []

        its = self._iters[account]['contacts'][jid]

        if not its:
            return []

        if model == self.model:
            return its

        its2 = []
        for it in its:
            try:
                its2.append(self.modelfilter.convert_child_iter_to_iter(it))
            except RuntimeError:
                pass
        return its2


    def _iter_is_separator(self, model, titer):
        """
        Return True if the given iter is a separator

        Keyword arguments:
        model -- the data model
        iter -- the gtk.TreeIter to test
        """
        if model[titer][0] == 'SEPARATOR':
            return True
        return False


Yann Leboulanger's avatar
Yann Leboulanger committed
#############################################################################
### Methods for adding and removing roster window items
steve-e's avatar
steve-e committed
#############################################################################
Yann Leboulanger's avatar
Yann Leboulanger committed

    def add_account(self, account):
        """
        Add account to roster and draw it. Do nothing if it is already in
        """
        if self._get_account_iter(account):
            # Will happen on reconnect or for merged accounts
            return

        if self.regroup:
            # Merged accounts view
            show = helpers.get_global_show()
            it = self.model.append(None, [
                gajim.interface.jabber_state_images['16'][show],
                _('Merged accounts'), 'account', '', 'all', None, None, None,
                None, None, None] + [None] * self.nb_ext_renderers)
            self._iters['MERGED']['account'] = it
        else:
            show = gajim.SHOW_LIST[gajim.connections[account].connected]
            our_jid = gajim.get_jid_from_account(account)

            tls_pixbuf = None
            if gajim.account_is_securely_connected(account):
                # the only way to create a pixbuf from stock
                tls_pixbuf = self.window.render_icon(
                        gtk.STOCK_DIALOG_AUTHENTICATION,
                        gtk.ICON_SIZE_MENU)

            it = self.model.append(None, [
                gajim.interface.jabber_state_images['16'][show],
                gobject.markup_escape_text(account), 'account', our_jid,
                account, None, None, None, None, None, tls_pixbuf] +
                [None] * self.nb_ext_renderers)
            self._iters[account]['account'] = it
    def add_account_contacts(self, account, improve_speed=True,
    draw_contacts=True):
        Add all contacts and groups of the given account to roster, draw them
        and account
        jids = gajim.contacts.get_jid_list(account)

        for jid in jids:
            self.add_contact(jid, account)

        if draw_contacts:
            # Do not freeze the GUI when drawing the contacts
            if jids:
                # Overhead is big, only invoke when needed
                self._idle_draw_jids_of_account(jids, account)
            # Draw all known groups
            for group in gajim.groups[account]:
                self.draw_group(group, account)
            self.draw_account(account)
    def _add_group_iter(self, account, group):
        """
        Add a group iter in roster and return the newly created iter
        """
        if self.regroup:
            account_group = 'MERGED'
        else:
            account_group = account
        delimiter = gajim.connections[account].nested_group_delimiter
        group_splited = group.split(delimiter)
        parent_group = delimiter.join(group_splited[:-1])
        if parent_group in self._iters[account_group]['groups']:
            iter_parent = self._iters[account_group]['groups'][parent_group]
        elif parent_group:
            iter_parent = self._add_group_iter(account, parent_group)
            if parent_group not in gajim.groups[account]:
                if account + parent_group in self.collapsed_rows:
                    is_expanded = False
                else:
                    is_expanded = True
                gajim.groups[account][parent_group] = {'expand': is_expanded}
        else:
            iter_parent = self._get_account_iter(account, self.model)
        iter_group = self.model.append(iter_parent,
            [gajim.interface.jabber_state_images['16']['closed'],
            gobject.markup_escape_text(group), 'group', group, account, None,
            None, None, None, None, None] + [None] * self.nb_ext_renderers)
        self.draw_group(group, account)
        self._iters[account_group]['groups'][group] = iter_group
        return iter_group

    def _add_entity(self, contact, account, groups=None,
    big_brother_contact=None, big_brother_account=None):
        """
        Add the given contact to roster data model

        Contact is added regardless if he is already in roster or not. Return
        list of newly added iters.

        Keyword arguments:
        contact -- the contact to add
        account -- the contacts account
        groups -- list of groups to add the contact to.
                  (default groups in contact.get_shown_groups()).
                Parameter ignored when big_brother_contact is specified.
        big_brother_contact -- if specified contact is added as child
                  big_brother_contact. (default None)
        """
        added_iters = []
        if big_brother_contact:
            # Add contact under big brother

            parent_iters = self._get_contact_iter(
                    big_brother_contact.jid, big_brother_account,
                    big_brother_contact, self.model)
            assert len(parent_iters) > 0, 'Big brother is not yet in roster!'

            # Do not confuse get_contact_iter: Sync groups of family members
            contact.groups = big_brother_contact.get_shown_groups()[:]

            for child_iter in parent_iters:
                it = self.model.append(child_iter, [None,
                    contact.get_shown_name(), 'contact', contact.jid, account,
                    None, None, None, None, None, None] + \
                    [None] * self.nb_ext_renderers)
                if contact.jid in self._iters[account]['contacts']:
                    self._iters[account]['contacts'][contact.jid].append(it)
                else:
                    self._iters[account]['contacts'][contact.jid] = [it]
        else:
            # We are a normal contact. Add us to our groups.
            if not groups:
                groups = contact.get_shown_groups()
            for group in groups:
                child_iterG = self._get_group_iter(group, account,
                if not child_iterG:
                    # Group is not yet in roster, add it!
                    child_iterG = self._add_group_iter(account, group)

                if contact.is_transport():
                    typestr = 'agent'
                elif contact.is_groupchat():
                    typestr = 'groupchat'
                else:
                    typestr = 'contact'

                # we add some values here. see draw_contact
                # for more
                i_ = self.model.append(child_iterG, [None,
                    contact.get_shown_name(), typestr, contact.jid, account,
                    None, None, None, None, None, None] + \
                    [None] * self.nb_ext_renderers)
                if contact.jid in self._iters[account]['contacts']:
                    self._iters[account]['contacts'][contact.jid].append(i_)
                else:
                    self._iters[account]['contacts'][contact.jid] = [i_]

                # Restore the group expand state
                if account + group in self.collapsed_rows:
                    is_expanded = False
                else:
                    is_expanded = True
                if group not in gajim.groups[account]:
                    gajim.groups[account][group] = {'expand': is_expanded}

        assert len(added_iters), '%s has not been added to roster!' % \
        contact.jid
        return added_iters

    def _remove_entity(self, contact, account, groups=None):
        """
        Remove the given contact from roster data model

        Empty groups after contact removal are removed too.
        Return False if contact still has children and deletion was
        not performed.
        Return True on success.

        Keyword arguments:
        contact -- the contact to add
        account -- the contacts account
        groups -- list of groups to remove the contact from.
        """
        iters = self._get_contact_iter(contact.jid, account, contact,
            self.model)
        assert iters, '%s shall be removed but is not in roster' % contact.jid

        parent_iter = self.model.iter_parent(iters[0])
        parent_type = self.model[parent_iter][C_TYPE]

        if groups:
            # Only remove from specified groups
            all_iters = iters[:]
            group_iters = [self._get_group_iter(group, account)
                    for group in groups]
            iters = [titer for titer in all_iters
                    if self.model.iter_parent(titer) in group_iters]

        iter_children = self.model.iter_children(iters[0])

        if iter_children:
            # We have children. We cannot be removed!
            return False
        # Remove us and empty groups from the model
        for i in iters:
            assert self.model[i][C_JID] == contact.jid and \
                    self.model[i][C_ACCOUNT] == account, \
                    "Invalidated iters of %s" % contact.jid

            parent_i = self.model.iter_parent(i)
            parent_type = self.model[parent_i][C_TYPE]

            to_be_removed = i
            while parent_type == 'group' and \
            self.model.iter_n_children(parent_i) == 1:
                if self.regroup:
                    account_group = 'MERGED'
                else:
                    account_group = account
                group = self.model[parent_i][C_JID].decode('utf-8')
                if group in gajim.groups[account]:
                    del gajim.groups[account][group]
                to_be_removed = parent_i
                del self._iters[account_group]['groups'][group]
                parent_i = self.model.iter_parent(parent_i)
            self.model.remove(to_be_removed)
        del self._iters[account]['contacts'][contact.jid]
        return True

    def _add_metacontact_family(self, family, account):
        """
        Add the give Metacontact family to roster data model

        Add Big Brother to his groups and all others under him.
        Return list of all added (contact, account) tuples with
        Big Brother as first element.

        Keyword arguments:
        family -- the family, see Contacts.get_metacontacts_family()
        """

        nearby_family, big_brother_jid, big_brother_account = \
                self._get_nearby_family_and_big_brother(family, account)
        big_brother_contact = gajim.contacts.get_first_contact_from_jid(
                big_brother_account, big_brother_jid)

        assert len(self._get_contact_iter(big_brother_jid,
                big_brother_account, big_brother_contact, self.model)) == 0, \
                'Big brother %s already in roster\n Family: %s' \
                % (big_brother_jid, family)
        self._add_entity(big_brother_contact, big_brother_account)

        brothers = []
        # Filter family members
        for data in nearby_family:
            _account = data['account']
            _jid = data['jid']
            _contact = gajim.contacts.get_first_contact_from_jid(
                    _account, _jid)

            if not _contact or _contact == big_brother_contact:
                # Corresponding account is not connected
                # or brother already added
                continue

            assert len(self._get_contact_iter(_jid, _account,
                    _contact, self.model)) == 0, \
                    "%s already in roster.\n Family: %s" % (_jid, nearby_family)
            self._add_entity(_contact, _account,
                    big_brother_contact = big_brother_contact,
                    big_brother_account = big_brother_account)
            brothers.append((_contact, _account))

        brothers.insert(0, (big_brother_contact, big_brother_account))
        return brothers

    def _remove_metacontact_family(self, family, account):
        """
        Remove the given Metacontact family from roster data model

        See Contacts.get_metacontacts_family() and
        RosterWindow._remove_entity()
        """
        nearby_family = self._get_nearby_family_and_big_brother(
                family, account)[0]

        # Family might has changed (actual big brother not on top).
        # Remove childs first then big brother
        family_in_roster = False
        for data in nearby_family:
            _account = data['account']
            _jid = data['jid']
            _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid)

            iters = self._get_contact_iter(_jid, _account, _contact, self.model)
            if not iters or not _contact:
                # Family might not be up to date.
                # Only try to remove what is actually in the roster
                continue
            assert iters, '%s shall be removed but is not in roster \
                    \n Family: %s' % (_jid, family)

            family_in_roster = True

            parent_iter = self.model.iter_parent(iters[0])
            parent_type = self.model[parent_iter][C_TYPE]

            if parent_type != 'contact':
                # The contact on top
                old_big_account = _account
                old_big_contact = _contact
                old_big_jid = _jid
                continue

            ok = self._remove_entity(_contact, _account)
            assert ok, '%s was not removed' % _jid
            assert len(self._get_contact_iter(_jid, _account, _contact,
                self.model)) == 0, '%s is removed but still in roster' % _jid

        if not family_in_roster:
            return False

        assert old_big_jid, 'No Big Brother in nearby family % (Family: %)' % \
        iters = self._get_contact_iter(old_big_jid, old_big_account,
            old_big_contact, self.model)
        assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \
            old_big_jid
        assert not self.model.iter_children(iters[0]), \
            'Old Big Brother %s still has children' % old_big_jid

        ok = self._remove_entity(old_big_contact, old_big_account)
        assert ok, "Old Big Brother %s not removed" % old_big_jid
        assert len(self._get_contact_iter(old_big_jid, old_big_account,
            old_big_contact, self.model)) == 0, \
            'Old Big Brother %s is removed but still in roster' % old_big_jid

        return True

    def _recalibrate_metacontact_family(self, family, account):
        """
        Regroup metacontact family if necessary
        """

        brothers = []
        nearby_family, big_brother_jid, big_brother_account = \
            self._get_nearby_family_and_big_brother(family, account)
        big_brother_contact = gajim.contacts.get_contact(big_brother_account,
            big_brother_jid)
        child_iters = self._get_contact_iter(big_brother_jid,
            big_brother_account, model=self.model)
        if child_iters:
            parent_iter = self.model.iter_parent(child_iters[0])
            parent_type = self.model[parent_iter][C_TYPE]

            # Check if the current BigBrother has even been before.
            if parent_type == 'contact':
                for data in nearby_family:
                    # recalibrate after remove to keep highlight
                    if data['jid'] in gajim.to_be_removed[data['account']]:
                        return

                self._remove_metacontact_family(family, account)
                brothers = self._add_metacontact_family(family, account)

                for c, acc in brothers:
                    self.draw_completely(c.jid, acc)

        # Check is small brothers are under the big brother
        for child in nearby_family:
            _jid = child['jid']
            _account = child['account']
            if _account == big_brother_account and _jid == big_brother_jid:
                continue
            child_iters = self._get_contact_iter(_jid, _account,
                model=self.model)
            if not child_iters:
                continue
            parent_iter = self.model.iter_parent(child_iters[0])
            parent_type = self.model[parent_iter][C_TYPE]
            if parent_type != 'contact':
                _contact = gajim.contacts.get_contact(_account, _jid)
                self._remove_entity(_contact, _account)
                self._add_entity(_contact, _account, groups=None,
                        big_brother_contact=big_brother_contact,
                        big_brother_account=big_brother_account)

    def _get_nearby_family_and_big_brother(self, family, account):
        return gajim.contacts.get_nearby_family_and_big_brother(family, account)

    def _add_self_contact(self, account):
        """
        Add account's SelfContact to roster and draw it and the account

        Return the SelfContact contact instance
        """
        jid = gajim.get_jid_from_account(account)
        contact = gajim.contacts.get_first_contact_from_jid(account, jid)

        assert len(self._get_contact_iter(jid, account, contact,
        self.model)) == 0, 'Self contact %s already in roster' % jid

        child_iterA = self._get_account_iter(account, self.model)
        self._iters[account]['contacts'][jid] = [self.model.append(child_iterA,
            [None, gajim.nicks[account], 'self_contact', jid, account, None,
            None, None, None, None, None] + [None] * self.nb_ext_renderers)]

        self.draw_completely(jid, account)
        self.draw_account(account)

        return contact

    def redraw_metacontacts(self, account):
        for family in gajim.contacts.iter_metacontacts_families(account):
            self._recalibrate_metacontact_family(family, account)

    def add_contact(self, jid, account):
        """
        Add contact to roster and draw him

        Add contact to all its group and redraw the groups, the contact and the
        account. If it's a Metacontact, add and draw the whole family.
        Do nothing if the contact is already in roster.

        Return the added contact instance. If it is a Metacontact return
        Big Brother.

        Keyword arguments:
        jid -- the contact's jid or SelfJid to add SelfContact
        account -- the corresponding account.
        """
        contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
        if len(self._get_contact_iter(jid, account, contact, self.model)):
            # If contact already in roster, do nothing
            return

        if jid == gajim.get_jid_from_account(account):
            show_self_contact = gajim.config.get('show_self_contact')
            if show_self_contact == 'never':
                return
            if (contact.resource != gajim.connections[account].server_resource \
            and show_self_contact == 'when_other_resource') or \
            show_self_contact == 'always':
                return self._add_self_contact(account)
            return

        is_observer = contact.is_observer()
        if is_observer:
            # if he has a tag, remove it
            gajim.contacts.remove_metacontact(account, jid)

        # Add contact to roster
        family = gajim.contacts.get_metacontacts_family(account, jid)
        contacts = []
        if family:
            # We have a family. So we are a metacontact.
            # Add all family members that we shall be grouped with
            if self.regroup:
                # remove existing family members to regroup them
                self._remove_metacontact_family(family, account)
            contacts = self._add_metacontact_family(family, account)
        else:
            # We are a normal contact
            contacts = [(contact, account), ]
            self._add_entity(contact, account)

        # Draw the contact and its groups contact
        if not self.starting:
            for c, acc in contacts:
                self.draw_completely(c.jid, acc)
            for group in contact.get_shown_groups():
                self.draw_group(group, account)
                self._adjust_group_expand_collapse_state(group, account)
            self.draw_account(account)

        return contacts[0][0] # it's contact/big brother with highest priority

    def remove_contact(self, jid, account, force=False, backend=False):
        """
        Remove contact from roster

        Remove contact from all its group. Remove empty groups or redraw
        otherwise.
        Draw the account.
        If it's a Metacontact, remove the whole family.
        Do nothing if the contact is not in roster.

        Keyword arguments:
        jid -- the contact's jid or SelfJid to remove SelfContact
        account -- the corresponding account.
        force -- remove contact even it has pending evens (Default False)
        backend -- also remove contact instance (Default False)
        """
        contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
        if not contact:
            return

        if not force and self.contact_has_pending_roster_events(contact,
        account):
            return False

        iters = self._get_contact_iter(jid, account, contact, self.model)
        if iters:
            # no more pending events
            # Remove contact from roster directly
            family = gajim.contacts.get_metacontacts_family(account, jid)
            if family:
                # We have a family. So we are a metacontact.
                self._remove_metacontact_family(family, account)
            else:
                self._remove_entity(contact, account)

        old_grps = []
        if backend:
            if not gajim.interface.msg_win_mgr.get_control(jid, account) or \
            force:
                # If a window is still opened: don't remove contact instance
                # Remove contact before redrawing, otherwise the old
                # numbers will still be show
                gajim.contacts.remove_jid(account, jid, remove_meta=True)
                if iters:
                    rest_of_family = [data for data in family
                        if account != data['account'] or jid != data['jid']]
                    if rest_of_family:
                        # reshow the rest of the family
                        brothers = self._add_metacontact_family(rest_of_family,
                            account)
                        for c, acc in brothers:
                            self.draw_completely(c.jid, acc)
            else:
                for c in gajim.contacts.get_contacts(account, jid):
                    c.sub = 'none'
                    c.show = 'not in roster'
                    c.status = ''
                    old_grps = c.get_shown_groups()
                    c.groups = [_('Not in Roster')]
                    self._add_entity(c, account)
                    self.draw_contact(jid, account)

        if iters:
            # Draw all groups of the contact
            for group in contact.get_shown_groups() + old_grps:
                self.draw_group(group, account)
            self.draw_account(account)

        return True

    def rename_self_contact(self, old_jid, new_jid, account):
        """
        Rename the self_contact jid

        Keyword arguments:
        old_jid -- our old jid
        new_jid -- our new jid
        account -- the corresponding account.
        """
        gajim.contacts.change_contact_jid(old_jid, new_jid, account)
        self_iter = self._get_self_contact_iter(account, model=self.model)
        if not self_iter:
            return
        self.model[self_iter][C_JID] = new_jid
        self.draw_contact(new_jid, account)

    def add_groupchat(self, jid, account, status=''):
        """
        Add groupchat to roster and draw it. Return the added contact instance
        """
        contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
        # Do not show gc if we are disconnected and minimize it
        if gajim.account_is_connected(account):
            show = 'online'
        else:
            show = 'offline'
            status = ''

        if contact is None:
            gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
                account)
            if gc_control:
                # there is a window that we can minimize
                gajim.interface.minimized_controls[account][jid] = gc_control
                name = gc_control.name
            elif jid in gajim.interface.minimized_controls[account]:
                name = gajim.interface.minimized_controls[account][jid].name
            else:
                name = jid.split('@')[0]
            # New groupchat
            contact = gajim.contacts.create_contact(jid=jid, account=account,
                name=name, groups=[_('Groupchats')], show=show, status=status,
                sub='none')
            gajim.contacts.add_contact(account, contact)
            self.add_contact(jid, account)
        else:
            if jid not in gajim.interface.minimized_controls[account]:
                # there is a window that we can minimize
                gc_control = gajim.interface.msg_win_mgr.get_gc_control(jid,
                        account)
                gajim.interface.minimized_controls[account][jid] = gc_control
            contact.show = show
            contact.status = status
            self.adjust_and_draw_contact_context(jid, account)

        return contact


    def remove_groupchat(self, jid, account):
        """
        Remove groupchat from roster and redraw account and group
        """
        contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
        if contact.is_groupchat():
            if jid in gajim.interface.minimized_controls[account]:
                del gajim.interface.minimized_controls[account][jid]
            self.remove_contact(jid, account, force=True, backend=True)
            return True
        else:
            return False


    # FIXME: This function is yet unused! Port to new API
    def add_transport(self, jid, account):
        """
        Add transport to roster and draw it. Return the added contact instance
        """
        contact = gajim.contacts.get_contact_with_highest_priority(account, jid)
        if contact is None:
            contact = gajim.contacts.create_contact(jid=jid, account=account,
                name=jid, groups=[_('Transports')], show='offline',
                status='offline', sub='from')
            gajim.contacts.add_contact(account, contact)
        self.add_contact(jid, account)
        return contact

    def remove_transport(self, jid, account):
        """
        Remove transport from roster and redraw account and group
        """
        self.remove_contact(jid, account, force=True, backend=True)
        return True

    def rename_group(self, old_name, new_name, account):
        """
        Rename a roster group
        """
        if old_name == new_name:
            return

        # Groups may not change name from or to a special groups
        for g in helpers.special_groups:
            if g in (new_name, old_name):
                return

        # update all contacts in the given group
        if self.regroup:
            accounts = gajim.connections.keys()
        else:

        for acc in accounts:
            changed_contacts = []
            for jid in gajim.contacts.get_jid_list(acc):
                contact = gajim.contacts.get_first_contact_from_jid(acc, jid)
                if old_name not in contact.groups:
                    continue

                self.remove_contact(jid, acc, force=True)

                contact.groups.remove(old_name)
                if new_name not in contact.groups:
                    contact.groups.append(new_name)

                changed_contacts.append({'jid': jid, 'name': contact.name,
                    'groups':contact.groups})

            gajim.connections[acc].update_contacts(changed_contacts)

            for c in changed_contacts:
                self.add_contact(c['jid'], acc)

            self._adjust_group_expand_collapse_state(new_name, acc)

            self.draw_group(old_name, acc)
            self.draw_group(new_name, acc)


    def add_contact_to_groups(self, jid, account, groups, update=True):
        """
        Add contact to given groups and redraw them

        Contact on server is updated too. When the contact has a family,
        the action will be performed for all members.

        Keyword Arguments:
        jid -- the jid
        account -- the corresponding account
        groups -- list of Groups to add the contact to.
        update -- update contact on the server
        """
        self.remove_contact(jid, account, force=True)
        for contact in gajim.contacts.get_contacts(account, jid):
            for group in groups:
                if group not in contact.groups:
                    # we might be dropped from meta to group
                    contact.groups.append(group)
            if update:
                gajim.connections[account].update_contact(jid, contact.name,
                        contact.groups)

        self.add_contact(jid, account)

        for group in groups:
            self._adjust_group_expand_collapse_state(group, account)

    def remove_contact_from_groups(self, jid, account, groups, update=True):
        """
        Remove contact from given groups and redraw them

        Contact on server is updated too. When the contact has a family,
        the action will be performed for all members.

        Keyword Arguments:
        jid -- the jid
        account -- the corresponding account
        groups -- list of Groups to remove the contact from
        update -- update contact on the server
        """
        self.remove_contact(jid, account, force=True)
        for contact in gajim.contacts.get_contacts(account, jid):
            for group in groups:
                if group in contact.groups:
                    # Needed when we remove from "General" or "Observers"
                    contact.groups.remove(group)
            if update:
                gajim.connections[account].update_contact(jid, contact.name,
                        contact.groups)
        self.add_contact(jid, account)

        # Also redraw old groups
        for group in groups:
            self.draw_group(group, account)

    # FIXME: maybe move to gajim.py
    def remove_newly_added(self, jid, account):
        if jid in gajim.newly_added[account]:
            gajim.newly_added[account].remove(jid)
            self.draw_contact(jid, account)

    # FIXME: maybe move to gajim.py
    def remove_to_be_removed(self, jid, account):
        if account not in gajim.interface.instances:
            # Account has been deleted during the timeout that called us
            return
        if jid in gajim.newly_added[account]:
            return
        if jid in gajim.to_be_removed[account]:
            gajim.to_be_removed[account].remove(jid)
            family = gajim.contacts.get_metacontacts_family(account, jid)
            if family:
                # Peform delayed recalibration
                self._recalibrate_metacontact_family(family, account)
            self.draw_contact(jid, account)

    # FIXME: integrate into add_contact()
    def add_to_not_in_the_roster(self, account, jid, nick='', resource=''):
        keyID = ''