root/branches/gajim_0.8.2/src/roster_window.py

Revision 3440, 77.1 kB (checked in by nk, 3 years ago)

fix in roster_window.py

Line 
1##      roster_window.py
2##
3## Gajim Team:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Vincent Hanquez <tab@snarc.org>
6##      - Nikos Kouremenos <kourem@gmail.com>
7##
8##      Copyright (C) 2003-2005 Gajim Team
9##
10## This program is free software; you can redistribute it and/or modify
11## it under the terms of the GNU General Public License as published
12## by the Free Software Foundation; version 2 only.
13##
14## This program is distributed in the hope that it will be useful,
15## but WITHOUT ANY WARRANTY; without even the implied warranty of
16## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17## GNU General Public License for more details.
18##
19
20import gtk
21import gtk.glade
22import pango
23import gobject
24import os
25import time
26
27import common.sleepy
28import tabbed_chat_window
29import groupchat_window
30import history_window
31import dialogs
32import config
33import gtkgui_helpers
34import cell_renderer_image
35import tooltips
36
37from gajim import Contact
38from common import gajim
39from common import helpers
40from common import i18n
41
42_ = i18n._
43APP = i18n.APP
44gtk.glade.bindtextdomain(APP, i18n.DIR)
45gtk.glade.textdomain(APP)
46
47GTKGUI_GLADE = 'gtkgui.glade'
48
49class RosterWindow:
50        '''Class for main window of gtkgui plugin'''
51
52        def get_account_iter(self, name):
53                if self.regroup or len(gajim.connections) == 1:
54                        return
55                model = self.tree.get_model()
56                if model is None:
57                        return
58                account = model.get_iter_root()
59                while account:
60                        account_name = model.get_value(account, 3).decode('utf-8')
61                        if name == account_name:
62                                break
63                        account = model.iter_next(account)
64                return account
65
66        def get_group_iter(self, name, account):
67                model = self.tree.get_model()
68                root = self.get_account_iter(account)
69                group = model.iter_children(root)
70                while group:
71                        group_name = model.get_value(group, 3).decode('utf-8')
72                        if name == group_name:
73                                break
74                        group = model.iter_next(group)
75                return group
76
77        def get_contact_iter(self, jid, account):
78                model = self.tree.get_model()
79                acct = self.get_account_iter(account)
80                found = []
81                fin = False
82                group = model.iter_children(acct)
83                while group:
84                        user = model.iter_children(group)
85                        while user:
86                                if jid == model.get_value(user, 3).decode('utf-8'):
87                                        found.append(user)
88                                user = model.iter_next(user)
89                        group = model.iter_next(group)
90                return found
91
92        def add_account_to_roster(self, account):
93                if self.regroup or len(gajim.connections) == 1:
94                        return
95                model = self.tree.get_model()
96                if self.get_account_iter(account):
97                        return
98                statuss = ['offline', 'connecting', 'online', 'chat',
99                        'away', 'xa', 'dnd', 'invisible']
100                status = statuss[gajim.connections[account].connected]
101
102                tls_pixbuf = None
103                if gajim.con_types.has_key(account) and \
104                        gajim.con_types[account] in ('tls', 'ssl'):
105                        tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION,
106                                gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock
107
108                model.append(None, [self.jabber_state_images[status], account,
109                        'account', account, account, False, tls_pixbuf])
110
111        def remove_newly_added(self, jid, account):
112                if jid in gajim.newly_added[account]:
113                        gajim.newly_added[account].remove(jid)
114                        self.draw_contact(jid, account)
115
116        def add_contact_to_roster(self, jid, account):
117                '''Add a contact to the roster and add groups if they aren't in roster'''
118                showOffline = gajim.config.get('showoffline')
119                if not gajim.contacts[account].has_key(jid):
120                        return
121                users = gajim.contacts[account][jid]
122                user = users[0]
123                if user.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
124                        user.groups = [_('Transports')]
125                elif user.groups == []:
126                        user.groups.append(_('General'))
127
128                if user.show in ('offline', 'error') and \
129                   not showOffline and (not _('Transports') in user.groups or \
130                        gajim.connections[account].connected < 2) and \
131                   not gajim.awaiting_messages[account].has_key(user.jid):
132                        return
133
134                model = self.tree.get_model()
135                for g in user.groups:
136                        iterG = self.get_group_iter(g, account)
137                        if not iterG:
138                                IterAcct = self.get_account_iter(account)
139                                iterG = model.append(IterAcct, [self.jabber_state_images['closed'],
140                                        g, 'group', g, account, False, None])
141                        if not gajim.groups[account].has_key(g): #It can probably never append
142                                if account + g in self.collapsed_rows:
143                                        ishidden = False
144                                else:
145                                        ishidden = True
146                                gajim.groups[account][g] = { 'expand': ishidden }
147                        if not account in self.collapsed_rows and \
148                           not gajim.config.get('mergeaccounts') and \
149                                len(gajim.connections) > 1: #if one acct: don't show acct line
150                                self.tree.expand_row((model.get_path(iterG)[0]), False)
151
152                        typestr = 'contact'
153                        if g == _('Transports'):
154                                typestr = 'agent'
155
156                        model.append(iterG, [self.jabber_state_images[user.show], user.name,
157                                        typestr, user.jid, account, False, None]) # FIXME None --> avatar
158                       
159                        if gajim.groups[account][g]['expand']:
160                                self.tree.expand_row(model.get_path(iterG), False)
161                self.draw_contact(jid, account)
162       
163        def really_remove_contact(self, user, account):
164                if user.jid in gajim.newly_added[account]:
165                        return
166                if user.jid.find('@') < 1 and gajim.connections[account].connected > 1: # It's an agent
167                        return
168                if user.jid in gajim.to_be_removed[account]:
169                        gajim.to_be_removed[account].remove(user.jid)
170                if gajim.config.get('showoffline'):
171                        self.draw_contact(user.jid, account)
172                        return
173                self.remove_contact(user, account)
174       
175        def remove_contact(self, user, account):
176                '''Remove a user from the roster'''
177                if user.jid in gajim.to_be_removed[account]:
178                        return
179                model = self.tree.get_model()
180                for i in self.get_contact_iter(user.jid, account):
181                        parent_i = model.iter_parent(i)
182                        group = model.get_value(parent_i, 3).decode('utf-8')
183                        model.remove(i)
184                        if model.iter_n_children(parent_i) == 0:
185                                model.remove(parent_i)
186                                # We need to check all contacts, even offline contacts
187                                for jid in gajim.contacts[account]:
188                                        if group in gajim.contacts[account][jid][0].groups:
189                                                break
190                                else:
191                                        del gajim.groups[account][group]
192
193        def get_appropriate_state_images(self, jid):
194                '''check jid and return the appropriate state images dict'''
195                transport = gajim.get_transport_name_from_jid(jid)
196                if transport:
197                        return self.transports_state_images[transport]
198                return self.jabber_state_images
199
200        def draw_contact(self, jid, account):
201                '''draw the correct state image and name'''
202                model = self.tree.get_model()
203                iters = self.get_contact_iter(jid, account)
204                if len(iters) == 0:
205                        return
206                contact_instances = gajim.get_contact_instances_from_jid(account, jid)
207                contact = gajim.get_highest_prio_contact_from_contacts(contact_instances)
208                name = contact.name
209                if len(contact_instances) > 1:
210                        name += ' (' + unicode(len(contact_instances)) + ')'
211
212                state_images = self.get_appropriate_state_images(jid)
213                if gajim.awaiting_messages[account].has_key(jid):
214                        img = state_images['message']
215                elif jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
216                        img = state_images[contact.show]                                       
217                else:
218                        if contact.sub == 'both':
219                                img = state_images[contact.show]
220                        else:
221                                if contact.ask == 'subscribe':
222                                        img = state_images['requested']
223                                else:
224                                        transport = gajim.get_transport_name_from_jid(jid)
225                                        if transport and state_images.has_key(contact.show):
226                                                img = state_images[contact.show]
227                                        else:
228                                                img = state_images['not in the roster']
229                for iter in iters:
230                        model.set_value(iter, 0, img)
231                        model.set_value(iter, 1, name)
232                        #FIXME: add avatar
233
234        def join_gc_room(self, account, room_jid, nick, password):
235                if room_jid in self.plugin.windows[account]['gc'] and \
236                gajim.gc_connected[account][room_jid]:
237                        dialogs.ErrorDialog(_('You are already in room %s') %room_jid
238                                ).get_response()
239                        return
240                room, server = room_jid.split('@')
241                if not room_jid in self.plugin.windows[account]['gc']:
242                        self.new_room(room_jid, nick, account)
243                self.plugin.windows[account]['gc'][room_jid].set_active_tab(room_jid)
244                self.plugin.windows[account]['gc'][room_jid].window.present()
245                gajim.connections[account].join_gc(nick, room, server, password)
246
247        def on_bookmark_menuitem_activate(self, widget, account, bookmark):
248                self.join_gc_room(account, bookmark['jid'], bookmark['nick'],
249                        bookmark['password'])
250
251        def on_bm_header_changed_state(self, widget, event):
252                widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state
253
254        def on_send_server_message_menuitem_activate(self, widget, account):
255                server = gajim.config.get_per('accounts', account, 'hostname')
256                server += '/announce/online'
257                dialogs.SingleMessageWindow(self.plugin, account, server, 'send')
258
259        def on_xml_console_menuitem_activate(self, widget, account):
260                if self.plugin.windows[account].has_key('xml_console'):
261                        self.plugin.windows[account]['xml_console'].window.present()
262                else:
263                        self.plugin.windows[account]['xml_console'].window.show_all()
264
265        def on_set_motd_menuitem_activate(self, widget, account):
266                server = gajim.config.get_per('accounts', account, 'hostname')
267                server += '/announce/motd'
268                dialogs.SingleMessageWindow(self.plugin, account, server, 'send')
269
270        def on_update_motd_menuitem_activate(self, widget, account):
271                server = gajim.config.get_per('accounts', account, 'hostname')
272                server += '/announce/motd/update'
273                dialogs.SingleMessageWindow(self.plugin, account, server, 'send')
274
275        def on_delete_motd_menuitem_activate(self, widget, account):
276                server = gajim.config.get_per('accounts', account, 'hostname')
277                server += '/announce/motd/delete'
278                gajim.connections[account].send_motd(server)   
279       
280        def on_online_users_menuitem_activate(self, widget, account):
281                pass #FIXME: impement disco in users for 0.9
282
283        def get_and_connect_advanced_menuitem_menu(self, account):
284                xml = gtk.glade.XML(GTKGUI_GLADE, 'advanced_menuitem_menu', APP)
285                advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu')
286               
287                send_single_message_menuitem = xml.get_widget(
288                        'send_single_message_menuitem')
289                xml_console_menuitem = xml.get_widget('xml_console_menuitem')
290                administrator_menuitem = xml.get_widget('administrator_menuitem')
291                online_users_menuitem = xml.get_widget('online_users_menuitem')
292                send_server_message_menuitem = xml.get_widget(
293                        'send_server_message_menuitem')
294                set_motd_menuitem = xml.get_widget('set_motd_menuitem')
295                update_motd_menuitem = xml.get_widget('update_motd_menuitem')
296                delete_motd_menuitem = xml.get_widget('delete_motd_menuitem')
297               
298                send_single_message_menuitem.connect('activate',
299                        self.on_send_single_message_menuitem_activate, account)
300
301                xml_console_menuitem.connect('activate',
302                        self.on_xml_console_menuitem_activate, account)
303
304                #FIXME: 0.9 should have this: it does disco in the place where users are
305                online_users_menuitem.set_no_show_all(True)
306                online_users_menuitem.hide()
307                online_users_menuitem.connect('activate',
308                        self.on_online_users_menuitem_activate, account)
309
310                send_server_message_menuitem.connect('activate',
311                        self.on_send_server_message_menuitem_activate, account)
312
313                set_motd_menuitem.connect('activate',
314                        self.on_set_motd_menuitem_activate, account)
315
316                update_motd_menuitem.connect('activate',
317                        self.on_update_motd_menuitem_activate, account)
318               
319                delete_motd_menuitem.connect('activate',
320                        self.on_delete_motd_menuitem_activate, account)
321               
322                advanced_menuitem_menu.show_all()
323                       
324                return advanced_menuitem_menu
325
326        def make_menu(self):
327                '''create the main window's menus'''
328                new_message_menuitem = self.xml.get_widget('new_message_menuitem')
329                join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
330                add_new_contact_menuitem  = self.xml.get_widget('add_new_contact_menuitem')
331                service_disco_menuitem  = self.xml.get_widget('service_disco_menuitem')
332                advanced_menuitem = self.xml.get_widget('advanced_menuitem')
333                show_offline_contacts_menuitem = self.xml.get_widget(
334                        'show_offline_contacts_menuitem')
335                profile_avatar_menuitem = self.xml.get_widget('profile_avatar_menuitem')
336               
337                # make it sensitive. it is insensitive only if no accounts are *available*
338                advanced_menuitem.set_sensitive(True)
339
340
341                if self.add_new_contact_handler_id:
342                        add_new_contact_menuitem.handler_disconnect(
343                                self.add_new_contact_handler_id)
344                        self.add_new_contact_handler_id = None
345
346                if self.service_disco_handler_id:
347                        service_disco_menuitem.handler_disconnect(
348                                self.service_disco_handler_id)
349                        self.service_disco_handler_id = None
350                       
351                if self.new_message_menuitem_handler_id:
352                        new_message_menuitem.handler_disconnect(
353                                self.new_message_menuitem_handler_id)
354                        self.new_message_menuitem_handler_id = None
355                       
356                #remove the existing submenus
357                add_new_contact_menuitem.remove_submenu()
358                service_disco_menuitem.remove_submenu()
359                join_gc_menuitem.remove_submenu()
360                new_message_menuitem.remove_submenu()
361                advanced_menuitem.remove_submenu()
362
363                #remove the existing accelerator
364                if self.have_new_message_accel:
365                        ag = gtk.accel_groups_from_object(self.window)[0]
366                        new_message_menuitem.remove_accelerator(ag, gtk.keysyms.n,
367                                gtk.gdk.CONTROL_MASK)
368                        self.have_new_message_accel = False
369
370                #join gc
371                sub_menu = gtk.Menu()
372                join_gc_menuitem.set_submenu(sub_menu)
373                at_least_one_account_connected = False
374                multiple_accounts = len(gajim.connections) >= 2 #FIXME: stop using bool var here
375                for account in gajim.connections:
376                        if gajim.connections[account].connected <= 1: #if offline or connecting
377                                continue
378                        if not at_least_one_account_connected:
379                                at_least_one_account_connected = True
380                        if multiple_accounts:
381                                label = gtk.Label()
382                                label.set_markup('<u>' + account.upper() +'</u>')
383                                item = gtk.MenuItem()
384                                item.add(label)
385                                item.connect('state-changed', self.on_bm_header_changed_state)
386                                sub_menu.append(item)
387                       
388                        item = gtk.MenuItem(_('New _Room'))
389                        item.connect('activate', self.on_join_gc_activate, account)
390                        sub_menu.append(item)
391
392                        for bookmark in gajim.connections[account].bookmarks:
393                                item = gtk.MenuItem(bookmark['name'])
394                                item.connect('activate', self.on_bookmark_menuitem_activate,
395                                        account, bookmark)
396                                sub_menu.append(item)
397
398                if at_least_one_account_connected: #FIXME: move this below where we do this check
399                        #and make sure it works
400                        newitem = gtk.SeparatorMenuItem() # seperator
401                        sub_menu.append(newitem)
402               
403                        newitem = gtk.ImageMenuItem(_('Manage Bookmarks...'))
404                        img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
405                                gtk.ICON_SIZE_MENU)
406                        newitem.set_image(img)
407                        newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
408                        sub_menu.append(newitem)
409                        sub_menu.show_all()
410
411                if multiple_accounts: # 2 or more accounts? make submenus
412                        #add
413                        sub_menu = gtk.Menu()
414                        for account in gajim.connections:
415                                if gajim.connections[account].connected <= 1:
416                                        #if offline or connecting
417                                        continue
418                                item = gtk.MenuItem(_('to %s account') % account)
419                                sub_menu.append(item)
420                                item.connect('activate', self.on_add_new_contact, account)
421                        add_new_contact_menuitem.set_submenu(sub_menu)
422                        sub_menu.show_all()
423                       
424                        #disco
425                        sub_menu = gtk.Menu()
426                        for account in gajim.connections:
427                                if gajim.connections[account].connected <= 1:
428                                        #if offline or connecting
429                                        continue
430                                item = gtk.MenuItem(_('using %s account') % account)
431                                sub_menu.append(item)
432                                item.connect('activate', self.on_service_disco_menuitem_activate,
433                                        account)
434
435                        service_disco_menuitem.set_submenu(sub_menu)
436                        sub_menu.show_all()
437                       
438                        #new message
439                        sub_menu = gtk.Menu()
440                        for account in gajim.connections:
441                                if gajim.connections[account].connected <= 1:
442                                        #if offline or connecting
443                                        continue
444                                our_jid = gajim.config.get_per('accounts', account, 'name') + '@' +\
445                                        gajim.config.get_per('accounts', account, 'hostname')
446                                item = gtk.MenuItem(_('as %s') % our_jid)
447                                sub_menu.append(item)
448                                item.connect('activate', self.on_new_message_menuitem_activate, 
449                                                                        account)
450       
451                        new_message_menuitem.set_submenu(sub_menu)
452                        sub_menu.show_all()
453                       
454                        #Advanced Actions
455                        sub_menu = gtk.Menu()
456                        for account in gajim.connections:
457                                item = gtk.MenuItem(_('for account %s') % account)
458                                sub_menu.append(item)
459                                advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
460                                        account)
461                                item.set_submenu(advanced_menuitem_menu)
462                       
463                        advanced_menuitem.set_submenu(sub_menu)
464                        sub_menu.show_all()
465                       
466                else:
467                        if len(gajim.connections) == 1: # user has only one account
468                                #add
469                                if not self.add_new_contact_handler_id:
470                                        self.add_new_contact_handler_id = add_new_contact_menuitem.connect(
471                                                'activate', self.on_add_new_contact, gajim.connections.keys()[0])
472                                #disco
473                                if not self.service_disco_handler_id:
474                                        self.service_disco_handler_id = service_disco_menuitem.connect( 
475                                                'activate', self.on_service_disco_menuitem_activate, 
476                                                gajim.connections.keys()[0])
477                                #new msg
478                                if not self.new_message_menuitem_handler_id:
479                                        self.new_message_menuitem_handler_id = new_message_menuitem.\
480                                                connect('activate', self.on_new_message_menuitem_activate, 
481                                                gajim.connections.keys()[0])
482                                #new msg accel
483                                if not self.have_new_message_accel:
484                                        ag = gtk.accel_groups_from_object(self.window)[0]
485                                        new_message_menuitem.add_accelerator('activate', ag,
486                                                gtk.keysyms.n,  gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
487                                        self.have_new_message_accel = True
488                               
489                                account = gajim.connections.keys()[0]
490                                advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
491                                        account)
492                                advanced_menuitem.set_submenu(advanced_menuitem_menu)
493                        elif len(gajim.connections) == 0: # user has no accounts
494                                advanced_menuitem.set_sensitive(False)
495
496               
497                #FIXME: Gajim 0.9 should have this visible
498                profile_avatar_menuitem.set_no_show_all(True)
499                profile_avatar_menuitem.hide()
500               
501                if at_least_one_account_connected:
502                        new_message_menuitem.set_sensitive(True)
503                        join_gc_menuitem.set_sensitive(True)
504                        add_new_contact_menuitem.set_sensitive(True)
505                        service_disco_menuitem.set_sensitive(True)
506                        show_offline_contacts_menuitem.set_sensitive(True)
507                else:
508                        # make the menuitems insensitive
509                        new_message_menuitem.set_sensitive(False)
510                        join_gc_menuitem.set_sensitive(False)
511                        add_new_contact_menuitem.set_sensitive(False)
512                        service_disco_menuitem.set_sensitive(False)
513                        show_offline_contacts_menuitem.set_sensitive(False)
514                        profile_avatar_menuitem.set_sensitive(False)
515
516        def draw_roster(self):
517                '''Clear and draw roster'''
518                self.tree.get_model().clear()
519                for acct in gajim.connections:
520                        self.add_account_to_roster(acct)
521                        for jid in gajim.contacts[acct].keys():
522                                self.add_contact_to_roster(jid, acct)
523                self.make_menu() # re-make menu in case an account was removed
524                #FIXME: maybe move thie make_menu() in where we remove the account?
525       
526        def fill_contacts_and_groups_dicts(self, array, account):
527                '''fill gajim.contacts and gajim.groups'''
528                if not gajim.contacts.has_key(account):
529                        gajim.contacts[account] = {}
530                if not gajim.groups.has_key(account):
531                        gajim.groups[account] = {}
532                for jid in array.keys():
533                        jids = jid.split('/')
534                        #get jid
535                        ji = jids[0]
536                        #get resource
537                        resource = ''
538                        if len(jids) > 1:
539                                resource = '/'.join(jids[1:])
540                        #get name
541                        name = array[jid]['name']
542                        if not name:
543                                if ji.find('@') <= 0:
544                                        name = ji
545                                else:
546                                        name = jid.split('@')[0]
547                        show = 'offline' # show is offline by default
548                        status = '' #no status message by default
549
550                        keyID = ''
551                        attached_keys = gajim.config.get_per('accounts', account,
552                                'attached_gpg_keys').split()
553                        if jid in attached_keys:
554                                keyID = attached_keys[attached_keys.index(jid) + 1]
555                        contact1 = Contact(jid = ji, name = name, groups = array[jid]['groups'],
556                                show = show, status = status, sub = array[jid]['subscription'],
557                                ask = array[jid]['ask'], resource = resource, keyID = keyID)
558
559                        # when we draw the roster, we avoid having the same contact
560                        # more than once (f.e. we avoid showing it twice when 2 resources)
561                        gajim.contacts[account][ji] = [contact1]
562                        for g in array[jid]['groups'] :
563                                if g in gajim.groups[account].keys():
564                                        continue
565
566                                if account + g in self.collapsed_rows:
567                                        ishidden = False
568                                else:
569                                        ishidden = True
570                                gajim.groups[account][g] = { 'expand': ishidden }
571
572        def chg_contact_status(self, contact, show, status, account):
573                '''When a contact changes his status'''
574                showOffline = gajim.config.get('showoffline')
575                model = self.tree.get_model()
576                contact_instances = gajim.contacts[account][contact.jid]
577                contact.show = show
578                contact.status = status
579                if show in ('offline', 'error') and \
580                   not gajim.awaiting_messages[account].has_key(contact.jid):
<