2 ## src/gui_menu_builder.py
4 ## Copyright (C) 2009 Yann Leboulanger <asterix AT lagaule.org>
6 ## This file is part of Gajim.
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/>.
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 ''' Build a submenu with contact's resources.
33 room_jid and room_account are for action self.on_invite_to_room '''
34 roster
= gajim
.interface
.roster
37 iconset
= gajim
.config
.get('iconset')
39 iconset
= gajim
.config
.DEFAULT_ICONSET
40 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
42 # icon MUST be different instance for every item
43 state_images
= gtkgui_helpers
.load_iconset(path
)
44 item
= gtk
.ImageMenuItem('%s (%s)' % (c
.resource
, str(c
.priority
)))
45 icon_name
= helpers
.get_icon_name_to_show(c
, account
)
46 icon
= state_images
[icon_name
]
50 if action
== roster
.on_invite_to_room
:
51 item
.connect('activate', action
, [(c
, account
)], room_jid
,
52 room_account
, c
.resource
)
53 elif action
== roster
.on_invite_to_new_room
:
54 item
.connect('activate', action
, [(c
, account
)], c
.resource
)
55 else: # start_chat, execute_command, send_file
56 item
.connect('activate', action
, c
, account
, c
.resource
)
58 if cap
and not gajim
.capscache
.is_supported(c
, cap
):
59 item
.set_sensitive(False)
63 def build_invite_submenu(invite_menuitem
, list_
):
64 '''list_ in a list of (contact, account)'''
65 roster
= gajim
.interface
.roster
66 # used if we invite only one contact with several resources
69 contact
, account
= list_
[0]
70 contact_list
= gajim
.contacts
.get_contacts(account
, contact
.jid
)
71 contacts_transport
= -1
72 connected_accounts
= []
73 # -1 is at start, False when not from the same, None when jabber
74 for (contact
, account
) in list_
:
75 if not account
in connected_accounts
:
76 connected_accounts
.append(account
)
77 transport
= gajim
.get_transport_name_from_jid(contact
.jid
)
78 if contacts_transport
== -1:
79 contacts_transport
= transport
80 elif contacts_transport
!= transport
:
81 contacts_transport
= False
83 if contacts_transport
== False:
84 # they are not all from the same transport
85 invite_menuitem
.set_sensitive(False)
87 invite_to_submenu
= gtk
.Menu()
88 invite_menuitem
.set_submenu(invite_to_submenu
)
89 invite_to_new_room_menuitem
= gtk
.ImageMenuItem(_('_New Group Chat'))
90 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NEW
, gtk
.ICON_SIZE_MENU
)
91 invite_to_new_room_menuitem
.set_image(icon
)
92 if len(contact_list
) > 1: # several resources
93 invite_to_new_room_menuitem
.set_submenu(build_resources_submenu(
94 contact_list
, account
, roster
.on_invite_to_new_room
, cap
=NS_MUC
))
95 elif len(list_
) == 1 and gajim
.capscache
.is_supported(contact
, NS_MUC
):
96 invite_menuitem
.set_sensitive(True)
97 # use resource if it's self contact
98 if contact
.jid
== gajim
.get_jid_from_account(account
):
99 resource
= contact
.resource
102 invite_to_new_room_menuitem
.connect('activate',
103 roster
.on_invite_to_new_room
, list_
, resource
)
105 invite_menuitem
.set_sensitive(False)
106 # transform None in 'jabber'
107 c_t
= contacts_transport
or 'jabber'
109 for account
in connected_accounts
:
110 for t
in gajim
.connections
[account
].muc_jid
:
111 muc_jid
[t
] = gajim
.connections
[account
].muc_jid
[t
]
112 if c_t
not in muc_jid
:
113 invite_to_new_room_menuitem
.set_sensitive(False)
114 rooms
= [] # a list of (room_jid, account) tuple
115 invite_to_submenu
.append(invite_to_new_room_menuitem
)
116 rooms
= [] # a list of (room_jid, account) tuple
117 minimized_controls
= []
118 for account
in connected_accounts
:
119 minimized_controls
+= gajim
.interface
.minimized_controls
[account
].values()
120 for gc_control
in gajim
.interface
.msg_win_mgr
.get_controls(
121 message_control
.TYPE_GC
) + minimized_controls
:
122 acct
= gc_control
.account
123 room_jid
= gc_control
.room_jid
124 if room_jid
in gajim
.gc_connected
[acct
] and \
125 gajim
.gc_connected
[acct
][room_jid
] and \
126 contacts_transport
== gajim
.get_transport_name_from_jid(room_jid
):
127 rooms
.append((room_jid
, acct
))
129 item
= gtk
.SeparatorMenuItem() # separator
130 invite_to_submenu
.append(item
)
131 for (room_jid
, account
) in rooms
:
132 menuitem
= gtk
.MenuItem(room_jid
.split('@')[0])
133 if len(contact_list
) > 1: # several resources
134 menuitem
.set_submenu(build_resources_submenu(
135 contact_list
, account
, roster
.on_invite_to_room
, room_jid
,
138 # use resource if it's self contact
139 if contact
.jid
== gajim
.get_jid_from_account(account
):
140 resource
= contact
.resource
143 menuitem
.connect('activate', roster
.on_invite_to_room
, list_
,
144 room_jid
, account
, resource
)
145 invite_to_submenu
.append(menuitem
)
147 def get_contact_menu(contact
, account
, use_multiple_contacts
=True,
148 show_start_chat
=True, show_encryption
=False, show_buttonbar_items
=True,
150 ''' Build contact popup menu for roster and chat window.
151 If control is not set, we hide invite_contacts_menuitem'''
156 our_jid
= jid
== gajim
.get_jid_from_account(account
)
157 roster
= gajim
.interface
.roster
159 xml
= gtkgui_helpers
.get_glade('contact_context_menu.glade')
160 contact_context_menu
= xml
.get_widget('contact_context_menu')
162 start_chat_menuitem
= xml
.get_widget('start_chat_menuitem')
163 execute_command_menuitem
= xml
.get_widget('execute_command_menuitem')
164 rename_menuitem
= xml
.get_widget('rename_menuitem')
165 edit_groups_menuitem
= xml
.get_widget('edit_groups_menuitem')
166 send_file_menuitem
= xml
.get_widget('send_file_menuitem')
167 assign_openpgp_key_menuitem
= xml
.get_widget('assign_openpgp_key_menuitem')
168 add_special_notification_menuitem
= xml
.get_widget(
169 'add_special_notification_menuitem')
170 information_menuitem
= xml
.get_widget('information_menuitem')
171 history_menuitem
= xml
.get_widget('history_menuitem')
172 send_custom_status_menuitem
= xml
.get_widget('send_custom_status_menuitem')
173 send_single_message_menuitem
= xml
.get_widget('send_single_message_menuitem')
174 invite_menuitem
= xml
.get_widget('invite_menuitem')
175 block_menuitem
= xml
.get_widget('block_menuitem')
176 unblock_menuitem
= xml
.get_widget('unblock_menuitem')
177 ignore_menuitem
= xml
.get_widget('ignore_menuitem')
178 unignore_menuitem
= xml
.get_widget('unignore_menuitem')
179 set_custom_avatar_menuitem
= xml
.get_widget('set_custom_avatar_menuitem')
180 # Subscription submenu
181 subscription_menuitem
= xml
.get_widget('subscription_menuitem')
182 send_auth_menuitem
, ask_auth_menuitem
, revoke_auth_menuitem
= \
183 subscription_menuitem
.get_submenu().get_children()
184 add_to_roster_menuitem
= xml
.get_widget('add_to_roster_menuitem')
185 remove_from_roster_menuitem
= xml
.get_widget(
186 'remove_from_roster_menuitem')
187 manage_contact_menuitem
= xml
.get_widget('manage_contact')
188 convert_to_gc_menuitem
= xml
.get_widget('convert_to_groupchat_menuitem')
189 encryption_separator
= xml
.get_widget('encryption_separator')
190 toggle_gpg_menuitem
= xml
.get_widget('toggle_gpg_menuitem')
191 toggle_e2e_menuitem
= xml
.get_widget('toggle_e2e_menuitem')
192 last_separator
= xml
.get_widget('last_separator')
196 # add a special img for send file menuitem
197 path_to_upload_img
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps', 'upload.png')
199 img
.set_from_file(path_to_upload_img
)
200 send_file_menuitem
.set_image(img
)
203 # add a special img for rename menuitem
204 path_to_kbd_input_img
= os
.path
.join(gajim
.DATA_DIR
, 'pixmaps',
207 img
.set_from_file(path_to_kbd_input_img
)
208 rename_menuitem
.set_image(img
)
210 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
212 convert_to_gc_menuitem
.set_image(muc_icon
)
214 contacts
= gajim
.contacts
.get_contacts(account
, jid
)
215 if len(contacts
) > 1 and use_multiple_contacts
: # several resources
216 start_chat_menuitem
.set_submenu(build_resources_submenu(contacts
,
217 account
, gajim
.interface
.on_open_chat_window
))
218 send_file_menuitem
.set_submenu(build_resources_submenu(contacts
,
219 account
, roster
.on_send_file_menuitem_activate
, cap
=NS_FILE
))
220 execute_command_menuitem
.set_submenu(build_resources_submenu(
221 contacts
, account
, roster
.on_execute_command
, cap
=NS_COMMANDS
))
223 start_chat_menuitem
.connect('activate',
224 gajim
.interface
.on_open_chat_window
, contact
, account
)
225 if gajim
.capscache
.is_supported(contact
, NS_FILE
):
226 send_file_menuitem
.set_sensitive(True)
227 send_file_menuitem
.connect('activate',
228 roster
.on_send_file_menuitem_activate
, contact
, account
)
230 send_file_menuitem
.set_sensitive(False)
232 if gajim
.capscache
.is_supported(contact
, NS_COMMANDS
):
233 execute_command_menuitem
.set_sensitive(True)
234 execute_command_menuitem
.connect('activate', roster
.on_execute_command
,
235 contact
, account
, contact
.resource
)
237 execute_command_menuitem
.set_sensitive(False)
239 rename_menuitem
.connect('activate', roster
.on_rename
, 'contact', jid
,
241 history_menuitem
.connect('activate', roster
.on_history
, contact
, account
)
244 convert_to_gc_menuitem
.connect('activate',
245 control
._on
_convert
_to
_gc
_menuitem
_activate
)
247 items_to_hide
.append(convert_to_gc_menuitem
)
249 if _('Not in Roster') not in contact
.get_shown_groups():
250 # contact is in normal group
251 edit_groups_menuitem
.connect('activate', roster
.on_edit_groups
, [(contact
,
254 if gajim
.connections
[account
].gpg
:
255 assign_openpgp_key_menuitem
.connect('activate',
256 roster
.on_assign_pgp_key
, contact
, account
)
258 assign_openpgp_key_menuitem
.set_sensitive(False)
260 # contact is in group 'Not in Roster'
261 edit_groups_menuitem
.set_sensitive(False)
262 assign_openpgp_key_menuitem
.set_sensitive(False)
264 # Hide items when it's self contact row
266 items_to_hide
+= [rename_menuitem
, edit_groups_menuitem
]
268 # Unsensitive many items when account is offline
269 if gajim
.account_is_disconnected(account
):
270 for widget
in (start_chat_menuitem
, rename_menuitem
,
271 edit_groups_menuitem
, send_file_menuitem
, convert_to_gc_menuitem
):
272 widget
.set_sensitive(False)
274 if not show_start_chat
:
275 items_to_hide
.append(start_chat_menuitem
)
277 if not show_encryption
or not control
:
278 items_to_hide
+= [encryption_separator
, toggle_gpg_menuitem
,
281 e2e_is_active
= control
.session
is not None and \
282 control
.session
.enable_encryption
284 # check if we support and use gpg
285 if not gajim
.config
.get_per('accounts', account
, 'keyid') or \
286 not gajim
.connections
[account
].USE_GPG
or gajim
.jid_is_transport(
288 toggle_gpg_menuitem
.set_sensitive(False)
290 toggle_gpg_menuitem
.set_sensitive(control
.gpg_is_active
or \
292 toggle_gpg_menuitem
.set_active(control
.gpg_is_active
)
293 toggle_gpg_menuitem
.connect('activate',
294 control
._on
_toggle
_gpg
_menuitem
_activate
)
296 # disable esessions if we or the other client don't support them
297 # XXX: Once we have fallback to disco, remove notexistant check
298 if not gajim
.HAVE_PYCRYPTO
or \
299 not gajim
.capscache
.is_supported(contact
, NS_ESESSION
) or \
300 gajim
.capscache
.is_supported(contact
, 'notexistant') or \
301 not gajim
.config
.get_per('accounts', account
, 'enable_esessions'):
302 toggle_e2e_menuitem
.set_sensitive(False)
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
_e
2e
_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
]
315 items_to_hide
.append(convert_to_gc_menuitem
)
317 for item
in items_to_hide
:
318 item
.set_no_show_all(True)
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)
330 if contact
.show
in ('offline', 'error'):
331 information_menuitem
.set_sensitive(False)
332 send_file_menuitem
.set_sensitive(False)
334 information_menuitem
.connect('activate', roster
.on_info_zeroconf
,
337 contact_context_menu
.connect('selection-done',
338 gtkgui_helpers
.destroy_widget
)
339 contact_context_menu
.show_all()
340 return contact_context_menu
344 # send custom status icon
346 if helpers
.jid_is_blocked(account
, jid
):
349 for group
in contact
.get_shown_groups():
350 if helpers
.group_is_blocked(account
, group
):
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)
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
]))
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')
369 invite_menuitem
.set_image(muc_icon
)
371 build_invite_submenu(invite_menuitem
, [(contact
, account
)])
373 # One or several resource, we do the same for send_custom_status
374 status_menuitems
= gtk
.Menu()
375 send_custom_status_menuitem
.set_submenu(status_menuitems
)
376 iconset
= gajim
.config
.get('iconset')
377 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
378 for s
in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
379 # icon MUST be different instance for every item
380 state_images
= gtkgui_helpers
.load_iconset(path
)
381 status_menuitem
= gtk
.ImageMenuItem(helpers
.get_uf_show(s
))
382 status_menuitem
.connect('activate', roster
.on_send_custom_status
,
383 [(contact
, account
)], s
)
384 icon
= state_images
[s
]
385 status_menuitem
.set_image(icon
)
386 status_menuitems
.append(status_menuitem
)
388 send_single_message_menuitem
.connect('activate',
389 roster
.on_send_single_message_menuitem_activate
, account
, contact
)
391 remove_from_roster_menuitem
.connect('activate', roster
.on_req_usub
,
392 [(contact
, account
)])
393 information_menuitem
.connect('activate', roster
.on_info
, contact
, account
)
395 if _('Not in Roster') not in contact
.get_shown_groups():
396 # contact is in normal group
397 add_to_roster_menuitem
.hide()
398 add_to_roster_menuitem
.set_no_show_all(True)
400 if contact
.sub
in ('from', 'both'):
401 send_auth_menuitem
.set_sensitive(False)
403 send_auth_menuitem
.connect('activate', roster
.authorize
, jid
, account
)
404 if contact
.sub
in ('to', 'both'):
405 ask_auth_menuitem
.set_sensitive(False)
406 add_special_notification_menuitem
.connect('activate',
407 roster
.on_add_special_notification_menuitem_activate
, jid
)
409 ask_auth_menuitem
.connect('activate', roster
.req_sub
, jid
,
410 _('I would like to add you to my roster'), account
,
411 contact
.groups
, contact
.name
)
412 if contact
.sub
in ('to', 'none') or gajim
.get_transport_name_from_jid(
413 jid
, use_config_setting
=False):
414 revoke_auth_menuitem
.set_sensitive(False)
416 revoke_auth_menuitem
.connect('activate', roster
.revoke_auth
, jid
,
420 # contact is in group 'Not in Roster'
421 add_to_roster_menuitem
.set_no_show_all(False)
422 subscription_menuitem
.set_sensitive(False)
424 add_to_roster_menuitem
.connect('activate', roster
.on_add_to_roster
,
427 set_custom_avatar_menuitem
.connect('activate',
428 roster
.on_set_custom_avatar_activate
, contact
, account
)
430 # Hide items when it's self contact row
432 manage_contact_menuitem
.set_sensitive(False)
434 # Unsensitive items when account is offline
435 if gajim
.account_is_disconnected(account
):
436 for widget
in (send_single_message_menuitem
, subscription_menuitem
,
437 add_to_roster_menuitem
, remove_from_roster_menuitem
,
438 execute_command_menuitem
, send_custom_status_menuitem
):
439 widget
.set_sensitive(False)
441 if gajim
.connections
[account
] and gajim
.connections
[account
].\
442 privacy_rules_supported
:
443 if helpers
.jid_is_blocked(account
, jid
):
444 block_menuitem
.set_no_show_all(True)
445 block_menuitem
.hide()
446 if gajim
.get_transport_name_from_jid(jid
, use_config_setting
=False):
447 unblock_menuitem
.set_no_show_all(True)
448 unblock_menuitem
.hide()
449 unignore_menuitem
.set_no_show_all(False)
450 unignore_menuitem
.connect('activate', roster
.on_unblock
, [(contact
,
453 unblock_menuitem
.connect('activate', roster
.on_unblock
, [(contact
,
456 unblock_menuitem
.set_no_show_all(True)
457 unblock_menuitem
.hide()
458 if gajim
.get_transport_name_from_jid(jid
, use_config_setting
=False):
459 block_menuitem
.set_no_show_all(True)
460 block_menuitem
.hide()
461 ignore_menuitem
.set_no_show_all(False)
462 ignore_menuitem
.connect('activate', roster
.on_block
, [(contact
,
465 block_menuitem
.connect('activate', roster
.on_block
, [(contact
,
468 unblock_menuitem
.set_no_show_all(True)
469 block_menuitem
.set_sensitive(False)
470 unblock_menuitem
.hide()
472 contact_context_menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
473 contact_context_menu
.show_all()
474 return contact_context_menu