2 ## src/gui_menu_builder.py
4 ## Copyright (C) 2009-2010 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):
33 Build a submenu with contact's resources. room_jid and room_account are for
34 action self.on_invite_to_room
36 roster
= gajim
.interface
.roster
39 iconset
= gajim
.config
.get('iconset')
41 iconset
= gajim
.config
.DEFAULT_ICONSET
42 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
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
]
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)
65 def build_invite_submenu(invite_menuitem
, list_
):
67 list_ in a list of (contact, account)
69 roster
= gajim
.interface
.roster
70 # used if we invite only one contact with several resources
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)
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
106 invite_to_new_room_menuitem
.connect('activate',
107 roster
.on_invite_to_new_room
, list_
, resource
)
109 invite_menuitem
.set_sensitive(True)
110 # transform None in 'jabber'
111 c_t
= contacts_transport
or 'jabber'
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
))
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
,
143 # use resource if it's self contact
144 if contact
.jid
== gajim
.get_jid_from_account(account
):
145 resource
= contact
.resource
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
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')
203 # add a special img for send file menuitem
204 path_to_upload_img
= gtkgui_helpers
.get_icon_path('gajim-upload')
206 img
.set_from_file(path_to_upload_img
)
207 send_file_menuitem
.set_image(img
)
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')
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
))
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
)
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
)
240 execute_command_menuitem
.set_sensitive(False)
242 rename_menuitem
.connect('activate', roster
.on_rename
, 'contact', jid
,
244 history_menuitem
.connect('activate', roster
.on_history
, contact
, account
)
247 convert_to_gc_menuitem
.connect('activate',
248 control
._on
_convert
_to
_gc
_menuitem
_activate
)
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
,
257 if gajim
.connections
[account
].gpg
:
258 assign_openpgp_key_menuitem
.connect('activate',
259 roster
.on_assign_pgp_key
, contact
, account
)
261 assign_openpgp_key_menuitem
.set_sensitive(False)
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
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
,
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(
291 toggle_gpg_menuitem
.set_sensitive(False)
293 toggle_gpg_menuitem
.set_sensitive(control
.gpg_is_active
or \
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)
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
)
372 if not gc_contact
.jid
:
373 # it's a pm and we don't know real JID
374 invite_menuitem
.set_sensitive(False)
376 build_invite_submenu(invite_menuitem
, [(gc_contact
, account
)])
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)
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
)
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)
423 revoke_auth_menuitem
.connect('activate', roster
.revoke_auth
, jid
,
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
,
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
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
,
460 unblock_menuitem
.connect('activate', roster
.on_unblock
, [(contact
,
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
,
472 block_menuitem
.connect('activate', roster
.on_block
, [(contact
,
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