roster_window.py 242 KB
Newer Older
1
# -*- coding: utf-8 -*-
roidelapluie's avatar
roidelapluie committed
2
## src/roster_window.py
3
##
Dicson's avatar
Dicson committed
4
## Copyright (C) 2003-2014 Yann Leboulanger <asterix AT lagaule.org>
roidelapluie's avatar
roidelapluie committed
5
## Copyright (C) 2005 Alex Mauer <hawke AT hawkesnest.net>
roidelapluie's avatar
roidelapluie committed
6
##                    Stéphan Kochen <stephan AT kochen.nl>
roidelapluie's avatar
roidelapluie committed
7 8 9 10 11 12 13 14 15 16 17 18
## 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>
19
##
Yann Leboulanger's avatar
Yann Leboulanger committed
20 21 22
## This file is part of Gajim.
##
## Gajim is free software; you can redistribute it and/or modify
23
## it under the terms of the GNU General Public License as published
Yann Leboulanger's avatar
Yann Leboulanger committed
24
## by the Free Software Foundation; version 3 only.
25
##
Yann Leboulanger's avatar
Yann Leboulanger committed
26
## Gajim is distributed in the hope that it will be useful,
27
## but WITHOUT ANY WARRANTY; without even the implied warranty of
roidelapluie's avatar
roidelapluie committed
28
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 30
## GNU General Public License for more details.
##
Yann Leboulanger's avatar
Yann Leboulanger committed
31
## You should have received a copy of the GNU General Public License
roidelapluie's avatar
roidelapluie committed
32
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
Yann Leboulanger's avatar
Yann Leboulanger committed
33
##
34

35 36 37 38 39
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import Pango
from gi.repository import GObject
Yann Leboulanger's avatar
Yann Leboulanger committed
40
from gi.repository import GLib
41
from gi.repository import Gio
42
import os
43
import sys
44
import time
45
import locale
46

47
from enum import IntEnum, unique
48

André's avatar
André committed
49 50 51 52 53 54 55 56 57 58 59
from gajim import history_window
from gajim import dialogs
from gajim import vcard
from gajim import config
from gajim import disco
from gajim import gtkgui_helpers
from gajim import gui_menu_builder
from gajim import cell_renderer_image
from gajim import tooltips
from gajim import message_control
from gajim import adhoc_commands
Philipp Hörist's avatar
Philipp Hörist committed
60
from gajim.common.const import AvatarSize
André's avatar
André committed
61

62
from gajim.common import app
André's avatar
André committed
63
from gajim.common import helpers
Philipp Hörist's avatar
Philipp Hörist committed
64
from gajim.common import idle
André's avatar
André committed
65 66
from gajim.common.exceptions import GajimGeneralException
from gajim.common import i18n
67
if app.is_installed('GEOCLUE'):
André's avatar
André committed
68
    from gajim.common import location_listener
André's avatar
André committed
69 70
from gajim.common import ged
from gajim.message_window import MessageWindowMgr
71
from nbxmpp.protocol import NS_FILE, NS_ROSTERX, NS_CONFERENCE
72

73

74
@unique
75 76 77 78 79 80 81 82 83 84
class Column(IntEnum):
    IMG = 0  # image to show state (online, new message etc)
    NAME = 1  # cellrenderer text that holds contact nickame
    TYPE = 2  # account, group or contact?
    JID = 3  # the jid of the row
    ACCOUNT = 4  # cellrenderer text that holds account name
    MOOD_PIXBUF = 5
    ACTIVITY_PIXBUF = 6
    TUNE_PIXBUF = 7
    LOCATION_PIXBUF = 8
85
    AVATAR_IMG = 9  # avatar_sha
86
    PADLOCK_PIXBUF = 10  # use for account row only
87
    VISIBLE = 11
88

Dicson's avatar
Dicson committed
89
empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1)
Dicson's avatar
Dicson committed
90
empty_pixbuf.fill(0xffffff00)
Dicson's avatar
Dicson committed
91 92


93
class RosterWindow:
94 95 96 97 98 99
    """
    Class for main window of the GTK+ interface
    """

    def _get_account_iter(self, name, model=None):
        """
100
        Return the Gtk.TreeIter of the given account or None if not found
101 102 103 104 105 106

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

111
        if self.regroup:
112
            name = 'MERGED'
113 114
        if name not in self._iters:
            return None
115
        it = self._iters[name]['account']
116 117 118 119

        if model == self.model or it is None:
            return it
        try:
120 121 122 123
            (ok, it) = self.modelfilter.convert_child_iter_to_iter(it)
            if ok:
                return it
            return None
124 125
        except RuntimeError:
            return None
126 127


128
    def _get_group_iter(self, name, account, model=None):
129
        """
130
        Return the Gtk.TreeIter of the given group or None if not found
131 132 133 134 135 136

        Keyword arguments:
        name -- the group name
        account -- the account name
        model -- the data model (default TreeFilterModel)
        """
137
        if model is None:
138
            model = self.modelfilter
139 140 141
            if model is None:
                return

142 143 144
        if self.regroup:
            account = 'MERGED'

145 146
        if account not in self._iters:
            return None
147 148 149 150 151 152 153
        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:
154 155 156 157
            (ok, it) = self.modelfilter.convert_child_iter_to_iter(it)
            if ok:
                return it
            return None
158 159
        except RuntimeError:
            return None
160 161 162 163


    def _get_self_contact_iter(self, account, model=None):
        """
164
        Return the Gtk.TreeIter of SelfContact or None if not found
165 166 167 168 169

        Keyword arguments:
        account -- the account of SelfContact
        model -- the data model (default TreeFilterModel)
        """
170
        jid = app.get_jid_from_account(account)
171 172 173
        its = self._get_contact_iter(jid, account, model=model)
        if its:
            return its[0]
174 175 176 177 178
        return None


    def _get_contact_iter(self, jid, account, contact=None, model=None):
        """
179
        Return a list of Gtk.TreeIter of the given contact
180 181 182 183 184 185 186

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

        if not contact:
194
            contact = app.contacts.get_first_contact_from_jid(account, jid)
195 196 197 198
            if not contact:
                # We don't know this contact
                return []

199 200 201
        if account not in self._iters:
            return []

202 203 204 205 206 207 208 209 210 211 212 213 214 215
        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:
216 217 218
                (ok, it) = self.modelfilter.convert_child_iter_to_iter(it)
                if ok:
                    its2.append(it)
219 220 221
            except RuntimeError:
                pass
        return its2
222

223 224
    @staticmethod
    def _iter_is_separator(model, titer):
225 226 227 228 229
        """
        Return True if the given iter is a separator

        Keyword arguments:
        model -- the data model
230
        iter -- the Gtk.TreeIter to test
231 232 233 234 235
        """
        if model[titer][0] == 'SEPARATOR':
            return True
        return False

236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
    @staticmethod
    def _status_cell_data_func(cell_layout, cell, tree_model, iter_):
        if isinstance(cell, Gtk.CellRendererPixbuf):
            icon_name = tree_model[iter_][1]
            if icon_name is None:
                return
            if tree_model[iter_][2] == 'status':
                cell.set_property('icon_name', icon_name)
            else:
                iconset_name = gtkgui_helpers.get_iconset_name_for(icon_name)
                cell.set_property('icon_name', iconset_name)
        else:
            show = tree_model[iter_][0]
            id_ = tree_model[iter_][2]
            if id_ not in ('status', 'desync'):
                show = helpers.get_uf_show(show)
            cell.set_property('text', show)


255

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

260 261 262 263 264 265 266 267 268 269 270
    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()
271
            it = self.model.append(None, [
272
                app.interface.jabber_state_images['16'][show],
273
                _('Merged accounts'), 'account', '', 'all', None, None, None,
274
                None, None, None, True] + [None] * self.nb_ext_renderers)
275
            self._iters['MERGED']['account'] = it
276
        else:
277 278
            show = app.SHOW_LIST[app.connections[account].connected]
            our_jid = app.get_jid_from_account(account)
279 280

            tls_pixbuf = None
281
            if app.account_is_securely_connected(account):
282
                tls_pixbuf = 'changes-prevent'
283

284
            it = self.model.append(None, [
285
                app.interface.jabber_state_images['16'][show],
Yann Leboulanger's avatar
Yann Leboulanger committed
286
                GLib.markup_escape_text(account), 'account', our_jid,
287
                account, None, None, None, None, None, tls_pixbuf, True] +
288
                [None] * self.nb_ext_renderers)
289
            self._iters[account]['account'] = it
290 291 292 293

        self.draw_account(account)


294 295
    def add_account_contacts(self, account, improve_speed=True,
    draw_contacts=True):
296
        """
297 298
        Add all contacts and groups of the given account to roster, draw them
        and account
299
        """
300 301
        if improve_speed:
            self._before_fill()
302
        jids = app.contacts.get_jid_list(account)
303 304 305 306

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

307 308 309 310 311
        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)
312

313
            # Draw all known groups
314
            for group in app.groups[account]:
315 316
                self.draw_group(group, account)
            self.draw_account(account)
317

318 319
        if improve_speed:
            self._after_fill()
320

321 322 323 324 325 326 327 328
    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
329
        delimiter = app.connections[account].nested_group_delimiter
330 331
        group_splited = group.split(delimiter)
        parent_group = delimiter.join(group_splited[:-1])
332
        if len(group_splited) > 1 and parent_group in self._iters[account_group]['groups']:
333 334 335
            iter_parent = self._iters[account_group]['groups'][parent_group]
        elif parent_group:
            iter_parent = self._add_group_iter(account, parent_group)
336
            if parent_group not in app.groups[account]:
337 338 339 340
                if account + parent_group in self.collapsed_rows:
                    is_expanded = False
                else:
                    is_expanded = True
341
                app.groups[account][parent_group] = {'expand': is_expanded}
342 343 344
        else:
            iter_parent = self._get_account_iter(account, self.model)
        iter_group = self.model.append(iter_parent,
345
            [app.interface.jabber_state_images['16']['closed'],
Yann Leboulanger's avatar
Yann Leboulanger committed
346
            GLib.markup_escape_text(group), 'group', group, account, None,
347
            None, None, None, None, None, False] + [None] * self.nb_ext_renderers)
348 349 350
        self.draw_group(group, account)
        self._iters[account_group]['groups'][group] = iter_group
        return iter_group
351 352

    def _add_entity(self, contact, account, groups=None,
353
    big_brother_contact=None, big_brother_account=None):
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369
        """
        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 = []
370
        visible = self.contact_is_visible(contact, account)
371 372 373 374 375 376 377 378 379
        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
380
            contact.groups = big_brother_contact.groups[:]
381 382

            for child_iter in parent_iters:
383
                it = self.model.append(child_iter, [None,
384
                    contact.get_shown_name(), 'contact', contact.jid, account,
385
                    None, None, None, None, None, None, visible] + \
386
                    [None] * self.nb_ext_renderers)
387
                added_iters.append(it)
388 389 390 391
                if contact.jid in self._iters[account]['contacts']:
                    self._iters[account]['contacts'][contact.jid].append(it)
                else:
                    self._iters[account]['contacts'][contact.jid] = [it]
392 393 394 395 396 397
        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,
398
                    model=self.model)
399 400
                if not child_iterG:
                    # Group is not yet in roster, add it!
401
                    child_iterG = self._add_group_iter(account, group)
402 403 404 405 406 407 408 409 410 411

                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
412 413
                i_ = self.model.append(child_iterG, [None,
                    contact.get_shown_name(), typestr, contact.jid, account,
414
                    None, None, None, None, None, None, visible] + \
415
                    [None] * self.nb_ext_renderers)
416
                added_iters.append(i_)
417 418 419 420
                if contact.jid in self._iters[account]['contacts']:
                    self._iters[account]['contacts'][contact.jid].append(i_)
                else:
                    self._iters[account]['contacts'][contact.jid] = [i_]
421 422 423 424 425 426

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

430 431
        assert len(added_iters), '%s has not been added to roster!' % \
        contact.jid
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447
        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.
        """
448 449
        iters = self._get_contact_iter(contact.jid, account, contact,
            self.model)
450 451 452
        assert iters, '%s shall be removed but is not in roster' % contact.jid

        parent_iter = self.model.iter_parent(iters[0])
453
        parent_type = self.model[parent_iter][Column.TYPE]
454 455 456 457 458 459 460 461 462 463 464 465 466 467

        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
468 469
        # Remove us and empty groups from the model
        for i in iters:
470 471
            assert self.model[i][Column.JID] == contact.jid and \
                    self.model[i][Column.ACCOUNT] == account, \
472 473 474
                    "Invalidated iters of %s" % contact.jid

            parent_i = self.model.iter_parent(i)
475
            parent_type = self.model[parent_i][Column.TYPE]
476 477 478 479 480 481 482 483

            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
484
                group = self.model[parent_i][Column.JID]
485 486
                if group in app.groups[account]:
                    del app.groups[account][group]
487 488 489
                to_be_removed = parent_i
                del self._iters[account_group]['groups'][group]
                parent_i = self.model.iter_parent(parent_i)
490
                parent_type = self.model[parent_i][Column.TYPE]
491
            self.model.remove(to_be_removed)
492

493 494
        del self._iters[account]['contacts'][contact.jid]
        return True
495 496 497 498 499 500 501 502 503 504 505 506 507 508 509

    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)
510 511
        if not big_brother_jid:
            return []
512
        big_brother_contact = app.contacts.get_first_contact_from_jid(
513 514 515 516 517 518 519 520 521 522 523 524 525
                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']
526
            _contact = app.contacts.get_first_contact_from_jid(
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
                    _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']
561
            _contact = app.contacts.get_first_contact_from_jid(_account, _jid)
562 563 564 565 566 567 568 569 570 571 572 573

            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])
574
            parent_type = self.model[parent_iter][Column.TYPE]
575 576 577 578 579 580 581 582 583 584 585

            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,
586
                self.model)) == 0, '%s is removed but still in roster' % _jid
587 588 589 590

        if not family_in_roster:
            return False

Yann Leboulanger's avatar
Yann Leboulanger committed
591
        assert old_big_jid, 'No Big Brother in nearby family %s (Family: %s)' %\
592
            (nearby_family, family)
593
        iters = self._get_contact_iter(old_big_jid, old_big_account,
594
            old_big_contact, self.model)
595
        assert len(iters) > 0, 'Old Big Brother %s is not in roster anymore' % \
596 597 598
            old_big_jid
        assert not self.model.iter_children(iters[0]), \
            'Old Big Brother %s still has children' % old_big_jid
599 600 601 602

        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,
603 604
            old_big_contact, self.model)) == 0, \
            'Old Big Brother %s is removed but still in roster' % old_big_jid
605 606 607 608 609 610 611 612 613 614

        return True

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

        brothers = []
        nearby_family, big_brother_jid, big_brother_account = \
615
            self._get_nearby_family_and_big_brother(family, account)
616
        big_brother_contact = app.contacts.get_contact(big_brother_account,
617 618 619
            big_brother_jid)
        child_iters = self._get_contact_iter(big_brother_jid,
            big_brother_account, model=self.model)
620 621
        if child_iters:
            parent_iter = self.model.iter_parent(child_iters[0])
622
            parent_type = self.model[parent_iter][Column.TYPE]
623 624 625 626 627

            # Check if the current BigBrother has even been before.
            if parent_type == 'contact':
                for data in nearby_family:
                    # recalibrate after remove to keep highlight
628
                    if data['jid'] in app.to_be_removed[data['account']]:
629 630 631 632 633 634 635 636 637 638 639 640 641 642
                        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
643 644
            child_iters = self._get_contact_iter(_jid, _account,
                model=self.model)
645 646 647
            if not child_iters:
                continue
            parent_iter = self.model.iter_parent(child_iters[0])
648
            parent_type = self.model[parent_iter][Column.TYPE]
649
            if parent_type != 'contact':
650
                _contact = app.contacts.get_contact(_account, _jid)
651 652 653 654 655 656
                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):
657
        return app.contacts.get_nearby_family_and_big_brother(family, account)
658 659 660 661 662 663 664

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

        Return the SelfContact contact instance
        """
665 666
        jid = app.get_jid_from_account(account)
        contact = app.contacts.get_first_contact_from_jid(account, jid)
667

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

        child_iterA = self._get_account_iter(account, self.model)
672
        self._iters[account]['contacts'][jid] = [self.model.append(child_iterA,
673
            [None, app.nicks[account], 'self_contact', jid, account, None,
674
            None, None, None, None, None, True] + [None] * self.nb_ext_renderers)]
675 676 677 678 679 680 681

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

        return contact

    def redraw_metacontacts(self, account):
682
        for family in app.contacts.iter_metacontacts_families(account):
683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
            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.
        """
700
        contact = app.contacts.get_contact_with_highest_priority(account, jid)
701 702 703 704
        if len(self._get_contact_iter(jid, account, contact, self.model)):
            # If contact already in roster, do nothing
            return

705 706
        if jid == app.get_jid_from_account(account):
            show_self_contact = app.config.get('show_self_contact')
707 708
            if show_self_contact == 'never':
                return
709
            if (contact.resource != app.connections[account].server_resource \
710 711
            and show_self_contact == 'when_other_resource') or \
            show_self_contact == 'always':
712 713 714 715 716 717
                return self._add_self_contact(account)
            return

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

        # Add contact to roster
721
        family = app.contacts.get_metacontacts_family(account, jid)
722 723 724 725 726 727 728 729 730 731
        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
732
            contacts = [(contact, account), ]
733 734 735 736 737 738 739 740 741 742 743 744 745
            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

746
    def remove_contact(self, jid, account, force=False, backend=False, maximize=False):
747 748 749 750 751 752 753 754 755 756 757 758 759 760 761
        """
        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)
        """
762
        contact = app.contacts.get_contact_with_highest_priority(account, jid)
763 764 765
        if not contact:
            return

766 767 768
        if not force and self.contact_has_pending_roster_events(contact,
        account):
            return False
769 770 771 772 773

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

781 782
        old_grps = []
        if backend:
783
            if not app.interface.msg_win_mgr.get_control(jid, account) or \
784 785 786 787
            force:
                # If a window is still opened: don't remove contact instance
                # Remove contact before redrawing, otherwise the old
                # numbers will still be show
788 789 790
                if not maximize:
                    # Dont remove contact when we maximize a room
                    app.contacts.remove_jid(account, jid, remove_meta=True)
791 792 793 794 795 796 797 798 799 800
                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:
801
                for c in app.contacts.get_contacts(account, jid):
802 803 804 805 806 807 808
                    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)
809 810 811

        if iters:
            # Draw all groups of the contact
812
            for group in contact.get_shown_groups() + old_grps:
813 814 815 816 817 818 819 820 821 822 823 824 825 826
                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.
        """
827
        app.contacts.change_contact_jid(old_jid, new_jid, account)
828 829 830
        self_iter = self._get_self_contact_iter(account, model=self.model)
        if not self_iter:
            return
831
        self.model[self_iter][Column.JID] = new_jid
832 833
        self.draw_contact(new_jid, account)

834 835 836 837 838 839
    def minimize_groupchat(self, account, jid, status=''):
        gc_control = app.interface.msg_win_mgr.get_gc_control(jid, account)
        app.interface.minimized_controls[account][jid] = gc_control
        self.add_groupchat(jid, account)

    def add_groupchat(self, jid, account):
840 841 842
        """
        Add groupchat to roster and draw it. Return the added contact instance
        """
843 844
        contact = app.contacts.get_groupchat_contact(account, jid)
        show = 'offline'
845
        if app.account_is_connected(account):
846 847
            show = 'online'

848 849
        contact.show = show
        self.add_contact(jid, account)
850 851 852

        return contact

853
    def remove_groupchat(self, jid, account, maximize=False):
854 855 856
        """
        Remove groupchat from roster and redraw account and group
        """
857
        contact = app.contacts.get_contact_with_highest_priority(account, jid)
858
        if contact.is_groupchat():
859 860
            if jid in app.interface.minimized_controls[account]:
                del app.interface.minimized_controls[account][jid]
861
            self.remove_contact(jid, account, force=True, backend=True, maximize=maximize)
862 863 864 865 866 867 868 869 870 871
            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
        """
872
        contact = app.contacts.get_contact_with_highest_priority(account, jid)
873
        if contact is None:
874
            contact = app.contacts.create_contact(jid=jid, account=account,
875 876
                name=jid, groups=[_('Transports')], show='offline',
                status='offline', sub='from')
877
            app.contacts.add_contact(account, contact)
878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
        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:
902
            accounts = app.connections.keys()
903
        else:
904
            accounts = [account, ]
905 906 907

        for acc in accounts:
            changed_contacts = []
908 909
            for jid in app.contacts.get_jid_list(acc):
                contact = app.contacts.get_first_contact_from_jid(acc, jid)
910 911 912 913 914 915 916 917 918
                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)

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

922
            app.connections[acc].update_contacts(changed_contacts)
923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946

            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)
947
        for contact in app.contacts.get_contacts(account, jid):
948 949 950 951 952
            for group in groups:
                if group not in contact.groups:
                    # we might be dropped from meta to group
                    contact.groups.append(group)
            if update:
953
                app.connections[account].update_contact(jid, contact.name,
954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974
                        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)
975
        for contact in app.contacts.get_contacts(account, jid):
976 977 978 979 980
            for group in groups:
                if group in contact.groups:
                    # Needed when we remove from "General" or "Observers"
                    contact.groups.remove(group)
            if update:
981
                app.connections[account].update_contact(jid, contact.name,
982 983 984 985 986 987 988
                        contact.groups)
        self.add_contact(jid, account)

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

989
    # FIXME: maybe move to app.py
990
    def remove_newly_added(self, jid, account):
991
        if account not in app.newly_added:
992 993
            # Account has been deleted during the timeout that called us
            return
994 995
        if jid in app.newly_added[account]:
            app.newly_added[account].remove(jid)
996 997
            self.draw_contact(jid, account)

998
    # FIXME: maybe move to app.py
999
    def remove_to_be_removed(self, jid, account):
1000
        if account not in app.interface.instances:
1001 1002
            # Account has been deleted during the timeout that called us
            return
1003
        if jid in app.newly_added[account]:
1004
            return
1005 1006 1007
        if jid in app.to_be_removed[account]:
            app.to_be_removed[account].remove(jid)
            family = app.contacts.get_metacontacts_family(account, jid)
1008 1009 1010 1011
            if family:
                # Peform delayed recalibration
                self._recalibrate_metacontact_family(family, account)
            self.draw_contact(jid, account)
1012 1013
            # Hide Group if all childs are hidden
            contact = app.contacts.get_contact(account, jid)
Yann Leboulanger's avatar
Yann Leboulanger committed
1014 1015
            if not contact:
                return
1016 1017
            for group in contact.get_shown_groups():
                self.draw_group(group, account)
1018 1019 1020 1021

    # FIXME: integrate into add_contact()
    def add_to_not_in_the_roster(self, account, jid, nick='', resource=''):
        keyID = ''
1022
        attached_keys = app.config.get_per('accounts', account,
1023 1024 1025
                'attached_gpg_keys').split()
        if jid in attached_keys:
            keyID = attached_keys[attached_keys.index(jid) + 1]
1026
        contact = app.contacts.create_not_in_roster_contact(jid=jid,
1027
                account=account, resource=resource, name=nick, keyID=keyID)
1028
        app.contacts.add_contact(account, contact)
1029 1030
        self.add_contact(contact.jid, account)
        return contact
Yann Leboulanger's avatar
Yann Leboulanger committed
1031 1032 1033


################################################################################
1034
### Methods for adding and removing roster window items
Yann Leboulanger's avatar
Yann Leboulanger committed
1035
################################################################################
1036

1037
    def _really_draw_account(self, account):
1038 1039 1040 1041 1042
        child_iter = self._get_account_iter(account, self.model)
        if not child_iter:
            assert False, 'Account iter of %s could not be found.' % account
            return

1043 1044
        num_of_accounts = app.get_number_of_connected_accounts()
        num_of_secured = app.get_number_of_securely_connected_accounts()
1045

1046
        tls_pixbuf = None
1047
        if app.account_is_securely_connected(account) and not self.regroup or\
1048
        self.regroup and num_of_secured and num_of_secured == num_of_accounts:
1049
            tls_pixbuf = 'changes-prevent'
1050
            self.model[child_iter][Column.PADLOCK_PIXBUF] = tls_pixbuf
1051 1052 1053 1054 1055

        if self.regroup:
            account_name = _('Merged accounts')
            accounts = []
        else:
Philipp Hörist's avatar
Philipp Hörist committed
1056 1057
            acclabel = app.config.get_per('accounts', account, 'account_label')
            account_name = acclabel or account
1058 1059 1060 1061 1062 1063
            accounts = [account]

        if account in self.collapsed_rows and \
        self.model.iter_has_child(child_iter):
            account_name = '[%s]' % account_name

1064 1065
        if (app.account_is_connected(account) or (self.regroup and \
        app.get_number_of_connected_accounts())) and app.config.get(
1066
        'show_contacts_number'):
1067
            nbr_on, nbr_total = app.contacts.get_nb_online_total_contacts(
1068 1069 1070
                    accounts = accounts)
            account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))

1071
        self.model[child_iter][Column.NAME] = GLib.markup_escape_text(account_name)
1072

1073 1074
        pep_dict = app.connections[account].pep
        if app.config.get('show_mood_in_roster') and 'mood' in pep_dict:
1075
            self.model[child_iter][Column.MOOD_PIXBUF] = \
1076
                gtkgui_helpers.get_pep_as_pixbuf(pep_dict['mood'])
1077
        else:
1078
            self.model[child_iter][Column.MOOD_PIXBUF] = empty_pixbuf
1079

1080
        if app.config.get('show_activity_in_roster') and 'activity' in \
1081
        pep_dict:
1082
            self.model[child_iter][Column.ACTIVITY_PIXBUF] = \
1083
                gtkgui_helpers.get_pep_as_pixbuf(pep_dict['activity'])
1084
        else:
1085
            self.model[child_iter][Column.ACTIVITY_PIXBUF] = empty_pixbuf
1086

1087
        if app.config.get('show_tunes_in_roster') and 'tune' in pep_dict:
1088
            self.model[child_iter][Column.TUNE_PIXBUF] = \
1089
                gtkgui_helpers.get_pep_as_pixbuf(pep_dict['tune'])
1090
        else:
1091
            self.model[child_iter][Column.TUNE_PIXBUF] = empty_pixbuf
1092

1093
        if app.config.get('show_location_in_roster') and 'location' in \
1094
        pep_dict:
1095
            self.model[child_iter][Column.LOCATION_PIXBUF] = \
1096
                gtkgui_helpers.get_pep_as_pixbuf(pep_dict['location'])
1097
        else:
1098
            self.model[child_iter][Column.LOCATION_PIXBUF] = empty_pixbuf
1099 1100 1101 1102 1103

    def _really_draw_accounts(self):
        for acct in self.accounts_to_draw:
            self._really_draw_account(acct)
        self.accounts_to_draw = []
1104 1105
        return False

1106 1107 1108 1109 1110
    def draw_account(self, account):
        if account in self.accounts_to_draw:
            return
        self.accounts_to_draw.append(account)
        if len(self.accounts_to_draw) == 1:
Yann Leboulanger's avatar
Yann Leboulanger committed
1111
            GLib.timeout_add(200, self._really_draw_accounts)
1112 1113

    def _really_draw_group(self, group, account):
1114 1115
        child_iter = self._get_group_iter(group, account, model=self.model)
        if not child_iter:
Alexander Krotov's avatar
Alexander Krotov committed
1116
            # Eg. We redraw groups after we removed a entity
1117 1118 1119 1120 1121 1122
            # and its empty groups
            return
        if self.regroup:
            accounts = []
        else:
            accounts = [account]
Yann Leboulanger's avatar
Yann Leboulanger committed
1123
        text = GLib.markup_escape_text(group)
1124 1125
        if helpers.group_is_blocked(account, group):
            text = '<span strikethrough="true">%s</span>' % text
1126 1127
        if app.config.get('show_contacts_number'):
            nbr_on, nbr_total = app.contacts.get_nb_online_total_contacts(
1128 1129 1130
                    accounts = accounts, groups = [group])
            text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total))

1131
        self.model[child_iter][Column.NAME] = text
1132

1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150
        # Hide group if no more contacts
        iterG = self._get_group_iter(group, account, model=self.modelfilter)
        to_hide = []
        while(iterG):
            parent = self.modelfilter.iter_parent(iterG)
            if (not self.modelfilter.iter_has_child(iterG)) or (len(to_hide) > \
            0 and self.modelfilter.iter_n_children(iterG) == 1):
                to_hide.append(iterG)
                if not parent or self.modelfilter[parent][Column.TYPE] != \
                'group':
                    iterG = None
                else:
                    iterG = parent
            else:
                iterG = None
        for iter_ in to_hide:
            self.modelfilter[iter_][Column.VISIBLE] = False

1151
    def _really_draw_groups(self):
1152
        for ag in self.groups_to_draw.values():
1153 1154 1155 1156
            acct = ag['account']
            grp = ag['group']
            self._really_draw_group(grp, acct)
        self.groups_to_draw = {}
1157 1158
        return False

1159 1160 1161 1162 1163 1164
    def draw_group(self, group, account):
        ag = account + group
        if ag in self.groups_to_draw:
            return
        self.groups_to_draw[ag] = {'group': group, 'account': account}
        if len(self.groups_to_draw) == 1:
Yann Leboulanger's avatar
Yann Leboulanger committed
1165
            GLib.timeout_add(200, self._really_draw_groups)
1166

1167 1168 1169 1170 1171
    def draw_parent_contact(self, jid, account):
        child_iters = self._get_contact_iter(jid, account, model=self.model)
        if not child_iters:
            return False
        parent_iter = self.model.iter_parent(child_iters[0])
1172
        if self.model[parent_iter][Column.TYPE] != 'contact':
1173 1174
            # parent is not a contact
            return
1175 1176
        parent_jid = self.model[parent_iter][Column.JID]
        parent_account = self.model[parent_iter][Column.ACCOUNT]
1177 1178 1179
        self.draw_contact(parent_jid, parent_account)
        return False

1180 1181
    def draw_contact(self, jid, account, selected=False, focus=False,
    contact_instances=None, contact=None):
1182 1183 1184 1185 1186 1187
        """
        Draw the correct state image, name BUT not avatar
        """
        # focus is about if the roster window has toplevel-focus or not
        # FIXME: We really need a custom cell_renderer

1188
        if not contact_instances:
1189
            contact_instances = app.contacts.get_contacts(account, jid)
1190
        if not contact:
1191
            contact = app.contacts.get_highest_prio_contact_from_contacts(
1192
                contact_instances)
1193 1194
        if not contact:
            return False
1195 1196 1197 1198 1199

        child_iters = self._get_contact_iter(jid, account, contact, self.model)
        if not child_iters:
            return False

Yann Leboulanger's avatar
Yann Leboulanger committed
1200
        name = GLib.markup_escape_text(contact.get_shown_name())
1201 1202

        # gets number of unread gc marked messages
1203 1204 1205
        if jid in app.interface.minimized_controls[account] and \
        app.interface.minimized_controls[account][jid]:
            nb_unread = len(app.events.get_events(account, jid,
1206
                    ['printed_marked_gc_msg']))
1207
            nb_unread += app.interface.minimized_controls \
1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233
                    [account][jid].get_nb_unread_pm()

            if nb_unread == 1:
                name = '%s *' % name
            elif nb_unread > 1:
                name = '%s [%s]' % (name, str(nb_unread))

        # Strike name if blocked
        strike = False
        if helpers.jid_is_blocked(account, jid):
            strike = True
        else:
            for group in contact.get_shown_groups():
                if helpers.group_is_blocked(account, group):
                    strike = True
                    break
        if strike:
            name = '<span strikethrough="true">%s</span>' % name

        # Show resource counter
        nb_connected_contact = 0
        for c in contact_instances:
            if c.show not in ('error', 'offline'):
                nb_connected_contact += 1
        if nb_connected_contact > 1:
            # switch back to default writing direction
1234
            name += i18n.paragraph_direction_mark(name)
1235
            name += ' (%d)' % nb_connected_contact
1236 1237 1238

        # add status msg, if not empty, under contact name in
        # the treeview
1239
        if contact.status and app.config.get('show_status_msgs_in_roster'):
1240
            status = contact.status.strip()
Dicson's avatar
Dicson committed
1241 1242 1243
            if status != '':
                status = helpers.reduce_chars_newlines(status,
                    max_lines = 1)
1244 1245 1246
                # escape markup entities and make them small
                # italic and fg color color is calcuted to be
                # always readable
1247 1248
                color = gtkgui_helpers.get_fade_color(self.tree, selected,
                    focus)
Yann Leboulanger's avatar
Yann Leboulanger committed
1249 1250
                colorstring = '#%04x%04x%04x' % (int(color.red * 65535),
                    int(color.green * 65535), int(color.blue * 65535))
1251
                name += '\n<span size="small" style="italic" ' \
1252
                    'foreground="%s">%s</span>' % (colorstring,
Yann Leboulanger's avatar
Yann Leboulanger committed
1253
                    GLib.markup_escape_text(status))
1254 1255 1256 1257 1258 1259 1260 1261 1262 1263

        icon_name = helpers.get_icon_name_to_show(contact, account)
        # look if another resource has awaiting events
        for c in contact_instances:
            c_icon_name = helpers.get_icon_name_to_show(c, account)
            if c_icon_name in ('event', 'muc_active', 'muc_inactive'):
                icon_name = c_icon_name
                break

        # Check for events of collapsed (hidden) brothers
1264
        family = app.contacts.get_metacontacts_family(account, jid)
1265 1266 1267 1268
        is_big_brother = False
        have_visible_children = False
        if family:
            bb_jid, bb_account = \
1269
                self._get_nearby_family_and_big_brother(family, account)[1:]
1270 1271
            is_big_brother = (jid, account) == (bb_jid, bb_account)
            iters = self._get_contact_iter(jid, account)
Dicson's avatar
Dicson committed
1272 1273
            have_visible_children = iters and \
                self.modelfilter.iter_has_child(iters[0])
1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287

        if have_visible_children:
            # We are the big brother and have a visible family
            for child_iter in child_iters:
                child_path = self.model.get_path(child_iter)
                path = self.modelfilter.convert_child_path_to_path(child_path)

                if not path:
                    continue

                if not self.tree.row_expanded(path) and icon_name != 'event':
                    iterC = self.model.iter_children(child_iter)
                    while iterC:
                        # a child has awaiting messages?
1288 1289
                        jidC = self.model[iterC][Column.JID]
                        accountC = self.model[iterC][Column.ACCOUNT]
1290
                        if len(app.events.get_events(accountC, jidC)):
1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306
                            icon_name = 'event'
                            break
                        iterC = self.model.iter_next(iterC)

                if self.tree.row_expanded(path):
                    state_images = self.get_appropriate_state_images(
                            jid, size = 'opened',
                            icon_name = icon_name)
                else:
                    state_images = self.get_appropriate_state_images(
                            jid, size = 'closed',
                            icon_name = icon_name)

                # Expand/collapse icon might differ per iter
                # (group)
                img = state_images[icon_name]
1307 1308
                self.model[child_iter][Column.IMG] = img
                self.model[child_iter][Column.NAME] = name
1309 1310 1311
                #TODO: compute visible
                visible = True
                self.model[child_iter][Column.VISIBLE] = visible
1312 1313 1314 1315 1316
        else:
            # A normal contact or little brother
            state_images = self.get_appropriate_state_images(jid,
                    icon_name = icon_name)

1317
            visible = self.contact_is_visible(contact, account)
1318 1319 1320
            # All iters have the same icon (no expand/collapse)
            img = state_images[icon_name]
            for child_iter in child_iters:
1321 1322
                self.model[child_iter][Column.IMG] = img
                self.model[child_iter][Column.NAME] = name
1323 1324 1325 1326
                self.model[child_iter][Column.VISIBLE] = visible
                if visible:
                    parent_iter = self.model.iter_parent(child_iter)
                    self.model[parent_iter][Column.VISIBLE] = True
1327 1328 1329 1330 1331

            # We are a little brother
            if family and not is_big_brother and not self.starting:
                self.draw_parent_contact(jid, account)

1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343
        if visible:
            delimiter = app.connections[account].nested_group_delimiter
            for group in contact.get_shown_groups():
                group_splited = group.split(delimiter)
                i = 1
                while i < len(group_splited) + 1:
                    g = delimiter.join(group_splited[:i])
                    iterG = self._get_group_iter(g, account, model=self.model)
                    if iterG:
                        # it's not self contact
                        self.model[iterG][Column.VISIBLE] = True
                    i += 1
1344

1345
        app.plugin_manager.gui_extension_point('roster_draw_contact', self,
1346 1347
            jid, account, contact)

1348 1349 1350 1351
        return False

    def _is_pep_shown_in_roster(self, pep_type):
        if pep_type == 'mood':
1352
            return app.config.get('show_mood_in_roster')
1353
        elif pep_type == 'activity':
1354
            return app.config.get('show_activity_in_roster')
1355
        elif pep_type == 'tune':
1356
            return  app.config.get('show_tunes_in_roster')
1357
        elif pep_type == 'location':
1358
            return  app.config.get('show_location_in_roster')
1359 1360 1361
        else:
            return False

1362
    def draw_all_pep_types(self, jid, account, contact=None):
1363
        for pep_type in self._pep_type_to_model_column:
1364
            self.draw_pep(jid, account, pep_type, contact=contact)