root/branches/gajim_0.8.1/src/systraywin32.py

Revision 3241, 9.6 kB (checked in by nk, 3 years ago)

[win32 only] add a menuitem to hide context menu of trayicon

Line 
1## src/systraywin32.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##      - Dimitur Kirov <dkirov@gmail.com>
8##
9## code initially based on
10## http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/334779
11## with some ideas/help from pysystray.sf.net
12##
13##      Copyright (C) 2003-2005 Gajim Team
14##
15## This program is free software; you can redistribute it and/or modify
16## it under the terms of the GNU General Public License as published
17## by the Free Software Foundation; version 2 only.
18##
19## This program is distributed in the hope that it will be useful,
20## but WITHOUT ANY WARRANTY; without even the implied warranty of
21## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22## GNU General Public License for more details.
23##
24
25
26import win32gui
27import win32con # winapi contants
28import systray
29import gtk
30import os
31
32WM_TASKBARCREATED = win32gui.RegisterWindowMessage('TaskbarCreated')
33WM_TRAYMESSAGE = win32con.WM_USER + 20
34
35from common import gajim
36from common import i18n
37_ = i18n._
38APP = i18n.APP
39gtk.glade.bindtextdomain(APP, i18n.DIR)
40gtk.glade.textdomain(APP)
41
42GTKGUI_GLADE = 'gtkgui.glade'
43
44class SystrayWINAPI:
45        def __init__(self, gtk_window):
46                self._window = gtk_window
47                self._hwnd = gtk_window.window.handle
48                self._message_map = {}
49
50                self.notify_icon = None           
51
52                # Sublass the window and inject a WNDPROC to process messages.
53                self._oldwndproc = win32gui.SetWindowLong(self._hwnd, win32con.GWL_WNDPROC,
54                                                                                                self._wndproc)
55
56
57        def add_notify_icon(self, menu, hicon=None, tooltip=None):
58                """ Creates a notify icon for the gtk window. """
59                if not self.notify_icon:
60                        if not hicon:
61                                hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
62                        self.notify_icon = NotifyIcon(self._hwnd, hicon, tooltip)
63
64                        # Makes redraw if the taskbar is restarted.   
65                        self.message_map({WM_TASKBARCREATED: self.notify_icon._redraw})
66
67
68        def message_map(self, msg_map={}):
69                """ Maps message processing to callback functions ala win32gui. """
70                if msg_map:
71                        if self._message_map:
72                                duplicatekeys = [key for key in msg_map.keys()
73                                                                if self._message_map.has_key(key)]
74                               
75                                for key in duplicatekeys:
76                                        new_value = msg_map[key]
77                                       
78                                        if isinstance(new_value, list):
79                                                raise TypeError('Dict cannot have list values')
80                                       
81                                        value = self._message_map[key]
82                                       
83                                        if new_value != value:
84                                                new_value = [new_value]
85                                               
86                                                if isinstance(value, list):
87                                                        value += new_value
88                                                else:
89                                                        value = [value] + new_value
90                                               
91                                                msg_map[key] = value
92                        self._message_map.update(msg_map)
93
94        def message_unmap(self, msg, callback=None):
95                if self._message_map.has_key(msg):
96                        if callback:
97                                cblist = self._message_map[key]
98                                if isinstance(cblist, list):
99                                        if not len(cblist) < 2:
100                                                for i in range(len(cblist)):
101                                                        if cblist[i] == callback:
102                                                                del self._message_map[key][i]
103                                                                return
104                        del self._message_map[key]
105
106        def remove_notify_icon(self):
107                """ Removes the notify icon. """
108                if self.notify_icon:
109                        self.notify_icon.remove()
110                        self.notify_icon = None
111
112        def remove(self, *args):
113                """ Unloads the extensions. """
114                self._message_map = {}
115                self.remove_notify_icon()
116                self = None
117
118        def show_balloon_tooltip(self, title, text, timeout=10,
119                                                        icon=win32gui.NIIF_NONE):
120                """ Shows a baloon tooltip. """
121                if not self.notify_icon:
122                        self.add_notifyicon()
123                self.notify_icon.show_balloon(title, text, timeout, icon)
124
125        def _wndproc(self, hwnd, msg, wparam, lparam):
126                """ A WINDPROC to process window messages. """
127                if self._message_map.has_key(msg):
128                        callback = self._message_map[msg]
129                        if isinstance(callback, list):
130                                for cb in callback:
131                                        cb(hwnd, msg, wparam, lparam)
132                        else:
133                                callback(hwnd, msg, wparam, lparam)
134
135                return win32gui.CallWindowProc(self._oldwndproc, hwnd, msg, wparam,
136                                                                        lparam)
137                                                                       
138
139class NotifyIcon:
140
141        def __init__(self, hwnd, hicon, tooltip=None):
142                self._hwnd = hwnd
143                self._id = 0
144                self._flags = win32gui.NIF_MESSAGE | win32gui.NIF_ICON
145                self._callbackmessage = WM_TRAYMESSAGE
146                self._hicon = hicon
147
148                win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid())
149                if tooltip: self.set_tooltip(tooltip)
150
151
152        def _get_nid(self):
153                """ Function to initialise & retrieve the NOTIFYICONDATA Structure. """
154                nid = [self._hwnd, self._id, self._flags, self._callbackmessage, self._hicon]
155
156                if not hasattr(self, '_tip'): self._tip = ''
157                nid.append(self._tip)
158
159                if not hasattr(self, '_info'): self._info = ''
160                nid.append(self._info)
161                       
162                if not hasattr(self, '_timeout'): self._timeout = 0
163                nid.append(self._timeout)
164
165                if not hasattr(self, '_infotitle'): self._infotitle = ''
166                nid.append(self._infotitle)
167                       
168                if not hasattr(self, '_infoflags'):self._infoflags = win32gui.NIIF_NONE
169                nid.append(self._infoflags)
170
171                return tuple(nid)
172       
173        def remove(self):
174                """ Removes the tray icon. """
175                try:
176                        win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, self._get_nid())
177                except: # maybe except just pywintypes.error ? anyways..
178                        pass
179
180
181        def set_tooltip(self, tooltip):
182                """ Sets the tray icon tooltip. """
183                self._flags = self._flags | win32gui.NIF_TIP
184                self._tip = tooltip
185                win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid())
186               
187               
188        def show_balloon(self, title, text, timeout=10, icon=win32gui.NIIF_NONE):
189                """ Shows a balloon tooltip from the tray icon. """
190                self._flags = self._flags | win32gui.NIF_INFO
191                self._infotitle = title
192                self._info = text
193                self._timeout = timeout * 1000
194                self._infoflags = icon
195                win32gui.Shell_NotifyIcon(win32gui.NIM_MODIFY, self._get_nid())
196
197        def _redraw(self, *args):
198                """ Redraws the tray icon. """
199                self.remove()
200                win32gui.Shell_NotifyIcon(win32gui.NIM_ADD, self._get_nid())
201
202
203class SystrayWin32(systray.Systray):
204        def __init__(self, plugin):
205                # Note: gtk window must be realized before installing extensions.
206                systray.Systray.__init__(self, plugin)
207                self.plugin = plugin
208                self.jids = []
209                self.status = 'offline'
210                self.xml = gtk.glade.XML(GTKGUI_GLADE, 'systray_context_menu', APP)
211                self.systray_context_menu = self.xml.get_widget('systray_context_menu')
212                self.added_hide_menuitem = False
213               
214                self.tray_ico_imgs = self.load_icos()
215               
216                #self.plugin.roster.window.realize()
217                #self.plugin.roster.window.show_all()
218                w = gtk.Window() # just a window to pass
219                w.realize() # realize it so gtk window exists
220                self.systray_winapi = SystrayWINAPI(w)
221               
222                # this fails if you move the window
223                #self.systray_winapi = SystrayWINAPI(self.plugin.roster.window)
224               
225
226                self.xml.signal_autoconnect(self)
227               
228                # Set up the callback messages
229                self.systray_winapi.message_map({
230                        WM_TRAYMESSAGE: self.on_clicked
231                        }) 
232
233        def show_icon(self):
234                #self.systray_winapi.add_notify_icon(self.systray_context_menu, tooltip = 'Gajim')
235                #self.systray_winapi.notify_icon.menu = self.systray_context_menu
236                # do not remove set_img does both above.
237                # maybe I can only change img without readding
238                # the notify icon? HOW??
239                self.set_img()
240
241        def hide_icon(self):
242                self.systray_winapi.remove()
243
244        def on_clicked(self, hwnd, message, wparam, lparam):
245                if lparam == win32con.WM_RBUTTONUP: # Right click
246                        self.make_menu()
247                        self.systray_winapi.notify_icon.menu.popup(None, None, None, 0, 0)
248                elif lparam == win32con.WM_MBUTTONUP: # Middle click
249                        self.on_middle_click()
250                elif lparam == win32con.WM_LBUTTONUP: # Left click
251                        self.on_left_click()
252
253        def add_jid(self, jid, account):
254                l = [account, jid]
255                if not l in self.jids:
256                        self.jids.append(l)
257                        self.set_img()
258                # we append to the number of unread messages
259                nb = self.plugin.roster.nb_unread
260                for acct in gajim.connections:
261                        # in chat / groupchat windows
262                        for kind in ['chats', 'gc']:
263                                jids = self.plugin.windows[acct][kind]
264                                for jid in jids:
265                                        if jid != 'tabbed':
266                                                nb += jids[jid].nb_unread[jid]
267               
268                text = i18n.ngettext(
269                                        'Gajim - one unread message',
270                                        'Gajim - %d unread messages',
271                                        nb, None, nb)
272
273                self.systray_winapi.notify_icon.set_tooltip(text)
274
275        def remove_jid(self, jid, account):
276                l = [account, jid]
277                if l in self.jids:
278                        self.jids.remove(l)
279                        self.set_img()
280                # we remove from the number of unread messages
281                nb = self.plugin.roster.nb_unread
282                for acct in gajim.connections:
283                        # in chat / groupchat windows
284                        for kind in ['chats', 'gc']:
285                                for jid in self.plugin.windows[acct][kind]:
286                                        if jid != 'tabbed':
287                                                nb += self.plugin.windows[acct][kind][jid].nb_unread[jid]
288               
289                #FIXME: prepare me for transltaion (ngeetext() and all) for 0.9
290                if nb > 0:
291                        text = i18n.ngettext(
292                                        'Gajim - one unread message',
293                                        'Gajim - %d unread messages',
294                                        nb, None, nb)
295                else:
296                        text = 'Gajim'
297                self.systray_winapi.notify_icon.set_tooltip(text)
298
299        def set_img(self):
300                self.systray_winapi.remove_notify_icon()
301                if len(self.jids) > 0:
302                        state = 'message'
303                else:
304                        state = self.status
305                hicon = self.tray_ico_imgs[state]
306               
307                self.systray_winapi.add_notify_icon(self.systray_context_menu, hicon,
308                        'Gajim')
309                self.systray_winapi.notify_icon.menu = self.systray_context_menu
310
311        def load_icos(self):
312                '''load .ico files and return them to a dic of SHOW --> img_obj'''
313                #iconset = gajim.config.get('iconset')
314                #if not iconset:
315                #       iconset = 'sun'
316                iconset = 'gnome'
317               
318                imgs = {}
319                path = os.path.join(gajim.DATA_DIR, 'iconsets/' + iconset + '/16x16/icos/')
320                states_list = gajim.SHOW_LIST
321                # trayicon apart from show holds message state too
322                states_list.append('message')
323                for state in states_list:
324                        path_to_ico = path + state + '.ico'
325                        if os.path.exists(path_to_ico):
326                                hinst = win32gui.GetModuleHandle(None)
327                                img_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
328                                image = win32gui.LoadImage(hinst, path_to_ico, win32con.IMAGE_ICON, 
329                                        0, 0, img_flags)
330                                imgs[state] = image
331               
332                return imgs
Note: See TracBrowser for help on using the browser.