clients_icons.py 28.6 KB
Newer Older
Dicson's avatar
Dicson committed
1 2 3
# -*- coding: utf-8 -*-
##

Dicson's avatar
Dicson committed
4 5
from gi.repository import Gtk
from gi.repository import GdkPixbuf
Dicson's avatar
Dicson committed
6 7
import os

Dicson's avatar
Dicson committed
8
from plugins.gui import GajimPluginConfigDialog
Dicson's avatar
Dicson committed
9
from plugins import GajimPlugin
Dicson's avatar
Dicson committed
10
from plugins.helpers import log_calls
Dicson's avatar
Dicson committed
11 12 13 14 15
from common import ged
from common import gajim
import cell_renderer_image

clients = {
Yann Leboulanger's avatar
Yann Leboulanger committed
16 17 18 19 20 21 22 23 24
    'http://gajim.org': ['gajim.png', 'Gajim'],
    'http://gajim.org/caps': ['gajim.png', 'Gajim'],
    'http://bombus-im.org/java': ['bombus.png', 'Bombus'],
    'http://bombusmod.net.ru/caps': ['bombusmod.png', 'Bombusmod'],
    'http://psi-dev.googlecode.com/caps': ['psiplus.png', 'Psi+'],
    'http://bombusng-md.googlecode.com': ['bombusng.png', 'Bombus-NG'],
    'http://bombus-im.org/ng': ['bombusng.png', 'Bombus-NG'],
    'http://voffk.org.ru/bombus': ['bombusplus.png', 'Bombus+'],
    'http://bombusng-qd.googlecode.com': ['bombusqd.png', 'Bombus-NG'],
25
    'http://bombusmod-qd.wen.ru/caps': ['bombusqd.png', 'BombusQD'],
Yann Leboulanger's avatar
Yann Leboulanger committed
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
    'http://bombusmod.net.ru': ['bombusmod.png', 'Bombusmod'],
    'http://ex-im.name/caps': ['bombusmod.png', 'Bombusmod'],
    'http://bombusmod.eu,http://bombus.pl': ['bombuspl.png', 'Bombusmod'],
    'http://mcabber.com/caps': ['mcabber.png', 'MCabber'],
    'http://miranda-im.org/caps': ['miranda.png', 'Miranda'],
    'http://www.asterisk.org/xmpp/client/caps': ['asterisk.png', 'Asterisk'],
    'http://www.google.com/xmpp/client/caps': ['talkonaut.png', 'Talkonaut'],
    'http://talkgadget.google.com/client/caps': ['google.png', 'GTalk'],
    'http://oneteam.im/caps': ['oneteamiphone.png', 'OneTeam'],
    'http://tkabber.jabber.ru/': ['tkabber.png', 'Tkabber'],
    'http://pidgin.im/': ['pidgin.png', 'Pidgin'],
    'http://pidgin.im/caps': ['pidgin.png', 'Pidgin'],
    'http://qutim.org': ['qutim.png', 'QutIM'],
    'http://qutim.org/': ['qutim.png', 'QutIM'],
    'http://exodus.jabberstudio.org/caps': ['exodus.png', 'Exodus'],
    'http://bitlbee.org/xmpp/caps': ['bitlbee.png', 'Bitlbee'],
    'http://coccinella.sourceforge.net/protocol/caps': ['coccinella.png', 'Coccinella'],
    'http://mcabber.lilotux.net/caps': ['mcabber.png', 'MCabber'],
    'http://dev.jabbim.cz/jabbim/caps': ['jabbim.png', 'Jabbim'],
    'http://palringo.com/caps': ['palringo.png', 'Palringo'],
    'http://vacuum-im.googlecode.com': ['vacuum.png', 'Vacuum'],
    'http://code.google.com/p/vacuum-im/': ['vacuum.png', 'Vacuum'],
    'http://jajc.jrudevels.org/caps': ['jajc.png', 'JAJC'],
    'http://gaim.sf.net/caps': ['gaim.png', 'Gaim'],
    'http://mchat.mgslab.com/': ['mchat.png', 'Mchat'],
    'http://online.yandex.ru/caps': ['yaonline.png', 'Yaonline'],
    'http://psi-im.org/caps': ['psi.png', 'Psi'],
    'http://jimm.net.ru/caps': ['jimm-aspro.png', 'Jimm'],
Dicson's avatar
Dicson committed
54
    'http://jabga.ru': ['fin.png', 'Fin jabber'],
Yann Leboulanger's avatar
Yann Leboulanger committed
55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
    'http://bluendo.com/protocol/caps': ['lampiro.png', 'Lampiro'],
    'nimbuzz:caps': ['nimbuzz.png', 'Nimbuzz'],
    'http://nimbuzz.com/caps': ['nimbuzz.png', 'Nimbuzz'],
    'http://isida.googlecode.com': ['isida-bot.png', 'Isida'],
    'http://isida-bot.com': ['isida-bot.png', 'Isida'],
    'http://apps.radio-t.com/caps': ['radio-t.png', 'Radio-t'],
    'http://pda.qip.ru/caps': ['qippda.png', 'Qip-PDA'],
    'http://kopete.kde.org/jabber/caps': ['kopete.png', 'Kopete'],
    'http://www.apple.com/ichat/caps': ['ichat.png', 'IChat'],
    'http://pjc.googlecode.com/caps': ['jubo.png', 'Jubo'],
    'talk.google.com': ['talk.google.com.png', 'GTalk'],
    'http://www.android.com/gtalk/client/caps': ['talk.google.com.png', 'GTalk'],
    'http://swift.im': ['swift.png', 'Swift'],
    'http://fatal-bot.spb.ru/caps': ['bot.png', 'Fatal-bot'],
    'http://svn.posix.ru/fatal-bot/trunk': ['bot.png', 'Fatal-bot'],
    'http://storm-bot.googlecode.com/svn/trunk': ['bot.png', 'Storm-bot'],
    'http://talk.google.com/xmpp/bot/caps': ['google.png', 'GTalk'],
    'http://jabbrik.ru/caps': ['bot.png', 'Jabbrik'],
    'http://jabrvista.net.ru': ['bot.png', 'Jabvista'],
    'http://xu-6.jabbrik.ru/caps': ['bot.png', 'XU-6'],
    'http://jabber.pdg.pl/caps': ['bombus-klub.png', 'Bombus-klub'],
    'http://klub54.wen.ru': ['bombus-klub.png', 'Bombus-klub'],
    'http://aqq.eu/': ['aqq.png', 'Aqq'],
    'http://2010.qip.ru/caps': ['qip.png', 'Qip'],
    'http://qip.ru/caps': ['qip.png', 'Qip'],
    'http://qip.ru/caps?QIP': ['qip.png', 'Qip'],
    'http://glu.net/': ['glu.png', 'Glu'],
    'Siemens': ['siejc.png', 'Siemens'],  # Siemens Native Jabber Client
    'telepathy.': ['telepathy.freedesktop.org.png', 'Telepathy'],
    'http://live.gnome.org/empathy/caps': ['telepathy.freedesktop.org.png', 'Empathy'],
    'http://telepathy.freedesktop.org/caps': ['telepathy.freedesktop.org.png', 'Telepathy'],
    'http://www.adiumx.com/caps': ['adium.png', 'Adium'],
    'http://www.adiumx.com': ['adium.png', 'Adium'],
    'http://juick.com/caps': ['juick.png', 'Juick'],
    'vk.com': ['vkontakte.png', 'Vkontakte'],
    'facebook.com': ['facebook.png', 'Facebook'],
    'http://mail.google.com/xmpp/client/caps': ['google.com.png', 'GMail'],
    'http://snapi-bot.googlecode.com/caps': ['bot.png', 'Snapi-bot'],
    'http://www.barobin.com/caps': ['bayanicq.png', 'Bayanicq'],
    'http://chat.ovi.com/caps': ['ovi-chat.png', 'Ovi-chat'],
    'http://trillian.im/caps': ['trillian.png', 'Trillian'],
    'http://pandion.im/': ['pandion.png', 'Pandion'],
    'http://agent.mail.ru/caps': ['mailruagent.png', 'Mailruagent'],
    'http://agent.mail.ru/': ['mailruagent.png', 'Mailruagent'],
    'http://digsby.com/caps': ['digsby.png', 'Digsby'],
    'http://jabber-net.ru/caps/talisman-bot': ['bot.png', 'Talisman-bot'],
    'http://jabber-net.ru/talisman-bot/caps': ['bot.png', 'Talisman-bot'],
    'http://j-tmb.ru/caps': ['bot.png', 'J-tmb'],
    'http://simpleapps.ru/caps#blacksmith': ['bot.png', 'Blacksmith'],
    'http://blacksmith-2.googlecode.com/svn/': ['bot.png', 'Blacksmith-2'],
    'http://qabber.ru/jame-bot': ['bot.png', 'Jame-bot'],
    'http://chat.freize.org/caps': ['freize.png', 'Freize'],
    'http://pyvk-t.googlecode.com/caps': ['vkontakte.png', 'Vkontakte'],
    'http://pyvk': ['vkontakte.png', 'Vkontakte'],
    'http://pyicq': ['pyicq-t.png', 'PyICQ-t'],
    'http://spectrum.im/transport': ['pyicq-t.png', 'PyICQ-t'],
    'http://pyaim': ['pyaim-t.png', 'PyAIM-t'],
    'http://jabbroid.akuz.de': ['android.png', 'Jabbroid'],
    'http://jabbroid.akuz.de/caps': ['android.png', 'Jabbroid'],
    'http://witcher-team.ucoz.ru/': ['bot.png', 'Witcher'],
    'http://home.gna.org/': ['omnipresence.png', 'Omnipresence'],
    'http://ayttm.souceforge.net/caps': ['ayttm.png', 'Ayttm'],
117
    'http://www.process-one.net/en/solutions/oneteam_iphone/': \
Yann Leboulanger's avatar
Yann Leboulanger committed
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
        ['oneteamiphone.png', 'OneTeam-iphone'],
    'http://qq-im.com/caps': ['qq.png', 'QQ'],
    'http://qq.com/caps': ['qq.png', 'QQ'],
    'http://www.lonelycatgames.com/slick/caps': ['slick.png', 'Slick'],
    'http://sim-im.org/caps': ['sim.png', 'Sim'],
    'http://www.igniterealtime.org/project/spark/caps': ['spark.png', 'Spark'],
    'http://emess.eqx.su/caps': ['emess.png', 'Emess'],
    'https://www.jappix.com/': ['jappix.png', 'Jappix'],
    'http://japyt.googlecode.com': ['japyt.png', 'Japyt'],
    'http://www.xfire.com/': ['xfire.png', 'Xfire'],
    'http://www.xfire.com/caps': ['xfire.png', 'Xfire'],
    'http://lytgeygen.ru/caps': ['bot.png', 'Lytgeygen'],
    'http://aspro.users.ru/historian-bot/': ['bot.png', 'Historian-bot'],
    'http://pigeon.vpro.ru/caps': ['pigeon.png', 'Pigeon'],
    'http://jtalk.ustyugov.net/caps': ['jtalk.png', 'Jtalk'],
    'psto@psto.net': ['psto.png', 'Psto'],
    'http://jasmineicq.ru/caps': ['jasmine.png', 'Jasmine'],
    'http://tomclaw.com/mandarin_im/caps': ['mandarin.png', 'Mandarin'],
    'http://gabber.sourceforge.net': ['gabber.png', 'Gabber'],
    'http://www.igniterealtime.org/projects/smack/': ['xabber.png', 'Xabber'],
    'http://megafonvolga.ru/': ['megafon.png', 'Megafon'],
    'rss@isida-bot.com': ['osiris.png', 'Osiris'],
    'libpurple': ['libpurple.png', 'Libpurple'],
    'http://www.adium.im/': ['adium.png', 'Adium'],
    'http://eqo.com/': ['libpurple.png', 'Eqo'],
    'http://instantbird.com/': ['instantbird.png', 'Instantbird'],
    'https://www.meebo.com/': ['meebo.png', 'Meebo'],
    'http://opensource.palm.com/packages.html': ['palm.png', 'Palm'],
    'http://spectrum.im/': ['spectrum.png', 'Spectrum'],
147
    'http://tigase.org/messenger': ['tigase.png', 'Tigase'],
Dicson's avatar
Dicson committed
148
    'httр://sleekxmpp.com/ver/1.1.11': ['poezio.png', 'Poezio'],
Dicson's avatar
Dicson committed
149
    'http://jitsi.org' :['jitsi.png', 'Jitsi'],
150 151 152 153 154 155 156 157 158 159 160
}
libpurple_clients ={
    'adium': 'http://www.adium.im/',
    'eqo': 'http://eqo.com/',
    'finch': 'http://pidgin.im/',
    'instantbird': 'http://instantbird.com/',
    'meebo': 'https://www.meebo.com/',
    'palm': 'http://opensource.palm.com/packages.html',
    'pidgin': 'http://pidgin.im/',
    'spectrum': 'http://spectrum.im/',
    'telepathy-haze': 'http://pidgin.im/'
Dicson's avatar
Dicson committed
161
}
Dicson's avatar
Dicson committed
162

Dicson's avatar
Dicson committed
163 164 165 166
class ClientsIconsPlugin(GajimPlugin):

    @log_calls('ClientsIconsPlugin')
    def init(self):
Dicson's avatar
Dicson committed
167
        self.description = _('Shows the client icons in the roster'
Dicson's avatar
Dicson committed
168
            ' and in groupchats.\nFor icons in tooltip support, '
Dicson's avatar
Dicson committed
169
            'you need to install Gajim r14117 or above.')
Dicson's avatar
Dicson committed
170
        self.pos_list = [_('after statusicon'), _('before avatar')]
171
        self.events_handlers = {'presence-received':
Dicson's avatar
Dicson committed
172 173
                                    (ged.POSTGUI, self.presence_received),
                                'gc-presence-received':
Dicson's avatar
Dicson committed
174
                                    (ged.POSTGUI, self.gc_presence_received), }
Dicson's avatar
Dicson committed
175
        self.gui_extension_points = {
Dicson's avatar
Dicson committed
176
            'groupchat_control': (self.connect_with_groupchat_control,
177
                                    self.disconnect_from_groupchat_control),
Dicson's avatar
Dicson committed
178
            'roster_draw_contact': (self.connect_with_roster_draw_contact,
179 180
                                    self.disconnect_from_roster_draw_contact),
            'roster_tooltip_populate': (self.connect_with_roster_tooltip_populate,
181 182 183
                                    self.disconnect_from_roster_tooltip_populate),
            'gc_tooltip_populate': (self.connect_with_gc_tooltip_populate,
                                    self.disconnect_from_gc_tooltip_populate),}
Dicson's avatar
Dicson committed
184
        self.config_default_values = {
Dicson's avatar
Dicson committed
185 186
                'show_in_roster': (True, ''),
                'show_in_groupchats': (True, ''),
187
                'show_in_tooltip': (True, ''),
Dicson's avatar
Dicson committed
188
                'show_unknown_icon': (True, ''),
189
                'pos_in_list': (0, ''),
190
                'show_facebook': (True, ''),}
Dicson's avatar
Dicson committed
191

192
        self.config_dialog = ClientsIconsPluginConfigDialog(self)
193
        icon_path = os.path.join(self.local_file_path('icons'), 'unknown.png')
Dicson's avatar
Dicson committed
194
        self.default_pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path,
195
            16, 16)
196
        self.icon_cache = {}
Dicson's avatar
Dicson committed
197

198 199 200 201 202 203 204
    @log_calls('ClientsIconsPlugin')
    def connect_with_gc_tooltip_populate(self, tooltip, contact,
    vcard_table):
        if not self.config['show_in_tooltip']:
            return

        #fill clients table
Dicson's avatar
Dicson committed
205 206 207 208
        self.table = Gtk.Grid()
        self.table.insert_row(0)
        self.table.insert_row(0)
        self.table.insert_column(0)
209 210 211 212 213
        self.table.set_property('column-spacing', 2)

        caps = contact.client_caps._node
        caps_image , client_name = self.get_icon(caps, contact)
        caps_image.set_alignment(0, 0)
Dicson's avatar
Dicson committed
214
        self.table.attach(caps_image, 1, 1, 1, 1)
Dicson's avatar
Dicson committed
215
        label = Gtk.Label()
216 217
        label.set_alignment(0, 0)
        label.set_markup(client_name)
Dicson's avatar
Dicson committed
218
        self.table.attach(label, 2, 1, 1, 1)
219
        # set label
Dicson's avatar
Dicson committed
220
        label = Gtk.Label()
221 222
        label.set_alignment(0, 0)
        label.set_markup(_('Client:'))
Dicson's avatar
Dicson committed
223
        vcard_table.attach(label, 1, 100, 1,1)
224
        # set client table to tooltip
Dicson's avatar
Dicson committed
225
        vcard_table.attach(self.table, 2, 100, 1,1)
226

227
    @log_calls('ClientsIconsPlugin')
228 229 230 231
    def connect_with_roster_tooltip_populate(self, tooltip, contacts,
    vcard_table):
        if not self.config['show_in_tooltip']:
            return
232 233
        if len(contacts) == 1 and contacts[0].jid in gajim.get_our_jids():
            return
234 235
        if contacts[0].is_groupchat():
            return
Dicson's avatar
Dicson committed
236

237 238 239 240 241 242 243 244 245 246
        # put contacts in dict, where key is priority
        num_resources = 0
        contacts_dict = {}
        for contact in contacts:
            if contact.resource:
                num_resources += 1
                if contact.priority in contacts_dict:
                    contacts_dict[contact.priority].append(contact)
                else:
                    contacts_dict[contact.priority] = [contact]
247 248 249
        contact_keys = sorted(contacts_dict.keys())
        contact_keys.reverse()

Dicson's avatar
Dicson committed
250
        #fill clients table
Dicson's avatar
Dicson committed
251 252 253 254
        self.table = Gtk.Grid()
        self.table.insert_row(0)
        self.table.insert_row(0)
        self.table.insert_column(0)
Dicson's avatar
Dicson committed
255
        self.table.set_property('column-spacing', 2)
Dicson's avatar
Dicson committed
256
        first_place = 100
257

Dicson's avatar
Dicson committed
258 259 260 261
        vcard_current_row = 0
        for priority in contact_keys:
            for acontact in contacts_dict[priority]:
                caps = acontact.client_caps._node
262
                caps_image , client_name = self.get_icon(caps, acontact)
Dicson's avatar
Dicson committed
263
                caps_image.set_alignment(0, 0)
Dicson's avatar
Dicson committed
264
                self.table.attach(caps_image, 1, vcard_current_row, 1, 1)
Dicson's avatar
Dicson committed
265
                label = Gtk.Label()
Dicson's avatar
Dicson committed
266 267
                label.set_alignment(0, 0)
                label.set_markup(client_name)
Dicson's avatar
Dicson committed
268
                self.table.attach(label, 2, vcard_current_row, 1, 1)
Dicson's avatar
Dicson committed
269
                vcard_current_row += 1
270
        # set label
Dicson's avatar
Dicson committed
271
        label = Gtk.Label()
272 273 274 275 276 277 278
        label.set_alignment(0, 0)
        if num_resources > 1:
            label.set_markup(_('Clients:'))
        else:
            if contact.show == 'offline':
                return
            label.set_markup(_('Client:'))
Dicson's avatar
Dicson committed
279
        vcard_table.attach(label, 1, first_place,  1, 1)
280
        # set clients table to tooltip
Dicson's avatar
Dicson committed
281
        vcard_table.attach(self.table, 2, first_place, 1, 1)
282 283 284

    def get_icon(self, caps, contact=None):
        if not caps:
285
            return Gtk.Image.new_from_pixbuf(self.default_pixbuf), _('Unknown')
286

Dicson's avatar
Dicson committed
287
        if 'pidgin.im/' in caps:
288 289 290 291 292
            caps = 'libpurple'
            for client in libpurple_clients:
                if client in contact.resource.lower():
                    caps = libpurple_clients[client]

Dicson's avatar
Dicson committed
293 294 295
        if 'sleekxmpp.com'in caps:
            caps = 'httр://sleekxmpp.com/ver/1.1.11'

Dicson's avatar
Dicson committed
296 297 298 299
        caps_from_jid = self.check_jid(contact.jid)
        if caps_from_jid:
            caps = caps_from_jid

300 301 302
        caps_ = caps.split('#')[0].split()
        if caps_:
            client_icon = clients.get(caps_[0].split()[0], (None,))[0]
Dicson's avatar
Dicson committed
303
            client_name = clients.get(caps_[0].split()[0], ('', _('Unknown')))[1]
304 305 306 307
        else:
            client_icon = None

        if not client_icon:
308
            return Gtk.Image.new_from_pixbuf(self.default_pixbuf), _('Unknown')
309 310 311 312
        else:
            icon_path = os.path.join(self.local_file_path('icons'),
                client_icon)
            if icon_path in self.icon_cache:
313
                return Gtk.Image.new_from_pixbuf(self.icon_cache[icon_path]), \
314 315
                    client_name
            else:
Dicson's avatar
Dicson committed
316
                pb = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, 16, 16)
Dicson's avatar
Dicson committed
317
                self.icon_cache[icon_path] = pb
318
                return Gtk.Image.new_from_pixbuf(pb), client_name
319 320 321 322 323

    @log_calls('ClientsIconsPlugin')
    def disconnect_from_roster_tooltip_populate(self, tooltip, contacts,
    vcard_table):
        pass
Dicson's avatar
Dicson committed
324

Dicson's avatar
Dicson committed
325 326 327 328 329 330 331 332 333 334 335 336 337 338
    def check_jid(self, jid):
        caps = None
        if 'facebook.com' in jid and self.config['show_facebook']:
            caps = 'facebook.com'
        elif '@vk.com' in jid and self.config['show_facebook']:
            caps = 'vk.com'
        elif jid == 'juick@juick.com':
            caps = 'http://juick.com/caps'
        elif jid == 'psto@psto.net':
            caps = 'psto@psto.net'
        elif jid == 'rss@isida-bot.com':
            caps = 'rss@isida-bot.com'
        return caps

339
    @log_calls('ClientsIconsPlugin')
340 341 342 343 344 345 346 347 348
    def connect_with_roster_draw_contact(self, roster, jid, account, contact):
        if not self.active:
            return
        if not self.config['show_in_roster']:
            return
        child_iters = roster._get_contact_iter(jid, account, contact,
            roster.model)
        if not child_iters:
            return
349 350 351 352 353 354 355
        for iter_ in child_iters:
            if roster.model[iter_][self.renderer_num] is None:
                caps = contact.client_caps._node
                if not caps:
                    caps = self.check_jid(jid)
                self.set_icon(roster.model, iter_, self.renderer_num,
                    caps)
356

Dicson's avatar
Dicson committed
357 358 359
    @log_calls('ClientsIconsPlugin')
    def connect_with_groupchat_control(self, chat_control):
        chat_control.nb_ext_renderers += 1
Dicson's avatar
Dicson committed
360
        chat_control.columns += [GdkPixbuf.Pixbuf]
Dicson's avatar
Dicson committed
361 362
        self.groupchats_tree_is_transformed = True
        self.chat_control = chat_control
Dicson's avatar
Dicson committed
363
        col = Gtk.TreeViewColumn()
Dicson's avatar
Dicson committed
364
        self.muc_renderer_num = 4 + chat_control.nb_ext_renderers
Dicson's avatar
Dicson committed
365
        client_icon_rend = ('client_icon', Gtk.CellRendererPixbuf(), False,
Dicson's avatar
Dicson committed
366 367 368 369 370
                'pixbuf', self.muc_renderer_num,
                self.tree_cell_data_func, chat_control)
        # remove old column
        chat_control.list_treeview.remove_column(
            chat_control.list_treeview.get_column(0))
371 372 373
        # add new renderer in renderers list
        position_list = ['name', 'avatar']
        position = position_list[self.config['pos_in_list']]
Dicson's avatar
Dicson committed
374
        for renderer in chat_control.renderers_list:
375
            if renderer[0] == position:
Dicson's avatar
Dicson committed
376 377 378 379 380 381 382
                break
        num = chat_control.renderers_list.index(renderer)
        chat_control.renderers_list.insert(num, client_icon_rend)
        # fill and append column
        chat_control.fill_column(col)
        chat_control.list_treeview.insert_column(col, 0)
        # redraw roster
Dicson's avatar
Dicson committed
383
        chat_control.model = Gtk.TreeStore(*chat_control.columns)
384
        chat_control.model.set_sort_func(1, chat_control.tree_compare_iters)
Dicson's avatar
Dicson committed
385
        chat_control.model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
386
        chat_control.list_treeview.set_model(chat_control.model)
387 388 389 390 391 392 393 394 395 396 397
        # draw roster
        for nick in gajim.contacts.get_nick_list(chat_control.account,
            chat_control.room_jid):
            gc_contact = gajim.contacts.get_gc_contact(chat_control.account,
                chat_control.room_jid, nick)
            iter_ = chat_control.add_contact_to_roster(nick, gc_contact.show,
                gc_contact.role, gc_contact.affiliation, gc_contact.status,
                gc_contact.jid)
            if not self.config['show_in_groupchats']:
                continue
            caps = gc_contact.client_caps._node
398 399
            self.set_icon(chat_control.model, iter_, self.muc_renderer_num,
                caps)
400 401 402
        chat_control.draw_all_roles()
        # Recalculate column width for ellipsizin
        chat_control.list_treeview.columns_autosize()
Dicson's avatar
Dicson committed
403 404

    @log_calls('ClientsIconsPlugin')
405 406 407 408
    def disconnect_from_groupchat_control(self, gc_control):
        gc_control.nb_ext_renderers -= 1
        col = gc_control.list_treeview.get_column(0)
        gc_control.list_treeview.remove_column(col)
Dicson's avatar
Dicson committed
409
        col = Gtk.TreeViewColumn()
410 411 412 413 414 415 416
        for renderer in gc_control.renderers_list:
            if renderer[0] == 'client_icon':
                gc_control.renderers_list.remove(renderer)
                break
        gc_control.fill_column(col)
        gc_control.list_treeview.insert_column(col, 0)
        gc_control.columns = gc_control.columns[:self.muc_renderer_num] + \
Dicson's avatar
Dicson committed
417
            gc_control.columns[self.muc_renderer_num + 1:]
Dicson's avatar
Dicson committed
418
        gc_control.model = Gtk.TreeStore(*gc_control.columns)
Dicson's avatar
Dicson committed
419
        gc_control.model.set_sort_func(1, gc_control.tree_compare_iters)
Dicson's avatar
Dicson committed
420
        gc_control.model.set_sort_column_id(1, Gtk.SortType.ASCENDING)
Dicson's avatar
Dicson committed
421
        gc_control.list_treeview.set_model(gc_control.model)
422
        gc_control.draw_roster()
Dicson's avatar
Dicson committed
423

424 425 426 427 428
    @log_calls('ClientsIconsPlugin')
    def disconnect_from_roster_draw_contact(self, roster, jid, account,
        contact):
        pass

Dicson's avatar
Dicson committed
429 430
    @log_calls('ClientsIconsPlugin')
    def activate(self):
Dicson's avatar
Dicson committed
431
        self.active = None
Dicson's avatar
Dicson committed
432
        roster = gajim.interface.roster
Dicson's avatar
Dicson committed
433
        col = Gtk.TreeViewColumn()
Dicson's avatar
Dicson committed
434 435
        roster.nb_ext_renderers += 1
        self.renderer_num = 10 + roster.nb_ext_renderers
Dicson's avatar
Dicson committed
436
        self.renderer = Gtk.CellRendererPixbuf()
Dicson's avatar
Dicson committed
437
        client_icon_rend = ('client_icon', self.renderer, False,
Dicson's avatar
Dicson committed
438 439 440 441
                'pixbuf', self.renderer_num,
                roster._fill_pep_pixbuf_renderer, self.renderer_num)
        # remove old column
        roster.tree.remove_column(roster.tree.get_column(0))
442 443 444
        # add new renderer in renderers list
        position_list = ['name', 'avatar']
        position = position_list[self.config['pos_in_list']]
Dicson's avatar
Dicson committed
445
        for renderer in roster.renderers_list:
446
            if renderer[0] == position:
Dicson's avatar
Dicson committed
447 448
                break
        num = roster.renderers_list.index(renderer)
Dicson's avatar
Dicson committed
449
        roster.renderers_list.insert(num, client_icon_rend)
Dicson's avatar
Dicson committed
450 451 452 453
        # fill and append column
        roster.fill_column(col)
        roster.tree.insert_column(col, 0)
        # redraw roster
Dicson's avatar
Dicson committed
454
        roster.columns += [GdkPixbuf.Pixbuf]
Dicson's avatar
Dicson committed
455
        self.active = True
Dicson's avatar
Dicson committed
456 457 458 459
        roster.setup_and_draw_roster()

    @log_calls('ClientsIconsPlugin')
    def deactivate(self):
Dicson's avatar
Dicson committed
460
        self.active = None
Dicson's avatar
Dicson committed
461 462 463 464
        roster = gajim.interface.roster
        roster.nb_ext_renderers -= 1
        col = roster.tree.get_column(0)
        roster.tree.remove_column(col)
Dicson's avatar
Dicson committed
465
        col = Gtk.TreeViewColumn()
Dicson's avatar
Dicson committed
466 467 468 469 470 471
        for renderer in roster.renderers_list:
            if renderer[0] == 'client_icon':
                roster.renderers_list.remove(renderer)
                break
        roster.fill_column(col)
        roster.tree.insert_column(col, 0)
472
        roster.columns = roster.columns[:self.renderer_num] + roster.columns[
Dicson's avatar
Dicson committed
473
            self.renderer_num + 1:]
Dicson's avatar
Dicson committed
474 475
        roster.setup_and_draw_roster()

476
    def presence_received(self, iq_obj):
Dicson's avatar
Dicson committed
477 478
        if not self.config['show_in_roster']:
            return
Dicson's avatar
Dicson committed
479
        roster = gajim.interface.roster
480 481 482 483
        contact = gajim.contacts.get_contact_with_highest_priority(
            iq_obj.conn.name, iq_obj.jid)
        if not contact:
            return
484

Dicson's avatar
Dicson committed
485 486 487 488
        if iq_obj.resource == 'local':
            # zeroconf
            return

489 490 491
        iters = roster._get_contact_iter(iq_obj.jid, iq_obj.conn.name, contact,
            roster.model)
        iter_ = iters[0]
492 493 494 495 496

        if contact.show == 'error':
            self.set_icon(roster.model, iter_, self.renderer_num, None)
            return

Dicson's avatar
Dicson committed
497
        if contact != iq_obj.contact:
Dicson's avatar
Dicson committed
498
            # higest contact changed
499 500
            if roster.model[iter_][self.renderer_num] is not None:
                caps = contact.client_caps._node
Dicson's avatar
Dicson committed
501 502 503
                if caps:
                    self.set_icon(roster.model, iter_, self.renderer_num, caps)
                    return
Dicson's avatar
Dicson committed
504
        caps = None
Dicson's avatar
Dicson committed
505
        tag = iq_obj.stanza.getTags('c')
Dicson's avatar
Dicson committed
506 507
        if tag:
            caps = tag[0].getAttr('node')
Dicson's avatar
Dicson committed
508
            if caps:
Dicson's avatar
Dicson committed
509
                if 'pidgin.im/' in caps:
Dicson's avatar
Dicson committed
510 511 512 513
                    caps = 'libpurple'
                    for client in libpurple_clients:
                        if client in contact.resource.lower():
                            caps = libpurple_clients[client]
Dicson's avatar
Dicson committed
514 515
                if 'sleekxmpp.com'in caps:
                    caps = 'httр://sleekxmpp.com/ver/1.1.11'
Dicson's avatar
Dicson committed
516 517 518 519 520

        caps_from_jid = self.check_jid(iq_obj.jid)
        if caps_from_jid:
            caps = caps_from_jid

521 522
        for iter_ in iters:
            self.set_icon(roster.model, iter_, self.renderer_num, caps)
Dicson's avatar
Dicson committed
523 524

    def gc_presence_received(self, iq_obj):
Dicson's avatar
Dicson committed
525 526
        if not self.config['show_in_groupchats']:
            return
Dicson's avatar
Dicson committed
527
        contact = gajim.contacts.get_gc_contact(iq_obj.conn.name,
Dicson's avatar
Dicson committed
528
            iq_obj.presence_obj.jid, iq_obj.nick)
Dicson's avatar
Dicson committed
529 530 531
        if not contact:
            return
        caps = None
Dicson's avatar
Dicson committed
532
        tag = iq_obj.stanza.getTags('c')
Dicson's avatar
Dicson committed
533 534
        if tag:
            caps = tag[0].getAttr('node')
Dicson's avatar
Dicson committed
535
            if 'pidgin.im/' in caps:
536
                caps = 'libpurple'
Dicson's avatar
Dicson committed
537 538
            if 'sleekxmpp.com' in caps:
                caps = 'httр://sleekxmpp.com/ver/1.1.11'
Dicson's avatar
Dicson committed
539
        iter_ = iq_obj.gc_control.get_contact_iter(iq_obj.nick)
540
        model = iq_obj.gc_control.model
Dicson's avatar
Dicson committed
541 542
        if model[iter_][self.muc_renderer_num] is not None:
            return
543 544 545
        self.set_icon(model, iter_, self.muc_renderer_num, caps)

    def set_icon(self, model, iter_, pos, caps):
Dicson's avatar
Dicson committed
546
        if not caps:
Dicson's avatar
Dicson committed
547
            if self.config['show_unknown_icon']:
548
                model[iter_][pos] = self.default_pixbuf
Dicson's avatar
Dicson committed
549
            return
Dicson's avatar
Dicson committed
550 551 552

        caps_ = caps.split('#')[0].split()
        if caps_:
553
            client_icon = clients.get(caps_[0].split()[0], (None,))[0]
Dicson's avatar
Dicson committed
554 555 556
        else:
            client_icon = None

Dicson's avatar
Dicson committed
557 558
        if not client_icon:
            if self.config['show_unknown_icon']:
559
                model[iter_][pos] = self.default_pixbuf
Dicson's avatar
Dicson committed
560 561 562
        else:
            icon_path = os.path.join(self.local_file_path('icons'),
                client_icon)
563 564 565
            if icon_path in self.icon_cache:
                model[iter_][pos] = self.icon_cache[icon_path]
            else:
Dicson's avatar
Dicson committed
566
                pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, 16, 16)
567 568
                model[iter_][pos] = pixbuf
                self.icon_cache[icon_path] = pixbuf
Dicson's avatar
Dicson committed
569 570 571 572 573

    def tree_cell_data_func(self, column, renderer, model, iter_, control):
        if not model.iter_parent(iter_):
            renderer.set_property('visible', False)
            return
574 575
        elif model[iter_][self.muc_renderer_num]:
            renderer.set_property('visible', True)
Dicson's avatar
Dicson committed
576 577

        contact = gajim.contacts.get_gc_contact(control.account,
Dicson's avatar
Dicson committed
578
            control.room_jid, model[iter_][1])
Dicson's avatar
Dicson committed
579 580 581 582 583 584 585 586 587 588
        if not contact:
            return

        bgcolor = gajim.config.get_per('themes', gajim.config.get(
            'roster_theme'), 'contactbgcolor')
        if bgcolor:
            renderer.set_property('cell-background', bgcolor)
        else:
            renderer.set_property('cell-background', None)
        renderer.set_property('width', 16)
Dicson's avatar
Dicson committed
589

590 591 592 593 594
    @log_calls('ClientsIconsPlugin')
    def disconnect_from_gc_tooltip_populate(self, tooltip, contact,
    vcard_table):
        pass

Dicson's avatar
Dicson committed
595

Dicson's avatar
Dicson committed
596 597
class ClientsIconsPluginConfigDialog(GajimPluginConfigDialog):
    def init(self):
Dicson's avatar
Dicson committed
598
        self.Gtk_BUILDER_FILE_PATH = self.plugin.local_file_path(
Dicson's avatar
Dicson committed
599
                'config_dialog.ui')
Dicson's avatar
Dicson committed
600
        self.xml = Gtk.Builder()
601
        self.xml.set_translation_domain('gajim_plugins')
Dicson's avatar
Dicson committed
602
        self.xml.add_objects_from_file(self.Gtk_BUILDER_FILE_PATH,
Dicson's avatar
Dicson committed
603 604
                ['vbox1'])
        vbox = self.xml.get_object('vbox1')
Dicson's avatar
Dicson committed
605
        self.get_child().pack_start(vbox, True, True, 0)
606
        self.combo = self.xml.get_object('combobox1')
Dicson's avatar
Dicson committed
607
        self.liststore = Gtk.ListStore(str)
608
        self.combo.set_model(self.liststore)
Dicson's avatar
Dicson committed
609
        cellrenderer = Gtk.CellRendererText()
610 611 612 613 614 615
        self.combo.pack_start(cellrenderer, True)
        self.combo.add_attribute(cellrenderer, 'text', 0)

        for item in self.plugin.pos_list:
            self.liststore.append((item,))
        self.combo.set_active(self.plugin.config['pos_in_list'])
Dicson's avatar
Dicson committed
616

617 618 619 620 621 622
        self.xml.get_object('show_in_roster').set_active(
            self.plugin.config['show_in_roster'])
        self.xml.get_object('show_in_groupchats').set_active(
            self.plugin.config['show_in_groupchats'])
        self.xml.get_object('show_unknown_icon').set_active(
            self.plugin.config['show_unknown_icon'])
623 624
        self.xml.get_object('show_facebook').set_active(
            self.plugin.config['show_facebook'])
625 626
        self.xml.get_object('show_in_tooltip').set_active(
            self.plugin.config['show_in_tooltip'])
627

628 629 630
        self.xml.connect_signals(self)

    def redraw_all(self):
631 632
        self.plugin.deactivate()
        self.plugin.activate()
633 634 635 636
        for gc_control in gajim.interface.msg_win_mgr.get_controls('gc'):
            self.plugin.disconnect_from_groupchat_control(gc_control)
        for gc_control in gajim.interface.msg_win_mgr.get_controls('gc'):
            self.plugin.connect_with_groupchat_control(gc_control)
Dicson's avatar
Dicson committed
637 638 639

    def on_show_in_roster_toggled(self, widget):
        self.plugin.config['show_in_roster'] = widget.get_active()
640 641
        self.plugin.deactivate()
        self.plugin.activate()
Dicson's avatar
Dicson committed
642

643 644 645
    def on_show_in_tooltip_toggled(self, widget):
        self.plugin.config['show_in_tooltip'] = widget.get_active()

Dicson's avatar
Dicson committed
646 647
    def on_show_in_groupchats_toggled(self, widget):
        self.plugin.config['show_in_groupchats'] = widget.get_active()
648 649 650 651
        for gc_control in gajim.interface.msg_win_mgr.get_controls('gc'):
            self.plugin.disconnect_from_groupchat_control(gc_control)
        for gc_control in gajim.interface.msg_win_mgr.get_controls('gc'):
            self.plugin.connect_with_groupchat_control(gc_control)
Dicson's avatar
Dicson committed
652 653 654

    def on_show_unknown_icon_toggled(self, widget):
        self.plugin.config['show_unknown_icon'] = widget.get_active()
655
        self.redraw_all()
656

657 658 659 660
    def on_show_facebook_toggled(self, widget):
        self.plugin.config['show_facebook'] = widget.get_active()
        self.redraw_all()

661 662
    def on_combobox1_changed(self, widget):
        self.plugin.config['pos_in_list'] = widget.get_active()
663
        self.redraw_all()