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_
, ignore_rooms
=[]):
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 ignore_rooms
:
131 if room_jid
in gajim
.gc_connected
[acct
] and \
132 gajim
.gc_connected
[acct
][room_jid
] and \
133 contacts_transport
== gajim
.get_transport_name_from_jid(room_jid
):
134 rooms
.append((room_jid
, acct
))
136 item
= gtk
.SeparatorMenuItem() # separator
137 invite_to_submenu
.append(item
)
138 for (room_jid
, account
) in rooms
:
139 menuitem
= gtk
.MenuItem(room_jid
.split('@')[0])
140 if len(contact_list
) > 1: # several resources
141 menuitem
.set_submenu(build_resources_submenu(
142 contact_list
, account
, roster
.on_invite_to_room
, room_jid
,
145 # use resource if it's self contact
146 if contact
.jid
== gajim
.get_jid_from_account(account
):
147 resource
= contact
.resource
150 menuitem
.connect('activate', roster
.on_invite_to_room
, list_
,
151 room_jid
, account
, resource
)
152 invite_to_submenu
.append(menuitem
)
154 def get_contact_menu(contact
, account
, use_multiple_contacts
=True,
155 show_start_chat
=True, show_encryption
=False, show_buttonbar_items
=True,
156 control
=None, gc_contact
=None):
158 Build contact popup menu for roster and chat window. If control is not set,
159 we hide invite_contacts_menuitem
165 our_jid
= jid
== gajim
.get_jid_from_account(account
)
166 roster
= gajim
.interface
.roster
168 xml
= gtkgui_helpers
.get_gtk_builder('contact_context_menu.ui')
169 contact_context_menu
= xml
.get_object('contact_context_menu')
171 start_chat_menuitem
= xml
.get_object('start_chat_menuitem')
172 execute_command_menuitem
= xml
.get_object('execute_command_menuitem')
173 rename_menuitem
= xml
.get_object('rename_menuitem')
174 edit_groups_menuitem
= xml
.get_object('edit_groups_menuitem')
175 send_file_menuitem
= xml
.get_object('send_file_menuitem')
176 assign_openpgp_key_menuitem
= xml
.get_object('assign_openpgp_key_menuitem')
177 add_special_notification_menuitem
= xml
.get_object(
178 'add_special_notification_menuitem')
179 information_menuitem
= xml
.get_object('information_menuitem')
180 history_menuitem
= xml
.get_object('history_menuitem')
181 send_custom_status_menuitem
= xml
.get_object('send_custom_status_menuitem')
182 send_single_message_menuitem
= xml
.get_object('send_single_message_menuitem')
183 invite_menuitem
= xml
.get_object('invite_menuitem')
184 block_menuitem
= xml
.get_object('block_menuitem')
185 unblock_menuitem
= xml
.get_object('unblock_menuitem')
186 ignore_menuitem
= xml
.get_object('ignore_menuitem')
187 unignore_menuitem
= xml
.get_object('unignore_menuitem')
188 set_custom_avatar_menuitem
= xml
.get_object('set_custom_avatar_menuitem')
189 # Subscription submenu
190 subscription_menuitem
= xml
.get_object('subscription_menuitem')
191 send_auth_menuitem
, ask_auth_menuitem
, revoke_auth_menuitem
= \
192 subscription_menuitem
.get_submenu().get_children()
193 add_to_roster_menuitem
= xml
.get_object('add_to_roster_menuitem')
194 remove_from_roster_menuitem
= xml
.get_object(
195 'remove_from_roster_menuitem')
196 manage_contact_menuitem
= xml
.get_object('manage_contact')
197 convert_to_gc_menuitem
= xml
.get_object('convert_to_groupchat_menuitem')
198 encryption_separator
= xml
.get_object('encryption_separator')
199 toggle_gpg_menuitem
= xml
.get_object('toggle_gpg_menuitem')
200 toggle_e2e_menuitem
= xml
.get_object('toggle_e2e_menuitem')
201 last_separator
= xml
.get_object('last_separator')
205 # add a special img for send file menuitem
206 path_to_upload_img
= gtkgui_helpers
.get_icon_path('gajim-upload')
208 img
.set_from_file(path_to_upload_img
)
209 send_file_menuitem
.set_image(img
)
212 # add a special img for rename menuitem
213 gtkgui_helpers
.add_image_to_menuitem(rename_menuitem
, 'gajim-kbd_input')
215 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
217 convert_to_gc_menuitem
.set_image(muc_icon
)
219 contacts
= gajim
.contacts
.get_contacts(account
, jid
)
220 if len(contacts
) > 1 and use_multiple_contacts
: # several resources
221 start_chat_menuitem
.set_submenu(build_resources_submenu(contacts
,
222 account
, gajim
.interface
.on_open_chat_window
))
223 send_file_menuitem
.set_submenu(build_resources_submenu(contacts
,
224 account
, roster
.on_send_file_menuitem_activate
, cap
=NS_FILE
))
225 execute_command_menuitem
.set_submenu(build_resources_submenu(
226 contacts
, account
, roster
.on_execute_command
, cap
=NS_COMMANDS
))
228 start_chat_menuitem
.connect('activate',
229 gajim
.interface
.on_open_chat_window
, contact
, account
)
230 if contact
.supports(NS_FILE
):
231 send_file_menuitem
.set_sensitive(True)
232 send_file_menuitem
.connect('activate',
233 roster
.on_send_file_menuitem_activate
, contact
, account
)
235 send_file_menuitem
.set_sensitive(False)
237 if contact
.supports(NS_COMMANDS
):
238 execute_command_menuitem
.set_sensitive(True)
239 execute_command_menuitem
.connect('activate', roster
.on_execute_command
,
240 contact
, account
, contact
.resource
)
242 execute_command_menuitem
.set_sensitive(False)
244 rename_menuitem
.connect('activate', roster
.on_rename
, 'contact', jid
,
246 history_menuitem
.connect('activate', roster
.on_history
, contact
, account
)
249 convert_to_gc_menuitem
.connect('activate',
250 control
._on
_convert
_to
_gc
_menuitem
_activate
)
252 items_to_hide
.append(convert_to_gc_menuitem
)
254 if _('Not in Roster') not in contact
.get_shown_groups():
255 # contact is in normal group
256 edit_groups_menuitem
.connect('activate', roster
.on_edit_groups
, [(contact
,
259 if gajim
.connections
[account
].gpg
:
260 assign_openpgp_key_menuitem
.connect('activate',
261 roster
.on_assign_pgp_key
, contact
, account
)
263 assign_openpgp_key_menuitem
.set_sensitive(False)
265 # contact is in group 'Not in Roster'
266 edit_groups_menuitem
.set_sensitive(False)
267 assign_openpgp_key_menuitem
.set_sensitive(False)
269 # Hide items when it's self contact row
271 items_to_hide
+= [rename_menuitem
, edit_groups_menuitem
]
273 # Unsensitive many items when account is offline
274 if gajim
.account_is_disconnected(account
):
275 for widget
in (start_chat_menuitem
, rename_menuitem
,
276 edit_groups_menuitem
, send_file_menuitem
, convert_to_gc_menuitem
):
277 widget
.set_sensitive(False)
279 if not show_start_chat
:
280 items_to_hide
.append(start_chat_menuitem
)
282 if not show_encryption
or not control
:
283 items_to_hide
+= [encryption_separator
, toggle_gpg_menuitem
,
286 e2e_is_active
= control
.session
is not None and \
287 control
.session
.enable_encryption
289 # check if we support and use gpg
290 if not gajim
.config
.get_per('accounts', account
, 'keyid') or \
291 not gajim
.connections
[account
].USE_GPG
or gajim
.jid_is_transport(
293 toggle_gpg_menuitem
.set_sensitive(False)
295 toggle_gpg_menuitem
.set_sensitive(control
.gpg_is_active
or \
297 toggle_gpg_menuitem
.set_active(control
.gpg_is_active
)
298 toggle_gpg_menuitem
.connect('activate',
299 control
._on
_toggle
_gpg
_menuitem
_activate
)
301 # disable esessions if we or the other client don't support them
302 if not gajim
.HAVE_PYCRYPTO
or not contact
.supports(NS_ESESSION
) or \
303 not gajim
.config
.get_per('accounts', account
, 'enable_esessions'):
304 toggle_e2e_menuitem
.set_sensitive(False)
306 toggle_e2e_menuitem
.set_active(e2e_is_active
)
307 toggle_e2e_menuitem
.set_sensitive(e2e_is_active
or \
308 not control
.gpg_is_active
)
309 toggle_e2e_menuitem
.connect('activate',
310 control
._on
_toggle
_e
2e
_menuitem
_activate
)
312 if not show_buttonbar_items
:
313 items_to_hide
+= [history_menuitem
, send_file_menuitem
,
314 information_menuitem
, convert_to_gc_menuitem
, last_separator
]
317 items_to_hide
.append(convert_to_gc_menuitem
)
319 for item
in items_to_hide
:
320 item
.set_no_show_all(True)
324 if gajim
.config
.get_per('accounts', account
, 'is_zeroconf'):
325 for item
in (send_custom_status_menuitem
, send_single_message_menuitem
,
326 invite_menuitem
, block_menuitem
, unblock_menuitem
, ignore_menuitem
,
327 unignore_menuitem
, set_custom_avatar_menuitem
, subscription_menuitem
,
328 manage_contact_menuitem
, convert_to_gc_menuitem
):
329 item
.set_no_show_all(True)
332 if contact
.show
in ('offline', 'error'):
333 information_menuitem
.set_sensitive(False)
334 send_file_menuitem
.set_sensitive(False)
336 information_menuitem
.connect('activate', roster
.on_info_zeroconf
,
339 contact_context_menu
.connect('selection-done',
340 gtkgui_helpers
.destroy_widget
)
341 contact_context_menu
.show_all()
342 return contact_context_menu
346 # send custom status icon
348 if helpers
.jid_is_blocked(account
, jid
):
351 for group
in contact
.get_shown_groups():
352 if helpers
.group_is_blocked(account
, group
):
355 if gajim
.get_transport_name_from_jid(jid
, use_config_setting
=False):
356 # Transport contact, send custom status unavailable
357 send_custom_status_menuitem
.set_sensitive(False)
359 send_custom_status_menuitem
.set_image(gtkgui_helpers
.load_icon('offline'))
360 send_custom_status_menuitem
.set_sensitive(False)
361 elif account
in gajim
.interface
.status_sent_to_users
and \
362 jid
in gajim
.interface
.status_sent_to_users
[account
]:
363 send_custom_status_menuitem
.set_image(gtkgui_helpers
.load_icon(
364 gajim
.interface
.status_sent_to_users
[account
][jid
]))
366 icon
= gtk
.image_new_from_stock(gtk
.STOCK_NETWORK
, gtk
.ICON_SIZE_MENU
)
367 send_custom_status_menuitem
.set_image(icon
)
369 muc_icon
= gtkgui_helpers
.load_icon('muc_active')
371 invite_menuitem
.set_image(muc_icon
)
374 if not gc_contact
.jid
:
375 # it's a pm and we don't know real JID
376 invite_menuitem
.set_sensitive(False)
378 build_invite_submenu(invite_menuitem
, [(gc_contact
, account
)])
380 build_invite_submenu(invite_menuitem
, [(contact
, account
)])
382 # One or several resource, we do the same for send_custom_status
383 status_menuitems
= gtk
.Menu()
384 send_custom_status_menuitem
.set_submenu(status_menuitems
)
385 iconset
= gajim
.config
.get('iconset')
386 path
= os
.path
.join(helpers
.get_iconset_path(iconset
), '16x16')
387 for s
in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'):
388 # icon MUST be different instance for every item
389 state_images
= gtkgui_helpers
.load_iconset(path
)
390 status_menuitem
= gtk
.ImageMenuItem(helpers
.get_uf_show(s
))
391 status_menuitem
.connect('activate', roster
.on_send_custom_status
,
392 [(contact
, account
)], s
)
393 icon
= state_images
[s
]
394 status_menuitem
.set_image(icon
)
395 status_menuitems
.append(status_menuitem
)
397 send_single_message_menuitem
.connect('activate',
398 roster
.on_send_single_message_menuitem_activate
, account
, contact
)
400 remove_from_roster_menuitem
.connect('activate', roster
.on_req_usub
,
401 [(contact
, account
)])
402 information_menuitem
.connect('activate', roster
.on_info
, contact
, account
)
404 if _('Not in Roster') not in contact
.get_shown_groups():
405 # contact is in normal group
406 add_to_roster_menuitem
.hide()
407 add_to_roster_menuitem
.set_no_show_all(True)
409 if contact
.sub
in ('from', 'both'):
410 send_auth_menuitem
.set_sensitive(False)
412 send_auth_menuitem
.connect('activate', roster
.authorize
, jid
, account
)
413 if contact
.sub
in ('to', 'both'):
414 ask_auth_menuitem
.set_sensitive(False)
415 add_special_notification_menuitem
.connect('activate',
416 roster
.on_add_special_notification_menuitem_activate
, jid
)
418 ask_auth_menuitem
.connect('activate', roster
.req_sub
, jid
,
419 _('I would like to add you to my roster'), account
,
420 contact
.groups
, contact
.name
)
421 if contact
.sub
in ('to', 'none') or gajim
.get_transport_name_from_jid(
422 jid
, use_config_setting
=False):
423 revoke_auth_menuitem
.set_sensitive(False)
425 revoke_auth_menuitem
.connect('activate', roster
.revoke_auth
, jid
,
429 # contact is in group 'Not in Roster'
430 add_to_roster_menuitem
.set_no_show_all(False)
431 subscription_menuitem
.set_sensitive(False)
433 add_to_roster_menuitem
.connect('activate', roster
.on_add_to_roster
,
436 set_custom_avatar_menuitem
.connect('activate',
437 roster
.on_set_custom_avatar_activate
, contact
, account
)
439 # Hide items when it's self contact row
441 manage_contact_menuitem
.set_sensitive(False)
443 # Unsensitive items when account is offline
444 if gajim
.account_is_disconnected(account
):
445 for widget
in (send_single_message_menuitem
, subscription_menuitem
,
446 add_to_roster_menuitem
, remove_from_roster_menuitem
,
447 execute_command_menuitem
, send_custom_status_menuitem
):
448 widget
.set_sensitive(False)
450 if gajim
.connections
[account
] and gajim
.connections
[account
].\
451 privacy_rules_supported
:
452 if helpers
.jid_is_blocked(account
, jid
):
453 block_menuitem
.set_no_show_all(True)
454 block_menuitem
.hide()
455 if gajim
.get_transport_name_from_jid(jid
, use_config_setting
=False):
456 unblock_menuitem
.set_no_show_all(True)
457 unblock_menuitem
.hide()
458 unignore_menuitem
.set_no_show_all(False)
459 unignore_menuitem
.connect('activate', roster
.on_unblock
, [(contact
,
462 unblock_menuitem
.connect('activate', roster
.on_unblock
, [(contact
,
465 unblock_menuitem
.set_no_show_all(True)
466 unblock_menuitem
.hide()
467 if gajim
.get_transport_name_from_jid(jid
, use_config_setting
=False):
468 block_menuitem
.set_no_show_all(True)
469 block_menuitem
.hide()
470 ignore_menuitem
.set_no_show_all(False)
471 ignore_menuitem
.connect('activate', roster
.on_block
, [(contact
,
474 block_menuitem
.connect('activate', roster
.on_block
, [(contact
,
477 unblock_menuitem
.set_no_show_all(True)
478 block_menuitem
.set_sensitive(False)
479 unblock_menuitem
.hide()
481 contact_context_menu
.connect('selection-done', gtkgui_helpers
.destroy_widget
)
482 contact_context_menu
.show_all()
483 return contact_context_menu