# 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.protocol import NS_COMMANDS, NS_FILE, NS_MUC from nbxmpp.protocol import NS_JINGLE_FILE_TRANSFER_5, NS_CONFERENCE from gajim import gtkgui_helpers from gajim import message_control from gajim.gtkgui_helpers import get_action from gajim.common import app from gajim.common import helpers from gajim.common.i18n import ngettext from gajim.common.i18n import _ from gajim.gtk.util import get_builder 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) elif action == roster.on_invite_to_new_room: # pylint: disable=comparison-with-callable item.connect('activate', action, [(c, 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) invite_to_new_room_menuitem = Gtk.MenuItem.new_with_mnemonic(_( '_New Group Chat')) if len(contact_list) > 1: # several resources invite_to_new_room_menuitem.set_submenu(build_resources_submenu( contact_list, account, roster.on_invite_to_new_room, cap=NS_MUC)) elif len(list_) == 1 and contact.supports(NS_MUC): invite_menuitem.set_sensitive(True) # use resource if it's self contact if contact.jid == app.get_jid_from_account(account) or force_resource: resource = contact.resource else: resource = None invite_to_new_room_menuitem.connect('activate', roster.on_invite_to_new_room, list_, resource) elif len(list_) > 1: list2 = [] for (c, a) in list_: if c.supports(NS_MUC): list2.append((c, a)) if list2: invite_to_new_room_menuitem.connect('activate', roster.on_invite_to_new_room, list2, None) else: invite_menuitem.set_sensitive(False) else: invite_menuitem.set_sensitive(False) # transform None in 'jabber' c_t = contacts_transport or 'jabber' muc_jid = {} for account in connected_accounts: for t in app.connections[account].muc_jid: muc_jid[t] = app.connections[account].muc_jid[t] if c_t not in muc_jid: invite_to_new_room_menuitem.set_sensitive(False) rooms = [] # a list of (room_jid, account) tuple invite_to_submenu.append(invite_to_new_room_menuitem) 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( message_control.TYPE_GC) + minimized_controls: acct = gc_control.account if acct not in connected_accounts: continue room_jid = gc_control.room_jid 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, acct)) if rooms: item = Gtk.SeparatorMenuItem.new() # separator invite_to_submenu.append(item) for (room_jid, account) in rooms: menuitem = Gtk.MenuItem.new_with_label(room_jid.split('@')[0]) 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.split('@')[0]) 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) 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_custom_status_menuitem = xml.get_object('send_custom_status_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 start_chat_menuitem.set_submenu(build_resources_submenu(contacts, account, app.interface.on_open_chat_window)) send_file_menuitem.set_submenu(build_resources_submenu(contacts, account, roster.on_send_file_menuitem_activate, cap=NS_FILE)) execute_command_menuitem.set_submenu(build_resources_submenu( contacts, account, roster.on_execute_command, cap=NS_COMMANDS)) else: start_chat_menuitem.connect('activate', app.interface.on_open_chat_window, contact, account) if contact.supports(NS_FILE) or contact.supports(NS_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(NS_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) 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 Roster') 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 Roster' 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.config.get_per('accounts', account, 'is_zeroconf'): for item in (send_custom_status_menuitem, 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 # send custom status icon blocked = False if helpers.jid_is_blocked(account, jid): blocked = True else: for group in contact.get_shown_groups(): if helpers.group_is_blocked(account, group): blocked = True break transport = app.get_transport_name_from_jid(jid, use_config_setting=False) if transport and transport != 'jabber': # Transport contact, send custom status unavailable send_custom_status_menuitem.set_sensitive(False) elif blocked: send_custom_status_menuitem.set_sensitive(False) 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(NS_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(NS_CONFERENCE), force_resource=force_resource) if app.account_is_disconnected(account): invite_menuitem.set_sensitive(False) # One or several resource, we do the same for send_custom_status status_menuitems = Gtk.Menu() send_custom_status_menuitem.set_submenu(status_menuitems) for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): # icon MUST be different instance for every item status_menuitem = Gtk.MenuItem.new_with_label(helpers.get_uf_show(s)) status_menuitem.connect('activate', roster.on_send_custom_status, [(contact, account)], s) status_menuitems.append(status_menuitem) 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 Roster') 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 roster'), 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 Roster' 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, send_custom_status_menuitem): widget.set_sensitive(False) con = app.connections[account] if con and (con.get_module('PrivacyLists').supported or con.get_module('Blocking').supported): if helpers.jid_is_blocked(account, jid): block_menuitem.set_no_show_all(True) block_menuitem.hide() if app.get_transport_name_from_jid(jid, use_config_setting=False)\ and transport != 'jabber': 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 app.get_transport_name_from_jid(jid, use_config_setting=False)\ and transport != 'jabber': 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 # Send Custom Status send_custom_status_menuitem = Gtk.MenuItem.new_with_mnemonic( _('Send Cus_tom Status')) if blocked: send_custom_status_menuitem.set_sensitive(False) else: status_menuitems = Gtk.Menu() send_custom_status_menuitem.set_submenu(status_menuitems) for s in ('online', 'chat', 'away', 'xa', 'dnd', 'offline'): status_menuitem = Gtk.MenuItem.new_with_label(helpers.get_uf_show( s)) status_menuitem.connect('activate', roster.on_send_custom_status, [(contact, account)], s) status_menuitems.append(status_menuitem) menu.append(send_custom_status_menuitem) if app.account_is_disconnected(account): send_custom_status_menuitem.set_sensitive(False) 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 show_save_as_menu(sha, name): menu = Gtk.Menu() menuitem = Gtk.MenuItem.new_with_mnemonic(_('Save _As')) menuitem.connect( 'activate', gtkgui_helpers.on_avatar_save_as_menuitem_activate, sha, name) menu.append(menuitem) menu.connect('selection-done', lambda w: w.destroy()) menu.show_all() menu.popup_at_pointer() def get_singlechat_menu(control_id, account, jid): singlechat_menu = [ (_('Send File…'), [ ('win.send-file-httpupload-', _('Upload File…')), ('win.send-file-jingle-', _('Send File Directly…')), ]), ('win.invite-contacts-', _('Invite Contacts')), ('win.add-to-roster-', _('Add to Roster')), ('win.toggle-audio-', _('Audio Session')), ('win.toggle-video-', _('Video Session')), ('win.information-', _('Information')), ('app.browse-history', _('History')), ] 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) else: menu.append(label, action_name + control_id) else: label, sub_menu = item # This is a submenu 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 = [ (_('Manage Room'), [ ('win.change-subject-', _('Change Subject')), ('win.configure-', _('Configure Room')), ('win.upload-avatar-', _('Upload Avatar…')), ('win.destroy-', _('Destroy Room')), ]), (_('Chat Settings'), [ ('win.print-join-left-', _('Show join/leave')), ('win.print-status-', _('Show status changes')), ('win.notify-on-message-', _('Notify on all messages')), ('win.minimize-on-close-', _('Minimize on close')), ('win.minimize-on-autojoin-', _('Minimize on autojoin')), (_('Send Chatstate'), ['chatstate']), ]), (_('Sync Threshold'), ['sync']), ('win.change-nick-', _('Change Nick')), ('win.bookmark-', _('Bookmark Room')), ('win.request-voice-', _('Request Voice')), ('win.execute-command-', _('Execute command')), ('app.browse-history', _('History')), ('win.disconnect-', _('Disconnect')), ] 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) else: menu.append(label, action_name + control_id) else: label, sub_menu = item if 'sync' in sub_menu: # Sync threshold menu submenu = build_sync_menu() elif 'chatstate' in sub_menu: submenu = build_chatstate_menu() else: # This is a submenu submenu = build_menu(sub_menu) menu.append_submenu(label, submenu) return menu def build_sync_menu(): menu = Gio.Menu() days = app.config.get('threshold_options').split(',') days = [int(day) for day in days] action_name = 'win.choose-sync-%s::' % control_id for day in days: if day == 0: label = _('No threshold') else: label = ngettext('%i day', '%i days', day, day, day) menu.append(label, '%s%s' % (action_name, day)) return menu def build_chatstate_menu(): menu = Gio.Menu() entrys = [ (_('Disabled'), 'disabled'), (_('Composing only'), 'composing_only'), (_('All chat states'), 'all') ] for entry in entrys: label, setting = entry action = 'win.send-chatstate-%s::%s' % (control_id, setting) menu.append(label, action) return menu return build_menu(groupchat_menu) def get_bookmarks_menu(account, rebuild=False): con = app.connections[account] bookmarks = con.get_module('Bookmarks').get_sorted_bookmarks(short_name=True) menu = Gio.Menu() # Build Join Groupchat action = 'app.{}-join-groupchat'.format(account) menuitem = Gio.MenuItem.new(_('Join Group Chat'), action) variant = GLib.Variant('as', [account, '']) menuitem.set_action_and_target_value(action, variant) menu.append_item(menuitem) # Build Bookmarks section = Gio.Menu() for bookmark in bookmarks: action = 'app.{}-activate-bookmark'.format(account) menuitem = Gio.MenuItem.new(bookmark.name, action) # Create Variant Dict dict_ = {'account': GLib.Variant('s', account), 'jid': GLib.Variant('s', bookmark.jid)} if bookmark.nick: dict_['nick'] = GLib.Variant('s', bookmark.nick) if bookmark.password: dict_['password'] = GLib.Variant('s', bookmark.password) variant_dict = GLib.Variant('a{sv}', dict_) menuitem.set_action_and_target_value(action, variant_dict) section.append_item(menuitem) menu.append_section(None, section) if not rebuild: get_action(account + '-activate-bookmark').set_enabled(True) return menu def get_account_menu(account): ''' [(action, label/sub_menu)] action: string label: string sub menu: list ''' account_menu = [ ('-add-contact', _('Add Contact…')), ('-join-groupchat', _('Join Group Chat')), ('-profile', _('Profile')), ('-services', _('Discover Services')), ('-start-single-chat', _('Send Single Message…')), (_('Advanced'), [ ('-archive', _('Archiving Preferences')), ('-blocking', _('Blocking List')), ('-sync-history', _('Synchronise History')), ('-privacylists', _('Privacy Lists')), ('-server-info', _('Server Info')), ('-xml-console', _('XML Console')) ]), (_('Admin'), [ ('-send-server-message', _('Send Server Message…')), ('-set-motd', _('Set MOTD…')), ('-update-motd', _('Update MOTD…')), ('-delete-motd', _('Delete MOTD…')) ]), ] zeroconf_menu = [ ('-xml-console', _('XML Console')), ] def build_menu(preset): menu = Gio.Menu() for item in preset: if isinstance(item[1], str): action, label = item if action == '-join-groupchat': bookmark_menu = get_bookmarks_menu(account, True) if bookmark_menu: menu.append_submenu(label, bookmark_menu) continue 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 if account == 'Local': return build_menu(zeroconf_menu) return build_menu(account_menu) def build_accounts_menu(): menubar = app.app.get_menubar() # Accounts Submenu menu_position = 1 if app.prefers_app_menu(): menu_position = 0 acc_menu = menubar.get_item_link(menu_position, 'submenu') acc_menu.remove_all() accounts_list = sorted(app.contacts.get_accounts()) if not accounts_list: no_accounts = _('No Accounts available') acc_menu.append_item(Gio.MenuItem.new(no_accounts, None)) return if len(accounts_list) > 1: for acc in accounts_list: label = escape_mnemonic(app.get_account_label(acc)) acc_menu.append_submenu( label, get_account_menu(acc)) else: acc_menu = get_account_menu(accounts_list[0]) menubar.remove(menu_position) menubar.insert_submenu(menu_position, _('Accounts'), acc_menu) def build_bookmark_menu(account): menubar = app.app.get_menubar() bookmark_menu = get_bookmarks_menu(account) if not bookmark_menu: return menu_position = 1 if app.prefers_app_menu(): menu_position = 0 # Accounts Submenu acc_menu = menubar.get_item_link(menu_position, 'submenu') # We have more than one Account active if acc_menu.get_item_link(0, 'submenu'): for i in range(acc_menu.get_n_items()): label = acc_menu.get_item_attribute_value(i, 'label') account_label = escape_mnemonic( app.config.get_per('accounts', account, 'account_label')) if label.get_string() in (account_label, account): menu = acc_menu.get_item_link(i, 'submenu') else: # We have only one Account active menu = acc_menu label = menu.get_item_attribute_value(1, 'label').get_string() menu.remove(1) menu.insert_submenu(1, label, bookmark_menu) def get_encryption_menu(control_id, type_id, 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 type_id == 'gc': if not hasattr(plugin, 'allow_groupchat'): continue if type_id == 'pm': 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, kind, text): if kind == 'xmpp': if '?join' in text: context_menu = [ ('copy-link', _('Copy JID')), ('-join-groupchat', _('Join Groupchat')), ] else: context_menu = [ ('copy-link', _('Copy JID')), ('-start-chat', _('Start Chat')), ('-add-contact', _('Add to Roster…')), ] elif kind == 'url': context_menu = [ ('copy-link', _('Copy Link Location')), ('open-link', _('Open Link in Browser')), ] elif kind == 'mail': context_menu = [ ('copy-link', _('Copy Email Address')), ('open-link', _('Open Email Composer')), ] elif kind == 'sth_at_sth': context_menu = [ ('copy-link', _('Copy JID/Email')), ('open-link', _('Open Email Composer')), ('-start-chat', _('Start Chat')), ('-join-groupchat', _('Join Groupchat')), ('-add-contact', _('Add to Roster…')), ] else: return menu = Gtk.Menu() for item in context_menu: action, label = item menuitem = Gtk.MenuItem() menuitem.set_label(label) if action.startswith('-'): text = text.replace('xmpp:', '') text = text.split('?')[0] action = 'app.%s%s' % (account, action) else: action = 'app.%s' % action menuitem.set_action_name(action) if action == 'app.open-link': value = GLib.Variant.new_strv([kind, text]) elif action == 'app.copy-link': value = GLib.Variant.new_string(text) else: value = GLib.Variant.new_strv([account, text]) menuitem.set_action_target_value(value) menuitem.show() menu.append(menuitem) return menu 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('_', '__')