root/branches/gajim_0.9.1/src/systray.py

Revision 4743, 13.1 kB (checked in by nk, 3 years ago)

failsafe to dcraven

  • Property svn:eol-style set to LF
Line 
1##      systray.py
2##
3## Contributors for this file:
4##      - Yann Le Boulanger <asterix@lagaule.org>
5##      - Nikos Kouremenos <kourem@gmail.com>
6##      - Dimitur Kirov <dkirov@gmail.com>
7##
8## Copyright (C) 2003-2004 Yann Le Boulanger <asterix@lagaule.org>
9##                         Vincent Hanquez <tab@snarc.org>
10## Copyright (C) 2005 Yann Le Boulanger <asterix@lagaule.org>
11##                    Vincent Hanquez <tab@snarc.org>
12##                    Nikos Kouremenos <nkour@jabber.org>
13##                    Dimitur Kirov <dkirov@gmail.com>
14##                    Travis Shirk <travis@pobox.com>
15##                    Norman Rasmussen <norman@rasmussen.co.za>
16##
17## This program is free software; you can redistribute it and/or modify
18## it under the terms of the GNU General Public License as published
19## by the Free Software Foundation; version 2 only.
20##
21## This program is distributed in the hope that it will be useful,
22## but WITHOUT ANY WARRANTY; without even the implied warranty of
23## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24## GNU General Public License for more details.
25##
26
27import gtk
28import gtk.glade
29import gobject
30import dialogs
31import os
32
33import tooltips
34import gtkgui_helpers
35
36from gajim import Contact
37from common import gajim
38from common import helpers
39from common import i18n
40
41try:
42        import egg.trayicon as trayicon # gnomepythonextras trayicon
43except:
44        try:
45                import trayicon # our trayicon
46        except:
47                gajim.log.debug('No trayicon module available')
48                pass
49
50_ = i18n._
51APP = i18n.APP
52gtk.glade.bindtextdomain(APP, i18n.DIR)
53gtk.glade.textdomain(APP)
54
55GTKGUI_GLADE = 'gtkgui.glade'
56
57class Systray:
58        '''Class for icon in the notification area
59        This class is both base class (for systraywin32.py) and normal class
60        for trayicon in GNU/Linux'''
61       
62        def __init__(self):
63                self.jids = [] # Contain things like [account, jid, type_of_msg]
64                self.new_message_handler_id = None
65                self.t = None
66                self.img_tray = gtk.Image()
67                self.status = 'offline'
68                self.xml = gtk.glade.XML(GTKGUI_GLADE, 'systray_context_menu', APP)
69                self.systray_context_menu = self.xml.get_widget('systray_context_menu')
70                self.xml.signal_autoconnect(self)
71
72        def set_img(self):
73                if len(self.jids) > 0:
74                        state = 'message'
75                else:
76                        state = self.status
77                image = gajim.interface.roster.jabber_state_images['16'][state]
78                if image.get_storage_type() == gtk.IMAGE_ANIMATION:
79                        self.img_tray.set_from_animation(image.get_animation())
80                elif image.get_storage_type() == gtk.IMAGE_PIXBUF:
81                        self.img_tray.set_from_pixbuf(image.get_pixbuf())
82
83        def add_jid(self, jid, account, typ):
84                l = [account, jid, typ]
85                # We can keep several single message 'cause we open them one by one
86                if not l in self.jids or typ == 'normal':
87                        self.jids.append(l)
88                        self.set_img()
89
90        def remove_jid(self, jid, account, typ):
91                l = [account, jid, typ]
92                if l in self.jids:
93                        self.jids.remove(l)
94                        self.set_img()
95
96        def change_status(self, global_status):
97                ''' set tray image to 'global_status' '''
98                # change image and status, only if it is different
99                if global_status is not None and self.status != global_status:
100                        self.status = global_status
101                self.set_img()
102       
103        def start_chat(self, widget, account, jid):
104                if gajim.interface.instances[account]['chats'].has_key(jid):
105                        gajim.interface.instances[account]['chats'][jid].window.present()
106                        gajim.interface.instances[account]['chats'][jid].set_active_tab(jid)
107                elif gajim.contacts[account].has_key(jid):
108                        gajim.interface.roster.new_chat(
109                                gajim.contacts[account][jid][0], account)
110                        gajim.interface.instances[account]['chats'][jid].set_active_tab(jid)
111       
112        def on_new_message_menuitem_activate(self, widget, account):
113                """When new message menuitem is activated:
114                call the NewMessageDialog class"""
115                dialogs.NewMessageDialog(account)
116
117        def make_menu(self, event = None):
118                '''create chat with and new message (sub) menus/menuitems
119                event is None when we're in Windows
120                '''
121               
122                chat_with_menuitem = self.xml.get_widget('chat_with_menuitem')
123                new_message_menuitem = self.xml.get_widget('new_message_menuitem')
124                status_menuitem = self.xml.get_widget('status_menu')
125               
126                if self.new_message_handler_id:
127                        new_message_menuitem.handler_disconnect(
128                                self.new_message_handler_id)
129                        self.new_message_handler_id = None
130
131                sub_menu = gtk.Menu()
132                status_menuitem.set_submenu(sub_menu)
133
134                # We need our own set of status icons, let's make 'em!
135                iconset = gajim.config.get('iconset')
136                if not iconset:
137                        iconset = 'dcraven'
138                path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
139                state_images = gajim.interface.roster.load_iconset(path)
140
141                for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
142                        uf_show = helpers.get_uf_show(show, use_mnemonic = True)
143                        item = gtk.ImageMenuItem(uf_show)
144                        item.set_image(state_images[show])
145                        sub_menu.append(item)
146                        item.connect('activate', self.on_show_menuitem_activate, show)
147
148                item = gtk.SeparatorMenuItem()
149                sub_menu.append(item)
150
151                item = gtk.ImageMenuItem(_('_Change Status Message...'))
152                path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'rename.png')
153                img = gtk.Image()
154                img.set_from_file(path)
155                item.set_image(img)
156                sub_menu.append(item)
157                item.connect('activate', self.on_change_status_message_activate)
158                if not helpers.one_account_connected():
159                        item.set_sensitive(False)
160
161                item = gtk.SeparatorMenuItem()
162                sub_menu.append(item)
163
164                uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
165                item = gtk.ImageMenuItem(uf_show)
166                item.set_image(state_images['offline'])
167                sub_menu.append(item)
168                item.connect('activate', self.on_show_menuitem_activate, 'offline')
169
170                iskey = len(gajim.connections) > 0
171                chat_with_menuitem.set_sensitive(iskey)
172                new_message_menuitem.set_sensitive(iskey)
173               
174                if len(gajim.connections) >= 2: # 2 or more connections? make submenus
175                        account_menu_for_chat_with = gtk.Menu()
176                        chat_with_menuitem.set_submenu(account_menu_for_chat_with)
177
178                        account_menu_for_new_message = gtk.Menu()
179                        new_message_menuitem.set_submenu(account_menu_for_new_message)
180
181                        for account in gajim.connections:
182                                #for chat_with
183                                item = gtk.MenuItem(_('using account ') + account)
184                                account_menu_for_chat_with.append(item)
185                                group_menu = self.make_groups_submenus_for_chat_with(account)
186                                item.set_submenu(group_menu)
187                                #for new_message
188                                item = gtk.MenuItem(_('using account ') + account)
189                                item.connect('activate',
190                                        self.on_new_message_menuitem_activate, account)
191                                account_menu_for_new_message.append(item)
192                               
193                elif len(gajim.connections) == 1: # one account
194                        # one account, no need to show 'as jid'
195                        # for chat_with
196                        account = gajim.connections.keys()[0]
197                       
198                        group_menu = self.make_groups_submenus_for_chat_with(account)
199                        chat_with_menuitem.set_submenu(group_menu)
200                                       
201                        # for new message
202                        self.new_message_handler_id = new_message_menuitem.connect(
203                                'activate', self.on_new_message_menuitem_activate, account)
204
205                if event is None:
206                        # None means windows (we explicitly popup in systraywin32.py)
207                        if self.added_hide_menuitem is False:
208                                self.systray_context_menu.prepend(gtk.SeparatorMenuItem())
209                                item = gtk.MenuItem(_('Hide this menu'))
210                                self.systray_context_menu.prepend(item)
211                                self.added_hide_menuitem = True
212                       
213                else: # GNU and Unices
214                        self.systray_context_menu.popup(None, None, None, event.button, event.time)
215                self.systray_context_menu.show_all()
216
217        def on_show_all_events_menuitem_activate(self, widget):
218                while len(self.jids):
219                        self.handle_first_event()
220
221        def on_show_roster_menuitem_activate(self, widget):
222                win = gajim.interface.roster.window
223                win.present()
224
225        def on_preferences_menuitem_activate(self, widget):
226                if gajim.interface.instances['preferences'].window.get_property('visible'):
227                        gajim.interface.instances['preferences'].window.present()
228                else:
229                        gajim.interface.instances['preferences'].window.show_all()
230
231        def on_quit_menuitem_activate(self, widget):   
232                gajim.interface.roster.on_quit_menuitem_activate(widget)
233
234        def make_groups_submenus_for_chat_with(self, account):
235                iconset = gajim.config.get('iconset')
236                if not iconset:
237                        iconset = 'dcraven'
238                path = os.path.join(gajim.DATA_DIR, 'iconsets', iconset, '16x16')
239                state_images = gajim.interface.roster.load_iconset(path)
240               
241                groups_menu = gtk.Menu()
242               
243                for group in gajim.groups[account].keys():
244                        if group == _('Transports'):
245                                continue
246                        # at least one 'not offline' or 'without errors' in this group
247                        at_least_one = False
248                        item = gtk.MenuItem(group)
249                        groups_menu.append(item)
250                        contacts_menu = gtk.Menu()
251                        item.set_submenu(contacts_menu)
252                        for contacts in gajim.contacts[account].values():
253                                contact = gajim.get_highest_prio_contact_from_contacts(contacts)
254                                if group in contact.groups and contact.show != 'offline' and \
255                                                contact.show != 'error':
256                                        at_least_one = True
257                                        s = contact.name.replace('_', '__') # two _ show one _ and no underline happens
258                                        item = gtk.ImageMenuItem(s)
259                                        # any given gtk widget can only be used in one place
260                                        # (here we use it in status menu too)
261                                        # gtk.Image is a widget, it's better we refactor to use gdk.gdk.Pixbuf allover
262                                        img = state_images[contact.show]
263                                        img_copy = gobject.new(gtk.Image, pixbuf=img.get_pixbuf())
264                                        item.set_image(img_copy)
265                                        item.connect('activate', self.start_chat, account,
266                                                        contact.jid)
267                                        contacts_menu.append(item)
268                       
269                        if not at_least_one:
270                                message = _('All contacts in this group are offline or have errors')
271                                item = gtk.MenuItem(message)
272                                item.set_sensitive(False)
273                                contacts_menu.append(item)
274
275                return groups_menu
276
277        def on_left_click(self):
278                win = gajim.interface.roster.window
279                if len(self.jids) == 0:
280                        # no pending events, so toggle visible/hidden for roster window
281                        if win.get_property('visible'): # visible in ANY virtual desktop?
282                                win.hide() # we hide it from VD that was visible in
283                               
284                                # but we could be in another VD right now. eg vd2
285                                # and we want not only to hide it in vd1 but also show it in vd2
286                                gtkgui_helpers.possibly_move_window_in_current_desktop(win)
287                        else:
288                                win.present()
289                else:
290                        self.handle_first_event()
291
292        def handle_first_event(self):
293                account = self.jids[0][0]
294                jid = self.jids[0][1]
295                typ = self.jids[0][2]
296                gajim.interface.handle_event(account, jid, typ)
297
298        def on_middle_click(self):
299                '''middle click raises window to have complete focus (fe. get kbd events)
300                but if already raised, it hides it'''
301                win = gajim.interface.roster.window
302                if win.is_active(): # is it fully raised? (eg does it receive kbd events?)
303                        win.hide()
304                else:
305                        win.present()
306
307        def on_clicked(self, widget, event):
308                self.on_tray_leave_notify_event(widget, None)
309                if event.button == 1: # Left click
310                        self.on_left_click()
311                if event.button == 2: # middle click
312                        self.on_middle_click()
313                if event.button == 3: # right click
314                        self.make_menu(event)
315       
316        def on_show_menuitem_activate(self, widget, show):
317                # we all add some fake (we cannot select those nor have them as show)
318                # but this helps to align with roster's status_combobox index positions
319                l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR',
320                        'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline']
321                index = l.index(show)
322                gajim.interface.roster.status_combobox.set_active(index)
323
324        def on_change_status_message_activate(self, widget):
325                model = gajim.interface.roster.status_combobox.get_model()
326                active = gajim.interface.roster.status_combobox.get_active()
327                status = model[active][2].decode('utf-8')
328                dlg = dialogs.ChangeStatusMessageDialog(status)
329                message = dlg.run()
330                if message is not None: # None if user press Cancel
331                        accounts = gajim.connections.keys()
332                        for acct in accounts:
333                                if not gajim.config.get_per('accounts', acct,
334                                        'sync_with_global_status'):
335                                        continue
336                                show = gajim.SHOW_LIST[gajim.connections[acct].connected]
337                                gajim.interface.roster.send_status(acct, show, message)
338
339        def show_tooltip(self, widget):
340                position = widget.window.get_origin()
341                if self.tooltip.id == position:
342                        size = widget.window.get_size()
343                        self.tooltip.show_tooltip('', 
344                                (widget.window.get_pointer()[0], size[1]), position)
345                       
346        def on_tray_motion_notify_event(self, widget, event):
347                wireq=widget.size_request()
348                position = widget.window.get_origin()
349                if self.tooltip.timeout > 0:
350                        if self.tooltip.id != position:
351                                self.tooltip.hide_tooltip()
352                if self.tooltip.timeout == 0 and \
353                        self.tooltip.id != position:
354                        self.tooltip.id = position
355                        self.tooltip.timeout = gobject.timeout_add(500,
356                                self.show_tooltip, widget)
357       
358        def on_tray_leave_notify_event(self, widget, event):
359                position = widget.window.get_origin()
360                if self.tooltip.timeout > 0 and \
361                        self.tooltip.id == position:
362                        self.tooltip.hide_tooltip()
363               
364        def show_icon(self):
365                if not self.t:
366                        self.t = trayicon.TrayIcon('Gajim')
367                        eb = gtk.EventBox()
368                        # avoid draw seperate bg color in some gtk themes
369                        eb.set_visible_window(False)
370                        eb.set_events(gtk.gdk.POINTER_MOTION_MASK)
371                        eb.connect('button-press-event', self.on_clicked)
372                        eb.connect('motion-notify-event', self.on_tray_motion_notify_event)
373                        eb.connect('leave-notify-event', self.on_tray_leave_notify_event)
374                        self.tooltip = tooltips.NotificationAreaTooltip()
375
376                        self.img_tray = gtk.Image()
377                        eb.add(self.img_tray)
378                        self.t.add(eb)
379                        self.set_img()
380                self.t.show_all()
381       
382        def hide_icon(self):
383                if self.t:
384                        self.t.destroy()
385                        self.t = None
Note: See TracBrowser for help on using the browser.