# Copyright (C) 2009-2014 Yann Leboulanger # # This file is part of Gajim. # # Gajim is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published # by the Free Software Foundation; version 3 only. # # Gajim is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Gajim. If not, see . from gi.repository import Gtk, Gio, GLib from nbxmpp.namespaces import Namespace from gajim import gtkgui_helpers from gajim.common import app from gajim.common import helpers from gajim.common.helpers import is_affiliation_change_allowed from gajim.common.helpers import is_role_change_allowed from gajim.common.i18n import _ from gajim.common.const import URIType from gajim.common.const import URIAction from gajim.gui.util import get_builder from gajim.gui.const import ControlType def build_resources_submenu(contacts, account, action, room_jid=None, room_account=None, cap=None): """ Build a submenu with contact's resources. room_jid and room_account are for action self.on_invite_to_room """ roster = app.interface.roster sub_menu = Gtk.Menu() for c in contacts: item = Gtk.MenuItem.new_with_label( '%s (%s)' % (c.resource, str(c.priority))) sub_menu.append(item) if action == roster.on_invite_to_room: # pylint: disable=comparison-with-callable item.connect('activate', action, [(c, account)], room_jid, room_account, c.resource) else: # start_chat, execute_command, send_file item.connect('activate', action, c, account, c.resource) if cap and not c.supports(cap): item.set_sensitive(False) return sub_menu def build_invite_submenu(invite_menuitem, list_, ignore_rooms=None, show_bookmarked=False, force_resource=False): """ list_ in a list of (contact, account) force_resource means we want to send invitation even if there is only one resource """ if ignore_rooms is None: ignore_rooms = [] roster = app.interface.roster # used if we invite only one contact with several resources contact_list = [] if len(list_) == 1: contact, account = list_[0] contact_list = app.contacts.get_contacts(account, contact.jid) contacts_transport = -1 connected_accounts = [] # -1 is at start, False when not from the same, None when jabber for (contact, account) in list_: if not account in connected_accounts: connected_accounts.append(account) transport = app.get_transport_name_from_jid(contact.jid) if transport == 'jabber': transport = None if contacts_transport == -1: contacts_transport = transport elif contacts_transport != transport: contacts_transport = False if contacts_transport is False: # they are not all from the same transport invite_menuitem.set_sensitive(False) return invite_to_submenu = Gtk.Menu() invite_menuitem.set_submenu(invite_to_submenu) rooms = [] # a list of (room_jid, account) tuple minimized_controls = [] for account in connected_accounts: minimized_controls += \ list(app.interface.minimized_controls[account].values()) for gc_control in app.interface.msg_win_mgr.get_controls( ControlType.GROUPCHAT) + minimized_controls: acct = gc_control.account if acct not in connected_accounts: continue room_jid = gc_control.room_jid room_name = gc_control.room_name if room_jid in ignore_rooms: continue if room_jid in app.gc_connected[acct] and \ app.gc_connected[acct][room_jid] and \ contacts_transport in ['jabber', None]: rooms.append((room_jid, room_name, acct)) if rooms: item = Gtk.SeparatorMenuItem.new() # separator invite_to_submenu.append(item) for (room_jid, room_name, account) in rooms: menuitem = Gtk.MenuItem.new_with_label(room_name) if len(contact_list) > 1: # several resources menuitem.set_submenu(build_resources_submenu( contact_list, account, roster.on_invite_to_room, room_jid, account)) else: # use resource if it's self contact if contact.jid == app.get_jid_from_account(account): resource = contact.resource else: resource = None menuitem.connect('activate', roster.on_invite_to_room, list_, room_jid, account, resource) invite_to_submenu.append(menuitem) if not show_bookmarked: return rooms2 = [] # a list of (room_jid, account) tuple r_jids = [] # list of room jids for account in connected_accounts: con = app.connections[account] for bookmark in con.get_module('Bookmarks').bookmarks: if bookmark.jid in r_jids: continue if bookmark.jid not in app.gc_connected[account] or not \ app.gc_connected[account][bookmark.jid]: rooms2.append((bookmark.jid, account)) r_jids.append(bookmark.jid) if not rooms2: return item = Gtk.SeparatorMenuItem.new() # separator invite_to_submenu.append(item) for (room_jid, account) in rooms2: menuitem = Gtk.MenuItem.new_with_label(room_jid.localpart) if len(contact_list) > 1: # several resources menuitem.set_submenu(build_resources_submenu( contact_list, account, roster.on_invite_to_room, str(room_jid), account)) else: # use resource if it's self contact if contact.jid == app.get_jid_from_account(account): resource = contact.resource else: resource = None menuitem.connect('activate', roster.on_invite_to_room, list_, str(room_jid), account, resource) invite_to_submenu.append(menuitem) def get_contact_menu(contact, account, use_multiple_contacts=True, show_start_chat=True, show_encryption=False, show_buttonbar_items=True, control=None, gc_contact=None, is_anonymous=True): """ Build contact popup menu for roster and chat window. If control is not set, we hide invite_contacts_menuitem """ if not contact: return jid = contact.jid our_jid = jid == app.get_jid_from_account(account) roster = app.interface.roster xml = get_builder('contact_context_menu.ui') contact_context_menu = xml.get_object('contact_context_menu') start_chat_menuitem = xml.get_object('start_chat_menuitem') execute_command_menuitem = xml.get_object('execute_command_menuitem') rename_menuitem = xml.get_object('rename_menuitem') edit_groups_menuitem = xml.get_object('edit_groups_menuitem') send_file_menuitem = xml.get_object('send_file_menuitem') information_menuitem = xml.get_object('information_menuitem') history_menuitem = xml.get_object('history_menuitem') send_single_message_menuitem = xml.get_object('send_single_message_menuitem') invite_menuitem = xml.get_object('invite_menuitem') block_menuitem = xml.get_object('block_menuitem') unblock_menuitem = xml.get_object('unblock_menuitem') ignore_menuitem = xml.get_object('ignore_menuitem') unignore_menuitem = xml.get_object('unignore_menuitem') # Subscription submenu subscription_menuitem = xml.get_object('subscription_menuitem') send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem = \ subscription_menuitem.get_submenu().get_children() add_to_roster_menuitem = xml.get_object('add_to_roster_menuitem') remove_from_roster_menuitem = xml.get_object( 'remove_from_roster_menuitem') manage_contact_menuitem = xml.get_object('manage_contact') convert_to_gc_menuitem = xml.get_object('convert_to_groupchat_menuitem') last_separator = xml.get_object('last_separator') items_to_hide = [] contacts = app.contacts.get_contacts(account, jid) if len(contacts) > 1 and use_multiple_contacts: # several resources send_file_menuitem.set_submenu(build_resources_submenu( contacts, account, roster.on_send_file_menuitem_activate, cap=Namespace.JINGLE_FILE_TRANSFER_5)) execute_command_menuitem.set_submenu(build_resources_submenu( contacts, account, roster.on_execute_command, cap=Namespace.COMMANDS)) else: if contact.supports(Namespace.JINGLE_FILE_TRANSFER_5): send_file_menuitem.set_sensitive(True) send_file_menuitem.connect('activate', roster.on_send_file_menuitem_activate, contact, account) else: send_file_menuitem.set_sensitive(False) if contact.supports(Namespace.COMMANDS): execute_command_menuitem.set_sensitive(True) if gc_contact and gc_contact.jid and not is_anonymous: execute_command_menuitem.connect('activate', roster.on_execute_command, gc_contact, account, gc_contact.resource) else: execute_command_menuitem.connect('activate', roster.on_execute_command, contact, account, contact.resource) else: execute_command_menuitem.set_sensitive(False) start_chat_menuitem.connect( 'activate', app.interface.on_open_chat_window, contact, account) rename_menuitem.connect('activate', roster.on_rename, 'contact', jid, account) history_menuitem.set_action_name('app.browse-history') dict_ = {'jid': GLib.Variant('s', contact.jid), 'account': GLib.Variant('s', account)} variant = GLib.Variant('a{sv}', dict_) history_menuitem.set_action_target_value(variant) if control: convert_to_gc_menuitem.connect('activate', control._on_convert_to_gc_menuitem_activate) else: items_to_hide.append(convert_to_gc_menuitem) if _('Not in contact list') not in contact.get_shown_groups(): # contact is in normal group edit_groups_menuitem.connect('activate', roster.on_edit_groups, [(contact, account)]) else: # contact is in group 'Not in contact list' edit_groups_menuitem.set_sensitive(False) # Hide items when it's self contact row if our_jid: items_to_hide += [rename_menuitem, edit_groups_menuitem] # Unsensitive many items when account is offline if app.account_is_disconnected(account): for widget in (start_chat_menuitem, rename_menuitem, edit_groups_menuitem, send_file_menuitem, convert_to_gc_menuitem, information_menuitem): widget.set_sensitive(False) if not show_start_chat: items_to_hide.append(start_chat_menuitem) if not show_buttonbar_items: items_to_hide += [history_menuitem, send_file_menuitem, information_menuitem, convert_to_gc_menuitem, last_separator] if not control: items_to_hide.append(convert_to_gc_menuitem) # Hide items when it's a pm if gc_contact: items_to_hide += [rename_menuitem, edit_groups_menuitem, subscription_menuitem, remove_from_roster_menuitem] for item in items_to_hide: item.set_no_show_all(True) item.hide() # Zeroconf Account if app.settings.get_account_setting(account, 'is_zeroconf'): for item in (send_single_message_menuitem, invite_menuitem, block_menuitem, unblock_menuitem, ignore_menuitem, unignore_menuitem, subscription_menuitem, manage_contact_menuitem, convert_to_gc_menuitem): item.set_no_show_all(True) item.hide() if contact.show in ('offline', 'error'): information_menuitem.set_sensitive(False) send_file_menuitem.set_sensitive(False) else: information_menuitem.connect('activate', roster.on_info_zeroconf, contact, account) contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget) contact_context_menu.show_all() return contact_context_menu # normal account if gc_contact: if not gc_contact.jid: # it's a pm and we don't know real JID invite_menuitem.set_sensitive(False) else: bookmarked = False c_ = app.contacts.get_contact(account, gc_contact.jid, gc_contact.resource) if c_ and c_.supports(Namespace.CONFERENCE): bookmarked = True build_invite_submenu(invite_menuitem, [(gc_contact, account)], show_bookmarked=bookmarked) else: force_resource = False if control and control.resource: force_resource = True build_invite_submenu(invite_menuitem, [(contact, account)], show_bookmarked=contact.supports(Namespace.CONFERENCE), force_resource=force_resource) if app.account_is_disconnected(account): invite_menuitem.set_sensitive(False) send_single_message_menuitem.connect('activate', roster.on_send_single_message_menuitem_activate, account, contact) remove_from_roster_menuitem.connect('activate', roster.on_req_usub, [(contact, account)]) information_menuitem.connect('activate', roster.on_info, contact, account) if _('Not in contact list') not in contact.get_shown_groups(): # contact is in normal group add_to_roster_menuitem.hide() add_to_roster_menuitem.set_no_show_all(True) if contact.sub in ('from', 'both'): send_auth_menuitem.set_sensitive(False) else: send_auth_menuitem.connect('activate', roster.authorize, jid, account) if contact.sub in ('to', 'both'): ask_auth_menuitem.set_sensitive(False) else: ask_auth_menuitem.connect('activate', roster.req_sub, jid, _('I would like to add you to my contact list'), account, contact.groups, contact.name) transport = app.get_transport_name_from_jid(jid, use_config_setting=False) if contact.sub in ('to', 'none') or transport not in ['jabber', None]: revoke_auth_menuitem.set_sensitive(False) else: revoke_auth_menuitem.connect('activate', roster.revoke_auth, jid, account) elif app.connections[account].roster_supported: # contact is in group 'Not in contact list' add_to_roster_menuitem.set_no_show_all(False) subscription_menuitem.set_sensitive(False) add_to_roster_menuitem.connect('activate', roster.on_add_to_roster, contact, account) else: add_to_roster_menuitem.hide() add_to_roster_menuitem.set_no_show_all(True) subscription_menuitem.set_sensitive(False) # Hide items when it's self contact row if our_jid: manage_contact_menuitem.set_sensitive(False) # Unsensitive items when account is offline if app.account_is_disconnected(account): for widget in (send_single_message_menuitem, subscription_menuitem, add_to_roster_menuitem, remove_from_roster_menuitem, execute_command_menuitem): widget.set_sensitive(False) con = app.connections[account] if con.get_module('Blocking').supported: transport = app.get_transport_name_from_jid(jid, use_config_setting=False) if helpers.jid_is_blocked(account, jid): block_menuitem.set_no_show_all(True) block_menuitem.hide() if transport not in ('jabber', None): unblock_menuitem.set_no_show_all(True) unblock_menuitem.hide() unignore_menuitem.set_no_show_all(False) unignore_menuitem.connect('activate', roster.on_unblock, [(contact, account)]) else: unblock_menuitem.connect('activate', roster.on_unblock, [(contact, account)]) else: unblock_menuitem.set_no_show_all(True) unblock_menuitem.hide() if transport not in ('jabber', None): block_menuitem.set_no_show_all(True) block_menuitem.hide() ignore_menuitem.set_no_show_all(False) ignore_menuitem.connect('activate', roster.on_block, [(contact, account)]) else: block_menuitem.connect('activate', roster.on_block, [(contact, account)]) else: unblock_menuitem.set_no_show_all(True) block_menuitem.set_sensitive(False) unblock_menuitem.hide() contact_context_menu.connect('selection-done', gtkgui_helpers.destroy_widget) contact_context_menu.show_all() return contact_context_menu def get_transport_menu(contact, account): roster = app.interface.roster jid = contact.jid menu = Gtk.Menu() # Send single message item = Gtk.MenuItem.new_with_mnemonic(_('Send Single _Message…')) item.connect('activate', roster.on_send_single_message_menuitem_activate, account, contact) menu.append(item) if app.account_is_disconnected(account): item.set_sensitive(False) blocked = False if helpers.jid_is_blocked(account, jid): blocked = True item = Gtk.SeparatorMenuItem.new() # separator menu.append(item) # Execute Command item = Gtk.MenuItem.new_with_mnemonic(_('E_xecute Command…')) menu.append(item) item.connect('activate', roster.on_execute_command, contact, account, contact.resource) if app.account_is_disconnected(account): item.set_sensitive(False) # Manage Transport submenu item = Gtk.MenuItem.new_with_mnemonic(_('_Manage Transport')) manage_transport_submenu = Gtk.Menu() item.set_submenu(manage_transport_submenu) menu.append(item) # Modify Transport item = Gtk.MenuItem.new_with_mnemonic(_('_Modify Transport')) manage_transport_submenu.append(item) item.connect('activate', roster.on_edit_agent, contact, account) if app.account_is_disconnected(account): item.set_sensitive(False) # Rename item = Gtk.MenuItem.new_with_mnemonic(_('_Rename…')) manage_transport_submenu.append(item) item.connect('activate', roster.on_rename, 'agent', jid, account) if app.account_is_disconnected(account): item.set_sensitive(False) item = Gtk.SeparatorMenuItem.new() # separator manage_transport_submenu.append(item) # Block if blocked: item = Gtk.MenuItem.new_with_mnemonic(_('_Unblock')) item.connect('activate', roster.on_unblock, [(contact, account)]) else: item = Gtk.MenuItem.new_with_mnemonic(_('_Block')) item.connect('activate', roster.on_block, [(contact, account)]) manage_transport_submenu.append(item) if app.account_is_disconnected(account): item.set_sensitive(False) # Remove item = Gtk.MenuItem.new_with_mnemonic(_('Remo_ve')) manage_transport_submenu.append(item) item.connect('activate', roster.on_remove_agent, [(contact, account)]) if app.account_is_disconnected(account): item.set_sensitive(False) item = Gtk.SeparatorMenuItem.new() # separator menu.append(item) # Information information_menuitem = Gtk.MenuItem.new_with_mnemonic(_('_Information')) menu.append(information_menuitem) information_menuitem.connect('activate', roster.on_info, contact, account) if app.account_is_disconnected(account): information_menuitem.set_sensitive(False) menu.connect('selection-done', gtkgui_helpers.destroy_widget) menu.show_all() return menu def get_singlechat_menu(control_id, account, jid, type_): singlechat_menu = [ (_('Send File'), [ ('win.send-file-httpupload-', _('Upload File…')), ('win.send-file-jingle-', _('Send File Directly…')), ]), ('win.send-marker-', _('Send Read Markers')), (_('Send Chatstate'), ['chatstate']), ('win.invite-contacts-', _('Invite Contacts…')), ('win.add-to-roster-', _('Add to Contact List…')), ('win.block-contact-', _('Block Contact…')), ('win.start-call-', _('Start Call…')), ('win.information-', _('Information')), ('app.browse-history', _('History')), ] def build_chatstate_menu(): menu = Gio.Menu() entries = [ (_('Disabled'), 'disabled'), (_('Composing Only'), 'composing_only'), (_('All Chat States'), 'all') ] for entry in entries: label, setting = entry action = 'win.send-chatstate-%s::%s' % (control_id, setting) menu.append(label, action) return menu def build_menu(preset): menu = Gio.Menu() for item in preset: if isinstance(item[1], str): action_name, label = item if action_name == 'win.send-marker-' and type_ == 'pm': continue if action_name == 'app.browse-history': menuitem = Gio.MenuItem.new(label, action_name) dict_ = {'account': GLib.Variant('s', account), 'jid': GLib.Variant('s', jid)} variant_dict = GLib.Variant('a{sv}', dict_) menuitem.set_action_and_target_value(action_name, variant_dict) menu.append_item(menuitem) else: menu.append(label, action_name + control_id) else: label, sub_menu = item if 'chatstate' in sub_menu: submenu = build_chatstate_menu() else: submenu = build_menu(sub_menu) menu.append_submenu(label, submenu) return menu return build_menu(singlechat_menu) def get_groupchat_menu(control_id, account, jid): groupchat_menu = [ ('win.information-', _('Information')), ('win.groupchat-settings-', _('Settings…')), (_('Manage Group Chat'), [ ('win.rename-groupchat-', _('Rename…')), ('win.change-subject-', _('Change Subject…')), ('win.upload-avatar-', _('Upload Avatar…')), ('win.configure-', _('Configure…')), ('win.destroy-', _('Destroy…')), ]), ('win.change-nickname-', _('Change Nickname…')), ('win.request-voice-', _('Request Voice')), ('win.execute-command-', _('Execute Command…')), ('app.browse-history', _('History')), ('win.disconnect-', _('Leave')), ] def build_menu(preset): menu = Gio.Menu() for item in preset: if isinstance(item[1], str): action_name, label = item if action_name == 'app.browse-history': menuitem = Gio.MenuItem.new(label, action_name) dict_ = {'account': GLib.Variant('s', account), 'jid': GLib.Variant('s', jid)} variant_dict = GLib.Variant('a{sv}', dict_) menuitem.set_action_and_target_value(action_name, variant_dict) menu.append_item(menuitem) elif action_name == 'win.execute-command-': action_name = action_name + control_id menuitem = Gio.MenuItem.new(label, action_name) menuitem.set_action_and_target_value(action_name, GLib.Variant('s', '')) menu.append_item(menuitem) else: menu.append(label, action_name + control_id) else: label, sub_menu = item submenu = build_menu(sub_menu) menu.append_submenu(label, submenu) return menu return build_menu(groupchat_menu) def get_account_menu(account): ''' [(action, label/sub_menu)] action: string label: string sub menu: list ''' account_menu = [ ('-add-contact', _('Add Contact…')), ('-profile', _('Profile')), ('-start-single-chat', _('Send Single Message…')), ('-services', _('Discover Services…')), ('-server-info', _('Server Info')), (_('Advanced'), [ ('-archive', _('Archiving Preferences')), ('-blocking', _('Blocking List')), ('-bookmarks', _('Bookmarks')), ('-pep-config', _('PEP Configuration')), ('-sync-history', _('Synchronise History…')), ]), (_('Admin'), [ ('-send-server-message', _('Send Server Message…')), ('-set-motd', _('Set MOTD…')), ('-update-motd', _('Update MOTD…')), ('-delete-motd', _('Delete MOTD…')) ]), ] def build_menu(preset): menu = Gio.Menu() for item in preset: if isinstance(item[1], str): action, label = item action = 'app.{}{}'.format(account, action) menuitem = Gio.MenuItem.new(label, action) if 'add-contact' in action: variant = GLib.Variant('as', [account, '']) else: variant = GLib.Variant('s', account) menuitem.set_action_and_target_value(action, variant) menu.append_item(menuitem) else: label, sub_menu = item # This is a submenu submenu = build_menu(sub_menu) menu.append_submenu(label, submenu) return menu return build_menu(account_menu) def build_accounts_menu(): menubar = app.app.get_menubar() # Accounts Submenu menu_position = 1 acc_menu = menubar.get_item_link(menu_position, 'submenu') acc_menu.remove_all() accounts_list = sorted(app.contacts.get_accounts()) if not accounts_list: modify_account_item = Gio.MenuItem.new(_('_Add Account…'), 'app.accounts::') acc_menu.append_item(modify_account_item) return if len(accounts_list) > 1: modify_account_item = Gio.MenuItem.new(_('_Modify Accounts…'), 'app.accounts::') acc_menu.append_item(modify_account_item) for acc in accounts_list: label = escape_mnemonic(app.get_account_label(acc)) if acc != 'Local': acc_menu.append_submenu( label, get_account_menu(acc)) else: acc_menu = get_account_menu(accounts_list[0]) modify_account_item = Gio.MenuItem.new(_('_Modify Account…'), 'app.accounts::') acc_menu.insert_item(0, modify_account_item) menubar.remove(menu_position) menubar.insert_submenu(menu_position, _('Accounts'), acc_menu) def get_encryption_menu(control_id, control_type, zeroconf=False): menu = Gio.Menu() menu.append( _('Disabled'), 'win.set-encryption-{}::{}'.format(control_id, 'disabled')) for name, plugin in app.plugin_manager.encryption_plugins.items(): if control_type.is_groupchat: if not hasattr(plugin, 'allow_groupchat'): continue if control_type.is_privatechat: if not hasattr(plugin, 'allow_privatechat'): continue if zeroconf: if not hasattr(plugin, 'allow_zeroconf'): continue menu_action = 'win.set-encryption-{}::{}'.format( control_id, name) menu.append(name, menu_action) if menu.get_n_items() == 1: return None return menu def get_conv_context_menu(account, uri): if uri.type == URIType.XMPP: if uri.action == URIAction.JOIN: context_menu = [ ('copy-text', _('Copy XMPP Address')), ('groupchat-join', _('Join Groupchat')), ] else: context_menu = [ ('copy-text', _('Copy XMPP Address')), ('-start-chat', _('Start Chat')), ('-add-contact', _('Add to Contact List…')), ] elif uri.type == URIType.WEB: context_menu = [ ('copy-text', _('Copy Link Location')), ('open-link', _('Open Link in Browser')), ] elif uri.type == URIType.MAIL: context_menu = [ ('copy-text', _('Copy Email Address')), ('open-mail', _('Open Email Composer')), ] elif uri.type == URIType.GEO: context_menu = [ ('copy-text', _('Copy Location')), ('open-link', _('Show Location')), ] elif uri.type == URIType.AT: context_menu = [ ('copy-text', _('Copy XMPP Address/Email')), ('open-mail', _('Open Email Composer')), ('-start-chat', _('Start Chat')), ('groupchat-join', _('Join Groupchat')), ('-add-contact', _('Add to Contact List…')), ] else: return menu = Gtk.Menu() for item in context_menu: action, label = item menuitem = Gtk.MenuItem() menuitem.set_label(label) if action.startswith('-'): action = 'app.%s%s' % (account, action) else: action = 'app.%s' % action menuitem.set_action_name(action) data = uri.data if uri.type == URIType.XMPP: data = uri.data['jid'] if action in ('app.open-mail', 'app.copy-text'): value = GLib.Variant.new_string(data) else: value = GLib.Variant.new_strv([account, data]) menuitem.set_action_target_value(value) menuitem.show() menu.append(menuitem) return menu def get_groupchat_roster_menu(account, control_id, self_contact, contact): menu = Gtk.Menu() item = Gtk.MenuItem(label=_('Information')) action = 'win.contact-information-%s::%s' % (control_id, contact.name) item.set_detailed_action_name(action) menu.append(item) item = Gtk.MenuItem(label=_('Add to Contact List…')) action = 'app.{account}-add-contact(["{account}", "{jid}"])'.format( account=account, jid=contact.jid or '') if contact.jid is None: item.set_sensitive(False) else: item.set_detailed_action_name(action) menu.append(item) item = Gtk.MenuItem(label=_('Invite')) if contact.jid is not None: build_invite_submenu(item, ((contact, account),), show_bookmarked=True) else: item.set_sensitive(False) menu.append(item) item = Gtk.MenuItem(label=_('Execute Command…')) action = 'win.execute-command-%s::%s' % (control_id, contact.name) item.set_detailed_action_name(action) menu.append(item) menu.append(Gtk.SeparatorMenuItem()) item = Gtk.MenuItem(label=_('Kick')) action = 'win.kick-%s::%s' % (control_id, contact.name) if is_role_change_allowed(self_contact, contact): item.set_detailed_action_name(action) else: item.set_sensitive(False) menu.append(item) item = Gtk.MenuItem(label=_('Ban')) action = 'win.ban-%s::%s' % (control_id, contact.jid or '') if is_affiliation_change_allowed(self_contact, contact, 'outcast'): item.set_detailed_action_name(action) else: item.set_sensitive(False) menu.append(item) menu.append(Gtk.SeparatorMenuItem()) item = Gtk.MenuItem(label=_('Make Owner')) action = 'win.change-affiliation-%s(["%s", "owner"])' % (control_id, contact.jid) if is_affiliation_change_allowed(self_contact, contact, 'owner'): item.set_detailed_action_name(action) else: item.set_sensitive(False) menu.append(item) item = Gtk.MenuItem(label=_('Make Admin')) action = 'win.change-affiliation-%s(["%s", "admin"])' % (control_id, contact.jid) if is_affiliation_change_allowed(self_contact, contact, 'admin'): item.set_detailed_action_name(action) else: item.set_sensitive(False) menu.append(item) item = Gtk.MenuItem(label=_('Make Member')) action = 'win.change-affiliation-%s(["%s", "member"])' % (control_id, contact.jid) if is_affiliation_change_allowed(self_contact, contact, 'member'): item.set_detailed_action_name(action) else: item.set_sensitive(False) menu.append(item) item = Gtk.MenuItem(label=_('Revoke Member')) action = 'win.change-affiliation-%s(["%s", "none"])' % (control_id, contact.jid) if is_affiliation_change_allowed(self_contact, contact, 'none'): item.set_detailed_action_name(action) else: item.set_sensitive(False) menu.append(item) if contact.role.is_visitor: label = _('Grant Voice') role = 'participant' else: label = _('Revoke Voice') role = 'visitor' item = Gtk.MenuItem(label=label) action = 'win.change-role-%s(["%s", "%s"])' % (control_id, contact.name, role) if is_role_change_allowed(self_contact, contact): item.set_detailed_action_name(action) else: item.set_sensitive(False) menu.append(item) menu.show_all() return menu class SearchMenu(Gtk.Menu): def __init__(self, treeview): Gtk.Menu.__init__(self) self._copy_item = Gtk.MenuItem(label=_('Copy')) self._copy_item.set_action_name('app.copy-text') self.set_copy_text('') self._copy_item.show() self.append(self._copy_item) self.attach_to_widget(treeview, None) def set_copy_text(self, text): self._copy_item.set_action_target_value(GLib.Variant('s', text)) def escape_mnemonic(label): if label is None: return # Underscore inside a label means the next letter is a keyboard # shortcut. To show an underscore we have to use double underscore return label.replace('_', '__')