[kepi] ability to use subkeys. Fixes #6051
[gajim.git] / src / gui_menu_builder.py
blob4645c46b45dcecdefffe24a3c039cf7a42995857
1 # -*- coding:utf-8 -*-
2 ## src/gui_menu_builder.py
3 ##
4 ## Copyright (C) 2009-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ##
6 ## This file is part of Gajim.
7 ##
8 ## Gajim is free software; you can redistribute it and/or modify
9 ## it under the terms of the GNU General Public License as published
10 ## by the Free Software Foundation; version 3 only.
12 ## Gajim is distributed in the hope that it will be useful,
13 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 ## GNU General Public License for more details.
17 ## You should have received a copy of the GNU General Public License
18 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
21 import gtk
22 import os
23 import gtkgui_helpers
24 import message_control
26 from common import gajim
27 from common import helpers
28 from common.xmpp.protocol import NS_COMMANDS, NS_FILE, NS_MUC, NS_ESESSION
30 def build_resources_submenu(contacts, account, action, room_jid=None,
31 room_account=None, cap=None):
32 """
33 Build a submenu with contact's resources. room_jid and room_account are for
34 action self.on_invite_to_room
35 """
36 roster = gajim.interface.roster
37 sub_menu = gtk.Menu()
39 iconset = gajim.config.get('iconset')
40 if not iconset:
41 iconset = gajim.config.DEFAULT_ICONSET
42 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
43 for c in contacts:
44 # icon MUST be different instance for every item
45 state_images = gtkgui_helpers.load_iconset(path)
46 item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority)))
47 icon_name = helpers.get_icon_name_to_show(c, account)
48 icon = state_images[icon_name]
49 item.set_image(icon)
50 sub_menu.append(item)
52 if action == roster.on_invite_to_room:
53 item.connect('activate', action, [(c, account)], room_jid,
54 room_account, c.resource)
55 elif action == roster.on_invite_to_new_room:
56 item.connect('activate', action, [(c, account)], c.resource)
57 else: # start_chat, execute_command, send_file
58 item.connect('activate', action, c, account, c.resource)
60 if cap and not c.supports(cap):
61 item.set_sensitive(False)
63 return sub_menu
65 def build_invite_submenu(invite_menuitem, list_):
66 """
67 list_ in a list of (contact, account)
68 """
69 roster = gajim.interface.roster
70 # used if we invite only one contact with several resources
71 contact_list = []
72 if len(list_) == 1:
73 contact, account = list_[0]
74 contact_list = gajim.contacts.get_contacts(account, contact.jid)
75 contacts_transport = -1
76 connected_accounts = []
77 # -1 is at start, False when not from the same, None when jabber
78 for (contact, account) in list_:
79 if not account in connected_accounts:
80 connected_accounts.append(account)
81 transport = gajim.get_transport_name_from_jid(contact.jid)
82 if contacts_transport == -1:
83 contacts_transport = transport
84 elif contacts_transport != transport:
85 contacts_transport = False
87 if contacts_transport == False:
88 # they are not all from the same transport
89 invite_menuitem.set_sensitive(False)
90 return
91 invite_to_submenu = gtk.Menu()
92 invite_menuitem.set_submenu(invite_to_submenu)
93 invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat'))
94 icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU)
95 invite_to_new_room_menuitem.set_image(icon)
96 if len(contact_list) > 1: # several resources
97 invite_to_new_room_menuitem.set_submenu(build_resources_submenu(
98 contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC))
99 elif len(list_) == 1 and contact.supports(NS_MUC):
100 invite_menuitem.set_sensitive(True)
101 # use resource if it's self contact
102 if contact.jid == gajim.get_jid_from_account(account):
103 resource = contact.resource
104 else:
105 resource = None
106 invite_to_new_room_menuitem.connect('activate',
107 roster.on_invite_to_new_room, list_, resource)
108 else:
109 invite_menuitem.set_sensitive(True)
110 # transform None in 'jabber'
111 c_t = contacts_transport or 'jabber'
112 muc_jid = {}
113 for account in connected_accounts:
114 for t in gajim.connections[account].muc_jid:
115 muc_jid[t] = gajim.connections[account].muc_jid[t]
116 if c_t not in muc_jid:
117 invite_to_new_room_menuitem.set_sensitive(False)
118 rooms = [] # a list of (room_jid, account) tuple
119 invite_to_submenu.append(invite_to_new_room_menuitem)
120 rooms = [] # a list of (room_jid, account) tuple
121 minimized_controls = []
122 for account in connected_accounts:
123 minimized_controls += \
124 gajim.interface.minimized_controls[account].values()
125 for gc_control in gajim.interface.msg_win_mgr.get_controls(
126 message_control.TYPE_GC) + minimized_controls:
127 acct = gc_control.account
128 room_jid = gc_control.room_jid
129 if room_jid in gajim.gc_connected[acct] and \
130 gajim.gc_connected[acct][room_jid] and \
131 contacts_transport == gajim.get_transport_name_from_jid(room_jid):
132 rooms.append((room_jid, acct))
133 if len(rooms):
134 item = gtk.SeparatorMenuItem() # separator
135 invite_to_submenu.append(item)
136 for (room_jid, account) in rooms:
137 menuitem = gtk.MenuItem(room_jid.split('@')[0])
138 if len(contact_list) > 1: # several resources
139 menuitem.set_submenu(build_resources_submenu(
140 contact_list, account, roster.on_invite_to_room, room_jid,
141 account))
142 else:
143 # use resource if it's self contact
144 if contact.jid == gajim.get_jid_from_account(account):
145 resource = contact.resource
146 else:
147 resource = None
148 menuitem.connect('activate', roster.on_invite_to_room, list_,
149 room_jid, account, resource)
150 invite_to_submenu.append(menuitem)
152 def get_contact_menu(contact, account, use_multiple_contacts=True,
153 show_start_chat=True, show_encryption=False, show_buttonbar_items=True,
154 control=None, gc_contact=None):
156 Build contact popup menu for roster and chat window. If control is not set,
157 we hide invite_contacts_menuitem
159 if not contact:
160 return
162 jid = contact.jid
163 our_jid = jid == gajim.get_jid_from_account(account)
164 roster = gajim.interface.roster
166 xml = gtkgui_helpers.get_gtk_builder('contact_context_menu.ui')
167 contact_context_menu = xml.get_object('contact_context_menu')
169 start_chat_menuitem = xml.get_object('start_chat_menuitem')
170 execute_command_menuitem = xml.get_object('execute_command_menuitem')
171 rename_menuitem = xml.get_object('rename_menuitem')
172 edit_groups_menuitem = xml.get_object('edit_groups_menuitem')
173 send_file_menuitem = xml.get_object('send_file_menuitem')
174 assign_openpgp_key_menuitem = xml.get_object('assign_openpgp_key_menuitem')
175 add_special_notification_menuitem = xml.get_object(
176 'add_special_notification_menuitem')
177 information_menuitem = xml.get_object('information_menuitem')
178 history_menuitem = xml.get_object('history_menuitem')
179 send_custom_status_menuitem = xml.get_object('send_custom_status_menuitem')
180 send_single_message_menuitem = xml.get_object('send_single_message_menuitem')
181 invite_menuitem = xml.get_object('invite_menuitem')
182 block_menuitem = xml.get_object('block_menuitem')
183 unblock_menuitem = xml.get_object('unblock_menuitem')
184 ignore_menuitem = xml.get_object('ignore_menuitem')
185 unignore_menuitem = xml.get_object('unignore_menuitem')
186 set_custom_avatar_menuitem = xml.get_object('set_custom_avatar_menuitem')
187 # Subscription submenu
188 subscription_menuitem = xml.get_object('subscription_menuitem')
189 send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem = \
190 subscription_menuitem.get_submenu().get_children()
191 add_to_roster_menuitem = xml.get_object('add_to_roster_menuitem')
192 remove_from_roster_menuitem = xml.get_object(
193 'remove_from_roster_menuitem')
194 manage_contact_menuitem = xml.get_object('manage_contact')
195 convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem')
196 encryption_separator = xml.get_object('encryption_separator')
197 toggle_gpg_menuitem = xml.get_object('toggle_gpg_menuitem')
198 toggle_e2e_menuitem = xml.get_object('toggle_e2e_menuitem')
199 last_separator = xml.get_object('last_separator')
201 items_to_hide = []
203 # add a special img for send file menuitem
204 path_to_upload_img = gtkgui_helpers.get_icon_path('gajim-upload')
205 img = gtk.Image()
206 img.set_from_file(path_to_upload_img)
207 send_file_menuitem.set_image(img)
209 if not our_jid:
210 # add a special img for rename menuitem
211 gtkgui_helpers.add_image_to_menuitem(rename_menuitem, 'gajim-kbd_input')
213 muc_icon = gtkgui_helpers.load_icon('muc_active')
214 if muc_icon:
215 convert_to_gc_menuitem.set_image(muc_icon)
217 contacts = gajim.contacts.get_contacts(account, jid)
218 if len(contacts) > 1 and use_multiple_contacts: # several resources
219 start_chat_menuitem.set_submenu(build_resources_submenu(contacts,
220 account, gajim.interface.on_open_chat_window))
221 send_file_menuitem.set_submenu(build_resources_submenu(contacts,
222 account, roster.on_send_file_menuitem_activate, cap=NS_FILE))
223 execute_command_menuitem.set_submenu(build_resources_submenu(
224 contacts, account, roster.on_execute_command, cap=NS_COMMANDS))
225 else:
226 start_chat_menuitem.connect('activate',
227 gajim.interface.on_open_chat_window, contact, account)
228 if contact.supports(NS_FILE):
229 send_file_menuitem.set_sensitive(True)
230 send_file_menuitem.connect('activate',
231 roster.on_send_file_menuitem_activate, contact, account)
232 else:
233 send_file_menuitem.set_sensitive(False)
235 if contact.supports(NS_COMMANDS):
236 execute_command_menuitem.set_sensitive(True)
237 execute_command_menuitem.connect('activate', roster.on_execute_command,
238 contact, account, contact.resource)
239 else:
240 execute_command_menuitem.set_sensitive(False)
242 rename_menuitem.connect('activate', roster.on_rename, 'contact', jid,
243 account)
244 history_menuitem.connect('activate', roster.on_history, contact, account)
246 if control:
247 convert_to_gc_menuitem.connect('activate',
248 control._on_convert_to_gc_menuitem_activate)
249 else:
250 items_to_hide.append(convert_to_gc_menuitem)
252 if _('Not in Roster') not in contact.get_shown_groups():
253 # contact is in normal group
254 edit_groups_menuitem.connect('activate', roster.on_edit_groups, [(contact,
255 account)])
257 if gajim.connections[account].gpg:
258 assign_openpgp_key_menuitem.connect('activate',
259 roster.on_assign_pgp_key, contact, account)
260 else:
261 assign_openpgp_key_menuitem.set_sensitive(False)
262 else:
263 # contact is in group 'Not in Roster'
264 edit_groups_menuitem.set_sensitive(False)
265 assign_openpgp_key_menuitem.set_sensitive(False)
267 # Hide items when it's self contact row
268 if our_jid:
269 items_to_hide += [rename_menuitem, edit_groups_menuitem]
271 # Unsensitive many items when account is offline
272 if gajim.account_is_disconnected(account):
273 for widget in (start_chat_menuitem, rename_menuitem,
274 edit_groups_menuitem, send_file_menuitem, convert_to_gc_menuitem):
275 widget.set_sensitive(False)
277 if not show_start_chat:
278 items_to_hide.append(start_chat_menuitem)
280 if not show_encryption or not control:
281 items_to_hide += [encryption_separator, toggle_gpg_menuitem,
282 toggle_e2e_menuitem]
283 else:
284 e2e_is_active = control.session is not None and \
285 control.session.enable_encryption
287 # check if we support and use gpg
288 if not gajim.config.get_per('accounts', account, 'keyid') or \
289 not gajim.connections[account].USE_GPG or gajim.jid_is_transport(
290 contact.jid):
291 toggle_gpg_menuitem.set_sensitive(False)
292 else:
293 toggle_gpg_menuitem.set_sensitive(control.gpg_is_active or \
294 not e2e_is_active)
295 toggle_gpg_menuitem.set_active(control.gpg_is_active)
296 toggle_gpg_menuitem.connect('activate',
297 control._on_toggle_gpg_menuitem_activate)
299 # disable esessions if we or the other client don't support them
300 if not gajim.HAVE_PYCRYPTO or not contact.supports(NS_ESESSION) or \
301 not gajim.config.get_per('accounts', account, 'enable_esessions'):
302 toggle_e2e_menuitem.set_sensitive(False)
303 else:
304 toggle_e2e_menuitem.set_active(e2e_is_active)
305 toggle_e2e_menuitem.set_sensitive(e2e_is_active or \
306 not control.gpg_is_active)
307 toggle_e2e_menuitem.connect('activate',
308 control._on_toggle_e2e_menuitem_activate)
310 if not show_buttonbar_items:
311 items_to_hide += [history_menuitem, send_file_menuitem,
312 information_menuitem, convert_to_gc_menuitem, last_separator]
314 if not control:
315 items_to_hide.append(convert_to_gc_menuitem)
317 for item in items_to_hide:
318 item.set_no_show_all(True)
319 item.hide()
321 # Zeroconf Account
322 if gajim.config.get_per('accounts', account, 'is_zeroconf'):
323 for item in (send_custom_status_menuitem, send_single_message_menuitem,
324 invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem,
325 unignore_menuitem, set_custom_avatar_menuitem, subscription_menuitem,
326 manage_contact_menuitem, convert_to_gc_menuitem):
327 item.set_no_show_all(True)
328 item.hide()
330 if contact.show in ('offline', 'error'):
331 information_menuitem.set_sensitive(False)
332 send_file_menuitem.set_sensitive(False)
333 else:
334 information_menuitem.connect('activate', roster.on_info_zeroconf,
335 contact, account)
337 contact_context_menu.connect('selection-done',
338 gtkgui_helpers.destroy_widget)
339 contact_context_menu.show_all()
340 return contact_context_menu
342 # normal account
344 # send custom status icon
345 blocked = False
346 if helpers.jid_is_blocked(account, jid):
347 blocked = True
348 else:
349 for group in contact.get_shown_groups():
350 if helpers.group_is_blocked(account, group):
351 blocked = True
352 break
353 if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
354 # Transport contact, send custom status unavailable
355 send_custom_status_menuitem.set_sensitive(False)
356 elif blocked:
357 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline'))
358 send_custom_status_menuitem.set_sensitive(False)
359 elif account in gajim.interface.status_sent_to_users and \
360 jid in gajim.interface.status_sent_to_users[account]:
361 send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon(
362 gajim.interface.status_sent_to_users[account][jid]))
363 else:
364 icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU)
365 send_custom_status_menuitem.set_image(icon)
367 muc_icon = gtkgui_helpers.load_icon('muc_active')
368 if muc_icon:
369 invite_menuitem.set_image(muc_icon)
371 if gc_contact:
372 if not gc_contact.jid:
373 # it's a pm and we don't know real JID
374 invite_menuitem.set_sensitive(False)
375 else:
376 build_invite_submenu(invite_menuitem, [(gc_contact, account)])
377 else:
378 build_invite_submenu(invite_menuitem, [(contact, account)])
380 # One or several resource, we do the same for send_custom_status
381 status_menuitems = gtk.Menu()
382 send_custom_status_menuitem.set_submenu(status_menuitems)
383 iconset = gajim.config.get('iconset')
384 path = os.path.join(helpers.get_iconset_path(iconset), '16x16')
385 for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
386 # icon MUST be different instance for every item
387 state_images = gtkgui_helpers.load_iconset(path)
388 status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s))
389 status_menuitem.connect('activate', roster.on_send_custom_status,
390 [(contact, account)], s)
391 icon = state_images[s]
392 status_menuitem.set_image(icon)
393 status_menuitems.append(status_menuitem)
395 send_single_message_menuitem.connect('activate',
396 roster.on_send_single_message_menuitem_activate, account, contact)
398 remove_from_roster_menuitem.connect('activate', roster.on_req_usub,
399 [(contact, account)])
400 information_menuitem.connect('activate', roster.on_info, contact, account)
402 if _('Not in Roster') not in contact.get_shown_groups():
403 # contact is in normal group
404 add_to_roster_menuitem.hide()
405 add_to_roster_menuitem.set_no_show_all(True)
407 if contact.sub in ('from', 'both'):
408 send_auth_menuitem.set_sensitive(False)
409 else:
410 send_auth_menuitem.connect('activate', roster.authorize, jid, account)
411 if contact.sub in ('to', 'both'):
412 ask_auth_menuitem.set_sensitive(False)
413 add_special_notification_menuitem.connect('activate',
414 roster.on_add_special_notification_menuitem_activate, jid)
415 else:
416 ask_auth_menuitem.connect('activate', roster.req_sub, jid,
417 _('I would like to add you to my roster'), account,
418 contact.groups, contact.name)
419 if contact.sub in ('to', 'none') or gajim.get_transport_name_from_jid(
420 jid, use_config_setting=False):
421 revoke_auth_menuitem.set_sensitive(False)
422 else:
423 revoke_auth_menuitem.connect('activate', roster.revoke_auth, jid,
424 account)
426 else:
427 # contact is in group 'Not in Roster'
428 add_to_roster_menuitem.set_no_show_all(False)
429 subscription_menuitem.set_sensitive(False)
431 add_to_roster_menuitem.connect('activate', roster.on_add_to_roster,
432 contact, account)
434 set_custom_avatar_menuitem.connect('activate',
435 roster.on_set_custom_avatar_activate, contact, account)
437 # Hide items when it's self contact row
438 if our_jid:
439 manage_contact_menuitem.set_sensitive(False)
441 # Unsensitive items when account is offline
442 if gajim.account_is_disconnected(account):
443 for widget in (send_single_message_menuitem, subscription_menuitem,
444 add_to_roster_menuitem, remove_from_roster_menuitem,
445 execute_command_menuitem, send_custom_status_menuitem):
446 widget.set_sensitive(False)
448 if gajim.connections[account] and gajim.connections[account].\
449 privacy_rules_supported:
450 if helpers.jid_is_blocked(account, jid):
451 block_menuitem.set_no_show_all(True)
452 block_menuitem.hide()
453 if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
454 unblock_menuitem.set_no_show_all(True)
455 unblock_menuitem.hide()
456 unignore_menuitem.set_no_show_all(False)
457 unignore_menuitem.connect('activate', roster.on_unblock, [(contact,
458 account)])
459 else:
460 unblock_menuitem.connect('activate', roster.on_unblock, [(contact,
461 account)])
462 else:
463 unblock_menuitem.set_no_show_all(True)
464 unblock_menuitem.hide()
465 if gajim.get_transport_name_from_jid(jid, use_config_setting=False):
466 block_menuitem.set_no_show_all(True)
467 block_menuitem.hide()
468 ignore_menuitem.set_no_show_all(False)
469 ignore_menuitem.connect('activate', roster.on_block, [(contact,
470 account)])
471 else:
472 block_menuitem.connect('activate', roster.on_block, [(contact,
473 account)])
474 else:
475 unblock_menuitem.set_no_show_all(True)
476 block_menuitem.set_sensitive(False)
477 unblock_menuitem.hide()
479 contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget)
480 contact_context_menu.show_all()
481 return contact_context_menu