don't open twice the same subscription request dialog. see #6762
[gajim.git] / src / statusicon.py
blobf580f423846bd8f39d1c781bb08b7f236e1398a2
1 # -*- coding:utf-8 -*-
2 ## src/statusicon.py
3 ##
4 ## Copyright (C) 2006 Nikos Kouremenos <kourem AT gmail.com>
5 ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
6 ## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org>
7 ## Copyright (C) 2007 Lukas Petrovicky <lukas AT petrovicky.net>
8 ## Julien Pivotto <roidelapluie AT gmail.com>
9 ## Copyright (C) 2008 Jonathan Schleifer <js-gajim AT webkeks.org>
11 ## This file is part of Gajim.
13 ## Gajim is free software; you can redistribute it and/or modify
14 ## it under the terms of the GNU General Public License as published
15 ## by the Free Software Foundation; version 3 only.
17 ## Gajim is distributed in the hope that it will be useful,
18 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ## GNU General Public License for more details.
22 ## You should have received a copy of the GNU General Public License
23 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
26 import sys
27 import gtk
28 import gobject
29 import os
31 import dialogs
32 import config
33 import tooltips
34 import gtkgui_helpers
35 import tooltips
37 from common import gajim
38 from common import helpers
39 from common import pep
41 class StatusIcon:
42 """
43 Class for the notification area icon
44 """
46 def __init__(self):
47 self.single_message_handler_id = None
48 self.show_roster_handler_id = None
49 self.new_chat_handler_id = None
50 # click somewhere else does not popdown menu. workaround this.
51 self.added_hide_menuitem = False
52 self.status = 'offline'
53 self.xml = gtkgui_helpers.get_gtk_builder('systray_context_menu.ui')
54 self.systray_context_menu = self.xml.get_object('systray_context_menu')
55 self.xml.connect_signals(self)
56 self.popup_menus = []
57 self.status_icon = None
58 self.tooltip = tooltips.NotificationAreaTooltip()
60 def subscribe_events(self):
61 """
62 Register listeners to the events class
63 """
64 gajim.events.event_added_subscribe(self.on_event_added)
65 gajim.events.event_removed_subscribe(self.on_event_removed)
67 def unsubscribe_events(self):
68 """
69 Unregister listeners to the events class
70 """
71 gajim.events.event_added_unsubscribe(self.on_event_added)
72 gajim.events.event_removed_unsubscribe(self.on_event_removed)
74 def on_event_added(self, event):
75 """
76 Called when an event is added to the event list
77 """
78 if event.show_in_systray:
79 self.set_img()
81 def on_event_removed(self, event_list):
82 """
83 Called when one or more events are removed from the event list
84 """
85 self.set_img()
87 def show_icon(self):
88 if not self.status_icon:
89 self.status_icon = gtk.StatusIcon()
90 self.statusicon_size = '16'
91 self.status_icon.set_property('has-tooltip', True)
92 self.status_icon.connect('activate', self.on_status_icon_left_clicked)
93 self.status_icon.connect('popup-menu',
94 self.on_status_icon_right_clicked)
95 self.status_icon.connect('query-tooltip',
96 self.on_status_icon_query_tooltip)
97 self.status_icon.connect('size-changed',
98 self.on_status_icon_size_changed)
100 self.set_img()
101 self.subscribe_events()
103 def on_status_icon_right_clicked(self, widget, event_button, event_time):
104 self.make_menu(event_button, event_time)
106 def on_status_icon_query_tooltip(self, widget, x, y, keyboard_mode, tooltip):
107 self.tooltip.populate()
108 tooltip.set_custom(self.tooltip.hbox)
109 return True
111 def hide_icon(self):
112 self.status_icon.set_visible(False)
113 self.unsubscribe_events()
115 def on_status_icon_left_clicked(self, widget):
116 self.on_left_click()
118 def on_status_icon_size_changed(self, statusicon, size):
119 if size > 31:
120 self.statusicon_size = '32'
121 else:
122 self.statusicon_size = '16'
123 if os.environ.get('KDE_FULL_SESSION') == 'true':
124 # detect KDE session. see #5476
125 self.statusicon_size = '32'
126 self.set_img()
128 def set_img(self):
130 Apart from image, we also update tooltip text here
132 if not gajim.interface.systray_enabled:
133 return
134 if gajim.config.get('trayicon') == 'always':
135 self.status_icon.set_visible(True)
136 if gajim.events.get_nb_systray_events():
137 self.status_icon.set_visible(True)
138 self.status_icon.set_blinking(True)
139 else:
140 if gajim.config.get('trayicon') == 'on_event':
141 self.status_icon.set_visible(False)
142 self.status_icon.set_blinking(False)
144 image = gajim.interface.jabber_state_images[self.statusicon_size][
145 self.status]
146 if image.get_storage_type() == gtk.IMAGE_PIXBUF:
147 self.status_icon.set_from_pixbuf(image.get_pixbuf())
148 # FIXME: oops they forgot to support GIF animation?
149 # or they were lazy to get it to work under Windows! WTF!
150 elif image.get_storage_type() == gtk.IMAGE_ANIMATION:
151 self.status_icon.set_from_pixbuf(
152 image.get_animation().get_static_image())
153 # self.img_tray.set_from_animation(image.get_animation())
155 def change_status(self, global_status):
157 Set tray image to 'global_status'
159 # change image and status, only if it is different
160 if global_status is not None and self.status != global_status:
161 self.status = global_status
162 self.set_img()
164 def start_chat(self, widget, account, jid):
165 contact = gajim.contacts.get_first_contact_from_jid(account, jid)
166 if gajim.interface.msg_win_mgr.has_window(jid, account):
167 gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
168 jid, account)
169 elif contact:
170 gajim.interface.new_chat(contact, account)
171 gajim.interface.msg_win_mgr.get_window(jid, account).set_active_tab(
172 jid, account)
174 def on_single_message_menuitem_activate(self, widget, account):
175 dialogs.SingleMessageWindow(account, action='send')
177 def on_new_chat(self, widget, account):
178 dialogs.NewChatDialog(account)
180 def make_menu(self, event_button, event_time):
182 Create chat with and new message (sub) menus/menuitems
184 for m in self.popup_menus:
185 m.destroy()
187 chat_with_menuitem = self.xml.get_object('chat_with_menuitem')
188 single_message_menuitem = self.xml.get_object(
189 'single_message_menuitem')
190 status_menuitem = self.xml.get_object('status_menu')
191 join_gc_menuitem = self.xml.get_object('join_gc_menuitem')
192 sounds_mute_menuitem = self.xml.get_object('sounds_mute_menuitem')
193 show_roster_menuitem = self.xml.get_object('show_roster_menuitem')
195 if self.single_message_handler_id:
196 single_message_menuitem.handler_disconnect(
197 self.single_message_handler_id)
198 self.single_message_handler_id = None
199 if self.new_chat_handler_id:
200 chat_with_menuitem.disconnect(self.new_chat_handler_id)
201 self.new_chat_handler_id = None
203 sub_menu = gtk.Menu()
204 self.popup_menus.append(sub_menu)
205 status_menuitem.set_submenu(sub_menu)
207 gc_sub_menu = gtk.Menu() # gc is always a submenu
208 join_gc_menuitem.set_submenu(gc_sub_menu)
210 # We need our own set of status icons, let's make 'em!
211 iconset = gajim.config.get('iconset')
212 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
213 state_images = gtkgui_helpers.load_iconset(path)
215 if 'muc_active' in state_images:
216 join_gc_menuitem.set_image(state_images['muc_active'])
218 for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'):
219 uf_show = helpers.get_uf_show(show, use_mnemonic = True)
220 item = gtk.ImageMenuItem(uf_show)
221 item.set_image(state_images[show])
222 sub_menu.append(item)
223 item.connect('activate', self.on_show_menuitem_activate, show)
225 item = gtk.SeparatorMenuItem()
226 sub_menu.append(item)
228 item = gtk.ImageMenuItem(_('_Change Status Message...'))
229 gtkgui_helpers.add_image_to_menuitem(item, 'gajim-kbd_input')
230 sub_menu.append(item)
231 item.connect('activate', self.on_change_status_message_activate)
233 connected_accounts = gajim.get_number_of_connected_accounts()
234 if connected_accounts < 1:
235 item.set_sensitive(False)
237 connected_accounts_with_private_storage = 0
239 item = gtk.SeparatorMenuItem()
240 sub_menu.append(item)
242 uf_show = helpers.get_uf_show('offline', use_mnemonic = True)
243 item = gtk.ImageMenuItem(uf_show)
244 item.set_image(state_images['offline'])
245 sub_menu.append(item)
246 item.connect('activate', self.on_show_menuitem_activate, 'offline')
248 iskey = connected_accounts > 0 and not (connected_accounts == 1 and
249 gajim.connections[gajim.connections.keys()[0]].is_zeroconf)
250 chat_with_menuitem.set_sensitive(iskey)
251 single_message_menuitem.set_sensitive(iskey)
252 join_gc_menuitem.set_sensitive(iskey)
254 accounts_list = sorted(gajim.contacts.get_accounts())
255 # items that get shown whether an account is zeroconf or not
256 if connected_accounts > 1: # 2 or more connections? make submenus
257 account_menu_for_chat_with = gtk.Menu()
258 chat_with_menuitem.set_submenu(account_menu_for_chat_with)
259 self.popup_menus.append(account_menu_for_chat_with)
261 for account in accounts_list:
262 if gajim.account_is_connected(account):
263 # for chat_with
264 item = gtk.MenuItem(_('using account %s') % account)
265 account_menu_for_chat_with.append(item)
266 item.connect('activate', self.on_new_chat, account)
268 elif connected_accounts == 1: # one account
269 # one account connected, no need to show 'as jid'
270 for account in gajim.connections:
271 if gajim.connections[account].connected > 1:
272 # for start chat
273 self.new_chat_handler_id = chat_with_menuitem.connect(
274 'activate', self.on_new_chat, account)
275 break # No other connected account
277 # menu items that don't apply to zeroconf connections
278 if connected_accounts == 1 or (connected_accounts == 2 and \
279 gajim.zeroconf_is_connected()):
280 # only one 'real' (non-zeroconf) account is connected, don't need
281 # submenus
282 for account in gajim.connections:
283 if gajim.account_is_connected(account) and \
284 not gajim.config.get_per('accounts', account, 'is_zeroconf'):
285 if gajim.connections[account].private_storage_supported:
286 connected_accounts_with_private_storage += 1
288 # for single message
289 single_message_menuitem.remove_submenu()
290 self.single_message_handler_id = single_message_menuitem.\
291 connect('activate',
292 self.on_single_message_menuitem_activate, account)
293 # join gc
294 gajim.interface.roster.add_bookmarks_list(gc_sub_menu,
295 account)
296 break # No other account connected
297 else:
298 # 2 or more 'real' accounts are connected, make submenus
299 account_menu_for_single_message = gtk.Menu()
300 single_message_menuitem.set_submenu(
301 account_menu_for_single_message)
302 self.popup_menus.append(account_menu_for_single_message)
304 for account in accounts_list:
305 if gajim.connections[account].is_zeroconf or \
306 not gajim.account_is_connected(account):
307 continue
308 if gajim.connections[account].private_storage_supported:
309 connected_accounts_with_private_storage += 1
310 # for single message
311 item = gtk.MenuItem(_('using account %s') % account)
312 item.connect('activate',
313 self.on_single_message_menuitem_activate, account)
314 account_menu_for_single_message.append(item)
316 # join gc
317 gc_item = gtk.MenuItem(_('using account %s') % account, False)
318 gc_sub_menu.append(gc_item)
319 gc_menuitem_menu = gtk.Menu()
320 gajim.interface.roster.add_bookmarks_list(gc_menuitem_menu,
321 account)
322 gc_item.set_submenu(gc_menuitem_menu)
323 gc_sub_menu.show_all()
325 newitem = gtk.SeparatorMenuItem() # separator
326 gc_sub_menu.append(newitem)
327 newitem = gtk.ImageMenuItem(_('_Manage Bookmarks...'))
328 img = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU)
329 newitem.set_image(img)
330 newitem.connect('activate',
331 gajim.interface.roster.on_manage_bookmarks_menuitem_activate)
332 gc_sub_menu.append(newitem)
333 if connected_accounts_with_private_storage == 0:
334 newitem.set_sensitive(False)
336 sounds_mute_menuitem.set_active(not gajim.config.get('sounds_on'))
338 win = gajim.interface.roster.window
339 if self.show_roster_handler_id:
340 show_roster_menuitem.handler_disconnect(self.show_roster_handler_id)
341 if win.get_property('has-toplevel-focus'):
342 show_roster_menuitem.get_children()[0].set_label(_('Hide _Roster'))
343 self.show_roster_handler_id = show_roster_menuitem.connect(
344 'activate', self.on_hide_roster_menuitem_activate)
345 else:
346 show_roster_menuitem.get_children()[0].set_label(_('Show _Roster'))
347 self.show_roster_handler_id = show_roster_menuitem.connect(
348 'activate', self.on_show_roster_menuitem_activate)
350 if os.name == 'nt':
351 if self.added_hide_menuitem is False:
352 self.systray_context_menu.prepend(gtk.SeparatorMenuItem())
353 item = gtk.MenuItem(_('Hide this menu'))
354 self.systray_context_menu.prepend(item)
355 self.added_hide_menuitem = True
357 self.systray_context_menu.show_all()
358 self.systray_context_menu.popup(None, None, None, 0,
359 event_time)
361 def on_show_all_events_menuitem_activate(self, widget):
362 events = gajim.events.get_systray_events()
363 for account in events:
364 for jid in events[account]:
365 for event in events[account][jid]:
366 gajim.interface.handle_event(account, jid, event.type_)
368 def on_sounds_mute_menuitem_activate(self, widget):
369 gajim.config.set('sounds_on', not widget.get_active())
370 gajim.interface.save_config()
372 def on_show_roster_menuitem_activate(self, widget):
373 win = gajim.interface.roster.window
374 win.present()
376 def on_hide_roster_menuitem_activate(self, widget):
377 win = gajim.interface.roster.window
378 win.hide()
380 def on_preferences_menuitem_activate(self, widget):
381 if 'preferences' in gajim.interface.instances:
382 gajim.interface.instances['preferences'].window.present()
383 else:
384 gajim.interface.instances['preferences'] = config.PreferencesWindow()
386 def on_quit_menuitem_activate(self, widget):
387 gajim.interface.roster.on_quit_request()
389 def on_left_click(self):
390 win = gajim.interface.roster.window
391 if len(gajim.events.get_systray_events()) == 0:
392 # No pending events, so toggle visible/hidden for roster window
393 if win.get_property('visible') and (win.get_property(
394 'has-toplevel-focus') or os.name == 'nt'):
395 # visible in ANY virtual desktop?
397 # we could be in another VD right now. eg vd2
398 # and we want to show it in vd2
399 if not gtkgui_helpers.possibly_move_window_in_current_desktop(
400 win) and gajim.config.get('save-roster-position'):
401 x, y = win.get_position()
402 gajim.config.set('roster_x-position', x)
403 gajim.config.set('roster_y-position', y)
404 win.hide() # else we hide it from VD that was visible in
405 else:
406 if not win.get_property('visible'):
407 win.show_all()
408 if gajim.config.get('save-roster-position'):
409 gtkgui_helpers.move_window(win,
410 gajim.config.get('roster_x-position'),
411 gajim.config.get('roster_y-position'))
412 if not gajim.config.get('roster_window_skip_taskbar'):
413 win.set_property('skip-taskbar-hint', False)
414 win.present_with_time(gtk.get_current_event_time())
415 else:
416 self.handle_first_event()
418 def handle_first_event(self):
419 account, jid, event = gajim.events.get_first_systray_event()
420 if not event:
421 return
422 win = gajim.interface.roster.window
423 if not win.get_property('visible') and gajim.config.get(
424 'save-roster-position'):
425 gtkgui_helpers.move_window(win,
426 gajim.config.get('roster_x-position'),
427 gajim.config.get('roster_y-position'))
428 gajim.interface.handle_event(account, jid, event.type_)
430 def on_middle_click(self):
432 Middle click raises window to have complete focus (fe. get kbd events)
433 but if already raised, it hides it
435 win = gajim.interface.roster.window
436 if win.is_active(): # is it fully raised? (eg does it receive kbd events?)
437 win.hide()
438 else:
439 win.present()
441 def on_clicked(self, widget, event):
442 self.on_tray_leave_notify_event(widget, None)
443 if event.type != gtk.gdk.BUTTON_PRESS:
444 return
445 if event.button == 1: # Left click
446 self.on_left_click()
447 elif event.button == 2: # middle click
448 self.on_middle_click()
449 elif event.button == 3: # right click
450 self.make_menu(event.button, event.time)
452 def on_show_menuitem_activate(self, widget, show):
453 # we all add some fake (we cannot select those nor have them as show)
454 # but this helps to align with roster's status_combobox index positions
455 l = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'SEPARATOR',
456 'CHANGE_STATUS_MSG_MENUITEM', 'SEPARATOR', 'offline']
457 index = l.index(show)
458 if not helpers.statuses_unified():
459 gajim.interface.roster.status_combobox.set_active(index + 2)
460 return
461 current = gajim.interface.roster.status_combobox.get_active()
462 if index != current:
463 gajim.interface.roster.status_combobox.set_active(index)
465 def on_change_status_message_activate(self, widget):
466 model = gajim.interface.roster.status_combobox.get_model()
467 active = gajim.interface.roster.status_combobox.get_active()
468 status = model[active][2].decode('utf-8')
469 def on_response(message, pep_dict):
470 if message is None: # None if user press Cancel
471 return
472 accounts = gajim.connections.keys()
473 for acct in accounts:
474 if not gajim.config.get_per('accounts', acct,
475 'sync_with_global_status'):
476 continue
477 show = gajim.SHOW_LIST[gajim.connections[acct].connected]
478 gajim.interface.roster.send_status(acct, show, message)
479 gajim.interface.roster.send_pep(acct, pep_dict)
480 dlg = dialogs.ChangeStatusMessageDialog(on_response, status)
481 dlg.dialog.present()