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

Revision 3109, 76.6 kB (checked in by nk, 3 years ago)

add advanced_menuitem.set_sensitive(val) depending on conditions (if no accounts it is False, else True)

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)
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)
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):
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),
161                                                        False)
162                self.draw_contact(jid, account)
163       
164        def really_remove_contact(self, user, account):
165                if user.jid in gajim.newly_added[account]:
166                        return
167                if user.jid.find('@') < 1 and gajim.connections[account].connected > 1: # It's an agent
168                        return
169                if user.jid in gajim.to_be_removed[account]:
170                        gajim.to_be_removed[account].remove(user.jid)
171                if gajim.config.get('showoffline'):
172                        self.draw_contact(user.jid, account)
173                        return
174                self.remove_contact(user, account)
175       
176        def remove_contact(self, user, account):
177                '''Remove a user from the roster'''
178                if user.jid in gajim.to_be_removed[account]:
179                        return
180                model = self.tree.get_model()
181                for i in self.get_contact_iter(user.jid, account):
182                        parent_i = model.iter_parent(i)
183                        group = model.get_value(parent_i, 3)
184                        model.remove(i)
185                        if model.iter_n_children(parent_i) == 0:
186                                model.remove(parent_i)
187                                # We need to check all contacts, even offline contacts
188                                group_empty = True
189                                for jid in gajim.contacts[account]:
190                                        if group in gajim.contacts[account][jid][0].groups:
191                                                group_empty = False
192                                                break
193                                if group_empty:
194                                        del gajim.groups[account][group]
195
196        def get_transport_name_by_jid(self,jid):
197                if not jid or not gajim.config.get('use_transports_iconsets'):
198                        return None
199                host = jid.split('@')[-1]
200                if host.startswith('aim'):
201                        return 'aim'
202                elif host.startswith('gadugadu'):
203                        return 'gadugadu'
204                elif host.startswith('gg'):
205                        return 'gadugadu'
206                elif host.startswith('irc'):
207                        return 'irc'
208                # abc@icqsucks.org will match as ICQ, but what to do..
209                elif host.startswith('icq'):
210                        return 'icq'
211                elif host.startswith('msn'):
212                        return 'msn'
213                elif host.startswith('sms'):
214                        return 'sms'
215                elif host.startswith('tlen'):
216                        return 'tlen'
217                elif host.startswith('yahoo'):
218                        return 'yahoo'
219                return None
220
221        def get_appropriate_state_images(self, jid):
222                '''check jid and return the appropriate state images dict'''
223                transport = self.get_transport_name_by_jid(jid)
224                if transport:
225                        return self.transports_state_images[transport]
226                return self.jabber_state_images
227
228        def draw_contact(self, jid, account):
229                '''draw the correct state image and name'''
230                model = self.tree.get_model()
231                iters = self.get_contact_iter(jid, account)
232                if len(iters) == 0:
233                        return
234                contact_instances = gajim.get_contact_instances_from_jid(account, jid)
235                contact = gajim.get_highest_prio_contact_from_contacts(contact_instances)
236                name = contact.name
237                if len(contact_instances) > 1:
238                        name += ' (' + str(len(contact_instances)) + ')'
239
240                state_images = self.get_appropriate_state_images(jid)
241                if gajim.awaiting_messages[account].has_key(jid):
242                        img = state_images['message']
243                elif jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent
244                        img = state_images[contact.show]                                       
245                else:
246                        if contact.sub == 'both':
247                                img = state_images[contact.show]
248                        else:
249                                if contact.ask == 'subscribe':
250                                        img = state_images['requested']
251                                else:
252                                        transport = self.get_transport_name_by_jid(jid)
253                                        if transport and state_images.has_key(contact.show):
254                                                img = state_images[contact.show]
255                                        else:
256                                                img = state_images['not in the roster']
257                for iter in iters:
258                        model.set_value(iter, 0, img)
259                        model.set_value(iter, 1, name)
260                        #FIXME: add avatar
261
262        def join_gc_room(self, account, room_jid, nick, password):
263                if room_jid in self.plugin.windows[account]['gc'] and \
264                gajim.gc_connected[account][room_jid]:
265                        dialogs.ErrorDialog(_('You are already in room %s') %room_jid
266                                ).get_response()
267                        return
268                room, server = room_jid.split('@')
269                if not room_jid in self.plugin.windows[account]['gc']:
270                        self.new_room(room_jid, nick, account)
271                self.plugin.windows[account]['gc'][room_jid].set_active_tab(room_jid)
272                self.plugin.windows[account]['gc'][room_jid].window.present()
273                gajim.connections[account].join_gc(nick, room, server, password)
274
275        def on_bookmark_menuitem_activate(self, widget, account, bookmark):
276                self.join_gc_room(account, bookmark['jid'], bookmark['nick'],
277                        bookmark['password'])
278
279        def on_bm_header_changed_state(self, widget, event):
280                widget.set_state(gtk.STATE_NORMAL) #do not allow selected_state
281
282        def on_send_server_message_menuitem_activate(self, widget, account):
283                server = gajim.config.get_per('accounts', account, 'hostname')
284                server += '/announce/online'
285                dialogs.SingleMessageWindow(self.plugin, account, server, 'send')
286
287        def on_xml_console_menuitem_activate(self, widget, account):
288                if self.plugin.windows[account].has_key('xml_console'):
289                        self.plugin.windows[account]['xml_console'].window.present()
290                else:
291                        self.plugin.windows[account]['xml_console'].window.show_all()
292
293        def on_set_motd_menuitem_activate(self, widget, account):
294                server = gajim.config.get_per('accounts', account, 'hostname')
295                server += '/announce/motd'
296                dialogs.SingleMessageWindow(self.plugin, account, server, 'send')
297
298        def on_update_motd_menuitem_activate(self, widget, account):
299                server = gajim.config.get_per('accounts', account, 'hostname')
300                server += '/announce/motd/update'
301                dialogs.SingleMessageWindow(self.plugin, account, server, 'send')
302
303        def on_delete_motd_menuitem_activate(self, widget, account):
304                server = gajim.config.get_per('accounts', account, 'hostname')
305                server += '/announce/motd/delete'
306                gajim.connections[account].send_motd(server)   
307       
308        def on_online_users_menuitem_activate(self, widget, account):
309                pass #FIXME: impement disco in users for 0.9
310
311        def get_and_connect_advanced_menuitem_menu(self, account):
312                xml = gtk.glade.XML(GTKGUI_GLADE, 'advanced_menuitem_menu', APP)
313                advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu')
314               
315                send_single_message_menuitem = xml.get_widget(
316                        'send_single_message_menuitem')
317                xml_console_menuitem = xml.get_widget('xml_console_menuitem')
318                administrator_menuitem = xml.get_widget('administrator_menuitem')
319                online_users_menuitem = xml.get_widget('online_users_menuitem')
320                send_server_message_menuitem = xml.get_widget(
321                        'send_server_message_menuitem')
322                set_motd_menuitem = xml.get_widget('set_motd_menuitem')
323                update_motd_menuitem = xml.get_widget('update_motd_menuitem')
324                delete_motd_menuitem = xml.get_widget('delete_motd_menuitem')
325               
326                send_single_message_menuitem.connect('activate',
327                        self.on_send_single_message_menuitem_activate, account)
328
329                xml_console_menuitem.connect('activate',
330                        self.on_xml_console_menuitem_activate, account)
331
332                #FIXME: 0.9 should have this: it does disco in the place where users are
333                online_users_menuitem.set_no_show_all(True)
334                online_users_menuitem.hide()
335                online_users_menuitem.connect('activate',
336                        self.on_online_users_menuitem_activate, account)
337
338                send_server_message_menuitem.connect('activate',
339                        self.on_send_server_message_menuitem_activate, account)
340
341                set_motd_menuitem.connect('activate',
342                        self.on_set_motd_menuitem_activate, account)
343
344                update_motd_menuitem.connect('activate',
345                        self.on_update_motd_menuitem_activate, account)
346               
347                delete_motd_menuitem.connect('activate',
348                        self.on_delete_motd_menuitem_activate, account)
349               
350                advanced_menuitem_menu.show_all()
351                       
352                return advanced_menuitem_menu
353
354        def make_menu(self):
355                '''create the main window's menus'''
356                new_message_menuitem = self.xml.get_widget('new_message_menuitem')
357                join_gc_menuitem = self.xml.get_widget('join_gc_menuitem')
358                add_new_contact_menuitem  = self.xml.get_widget('add_new_contact_menuitem')
359                service_disco_menuitem  = self.xml.get_widget('service_disco_menuitem')
360                advanced_menuitem = self.xml.get_widget('advanced_menuitem')
361                show_offline_contacts_menuitem = self.xml.get_widget(
362                        'show_offline_contacts_menuitem')
363                profile_avatar_menuitem = self.xml.get_widget('profile_avatar_menuitem')
364               
365                # make it sensitive. it is insensitive only if no accounts are *available*
366                advanced_menuitem.set_sensitive(True)
367
368
369                if self.add_new_contact_handler_id:
370                        add_new_contact_menuitem.handler_disconnect(
371                                self.add_new_contact_handler_id)
372                        self.add_new_contact_handler_id = None
373
374                if self.service_disco_handler_id:
375                        service_disco_menuitem.handler_disconnect(
376                                self.service_disco_handler_id)
377                        self.service_disco_handler_id = None
378                       
379                if self.new_message_menuitem_handler_id:
380                        new_message_menuitem.handler_disconnect(
381                                self.new_message_menuitem_handler_id)
382                        self.new_message_menuitem_handler_id = None
383                       
384                #remove the existing submenus
385                add_new_contact_menuitem.remove_submenu()
386                service_disco_menuitem.remove_submenu()
387                join_gc_menuitem.remove_submenu()
388                new_message_menuitem.remove_submenu()
389                advanced_menuitem.remove_submenu()
390
391                #remove the existing accelerator
392                if self.have_new_message_accel:
393                        ag = gtk.accel_groups_from_object(self.window)[0]
394                        new_message_menuitem.remove_accelerator(ag, gtk.keysyms.n,
395                                gtk.gdk.CONTROL_MASK)
396                        self.have_new_message_accel = False
397
398                #join gc
399                sub_menu = gtk.Menu()
400                join_gc_menuitem.set_submenu(sub_menu)
401                at_least_one_account_connected = False
402                multiple_accounts = len(gajim.connections) >= 2 #FIXME: stop using bool var here
403                for account in gajim.connections:
404                        if gajim.connections[account].connected <= 1: #if offline or connecting
405                                continue
406                        if not at_least_one_account_connected:
407                                at_least_one_account_connected = True
408                        if multiple_accounts:
409                                label = gtk.Label()
410                                label.set_markup('<u>' + account.upper() +'</u>')
411                                item = gtk.MenuItem()
412                                item.add(label)
413                                item.connect('state-changed', self.on_bm_header_changed_state)
414                                sub_menu.append(item)
415                       
416                        item = gtk.MenuItem(_('New _Room'))
417                        item.connect('activate', self.on_join_gc_activate, account)
418                        sub_menu.append(item)
419
420                        for bookmark in gajim.connections[account].bookmarks:
421                                item = gtk.MenuItem(bookmark['name'])
422                                item.connect('activate', self.on_bookmark_menuitem_activate,
423                                        account, bookmark)
424                                sub_menu.append(item)
425
426                if at_least_one_account_connected: #FIXME: move this below where we do this check
427                        #and make sure it works
428                        newitem = gtk.SeparatorMenuItem() # seperator
429                        sub_menu.append(newitem)
430               
431                        newitem = gtk.ImageMenuItem(_('Manage Bookmarks...'))
432                        img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES,
433                                gtk.ICON_SIZE_MENU)
434                        newitem.set_image(img)
435                        newitem.connect('activate', self.on_manage_bookmarks_menuitem_activate)
436                        sub_menu.append(newitem)
437                        sub_menu.show_all()
438
439                if multiple_accounts: # 2 or more accounts? make submenus
440                        #add
441                        sub_menu = gtk.Menu()
442                        for account in gajim.connections:
443                                if gajim.connections[account].connected <= 1:
444                                        #if offline or connecting
445                                        continue
446                                item = gtk.MenuItem(_('to %s account') % account)
447                                sub_menu.append(item)
448                                item.connect('activate', self.on_add_new_contact, account)
449                        add_new_contact_menuitem.set_submenu(sub_menu)
450                        sub_menu.show_all()
451                       
452                        #disco
453                        sub_menu = gtk.Menu()
454                        for account in gajim.connections:
455                                if gajim.connections[account].connected <= 1:
456                                        #if offline or connecting
457                                        continue
458                                item = gtk.MenuItem(_('using %s account') % account)
459                                sub_menu.append(item)
460                                item.connect('activate', self.on_service_disco_menuitem_activate,
461                                        account)
462
463                        service_disco_menuitem.set_submenu(sub_menu)
464                        sub_menu.show_all()
465                       
466                        #new message
467                        sub_menu = gtk.Menu()
468                        for account in gajim.connections:
469                                if gajim.connections[account].connected <= 1:
470                                        #if offline or connecting
471                                        continue
472                                our_jid = gajim.config.get_per('accounts', account, 'name') + '@' +\
473                                        gajim.config.get_per('accounts', account, 'hostname')
474                                item = gtk.MenuItem(_('as %s') % our_jid)
475                                sub_menu.append(item)
476                                item.connect('activate', self.on_new_message_menuitem_activate, 
477                                                                        account)
478       
479                        new_message_menuitem.set_submenu(sub_menu)
480                        sub_menu.show_all()
481                       
482                        #Advanced Actions
483                        sub_menu = gtk.Menu()
484                        for account in gajim.connections:
485                                item = gtk.MenuItem(_('for account %s') % account)
486                                sub_menu.append(item)
487                                advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
488                                        account)
489                                item.set_submenu(advanced_menuitem_menu)
490                       
491                        advanced_menuitem.set_submenu(sub_menu)
492                        sub_menu.show_all()
493                       
494                else:
495                        if len(gajim.connections) == 1: # user has only one account
496                                #add
497                                if not self.add_new_contact_handler_id:
498                                        self.add_new_contact_handler_id = add_new_contact_menuitem.connect(
499                                                'activate', self.on_add_new_contact, gajim.connections.keys()[0])
500                                #disco
501                                if not self.service_disco_handler_id:
502                                        self.service_disco_handler_id = service_disco_menuitem.connect( 
503                                                'activate', self.on_service_disco_menuitem_activate, 
504                                                gajim.connections.keys()[0])
505                                #new msg
506                                if not self.new_message_menuitem_handler_id:
507                                        self.new_message_menuitem_handler_id = new_message_menuitem.\
508                                                connect('activate', self.on_new_message_menuitem_activate, 
509                                                gajim.connections.keys()[0])
510                                #new msg accel
511                                if not self.have_new_message_accel:
512                                        ag = gtk.accel_groups_from_object(self.window)[0]
513                                        new_message_menuitem.add_accelerator('activate', ag,
514                                                gtk.keysyms.n,  gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
515                                        self.have_new_message_accel = True
516                               
517                                account = gajim.connections.keys()[0]
518                                advanced_menuitem_menu = self.get_and_connect_advanced_menuitem_menu(
519                                        account)
520                                advanced_menuitem.set_submenu(advanced_menuitem_menu)
521                        elif len(gajim.connections) == 0: # user has no accounts
522                                advanced_menuitem.set_sensitive(False)
523
524               
525                #FIXME: Gajim 0.9 should have this visible
526                profile_avatar_menuitem.set_no_show_all(True)
527                profile_avatar_menuitem.hide()
528               
529                if at_least_one_account_connected:
530                        new_message_menuitem.set_sensitive(True)
531                        join_gc_menuitem.set_sensitive(True)
532                        add_new_contact_menuitem.set_sensitive(True)
533                        service_disco_menuitem.set_sensitive(True)
534                        show_offline_contacts_menuitem.set_sensitive(True)
535                else:
536                        # make the menuitems insensitive
537                        new_message_menuitem.set_sensitive(False)
538                        join_gc_menuitem.set_sensitive(False)
539                        add_new_contact_menuitem.set_sensitive(False)
540                        service_disco_menuitem.set_sensitive(False)
541                        show_offline_contacts_menuitem.set_sensitive(False)
542                        profile_avatar_menuitem.set_sensitive(False)
543
544        def draw_roster(self):
545                '''Clear and draw roster'''
546                self.tree.get_model().clear()
547                for acct in gajim.connections:
548                        self.add_account_to_roster(acct)
549                        for jid in gajim.contacts[acct].keys():
550                                self.add_contact_to_roster(jid, acct)
551                self.make_menu() # re-make menu in case an account was removed
552                #FIXME: maybe move thie make_menu() in where we remove the account?
553       
554        def fill_contacts_and_groups_dicts(self, array, account):
555                '''fill gajim.contacts and gajim.groups'''
556                if not gajim.contacts.has_key(account):
557                        gajim.contacts[account] = {}
558                if not gajim.groups.has_key(account):
559                        gajim.groups[account] = {}
560                for jid in array.keys():
561                        jids = jid.split('/')
562                        #get jid
563                        ji = jids[0]
564                        #get resource
565                        resource = ''
566                        if len(jids) > 1:
567                                resource = '/'.join(jids[1:])
568                        #get name
569                        name = array[jid]['name']
570                        if not name:
571                                if ji.find('@') <= 0:
572                                        name = ji
573                                else:
574                                        name = jid.split('@')[0]
575                        show = 'offline' # show is offline by default
576                        status = '' #no status message by default
577
578                        keyID = ''
579                        attached_keys = gajim.config.get_per('accounts', account,
580                                'attached_gpg_keys').split()
581                        if jid in attached_keys:
582                                keyID = attached_keys[attached_keys.index(jid) + 1]
583                        contact1 = Contact(jid = ji, name = name, groups = array[jid]['groups'],
584                                show = show, status = status, sub = array[jid]['subscription'],
585                                ask = array[jid]['ask'], resource = resource, keyID =