From dfdeb65e8fdb43f586cc0a04143705e682eead51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Tue, 15 Aug 2017 19:12:30 +0200 Subject: [PATCH 1/2] New AccountsWindow --- data/gui/accounts_window.ui | 1633 +---------------- data/gui/synchronise_select_account_dialog.ui | 1 + data/style/gajim.css | 30 +- gajim/accounts_window.py | 698 +++++++ gajim/app_actions.py | 44 +- gajim/common/config.py | 1 + gajim/common/const.py | 31 + gajim/config.py | 15 +- gajim/dialogs.py | 117 +- gajim/gajim.py | 24 +- gajim/gtkgui_helpers.py | 11 +- gajim/options_dialog.py | 585 ++++++ gajim/roster_window.py | 5 +- 13 files changed, 1545 insertions(+), 1650 deletions(-) create mode 100644 gajim/accounts_window.py create mode 100644 gajim/options_dialog.py diff --git a/data/gui/accounts_window.ui b/data/gui/accounts_window.ui index de0e541c6..60416ad0c 100644 --- a/data/gui/accounts_window.ui +++ b/data/gui/accounts_window.ui @@ -1,1573 +1,114 @@ - + - - - 127 - 5 - 1 - 5 - - + + True False - gtk-add - - - True - False - gtk-remove - - - - - - - - - None - - - - - True - False - gtk-missing-image - - - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 12 - Accounts - dialog - - + vertical - + True False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical - 6 + - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 175 - True - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - vertical - 6 - - - 170 - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - in - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - False - - - - - - - - - True - True - 0 - - - - - Add - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - image1 - True - - - - False - True - 1 - - - - - Delete - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - image2 - True - - - - False - True - 2 - - - - - Re_name - True - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - rename_image - True - - - - False - True - 3 - - - - - False - False - - - - - True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 5 - False - False - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - - - - - - True - False - vertical - - - Active - True - True - False - True - 0.5 - True - - - - False - False - 0 - - - - - True - True - - - True - False - 6 - 6 - 6 - - - True - False - _Jabber ID: - True - 0 - - - 0 - 0 - - - - - True - False - _Password: - True - 0 - - - 0 - 1 - - - - - True - False - Resour_ce: - True - 0 - - - 0 - 2 - - - - - True - False - Priori_ty: - True - 0 - - - 0 - 3 - - - - - True - True - True - - - - 1 - 0 - - - - - True - False - True - False - True - - - - 1 - 1 - - - - - True - True - Resource is sent to the Jabber server in order to separate the same JID in two or more parts depending on the number of the clients connected in the same server with the same account. So you might be connected in the same account with resource 'Home' and 'Work' at the same time. The resource which has the highest priority will get the events. (see below) - Gajim - - - - 1 - 2 - 2 - - - - - A_djust to status - True - True - False - Priority will change automatically according to your status. - True - 0.5 - True - - - - 1 - 3 - - - - - Anonymous authentication - True - True - False - 0.5 - True - - - - 2 - 0 - - - - - Save pass_word - True - True - False - If checked, Gajim will remember the password for this account - True - False - 0.5 - True - - - - 2 - 1 - - - - - True - True - Priority is used in Jabber to determine who gets the events from the jabber server when two or more clients are connected using the same account; The client with the highest priority gets the events - adjustment1 - 1 - True - - - - 2 - 3 - - - - - True - True - - - True - False - 11 - True - - - Synchronize contacts - True - True - False - Click to request authorization to all contacts of another account - True - - - - False - True - 0 - - - - - Chan_ge Password - True - True - False - Click to change account's password - True - - - - False - True - 1 - - - - - - - True - False - Administration operations - - - - - 0 - 4 - 3 - - - - - True - True - - - True - False - vertical - 6 - - - True - False - 6 - - - True - False - _Client Cert File: - True - cert_entry1 - 0 - - - False - True - 0 - - - - - True - True - - - - - True - True - 1 - - - - - Browse... - True - True - True - True - - - - False - False - 2 - - - - - True - True - 0 - - - - - Certificate is e_ncrypted - True - True - False - True - 0.5 - True - - - - True - True - 1 - - - - - - - True - False - Client certificate - - - - - 0 - 5 - 3 - - - - - - - True - False - Account - - - False - - - - - True - False - 6 - vertical - 6 - - - C_onnect on Gajim startup - True - True - False - If checked, Gajim, when launched, will automatically connect to jabber using this account - True - 0.5 - True - - - - False - False - 0 - - - - - Auto-reconnect when connection is lost - True - True - False - True - 0.5 - True - - - - False - False - 1 - - - - - Save conversation _logs for all contacts - True - True - False - True - 0.5 - True - True - - - - False - False - 2 - - - - - Synchronize logs with server - True - True - False - True - 0.5 - True - True - - - - False - False - 3 - - - - - Synch_ronize account status with global status - True - True - False - If checked, any change to the global status (handled by the combobox at the bottom of the roster window) will change the status of this account accordingly - True - 0.5 - True - - - - False - False - 4 - - - - - Receive conversations from other resources (provided the server has support for it) - True - True - False - True - 0.5 - True - - - - False - False - 5 - - - - - Use file transfer proxies - True - True - False - If checked, Gajim will also broadcast some more IPs except from just your IP, so file transfer has higher chances of working. - True - 0.5 - True - - - - False - False - 6 - - - - - 1 - - - - - True - False - General - True - - - 1 - False - - - - - True - False - 6 - vertical - 12 - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 0 - none - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 12 - 6 - vertical - 6 - - - _use HTTP__PROXY environment variable - True - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - 0 - True - - - - False - True - 0 - - - - - True - False - 6 - - - True - False - liststore1 - - - - - 0 - - - - - True - True - 0 - - - - - _Manage... - True - True - False - True - - - - False - False - 1 - - - - - False - True - 1 - - - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - <b>Proxy</b> - True - - - - - False - True - 0 - - - - - True - False - 0 - none - - - True - False - 12 - 6 - vertical - 6 - - - _Warn before using an insecure connection - True - True - False - Check this so Gajim will ask you before sending your password over an insecure connection. - True - 0 - True - - - - False - False - 0 - - - - - Send _keep-alive packets - True - True - False - If checked, Gajim will send keep-alive packets to prevent connection timeout which results in disconnection - True - 0 - True - True - - - - False - False - 1 - - - - - Use cust_om hostname/port - True - True - False - True - 0 - True - - - - False - False - 2 - - - - - True - False - False - 6 - - - True - False - _Hostname: - True - - - False - False - 0 - - - - - True - True - - - - True - True - 1 - - - - - True - False - _Port: - True - - - False - False - 2 - - - - - True - True - 6 - 5222 - - - - False - True - 3 - - - - - True - True - 3 - - - - - - - True - False - <b>Miscellaneous</b> - True - - - - - False - True - 1 - - - - - 2 - - - - - True - False - Connection - - - 2 - False - - - - - True - False - 5 - vertical - 6 - - - True - False - 0 - none - - - True - False - 12 - 6 - vertical - 6 - - - True - False - 6 - - - True - False - No key selected - True - - - False - False - 0 - - - - - True - False - True - end - - - True - True - 1 - - - - - Choose _Key... - True - True - False - True - - - - False - False - 2 - - - - - False - True - 0 - - - - - Use G_PG Agent - True - False - True - False - If checked, Gajim will get the password from a GPG agent like seahorse - True - 0 - True - - - - False - False - 1 - - - - - - - True - False - <b>OpenPGP</b> - True - - - - - False - True - 0 - - - - - True - False - 0 - none - - - _Edit Personal Information... - True - True - False - Information about you, as stored in the server - 12 - 6 - True - - - - - - True - False - <b>Personal Information</b> - True - - - - - False - True - 1 - - - - - 3 - - - - - True - False - Personal Information - - - 3 - False - - - - - True - True - 1 - - - - - 1 - - - - - - - - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - vertical - - - Active - True - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - 0.5 - True - - - - False - True - 0 - - - - - True - True - - - True - False - 6 - vertical - 6 - - - Co_nnect on Gajim startup - True - True - False - If checked, Gajim, when launched, will automatically connect to jabber using this account - True - 0.5 - True - - - - False - False - 0 - - - - - Save conversation _logs for all contacts - True - True - False - True - 0.5 - True - - - - False - False - 1 - - - - - Synchroni_ze account status with global status - True - True - False - If checked, any change to the global status (handled by the combobox at the bottom of the roster window) will change the status of this account accordingly - True - 0.5 - True - - - - False - False - 2 - - - - - True - False - - - Use cust_om port: - True - True - False - If the default port that is used for incoming messages is unfitting for your setup you can select another one here. -You might consider to change possible firewall settings. - True - 0.5 - True - - - - False - False - 0 - - - - - True - True - 6 - - - - False - False - 1 - - - - - False - True - 10 - 3 - - - - - - - True - False - General - True - - - False - - - - - True - False - 11 - 5 - 5 - - - True - False - vertical - 5 - - - True - False - <b>OpenPGP</b> - True - 0 - - - False - False - 0 - - - - - True - False - 6 - - - True - False - No key selected - - - False - False - 0 - - - - - True - False - True - end - - - True - True - 1 - - - - - Choose _Key... - True - True - False - True - - - - False - False - 2 - - - - - False - True - 1 - - - - - Use G_PG Agent - True - True - False - If checked, Gajim will get the password from a GPG agent like seahorse - True - 0.5 - True - - - - False - False - 2 - - - - - 0 - 0 - 2 - - - - - True - False - <b>Personal Information</b> - True - 0 - - - 0 - 1 - 2 - - - - - True - False - First Name: - 1 - - - 0 - 2 - - - - - True - False - Last Name: - 1 - - - 0 - 3 - - - - - True - False - Jabber ID: - 1 - - - 0 - 4 - - - - - True - False - E-Mail: - 1 - - - 0 - 5 - - - - - True - True - True - - - - 1 - 2 - - - - - True - True - True - - - - 1 - 3 - - - - - True - True - True - - - - 1 - 4 - - - - - True - True - True - - - - 1 - 5 - - - - - 1 - - - - - True - False - Personal Information - - - 1 - False - - - - - True - True - 1 - - - - - 2 - - - - - - - - True - False - - - - - True - True - 0 - - - - - Mer_ge accounts - True - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - 0.5 - True - - - False - True - 1 - - - - + True False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - 6 - end + vertical - - gtk-close + True True - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - + never + in + 400 + True + + + True + False + + + OptionsBox + True + False + none + + + + + False - False + True 0 + + + True + False + False + + + False + True + 1 + + - False - True - 3 + main + page0 + + False + True + 0 + + + + + True + False + Accounts + True + :close + + + True + True + True + + + + True + False + go-previous-symbolic + + + + + + + True + True + True + + + True + False + open-menu-symbolic + + + + + 1 + diff --git a/data/gui/synchronise_select_account_dialog.ui b/data/gui/synchronise_select_account_dialog.ui index 58d066d46..365f862c1 100644 --- a/data/gui/synchronise_select_account_dialog.ui +++ b/data/gui/synchronise_select_account_dialog.ui @@ -9,6 +9,7 @@ 350 300 dialog + True diff --git a/data/style/gajim.css b/data/style/gajim.css index f735bc487..62c782586 100644 --- a/data/style/gajim.css +++ b/data/style/gajim.css @@ -30,14 +30,34 @@ popover#EmoticonPopover flowboxchild { padding-top: 5px; padding-bottom: 5px; } #ServerInfoGrid > list > label { padding:10px; color: @insensitive_fg_color; font-weight: bold; } #ServerInfoGrid > list > row.activatable:active { box-shadow: none; } -/* Generic Options Dialog */ -#OptionsDialog list > row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; } -#OptionsDialog list > row:last-child { border-bottom: 0px} -#OptionsDialog list > row { padding: 10px; } -#OptionsDialog list > row.activatable:active { box-shadow: none; } +/* OptionsBox */ +#OptionsBox > row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; } +#OptionsBox > row:last-child { border-bottom: 0px} +#OptionsBox > row.activatable:active { box-shadow: none; } +#OptionsBox > row { padding: 10px 20px 10px 10px; } +#OptionsBox > row:not(.activatable) label { color: @insensitive_fg_color } + +/* GenericOption */ +#SubDescription { color: @insensitive_fg_color;} +#GenericOptionBox { margin-left: 30px; } +#GenericOptionBox > label { padding-right: 3px; } /* Generic Popover Menu with Buttons */ .PopoverButtonListbox { padding-left: 0px; padding-right: 0px; } .PopoverButtonListbox > list { margin-top: 10px; margin-bottom: 10px; } .PopoverButtonListbox > list > row { padding: 10px 20px 10px 20px; } .PopoverButtonListbox > list > row.activatable:active { box-shadow: none; background-color: @theme_selected_bg_color } + +/* Accounts Window */ +#AccountsWindow > box { padding:30px 30px 30px 30px;} +#AccountsWindow scrolledwindow {border: none;} +#AccountsWindow list {border: 1px solid; border-color: @borders;} +#AccountsWindow > box actionbar box {border: none;} + +#AccountNameEntry:disabled { font-size: 16px; + font-weight: bold; + border: none; + background-color: @theme_unfocused_bg_color; + color: @theme_text_color; } + + diff --git a/gajim/accounts_window.py b/gajim/accounts_window.py new file mode 100644 index 000000000..3b4f3df1a --- /dev/null +++ b/gajim/accounts_window.py @@ -0,0 +1,698 @@ +from functools import partial + +from gi.repository import Gtk, Gio, GLib, Gdk + +from gajim.common import app +from gajim.gtkgui_helpers import get_image_button +from gajim import gtkgui_helpers +from gajim import gui_menu_builder +from gajim.common import passwords +from gajim import dialogs +from gajim import config +from gajim.common import helpers +from gajim.common.connection import Connection +from gajim.common.zeroconf.connection_zeroconf import ConnectionZeroconf +from gajim.options_dialog import OptionsDialog, OptionsBox +from gajim.common.const import Option, OptionKind, OptionType + + +class AccountsWindow(Gtk.ApplicationWindow): + def __init__(self): + Gtk.ApplicationWindow.__init__(self) + self.set_application(app.app) + self.set_position(Gtk.WindowPosition.CENTER) + self.set_show_menubar(False) + self.set_name('AccountsWindow') + self.set_size_request(500, -1) + self.set_resizable(False) + self.need_relogin = {} + + glade_objects = [ + 'stack', 'box', 'actionbar', 'headerbar', 'back_button', + 'menu_button', 'account_page', 'account_list'] + self.builder = gtkgui_helpers.get_gtk_builder('accounts_window.ui') + for obj in glade_objects: + setattr(self, obj, self.builder.get_object(obj)) + + self.set_titlebar(self.headerbar) + + menu = Gio.Menu() + menu.append('Merge Accounts', 'app.merge') + menu.append('Use PGP Agent', 'app.agent') + self.menu_button.set_menu_model(menu) + + button = get_image_button('list-add-symbolic', 'Add') + button.set_action_name('app.add-account') + self.actionbar.pack_start(button) + + accounts = app.config.get_per('accounts') + accounts.sort() + for account in accounts: + self.need_relogin[account] = self.get_relogin_options(account) + account_item = Account(account, self) + self.account_list.add(account_item) + account_item.set_activatable() + + self.add(self.box) + self.builder.connect_signals(self) + + self.connect('destroy', self.on_destroy) + self.connect('key-press-event', self.on_key_press) + self.show_all() + + def on_key_press(self, widget, event): + if event.keyval == Gdk.KEY_Escape: + self.destroy() + + def on_destroy(self, *args): + self.check_relogin() + del app.interface.instances['accounts'] + + def on_child_visible(self, stack, *args): + page = stack.get_visible_child_name() + if page is None: + return + if page == 'main': + self.menu_button.show() + self.back_button.hide() + self.check_relogin() + else: + self.back_button.show() + self.menu_button.hide() + + def on_back_button(self, *args): + page = self.stack.get_visible_child_name() + child = self.stack.get_visible_child() + self.remove_all_pages() + if page == 'account': + child.toggle.set_active(False) + self.stack.add_named(self.account_page, 'main') + self.stack.set_visible_child_name('main') + self.update_accounts() + else: + self.stack.add_named(child.parent, 'account') + self.stack.set_visible_child_name('account') + + def update_accounts(self): + for row in self.account_list.get_children(): + row.get_child().update() + + @staticmethod + def on_row_activated(listbox, row): + row.get_child().on_row_activated() + + def remove_all_pages(self): + for page in self.stack.get_children(): + self.stack.remove(page) + + def set_page(self, page, name): + self.remove_all_pages() + self.stack.add_named(page, name) + page.update() + page.show_all() + self.stack.set_visible_child(page) + + def update_proxy_list(self): + page = self.stack.get_child_by_name('connetion') + if page is None: + return + page.options['proxy'].update_values() + + def check_relogin(self): + for account in self.need_relogin: + options = self.get_relogin_options(account) + active = app.config.get_per('accounts', account, 'active') + if options != self.need_relogin[account]: + self.need_relogin[account] = options + if active: + self.relog(account) + break + + def relog(self, account): + if app.connections[account].connected == 0: + return + + if account == app.ZEROCONF_ACC_NAME: + app.connections[app.ZEROCONF_ACC_NAME].update_details() + return + + def login(account, show_before, status_before): + """ + Login with previous status + """ + # first make sure connection is really closed, + # 0.5 may not be enough + app.connections[account].disconnect(True) + app.interface.roster.send_status( + account, show_before, status_before) + + def relog(account): + show_before = app.SHOW_LIST[app.connections[account].connected] + status_before = app.connections[account].status + app.interface.roster.send_status( + account, 'offline', _('Be right back.')) + GLib.timeout_add(500, login, account, show_before, status_before) + + dialogs.YesNoDialog( + _('Relogin now?'), + _('If you want all the changes to apply instantly, ' + 'you must relogin.'), + transient_for=self, + on_response_yes=lambda *args: relog(account)) + + @staticmethod + def get_relogin_options(account): + if account == app.ZEROCONF_ACC_NAME: + options = ['zeroconf_first_name', 'zeroconf_last_name', + 'zeroconf_jabber_id', 'zeroconf_email', 'keyid'] + else: + options = ['client_cert', 'proxy', 'resource', + 'use_custom_host', 'custom_host', 'custom_port', + 'keyid'] + + values = [] + for option in options: + values.append(app.config.get_per('accounts', account, option)) + return values + + def on_remove_account(self, button, account): + if app.events.get_events(account): + dialogs.ErrorDialog( + _('Unread events'), + _('Read all pending events before removing this account.'), + transient_for=self) + return + + if app.config.get_per('accounts', account, 'is_zeroconf'): + # Should never happen as button is insensitive + return + + win_opened = False + if app.interface.msg_win_mgr.get_controls(acct=account): + win_opened = True + elif account in app.interface.instances: + for key in app.interface.instances[account]: + if (app.interface.instances[account][key] and + key != 'remove_account'): + win_opened = True + break + + # Detect if we have opened windows for this account + + def remove(account): + if (account in app.interface.instances and + 'remove_account' in app.interface.instances[account]): + dialog = app.interface.instances[account]['remove_account'] + dialog.window.present() + else: + if account not in app.interface.instances: + app.interface.instances[account] = {} + app.interface.instances[account]['remove_account'] = \ + config.RemoveAccountWindow(account) + if win_opened: + dialogs.ConfirmationDialog( + _('You have opened chat in account %s') % account, + _('All chat and groupchat windows will be closed. ' + 'Do you want to continue?'), + on_response_ok=(remove, account)) + else: + remove(account) + + def remove_account(self, account): + for row in self.account_list.get_children(): + if row.get_child().account == account: + self.account_list.remove(row) + del self.need_relogin[account] + break + + def add_account(self, account): + account_item = Account(account, self) + self.account_list.add(account_item) + account_item.set_activatable() + self.account_list.show_all() + self.stack.show_all() + self.need_relogin[account] = self.get_relogin_options(account) + + def select_account(self, account): + for row in self.account_list.get_children(): + if row.get_child().account == account: + self.account_list.emit('row-activated', row) + break + + @staticmethod + def enable_account(account): + if account == app.ZEROCONF_ACC_NAME: + app.connections[account] = ConnectionZeroconf(account) + else: + app.connections[account] = Connection(account) + + # update variables + app.interface.instances[account] = { + 'infos': {}, 'disco': {}, 'gc_config': {}, 'search': {}, + 'online_dialog': {}, 'sub_request': {}} + app.interface.minimized_controls[account] = {} + app.connections[account].connected = 0 + app.groups[account] = {} + app.contacts.add_account(account) + app.gc_connected[account] = {} + app.automatic_rooms[account] = {} + app.newly_added[account] = [] + app.to_be_removed[account] = [] + if account == app.ZEROCONF_ACC_NAME: + app.nicks[account] = app.ZEROCONF_ACC_NAME + else: + app.nicks[account] = app.config.get_per( + 'accounts', account, 'name') + app.block_signed_in_notifications[account] = True + app.sleeper_state[account] = 'off' + app.encrypted_chats[account] = [] + app.last_message_time[account] = {} + app.status_before_autoaway[account] = '' + app.transport_avatar[account] = {} + app.gajim_optional_features[account] = [] + app.caps_hash[account] = '' + helpers.update_optional_features(account) + # refresh roster + if len(app.connections) >= 2: + # Do not merge accounts if only one exists + app.interface.roster.regroup = app.config.get('mergeaccounts') + else: + app.interface.roster.regroup = False + app.interface.roster.setup_and_draw_roster() + gui_menu_builder.build_accounts_menu() + + @staticmethod + def disable_account(account): + app.interface.roster.close_all(account) + if account == app.ZEROCONF_ACC_NAME: + app.connections[account].disable_account() + app.connections[account].cleanup() + del app.connections[account] + del app.interface.instances[account] + del app.interface.minimized_controls[account] + del app.nicks[account] + del app.block_signed_in_notifications[account] + del app.groups[account] + app.contacts.remove_account(account) + del app.gc_connected[account] + del app.automatic_rooms[account] + del app.to_be_removed[account] + del app.newly_added[account] + del app.sleeper_state[account] + del app.encrypted_chats[account] + del app.last_message_time[account] + del app.status_before_autoaway[account] + del app.transport_avatar[account] + del app.gajim_optional_features[account] + del app.caps_hash[account] + if len(app.connections) >= 2: + # Do not merge accounts if only one exists + app.interface.roster.regroup = app.config.get('mergeaccounts') + else: + app.interface.roster.regroup = False + app.interface.roster.setup_and_draw_roster() + gui_menu_builder.build_accounts_menu() + + +class Account(Gtk.Box): + def __init__(self, account, parent): + Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL, + spacing=12) + self.account = account + if account == app.ZEROCONF_ACC_NAME: + self.options = ZeroConfPage(account) + else: + self.options = AccountPage(account) + self.parent = parent + + switch = Gtk.Switch() + switch.set_active(app.config.get_per('accounts', account, 'active')) + switch.set_vexpand(False) + switch.set_valign(Gtk.Align.CENTER) + switch.set_halign(Gtk.Align.START) + if account == app.ZEROCONF_ACC_NAME and not app.HAVE_ZEROCONF: + switch.set_sensitive(False) + switch.set_active(False) + switch.connect('notify::active', self.on_switch, self.account) + + account_label = app.config.get_per('accounts', account, 'account_label') + self.label = Gtk.Label(label=account_label or account) + self.label.set_halign(Gtk.Align.START) + self.label.set_hexpand(True) + + self.add(switch) + self.add(self.label) + + if account != app.ZEROCONF_ACC_NAME: + button = get_image_button('list-remove-symbolic', 'Remove') + button.connect('clicked', parent.on_remove_account, account) + self.add(button) + + def set_activatable(self): + if self.account == app.ZEROCONF_ACC_NAME: + self.get_parent().set_activatable(app.HAVE_ZEROCONF) + + def on_switch(self, switch, param, account): + old_state = app.config.get_per('accounts', account, 'active') + state = switch.get_active() + if old_state == state: + return + + if (account in app.connections and + app.connections[account].connected > 0): + # connecting or connected + dialogs.ErrorDialog( + _('You are currently connected to the server'), + _('To disable the account, you must be disconnected.'), + transient_for=self.parent) + switch.set_active(not state) + return + if state: + self.parent.enable_account(account) + else: + self.parent.disable_account(account) + app.config.set_per('accounts', account, 'active', state) + + def on_row_activated(self): + self.options.update_states() + self.parent.set_page(self.options, 'account') + + def update(self): + account_label = app.config.get_per( + 'accounts', self.account, 'account_label') + self.label.set_text(account_label or self.account) + + +class GenericOptionPage(Gtk.Box): + def __init__(self, account, parent, options): + Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL, spacing=12) + self.account = account + self.parent = parent + + self.toggle = get_image_button('document-edit-symbolic', + _('Rename account label'), toggle=True) + self.toggle.connect('toggled', self.set_entry_text) + + self.entry = Gtk.Entry() + self.entry.set_sensitive(False) + self.entry.set_name('AccountNameEntry') + self.set_entry_text(self.toggle, update=True) + + box = Gtk.Box() + if isinstance(self, AccountPage): + box.pack_start(self.toggle, False, True, 0) + box.pack_start(self.entry, True, True, 0) + + self.listbox = OptionsBox(account) + self.listbox.set_selection_mode(Gtk.SelectionMode.NONE) + + for option in options: + self.listbox.add_option(option) + self.listbox.update_states() + + self.pack_start(box, False, False, 0) + self.pack_start(self.listbox, True, True, 0) + + self.listbox.connect('row-activated', self.on_row_activated) + + def update_states(self): + self.listbox.update_states() + + def on_row_activated(self, listbox, row): + self.toggle.set_active(False) + row.get_child().on_row_activated() + + def set_entry_text(self, toggle, update=False): + account_label = app.config.get_per( + 'accounts', self.account, 'account_label') + if update: + self.entry.set_text(account_label or self.account) + return + if toggle.get_active(): + self.entry.set_sensitive(True) + self.entry.grab_focus() + else: + self.entry.set_sensitive(False) + value = self.entry.get_text() + if not value: + value = account_label or self.account + app.config.set_per('accounts', self.account, + 'account_label', value or self.account) + if app.config.get_per('accounts', self.account, 'active'): + app.interface.roster.draw_account(self.account) + + def update(self): + self.set_entry_text(self.toggle, update=True) + + def set_page(self, options, name): + options.update_states() + self.get_toplevel().set_page(options, name) + + +class AccountPage(GenericOptionPage): + def __init__(self, account, parent=None): + + general = partial( + self.set_page, GeneralPage(account, self), 'general') + connection = partial( + self.set_page, ConnectionPage(account, self), 'connection') + + options = [ + Option(OptionKind.LOGIN, _('Login'), OptionType.DIALOG, + props={'dialog': LoginDialog}), + + Option(OptionKind.ACTION, _('Profile'), OptionType.ACTION, + '-profile', props={'action_args': account}), + + Option(OptionKind.CALLBACK, _('General'), + name='general', props={'callback': general}), + + Option(OptionKind.CALLBACK, _('Connection'), + name='connection', props={'callback': connection}), + + Option(OptionKind.ACTION, _('Import Contacts'), OptionType.ACTION, + '-import-contacts', props={'action_args': account}), + + Option(OptionKind.DIALOG, _('Client Certificate'), + OptionType.DIALOG, props={'dialog': CertificateDialog}), + + Option(OptionKind.GPG, _('OpenPGP Key'), OptionType.DIALOG, + props={'dialog': None}), + ] + + GenericOptionPage.__init__(self, account, parent, options) + + +class GeneralPage(GenericOptionPage): + def __init__(self, account, parent=None): + + options = [ + Option(OptionKind.SWITCH, _('Connect on startup'), + OptionType.ACCOUNT_CONFIG, 'autoconnect'), + + Option(OptionKind.SWITCH, _('Reconnect when connection is lost'), + OptionType.ACCOUNT_CONFIG, 'autoreconnect'), + + Option(OptionKind.SWITCH, _('Save conversations for all contacts'), + OptionType.ACCOUNT_CONFIG, 'no_log_for', + desc=_('Store conversations on the harddrive')), + + Option(OptionKind.SWITCH, _('Server Message Archive'), + OptionType.ACCOUNT_CONFIG, 'sync_logs_with_server', + desc=_('Messages get stored on the server.\n' + 'The archive is used to sync messages\n' + 'between multiple devices.\n' + 'XEP-0313')), + + Option(OptionKind.SWITCH, _('Global Status'), + OptionType.ACCOUNT_CONFIG, 'sync_with_global_status', + desc=_('Synchronise the status of all accounts')), + + Option(OptionKind.SWITCH, _('Message Carbons'), + OptionType.ACCOUNT_CONFIG, 'enable_message_carbons', + desc=_('All your other online devices get copies\n' + 'of sent and received messages.\n' + 'XEP-0280')), + + Option(OptionKind.SWITCH, _('Use file transfer proxies'), + OptionType.ACCOUNT_CONFIG, 'use_ft_proxies'), + ] + GenericOptionPage.__init__(self, account, parent, options) + + +class ConnectionPage(GenericOptionPage): + def __init__(self, account, parent=None): + + options = [ + Option(OptionKind.SWITCH, 'HTTP_PROXY', + OptionType.ACCOUNT_CONFIG, 'use_env_http_proxy', + desc=_('Use environment variable')), + + Option(OptionKind.PROXY, _('Proxy'), + OptionType.ACCOUNT_CONFIG, 'proxy', name='proxy'), + + Option(OptionKind.SWITCH, _('Warn on insecure connection'), + OptionType.ACCOUNT_CONFIG, + 'warn_when_insecure_ssl_connection'), + + Option(OptionKind.SWITCH, _('Send keep-alive packets'), + OptionType.ACCOUNT_CONFIG, 'keep_alives_enabled'), + + Option(OptionKind.HOSTNAME, _('Hostname'), OptionType.DIALOG, + desc=_('Manually set the hostname for the server'), + props={'dialog': CutstomHostnameDialog}), + + Option(OptionKind.ENTRY, _('Resource'), + OptionType.ACCOUNT_CONFIG, 'resource'), + + Option(OptionKind.PRIORITY, _('Priority'), + OptionType.DIALOG, props={'dialog': PriorityDialog}), + ] + + GenericOptionPage.__init__(self, account, parent, options) + + +class ZeroConfPage(GenericOptionPage): + def __init__(self, account, parent=None): + + options = [ + Option(OptionKind.DIALOG, _('Credentials'), + OptionType.DIALOG, props={'dialog': CredentialsDialog}), + + Option(OptionKind.SWITCH, _('Connect on startup'), + OptionType.ACCOUNT_CONFIG, 'autoconnect', + desc=_('Use environment variable')), + + Option(OptionKind.SWITCH, _('Save conversations for all contacts'), + OptionType.ACCOUNT_CONFIG, 'no_log_for', + desc=_('Store conversations on the harddrive')), + + Option(OptionKind.SWITCH, _('Global Status'), + OptionType.ACCOUNT_CONFIG, 'sync_with_global_status', + desc=_('Synchronize the status of all accounts')), + + Option(OptionKind.GPG, _('OpenPGP Key'), + OptionType.DIALOG, props={'dialog': None}), + ] + + GenericOptionPage.__init__(self, account, parent, options) + + +class CredentialsDialog(OptionsDialog): + def __init__(self, account, parent): + + options = [ + Option(OptionKind.ENTRY, _('First Name'), + OptionType.ACCOUNT_CONFIG, 'zeroconf_first_name'), + + Option(OptionKind.ENTRY, _('Last Name'), + OptionType.ACCOUNT_CONFIG, 'zeroconf_last_name'), + + Option(OptionKind.ENTRY, _('Jabber ID'), + OptionType.ACCOUNT_CONFIG, 'zeroconf_jabber_id'), + + Option(OptionKind.ENTRY, _('Email'), + OptionType.ACCOUNT_CONFIG, 'zeroconf_email'), + ] + + OptionsDialog.__init__(self, parent, _('Credential Options'), + Gtk.DialogFlags.MODAL, options, account) + + +class PriorityDialog(OptionsDialog): + def __init__(self, account, parent): + + neg_priority = app.config.get('enable_negative_priority') + if neg_priority: + range_ = (-128, 127) + else: + range_ = (0, 127) + + options = [ + Option(OptionKind.SWITCH, _('Adjust to status'), + OptionType.ACCOUNT_CONFIG, 'adjust_priority_with_status', + 'adjust'), + + Option(OptionKind.SPIN, _('Priority'), + OptionType.ACCOUNT_CONFIG, 'priority', + enabledif=('adjust', False), props={'range_': range_}), + ] + + OptionsDialog.__init__(self, parent, _('Priority'), + Gtk.DialogFlags.MODAL, options, account) + + self.connect('destroy', self.on_destroy) + + def on_destroy(self, *args): + # Update priority + if self.account not in app.connections: + return + show = app.SHOW_LIST[app.connections[self.account].connected] + status = app.connections[self.account].status + app.connections[self.account].change_status(show, status) + + +class CutstomHostnameDialog(OptionsDialog): + def __init__(self, account, parent): + + options = [ + Option(OptionKind.SWITCH, _('Enable'), + OptionType.ACCOUNT_CONFIG, 'use_custom_host', name='custom'), + + Option(OptionKind.ENTRY, _('Hostname'), + OptionType.ACCOUNT_CONFIG, 'custom_host', + enabledif=('custom', True)), + + Option(OptionKind.ENTRY, _('Port'), + OptionType.ACCOUNT_CONFIG, 'custom_port', + enabledif=('custom', True)), + ] + + OptionsDialog.__init__(self, parent, _('Connection Options'), + Gtk.DialogFlags.MODAL, options, account) + + +class CertificateDialog(OptionsDialog): + def __init__(self, account, parent): + + options = [ + Option(OptionKind.FILECHOOSER, _('Client Certificate'), + OptionType.ACCOUNT_CONFIG, 'client_cert', + props={'filefilter': (_('PKCS12 Files'), '*.p12')}), + + Option(OptionKind.SWITCH, _('Encrypted Certificate'), + OptionType.ACCOUNT_CONFIG, 'client_cert_encrypted'), + ] + + OptionsDialog.__init__(self, parent, _('Certificate Options'), + Gtk.DialogFlags.MODAL, options, account) + + +class LoginDialog(OptionsDialog): + def __init__(self, account, parent): + + options = [ + Option(OptionKind.ENTRY, _('Password'), + OptionType.ACCOUNT_CONFIG, 'password', name='password', + enabledif=('savepass', True)), + + Option(OptionKind.SWITCH, _('Save Password'), + OptionType.ACCOUNT_CONFIG, 'savepass', name='savepass'), + + Option(OptionKind.CHANGEPASSWORD, _('Change Password'), + OptionType.DIALOG, callback=self.on_password_change, + props={'dialog': None}), + ] + + OptionsDialog.__init__(self, parent, _('Login Options'), + Gtk.DialogFlags.MODAL, options, account) + + self.connect('destroy', self.on_destroy) + + def on_password_change(self, new_password, data): + self.get_option('password').entry.set_text(new_password) + + def on_destroy(self, *args): + savepass = app.config.get_per('accounts', self.account, 'savepass') + if not savepass: + passwords.save_password(self.account, '') diff --git a/gajim/app_actions.py b/gajim/app_actions.py index a88a75bb7..e718dfa58 100644 --- a/gajim/app_actions.py +++ b/gajim/app_actions.py @@ -25,10 +25,12 @@ from gajim.common.exceptions import GajimGeneralException from gi.repository import Gtk import sys import os + from gajim import config from gajim import dialogs from gajim import features_window from gajim import shortcuts_window +from gajim import accounts_window import gajim.plugins.gui from gajim import history_window from gajim import disco @@ -57,10 +59,10 @@ class AppActions(): interface.instances['plugins'] = gajim.plugins.gui.PluginsWindow() def on_accounts(self, action, param): - if 'accounts' in interface.instances: - interface.instances['accounts'].window.present() + if 'accounts' in app.interface.instances: + app.interface.instances['accounts'].present() else: - interface.instances['accounts'] = config.AccountsWindow() + app.interface.instances['accounts'] = accounts_window.AccountsWindow() def on_history_manager(self, action, param): from gajim.history_manager import HistoryManager @@ -131,6 +133,35 @@ class AppActions(): def on_single_message(self, action, param): dialogs.SingleMessageWindow(param.get_string(), action='send') + def on_merge_accounts(self, action, param): + action.set_state(param) + value = param.get_boolean() + app.config.set('mergeaccounts', value) + if len(app.connections) >= 2: # Do not merge accounts if only one active + app.interface.roster.regroup = value + else: + app.interface.roster.regroup = False + app.interface.roster.setup_and_draw_roster() + + def on_use_pgp_agent(self, action, param): + action.set_state(param) + app.config.set('use_gpg_agent', param.get_boolean()) + + def on_add_account(self, action, param): + if 'account_creation_wizard' in app.interface.instances: + app.interface.instances['account_creation_wizard'].window.present() + else: + app.interface.instances['account_creation_wizard'] = \ + config.AccountCreationWizardWindow() + + def on_import_contacts(self, action, param): + account = param.get_string() + if 'import_contacts' in app.interface.instances: + app.interface.instances['import_contacts'].dialog.present() + else: + app.interface.instances['import_contacts'] = \ + dialogs.SynchroniseSelectAccountDialog(account) + # Advanced Actions def on_archiving_preferences(self, action, param): @@ -174,6 +205,13 @@ class AppActions(): interface.instances[account]['xml_console'] = \ dialogs.XMLConsoleWindow(account) + def on_manage_proxies(self, action, param): + if 'manage_proxies' in app.interface.instances: + app.interface.instances['manage_proxies'].window.present() + else: + app.interface.instances['manage_proxies'] = \ + config.ManageProxiesWindow(interface.roster.window) + # Admin Actions def on_set_motd(self, action, param): diff --git a/gajim/common/config.py b/gajim/common/config.py index 4b5f05ae6..f8ad19b2a 100644 --- a/gajim/common/config.py +++ b/gajim/common/config.py @@ -325,6 +325,7 @@ class Config: __options_per_key = { 'accounts': ({ 'name': [ opt_str, '', '', True ], + 'account_label': [ opt_str, '', '', False ], 'hostname': [ opt_str, '', '', True ], 'anonymous_auth': [ opt_bool, False ], 'client_cert': [ opt_str, '', '', True ], diff --git a/gajim/common/const.py b/gajim/common/const.py index 63a801b86..8985ced5c 100644 --- a/gajim/common/const.py +++ b/gajim/common/const.py @@ -1,3 +1,34 @@ +from enum import IntEnum, unique +from collections import namedtuple + +Option = namedtuple('Option', 'kind label type value name callback data desc enabledif props') +Option.__new__.__defaults__ = (None,) * len(Option._fields) + +@unique +class OptionKind(IntEnum): + ENTRY = 0 + SWITCH = 1 + SPIN = 2 + ACTION = 3 + LOGIN = 4 + DIALOG = 5 + CALLBACK = 6 + PROXY = 7 + HOSTNAME = 8 + PRIORITY = 9 + FILECHOOSER = 10 + CHANGEPASSWORD = 11 + GPG = 12 + +@unique +class OptionType(IntEnum): + ACCOUNT_CONFIG = 0 + CONFIG = 1 + BOOL = 2 + ACTION = 3 + DIALOG = 4 + + THANKS = u"""\ Alexander Futász Alexander V. Butenko diff --git a/gajim/config.py b/gajim/config.py index de0ac06bc..1245deabf 100644 --- a/gajim/config.py +++ b/gajim/config.py @@ -3158,10 +3158,10 @@ class RemoveAccountWindow: app.app.remove_account_actions(self.account) gui_menu_builder.build_accounts_menu() if 'accounts' in app.interface.instances: - app.interface.instances['accounts'].init_accounts() - app.interface.instances['accounts'].init_account() + app.interface.instances['accounts'].remove_account(self.account) self.window.destroy() + #---------- ManageBookmarksWindow class -------------# class ManageBookmarksWindow: def __init__(self): @@ -3733,7 +3733,7 @@ class AccountCreationWizardWindow: self.account = server i = 1 - while self.account in app.connections: + while self.account in app.config.get_per('accounts'): self.account = server + str(i) i += 1 @@ -3754,7 +3754,7 @@ class AccountCreationWizardWindow: return self.account = server i = 1 - while self.account in app.connections: + while self.account in app.config.get_per('accounts'): self.account = server + str(i) i += 1 @@ -3982,7 +3982,7 @@ class AccountCreationWizardWindow: def on_advanced_button_clicked(self, widget): if 'accounts' in app.interface.instances: - app.interface.instances['accounts'].window.present() + app.interface.instances['accounts'].present() else: app.interface.instances['accounts'] = AccountsWindow() app.interface.instances['accounts'].select_account(self.account) @@ -4083,9 +4083,11 @@ class AccountCreationWizardWindow: app.gajim_optional_features[self.account] = [] app.caps_hash[self.account] = '' helpers.update_optional_features(self.account) + # action must be added before account window is updated + app.app.add_account_actions(self.account) # refresh accounts window if 'accounts' in app.interface.instances: - app.interface.instances['accounts'].init_accounts() + app.interface.instances['accounts'].add_account(self.account) # refresh roster if len(app.connections) >= 2: # Do not merge accounts if only one exists @@ -4093,7 +4095,6 @@ class AccountCreationWizardWindow: else: app.interface.roster.regroup = False app.interface.roster.setup_and_draw_roster() - app.app.add_account_actions(self.account) gui_menu_builder.build_accounts_menu() class ManagePEPServicesWindow: diff --git a/gajim/dialogs.py b/gajim/dialogs.py index 3ae5d6301..8a3739e5e 100644 --- a/gajim/dialogs.py +++ b/gajim/dialogs.py @@ -48,6 +48,8 @@ from random import randrange from gajim.common import pep from gajim.common import ged from gajim.common import const +from gajim.options_dialog import OptionsDialog +from gajim.common.const import Option, OptionKind, OptionType try: from gajim import gtkspell @@ -2675,7 +2677,7 @@ class SynchroniseSelectAccountDialog: self.account = account self.xml = gtkgui_helpers.get_gtk_builder('synchronise_select_account_dialog.ui') self.dialog = self.xml.get_object('synchronise_select_account_dialog') - self.dialog.set_transient_for(app.interface.instances['accounts'].window) + self.dialog.set_transient_for(app.interface.instances['accounts']) self.accounts_treeview = self.xml.get_object('accounts_treeview') model = Gtk.ListStore(str, str, bool) self.accounts_treeview.set_model(model) @@ -2731,6 +2733,10 @@ class SynchroniseSelectAccountDialog: return self.dialog.destroy() + @staticmethod + def on_destroy(widget): + del app.interface.instances['import_contacts'] + class SynchroniseSelectContactsDialog: def __init__(self, account, remote_account): self.local_account = account @@ -3323,6 +3329,8 @@ class XMLConsoleWindow(Gtk.Window): setattr(self, obj, self.builder.get_object(obj)) self.set_titlebar(self.headerbar) + jid = app.get_jid_from_account(account) + self.headerbar.set_subtitle(jid) self.set_default_size(600, 600) self.add(self.box) @@ -3434,26 +3442,30 @@ class XMLConsoleWindow(Gtk.Window): def on_filter_options(self, *args): options = [ - SwitchOption('Presence', self.presence, - self.on_option, - 'presence'), - SwitchOption('Message', self.message, - self.on_option, - 'message'), - SwitchOption('Iq', self.iq, - self.on_option, - 'iq'), - SwitchOption('Stream\nManagement', self.stream, - self.on_option, - 'stream'), - SwitchOption('In', self.incoming, - self.on_option, - 'incoming'), - SwitchOption('Out', self.outgoing, - self.on_option, - 'outgoing')] + Option(OptionKind.SWITCH, 'Presence', + OptionType.BOOL, self.presence, + callback=self.on_option, data='presence'), - OptionsDialog(self, 'Filter', options) + Option(OptionKind.SWITCH, 'Message', + OptionType.BOOL, self.message, + callback=self.on_option, data='message'), + + Option(OptionKind.SWITCH, 'Iq', OptionType.BOOL, self.iq, + callback=self.on_option, data='iq'), + + Option(OptionKind.SWITCH, 'Stream\nManagement', + OptionType.BOOL, self.stream, + callback=self.on_option, data='stream'), + + Option(OptionKind.SWITCH, 'In', OptionType.BOOL, self.incoming, + callback=self.on_option, data='incoming'), + + Option(OptionKind.SWITCH, 'Out', OptionType.BOOL, self.outgoing, + callback=self.on_option, data='outgoing'), + ] + + OptionsDialog(self, 'Filter', Gtk.DialogFlags.DESTROY_WITH_PARENT, + options, self.account) def on_clear(self, *args): buffer_ = self.textview.get_buffer().set_text('') @@ -3468,13 +3480,12 @@ class XMLConsoleWindow(Gtk.Window): def on_enable(self, switch, param): self.enabled = switch.get_active() - def on_option(self, switch, param, *user_data): - kind = user_data[0] - setattr(self, kind, switch.get_active()) - value = not switch.get_active() + def on_option(self, value, data): + setattr(self, data, value) + value = not value table = self.textview.get_buffer().get_tag_table() - tag = table.lookup(kind) - if kind in ('incoming', 'outgoing'): + tag = table.lookup(data) + if data in ('incoming', 'outgoing'): if value: tag.set_priority(table.get_size() - 1) else: @@ -3522,58 +3533,6 @@ class XMLConsoleWindow(Gtk.Window): if at_the_end: GLib.idle_add(gtkgui_helpers.scroll_to_end, self.scrolled) - -class OptionsDialog(Gtk.Dialog): - def __init__(self, parent, title, options): - Gtk.Dialog.__init__(self, title, parent, - Gtk.DialogFlags.DESTROY_WITH_PARENT) - self.set_name('OptionsDialog') - self.set_resizable(False) - self.set_default_size(250, -1) - - self.remove(self.get_content_area()) - - listbox = Gtk.ListBox() - listbox.set_hexpand(True) - listbox.set_selection_mode(Gtk.SelectionMode.NONE) - - for option in options: - listbox.add(option) - - self.add(listbox) - - self.show_all() - listbox.connect('row-activated', self.on_row_activated) - - def on_row_activated(self, listbox, row): - row.get_child().set_switch_state() - - -class SwitchOption(Gtk.Grid): - def __init__(self, label, state, callback, *user_data): - Gtk.Grid.__init__(self) - self.set_column_spacing(6) - - label = Gtk.Label(label=label) - label.set_hexpand(True) - label.set_halign(Gtk.Align.START) - - self.switch = Gtk.Switch() - self.switch.set_active(state) - self.switch.connect("notify::active", callback, *user_data) - self.switch.set_hexpand(True) - self.switch.set_halign(Gtk.Align.END) - self.switch.set_valign(Gtk.Align.CENTER) - - self.add(label) - self.add(self.switch) - self.show_all() - - def set_switch_state(self): - state = self.switch.get_active() - self.switch.set_active(not state) - - #Action that can be done with an incoming list of contacts TRANSLATED_ACTION = {'add': _('add'), 'modify': _('modify'), 'remove': _('remove')} @@ -4580,7 +4539,7 @@ class ClientCertChooserDialog(FileChooserDialog): FileChooserDialog.__init__(self, title_text=_('Choose Client Cert #PCKS12'), - transient_for=app.interface.instances['accounts'].window, + transient_for=app.interface.instances['accounts'], action=Gtk.FileChooserAction.OPEN, buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK), diff --git a/gajim/gajim.py b/gajim/gajim.py index 269c1c8af..a3dd77267 100644 --- a/gajim/gajim.py +++ b/gajim/gajim.py @@ -319,6 +319,7 @@ class GajimApplication(Gtk.Application): def add_actions(self): ''' Build Application Actions ''' from gajim.app_actions import AppActions + from gajim.common import app action = AppActions(self) self.account_actions = [ @@ -339,12 +340,30 @@ class GajimApplication(Gtk.Application): ('-update-motd', action.on_update_motd, 'online', 's'), ('-delete-motd', action.on_delete_motd, 'online', 's'), ('-activate-bookmark', - action.on_activate_bookmark, 'online', 'a{sv}') + action.on_activate_bookmark, 'online', 'a{sv}'), + ('-import-contacts', action.on_import_contacts, 'online', 's') ] + # General Stateful Actions + + act = Gio.SimpleAction.new_stateful( + 'merge', None, + GLib.Variant.new_boolean(app.config.get('mergeaccounts'))) + act.connect('change-state', action.on_merge_accounts) + self.add_action(act) + + act = Gio.SimpleAction.new_stateful( + 'agent', None, + GLib.Variant.new_boolean(app.config.get('use_gpg_agent'))) + self.add_action(act) + + # General Actions + self.general_actions = [ ('quit', action.on_quit), ('accounts', action.on_accounts), + ('add-account', action.on_add_account), + ('manage-proxies', action.on_manage_proxies), ('bookmarks', action.on_manage_bookmarks), ('history-manager', action.on_history_manager), ('preferences', action.on_preferences), @@ -364,8 +383,7 @@ class GajimApplication(Gtk.Application): act.connect("activate", func) self.add_action(act) - from gajim.common import app - accounts_list = sorted(app.contacts.get_accounts()) + accounts_list = sorted(app.config.get_per('accounts')) if not accounts_list: return if len(accounts_list) > 1: diff --git a/gajim/gtkgui_helpers.py b/gajim/gtkgui_helpers.py index 599b6d92d..6bd46111e 100644 --- a/gajim/gtkgui_helpers.py +++ b/gajim/gtkgui_helpers.py @@ -107,15 +107,16 @@ def add_image_to_button(button, icon_name): button.set_image(img) def get_image_button(icon_name, tooltip, toggle=False): - icon = get_icon_pixmap(icon_name) - image = Gtk.Image() - image.set_from_pixbuf(icon) if toggle: button = Gtk.ToggleButton() + icon = get_icon_pixmap(icon_name) + image = Gtk.Image() + image.set_from_pixbuf(icon) + button.set_image(image) else: - button = Gtk.Button() + button = Gtk.Button.new_from_icon_name( + icon_name, Gtk.IconSize.MENU) button.set_tooltip_text(_(tooltip)) - button.set_image(image) return button GUI_DIR = os.path.join(app.DATA_DIR, 'gui') diff --git a/gajim/options_dialog.py b/gajim/options_dialog.py new file mode 100644 index 000000000..b1f74ab5e --- /dev/null +++ b/gajim/options_dialog.py @@ -0,0 +1,585 @@ +from gi.repository import Gtk, GLib, Gdk, GObject +from gajim.common import app +from gajim.common import passwords +from gajim import gtkgui_helpers +from gajim.common.const import OptionKind, OptionType +from gajim.common.exceptions import GajimGeneralException +from gajim import dialogs + + +class OptionsDialog(Gtk.ApplicationWindow): + def __init__(self, parent, title, flags, options, account): + Gtk.ApplicationWindow.__init__(self) + self.set_application(app.app) + self.set_show_menubar(False) + self.set_title(title) + self.set_transient_for(parent) + self.set_resizable(False) + self.set_default_size(250, -1) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + self.account = account + if flags == Gtk.DialogFlags.MODAL: + self.set_modal(True) + elif flags == Gtk.DialogFlags.DESTROY_WITH_PARENT: + self.set_destroy_with_parent(True) + + self.listbox = OptionsBox(account) + self.listbox.set_hexpand(True) + self.listbox.set_selection_mode(Gtk.SelectionMode.NONE) + + for option in options: + self.listbox.add_option(option) + self.listbox.update_states() + + self.add(self.listbox) + + self.show_all() + self.listbox.connect('row-activated', self.on_row_activated) + + @staticmethod + def on_row_activated(listbox, row): + row.get_child().on_row_activated() + + def get_option(self, name): + return self.listbox.get_option(name) + + +class OptionsBox(Gtk.ListBox): + def __init__(self, account): + Gtk.ListBox.__init__(self) + self.set_name('OptionsBox') + self.account = account + self.named_options = {} + + self.map = { + OptionKind.SWITCH: SwitchOption, + OptionKind.SPIN: SpinOption, + OptionKind.DIALOG: DialogOption, + OptionKind.ENTRY: EntryOption, + OptionKind.ACTION: ActionOption, + OptionKind.LOGIN: LoginOption, + OptionKind.FILECHOOSER: FileChooserOption, + OptionKind.CALLBACK: CallbackOption, + OptionKind.PROXY: ProxyComboOption, + OptionKind.PRIORITY: PriorityOption, + OptionKind.HOSTNAME: CutstomHostnameOption, + OptionKind.CHANGEPASSWORD: ChangePasswordOption, + OptionKind.GPG: GPGOption, + } + + def add_option(self, option): + if option.props is not None: + listitem = self.map[option.kind]( + self.account, *option[1:-1], **option.props) + else: + listitem = self.map[option.kind](self.account, *option[1:-1]) + listitem.connect('notify::option-value', self.on_option_changed) + if option.name is not None: + self.named_options[option.name] = listitem + self.add(listitem) + + def get_option(self, name): + return self.named_options[name] + + def update_states(self): + values = [] + values.append((None, None)) + for row in self.get_children(): + name = row.get_child().name + if name is None: + continue + value = row.get_child().get_property('option-value') + values.append((name, value)) + + for name, value in values: + for row in self.get_children(): + row.get_child().set_activatable(name, value) + + def on_option_changed(self, widget, *args): + value = widget.get_property('option-value') + for row in self.get_children(): + row.get_child().set_activatable(widget.name, value) + + +class GenericOption(Gtk.Grid): + def __init__(self, account, label, type_, value, + name, callback, data, desc, enabledif): + Gtk.Grid.__init__(self) + self.set_column_spacing(12) + self.set_size_request(-1, 25) + self.callback = callback + self.type_ = type_ + self.value = value + self.data = data + self.label = label + self.account = account + self.name = name + self.enabledif = enabledif + self.option_value = self.get_value() + + description_box = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=0) + description_box.set_valign(Gtk.Align.CENTER) + + optiontext = Gtk.Label(label=label) + optiontext.set_hexpand(True) + optiontext.set_halign(Gtk.Align.START) + optiontext.set_valign(Gtk.Align.CENTER) + optiontext.set_vexpand(True) + description_box.add(optiontext) + + if desc is not None: + description = Gtk.Label(label=desc) + description.set_name('SubDescription') + description.set_hexpand(True) + description.set_halign(Gtk.Align.START) + description.set_valign(Gtk.Align.CENTER) + description_box.add(description) + + self.add(description_box) + + self.option_box = Gtk.Box(spacing=6) + self.option_box.set_size_request(200, -1) + self.option_box.set_valign(Gtk.Align.CENTER) + self.option_box.set_name('GenericOptionBox') + self.add(self.option_box) + + def do_get_property(self, prop): + if prop.name == 'option-value': + return self.option_value + else: + raise AttributeError('unknown property %s' % prop.name) + + def do_set_property(self, prop, value): + if prop.name == 'option-value': + self.option_value = value + else: + raise AttributeError('unknown property %s' % prop.name) + + def get_value(self): + return self.__get_value(self.type_, self.value, self.account) + + @staticmethod + def __get_value(type_, value, account): + if value is None: + return + if type_ == OptionType.BOOL: + return value + elif type_ == OptionType.CONFIG: + return app.config.get(value) + elif type_ == OptionType.ACCOUNT_CONFIG: + if value == 'password': + return passwords.get_password(account) + elif value == 'no_log_for': + no_log = app.config.get_per( + 'accounts', account, 'no_log_for').split() + return account not in no_log + else: + return app.config.get_per('accounts', account, value) + elif type_ == OptionType.ACTION: + if value.startswith('-'): + return account + value + return value + else: + raise ValueError('Wrong OptionType?') + + def set_value(self, state): + if self.type_ == OptionType.CONFIG: + app.config.set(self.value, state) + if self.type_ == OptionType.ACCOUNT_CONFIG: + if self.value == 'password': + passwords.save_password(self.account, state) + if self.value == 'no_log_for': + self.set_no_log_for(self.account, state) + else: + app.config.set_per('accounts', self.account, self.value, state) + + if self.callback is not None: + self.callback(state, self.data) + + self.set_property('option-value', state) + + @staticmethod + def set_no_log_for(account, jid): + no_log = app.config.get_per('accounts', account, 'no_log_for').split() + if jid and account in no_log: + no_log.remove(account) + elif not jid and account not in no_log: + no_log.append(account) + app.config.set_per('accounts', account, 'no_log_for', ' '.join(no_log)) + + def on_row_activated(self): + raise NotImplementedError + + def set_activatable(self, name, value): + if self.enabledif is None or self.enabledif[0] != name: + return + activatable = (name, value) == self.enabledif + self.get_parent().set_activatable(activatable) + self.set_sensitive(activatable) + + +class SwitchOption(GenericOption): + + __gproperties__ = { + "option-value": (bool, 'Switch Value', '', False, + GObject.ParamFlags.READWRITE),} + + def __init__(self, *args): + GenericOption.__init__(self, *args) + + self.switch = Gtk.Switch() + self.switch.set_active(self.option_value) + self.switch.connect('notify::active', self.on_switch) + self.switch.set_hexpand(True) + self.switch.set_halign(Gtk.Align.END) + self.switch.set_valign(Gtk.Align.CENTER) + + self.option_box.add(self.switch) + + self.show_all() + + def on_row_activated(self): + state = self.switch.get_active() + self.switch.set_active(not state) + + def on_switch(self, switch, *args): + value = switch.get_active() + self.set_value(value) + + +class EntryOption(GenericOption): + + __gproperties__ = { + "option-value": (str, 'Entry Value', '', '', + GObject.ParamFlags.READWRITE),} + + def __init__(self, *args): + GenericOption.__init__(self, *args) + + self.entry = Gtk.Entry() + self.entry.set_text(str(self.option_value)) + self.entry.connect('notify::text', self.on_text_change) + self.entry.set_valign(Gtk.Align.CENTER) + + if self.value == 'password': + self.entry.set_invisible_char('*') + self.entry.set_visibility(False) + + self.option_box.pack_end(self.entry, True, True, 0) + + self.show_all() + + def on_text_change(self, *args): + text = self.entry.get_text() + self.set_value(text) + + def on_row_activated(self): + self.entry.grab_focus() + + +class DialogOption(GenericOption): + + __gproperties__ = { + "option-value": (str, 'Dummy', '', '', + GObject.ParamFlags.READWRITE),} + + def __init__(self, *args, dialog): + GenericOption.__init__(self, *args) + self.dialog = dialog + + self.option_value = Gtk.Label() + self.option_value.set_text(self.get_option_value()) + self.option_value.set_halign(Gtk.Align.END) + self.option_box.pack_start(self.option_value, True, True, 0) + + self.show_all() + + def show_dialog(self, parent): + if self.dialog: + dialog = self.dialog(self.account, parent) + dialog.connect('destroy', self.on_destroy) + + def on_destroy(self, *args): + self.option_value.set_text(self.get_option_value()) + + def get_option_value(self): + self.option_value.hide() + return '' + + def on_row_activated(self): + self.show_dialog(self.get_toplevel()) + + +class SpinOption(GenericOption): + + __gproperties__ = { + "option-value": (int, 'Priority', '', -128, 127, 0, + GObject.ParamFlags.READWRITE),} + + def __init__(self, *args, range_): + GenericOption.__init__(self, *args) + + lower, upper = range_ + adjustment = Gtk.Adjustment(0, lower, upper, 1, 10, 0) + + self.spin = Gtk.SpinButton() + self.spin.set_adjustment(adjustment) + self.spin.set_numeric(True) + self.spin.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID) + self.spin.set_value(self.option_value) + self.spin.set_halign(Gtk.Align.END) + self.spin.set_valign(Gtk.Align.CENTER) + self.spin.connect('notify::value', self.on_value_change) + + self.option_box.pack_start(self.spin, True, True, 0) + + self.show_all() + + def on_row_activated(self): + self.spin.grab_focus() + + def on_value_change(self, spin, *args): + value = spin.get_value_as_int() + self.set_value(value) + + +class FileChooserOption(GenericOption): + + __gproperties__ = { + "option-value": (str, 'Certificate Path', '', '', + GObject.ParamFlags.READWRITE),} + + def __init__(self, *args, filefilter): + GenericOption.__init__(self, *args) + + button = Gtk.FileChooserButton(self.label, Gtk.FileChooserAction.OPEN) + button.set_halign(Gtk.Align.END) + + # GTK Bug: The FileChooserButton expands without limit + # get the label and use set_max_wide_chars() + label = button.get_children()[0].get_children()[0].get_children()[1] + label.set_max_width_chars(20) + + if filefilter: + name, pattern = filefilter + filter_ = Gtk.FileFilter() + filter_.set_name(name) + filter_.add_pattern(pattern) + button.add_filter(filter_) + button.set_filter(filter_) + + filter_ = Gtk.FileFilter() + filter_.set_name(_('All files')) + filter_.add_pattern('*') + button.add_filter(filter_) + + if self.option_value: + button.set_filename(self.option_value) + button.connect('selection-changed', self.on_select) + + clear_button = gtkgui_helpers.get_image_button( + 'edit-clear-all-symbolic', 'Clear File') + clear_button.connect('clicked', lambda *args: button.unselect_all()) + self.option_box.pack_start(button, True, True, 0) + self.option_box.pack_start(clear_button, False, False, 0) + + self.show_all() + + def on_select(self, filechooser): + self.set_value(filechooser.get_filename() or '') + + def on_row_activated(self): + pass + + +class CallbackOption(GenericOption): + + __gproperties__ = { + "option-value": (str, 'Dummy', '', '', + GObject.ParamFlags.READWRITE),} + + def __init__(self, *args, callback): + GenericOption.__init__(self, *args) + self.callback = callback + self.show_all() + + def on_row_activated(self): + self.callback() + + +class ActionOption(GenericOption): + + __gproperties__ = { + "option-value": (str, 'Dummy', '', '', + GObject.ParamFlags.READWRITE),} + + def __init__(self, *args, action_args): + GenericOption.__init__(self, *args) + self.action = gtkgui_helpers.get_action(self.option_value) + self.variant = GLib.Variant.new_string(action_args) + self.on_enable() + + self.show_all() + self.action.connect('notify::enabled', self.on_enable) + + def on_enable(self, *args): + self.set_sensitive(self.action.get_enabled()) + + def on_row_activated(self): + self.action.activate(self.variant) + + +class LoginOption(DialogOption): + def __init__(self, *args, **kwargs): + DialogOption.__init__(self, *args, **kwargs) + self.option_value.set_selectable(True) + + def get_option_value(self): + jid = app.get_jid_from_account(self.account) + return jid + + def set_activatable(self, name, value): + DialogOption.set_activatable(self, name, value) + anonym = app.config.get_per('accounts', self.account, 'anonymous_auth') + self.get_parent().set_activatable(not anonym) + + +class ProxyComboOption(GenericOption): + + __gproperties__ = { + "option-value": (str, 'Proxy', '', '', + GObject.ParamFlags.READWRITE),} + + def __init__(self, *args): + GenericOption.__init__(self, *args) + + self.combo = Gtk.ComboBoxText() + self.update_values() + + self.combo.connect('changed', self.on_value_change) + self.combo.set_valign(Gtk.Align.CENTER) + + button = gtkgui_helpers.get_image_button( + 'preferences-system-symbolic', 'Manage Proxies') + button.set_action_name('app.manage-proxies') + button.set_valign(Gtk.Align.CENTER) + + self.option_box.pack_start(self.combo, True, True, 0) + self.option_box.pack_start(button, False, True, 0) + self.show_all() + + def update_values(self): + proxies = app.config.get_per('proxies') + proxies.insert(0, _('None')) + self.combo.remove_all() + for index, value in enumerate(proxies): + self.combo.insert_text(-1, value) + if value == self.option_value or index == 0: + self.combo.set_active(index) + + def on_value_change(self, combo): + self.set_value(combo.get_active_text()) + + def on_row_activated(self): + pass + + +class PriorityOption(DialogOption): + def __init__(self, *args, **kwargs): + DialogOption.__init__(self, *args, **kwargs) + + def get_option_value(self): + adjust = app.config.get_per( + 'accounts', self.account, 'adjust_priority_with_status') + if adjust: + return _('Adjust to Status') + + priority = app.config.get_per('accounts', self.account, 'priority') + return str(priority) + + +class CutstomHostnameOption(DialogOption): + def __init__(self, *args, **kwargs): + DialogOption.__init__(self, *args, **kwargs) + + def get_option_value(self): + custom = app.config.get_per('accounts', self.account, 'use_custom_host') + return _('On') if custom else _('Off') + + +class ChangePasswordOption(DialogOption): + def __init__(self, *args, **kwargs): + DialogOption.__init__(self, *args, **kwargs) + + def show_dialog(self, parent): + try: + self.change_dialog = dialogs.ChangePasswordDialog( + self.account, self.on_changed, parent) + except GajimGeneralException: + return + self.change_dialog.dialog.set_modal(True) + + def on_changed(self, new_password): + if new_password is not None: + app.connections[self.account].change_password(new_password) + self.set_value(new_password) + + def set_activatable(self, name, value): + activatable = False + if self.account in app.connections: + con = app.connections[self.account] + activatable = con.connected >= 2 and con.register_supported + self.get_parent().set_activatable(activatable) + + +class GPGOption(DialogOption): + def __init__(self, *args, **kwargs): + DialogOption.__init__(self, *args, **kwargs) + + def show_dialog(self, parent): + secret_keys = app.connections[self.account].ask_gpg_secrete_keys() + secret_keys[_('None')] = _('None') + + if not secret_keys: + dialogs.ErrorDialog( + _('Failed to get secret keys'), + _('There is no OpenPGP secret key available.'), + transient_for=parent) + return + + dialog = dialogs.ChooseGPGKeyDialog( + _('OpenPGP Key Selection'), _('Choose your OpenPGP key'), + secret_keys, self.on_key_selected, transient_for=parent) + dialog.window.connect('destroy', self.on_destroy) + + def on_key_selected(self, keyID): + if keyID is None: + return + keyid_new, keyname_new = keyID + + keyid = app.config.get_per('accounts', self.account, 'keyid') + + if keyid_new == _('None'): + if keyid == '': + return + app.config.set_per('accounts', self.account, 'keyname', '') + app.config.set_per('accounts', self.account, 'keyid', '') + else: + if keyid == keyid_new: + return + app.config.set_per( + 'accounts', self.account, 'keyname', keyname_new) + app.config.set_per( + 'accounts', self.account, 'keyid', keyid_new) + + def get_option_value(self): + keyid = app.config.get_per('accounts', self.account, 'keyid') + keyname = app.config.get_per('accounts', self.account, 'keyname') + if keyid is not None: + return '\n'.join((keyid, keyname)) + return '' + + def set_activatable(self, name, value): + active = self.account in app.connections + self.get_parent().set_activatable(app.HAVE_GPG and active) diff --git a/gajim/roster_window.py b/gajim/roster_window.py index e07a7f354..b7f70cf5e 100644 --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -1052,7 +1052,8 @@ class RosterWindow: account_name = _('Merged accounts') accounts = [] else: - account_name = account + acclabel = app.config.get_per('accounts', account, 'account_label') + account_name = acclabel or account accounts = [account] if account in self.collapsed_rows and \ @@ -3229,7 +3230,7 @@ class RosterWindow: def on_edit_account(self, widget, account): if 'accounts' in app.interface.instances: - app.interface.instances['accounts'].window.present() + app.interface.instances['accounts'].present() else: app.interface.instances['accounts'] = config.AccountsWindow() app.interface.instances['accounts'].select_account(account) From 539c1969db63a284916d8005f42ef881d58967d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 16 Sep 2017 15:27:31 +0200 Subject: [PATCH 2/2] Remove old AccountsWindow --- gajim/config.py | 1222 +---------------------------------------------- 1 file changed, 4 insertions(+), 1218 deletions(-) diff --git a/gajim/config.py b/gajim/config.py index 1245deabf..f1d743dd1 100644 --- a/gajim/config.py +++ b/gajim/config.py @@ -31,12 +31,14 @@ ## along with Gajim. If not, see . ## +import os + from gi.repository import Gtk from gi.repository import Gdk from gi.repository import Pango from gi.repository import GObject from gi.repository import GLib -import os + from gajim.common import config as c_config from gajim.common import sleepy from gajim.common.i18n import Q_ @@ -47,7 +49,6 @@ from gajim import cell_renderer_image from gajim import message_control from gajim.chat_control_base import ChatControlBase from gajim import dataforms_widget -from gajim import profile_window from gajim import gui_menu_builder try: @@ -59,11 +60,9 @@ except (ImportError, ValueError): from gajim.common import helpers from gajim.common import app from gajim.common import connection -from gajim.common import passwords -from gajim.common.zeroconf import connection_zeroconf from gajim.common import dataforms -from gajim.common import gpg from gajim.common import ged +from gajim.accounts_window import AccountsWindow try: from gajim.common.multimedia_helpers import AudioInputManager, AudioOutputManager @@ -72,9 +71,6 @@ try: except (ImportError, ValueError): HAS_GST = False -from gajim.common.exceptions import GajimGeneralException -from gajim.common.connection_handlers_events import InformationEvent - #---------- PreferencesWindow class -------------# class PreferencesWindow: """ @@ -1514,1216 +1510,6 @@ class ManageProxiesWindow: app.config.set_per('proxies', proxy, 'pass', value) -#---------- AccountsWindow class -------------# -class AccountsWindow: - """ - Class for accounts window: list of accounts - """ - - def on_accounts_window_destroy(self, widget): - del app.interface.instances['accounts'] - - def on_close_button_clicked(self, widget): - self.check_resend_relog() - self.window.destroy() - - def __init__(self): - self.xml = gtkgui_helpers.get_gtk_builder('accounts_window.ui') - self.window = self.xml.get_object('accounts_window') - self.window.set_transient_for(app.interface.roster.window) - self.accounts_treeview = self.xml.get_object('accounts_treeview') - self.remove_button = self.xml.get_object('remove_button') - self.rename_button = self.xml.get_object('rename_button') - self.change_password_button = self.xml.get_object('change_password_button1') - path_to_kbd_input_img = gtkgui_helpers.get_icon_path('gajim-kbd_input') - img = self.xml.get_object('rename_image') - img.set_from_file(path_to_kbd_input_img) - self.notebook = self.xml.get_object('notebook') - # Name - model = Gtk.ListStore(str) - self.accounts_treeview.set_model(model) - # column - renderer = Gtk.CellRendererText() - col = Gtk.TreeViewColumn() - col.set_title(_('Name')) - col.pack_start(renderer, False) - col.add_attribute(renderer, 'text', 0) - self.accounts_treeview.insert_column(col, -1) - - self.current_account = None - # When we fill info, we don't want to handle the changed signals - self.ignore_events = False - self.need_relogin = False - self.resend_presence = False - - self.update_proxy_list() - self.xml.connect_signals(self) - self.init_accounts() - self.window.show_all() - - # Merge accounts - st = app.config.get('mergeaccounts') - checkbutton = self.xml.get_object('merge_checkbutton') - checkbutton.set_active(st) - # prevent roster redraws by connecting the signal after button state is - # set - checkbutton.connect('toggled', self.on_merge_checkbutton_toggled) - - self.avahi_available = True - try: - import avahi - except ImportError: - self.avahi_available = False - - self.xml.get_object('close_button').grab_focus() - - def on_accounts_window_key_press_event(self, widget, event): - if event.keyval == Gdk.KEY_Escape: - self.check_resend_relog() - self.window.destroy() - - def select_account(self, account): - model = self.accounts_treeview.get_model() - iter_ = model.get_iter_first() - while iter_: - acct = model[iter_][0] - if account == acct: - self.accounts_treeview.set_cursor(model.get_path(iter_)) - return - iter_ = model.iter_next(iter_) - - def init_accounts(self): - """ - Initialize listStore with existing accounts - """ - self.remove_button.set_sensitive(False) - self.rename_button.set_sensitive(False) - self.change_password_button.set_sensitive(False) - self.current_account = None - model = self.accounts_treeview.get_model() - model.clear() - list_ = app.config.get_per('accounts') - list_.sort() - for account in list_: - iter_ = model.append() - model.set(iter_, 0, account) - - self.selection = self.accounts_treeview.get_selection() - self.selection.select_iter(model.get_iter_first()) - - def resend(self, account): - if not account in app.connections: - return - show = app.SHOW_LIST[app.connections[account].connected] - status = app.connections[account].status - app.connections[account].change_status(show, status) - - def check_resend_relog(self): - if self.need_relogin and self.current_account == \ - app.ZEROCONF_ACC_NAME: - if app.ZEROCONF_ACC_NAME in app.connections: - app.connections[app.ZEROCONF_ACC_NAME].update_details() - return - - elif self.need_relogin and self.current_account and \ - app.connections[self.current_account].connected > 0: - def login(account, show_before, status_before): - """ - Login with previous status - """ - # first make sure connection is really closed, - # 0.5 may not be enough - app.connections[account].disconnect(True) - app.interface.roster.send_status(account, show_before, - status_before) - - def relog(account): - self.dialog.destroy() - show_before = app.SHOW_LIST[app.connections[account].\ - connected] - status_before = app.connections[account].status - app.interface.roster.send_status(account, 'offline', - _('Be right back.')) - GLib.timeout_add(500, login, account, show_before, - status_before) - - def on_yes(checked, account): - relog(account) - def on_no(account): - if self.resend_presence: - self.resend(account) - if self.current_account in app.connections: - self.dialog = dialogs.YesNoDialog(_('Relogin now?'), - _('If you want all the changes to apply instantly, ' - 'you must relogin.'), on_response_yes=(on_yes, - self.current_account), on_response_no=(on_no, - self.current_account)) - elif self.resend_presence: - self.resend(self.current_account) - - self.need_relogin = False - self.resend_presence = False - - def on_accounts_treeview_cursor_changed(self, widget): - """ - Activate modify buttons when a row is selected, update accounts info - """ - sel = self.accounts_treeview.get_selection() - if sel: - (model, iter_) = sel.get_selected() - if iter_: - account = model[iter_][0] - else: - account = None - else: - iter_ = account = None - if self.current_account and self.current_account == account: - # We're comming back to our current account, no need to update - # widgets - return - # Save config for previous account if needed cause focus_out event is - # called after the changed event - if self.current_account and self.window.get_focus(): - focused_widget = self.window.get_focus() - focused_widget_name = focused_widget.get_name() - if focused_widget_name in ('jid_entry1', 'resource_entry1', - 'custom_port_entry', 'cert_entry1'): - if focused_widget_name == 'jid_entry1': - func = self.on_jid_entry1_focus_out_event - elif focused_widget_name == 'resource_entry1': - func = self.on_resource_entry1_focus_out_event - elif focused_widget_name == 'custom_port_entry': - func = self.on_custom_port_entry_focus_out_event - elif focused_widget_name == 'cert_entry1': - func = self.on_cert_entry1_focus_out_event - if func(focused_widget, None): - # Error detected in entry, don't change account, - # re-put cursor on previous row - self.select_account(self.current_account) - return True - self.window.set_focus(widget) - - self.check_resend_relog() - - if account: - self.remove_button.set_sensitive(True) - self.rename_button.set_sensitive(True) - - if (account != app.ZEROCONF_ACC_NAME and - account in app.connections): - self.change_password_button.set_sensitive( - app.connections[account].register_supported) - - else: - self.remove_button.set_sensitive(False) - self.rename_button.set_sensitive(False) - self.change_password_button.set_sensitive(False) - if iter_: - self.current_account = account - if account == app.ZEROCONF_ACC_NAME: - self.remove_button.set_sensitive(False) - self.init_account() - self.update_proxy_list() - - def on_browse_for_client_cert_button_clicked(self, widget, data=None): - def on_ok(widget, path_to_clientcert_file): - self.dialog.destroy() - if not path_to_clientcert_file: - return - self.xml.get_object('cert_entry1').set_text(path_to_clientcert_file) - app.config.set_per('accounts', self.current_account, - 'client_cert', path_to_clientcert_file) - - def on_cancel(widget): - self.dialog.destroy() - - path_to_clientcert_file = self.xml.get_object('cert_entry1').get_text() - self.dialog = dialogs.ClientCertChooserDialog(path_to_clientcert_file, - on_ok, on_cancel) - - def update_proxy_list(self): - if self.current_account: - our_proxy = app.config.get_per('accounts', self.current_account, - 'proxy') - else: - our_proxy = '' - - if not our_proxy: - our_proxy = _('None') - proxy_combobox = self.xml.get_object('proxies_combobox1') - model = Gtk.ListStore(str) - proxy_combobox.set_model(model) - l = app.config.get_per('proxies') - l.insert(0, _('None')) - for i in range(len(l)): - model.append([l[i]]) - if our_proxy == l[i]: - proxy_combobox.set_active(i) - - def init_account(self): - if not self.current_account: - self.notebook.set_current_page(0) - return - if app.config.get_per('accounts', self.current_account, - 'is_zeroconf'): - self.ignore_events = True - self.init_zeroconf_account() - self.ignore_events = False - self.notebook.set_current_page(2) - return - self.ignore_events = True - self.init_normal_account() - self.ignore_events = False - self.notebook.set_current_page(1) - - def init_zeroconf_account(self): - active = app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, - 'active') - self.xml.get_object('enable_zeroconf_checkbutton2').set_active(active) - if not app.HAVE_ZEROCONF: - self.xml.get_object('enable_zeroconf_checkbutton2').set_sensitive( - False) - self.xml.get_object('zeroconf_notebook').set_sensitive(active) - # General tab - st = app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, - 'autoconnect') - self.xml.get_object('autoconnect_checkbutton2').set_active(st) - - list_no_log_for = app.config.get_per('accounts', - app.ZEROCONF_ACC_NAME, 'no_log_for').split() - if app.ZEROCONF_ACC_NAME in list_no_log_for: - self.xml.get_object('log_history_checkbutton2').set_active(0) - else: - self.xml.get_object('log_history_checkbutton2').set_active(1) - - st = app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, - 'sync_with_global_status') - self.xml.get_object('sync_with_global_status_checkbutton2').set_active( - st) - - st = app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, - 'use_custom_host') - self.xml.get_object('custom_port_checkbutton2').set_active(st) - self.xml.get_object('custom_port_entry2').set_sensitive(st) - - st = app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, - 'custom_port') - if not st: - app.config.set_per('accounts', app.ZEROCONF_ACC_NAME, - 'custom_port', '5298') - st = '5298' - self.xml.get_object('custom_port_entry2').set_text(str(st)) - - # Personal tab - gpg_key_label = self.xml.get_object('gpg_key_label2') - if app.ZEROCONF_ACC_NAME in app.connections and \ - app.connections[app.ZEROCONF_ACC_NAME].gpg: - self.xml.get_object('gpg_choose_button2').set_sensitive(True) - self.init_account_gpg() - else: - gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) - self.xml.get_object('gpg_choose_button2').set_sensitive(False) - - for opt in ('first_name', 'last_name', 'jabber_id', 'email'): - st = app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, - 'zeroconf_' + opt) - self.xml.get_object(opt + '_entry2').set_text(st) - - def init_account_gpg(self): - account = self.current_account - keyid = app.config.get_per('accounts', account, 'keyid') - keyname = app.config.get_per('accounts', account, 'keyname') - use_gpg_agent = app.config.get('use_gpg_agent') - - if account == app.ZEROCONF_ACC_NAME: - widget_name_add = '2' - else: - widget_name_add = '1' - - gpg_key_label = self.xml.get_object('gpg_key_label' + widget_name_add) - gpg_name_label = self.xml.get_object('gpg_name_label' + widget_name_add) - use_gpg_agent_checkbutton = self.xml.get_object( - 'use_gpg_agent_checkbutton' + widget_name_add) - - if not keyid: - use_gpg_agent_checkbutton.set_sensitive(False) - gpg_key_label.set_text(_('No key selected')) - gpg_name_label.set_text('') - return - - gpg_key_label.set_text(keyid) - gpg_name_label.set_text(keyname) - use_gpg_agent_checkbutton.set_sensitive(True) - use_gpg_agent_checkbutton.set_active(use_gpg_agent) - - def draw_normal_jid(self): - account = self.current_account - self.ignore_events = True - active = app.config.get_per('accounts', account, 'active') - self.xml.get_object('enable_checkbutton1').set_active(active) - self.xml.get_object('normal_notebook1').set_sensitive(active) - if app.config.get_per('accounts', account, 'anonymous_auth'): - self.xml.get_object('anonymous_checkbutton1').set_active(True) - self.xml.get_object('jid_label1').set_text(_('Server:')) - save_password = self.xml.get_object('save_password_checkbutton1') - save_password.set_active(False) - save_password.set_sensitive(False) - password_entry = self.xml.get_object('password_entry1') - password_entry.set_text('') - password_entry.set_sensitive(False) - jid = app.config.get_per('accounts', account, 'hostname') - else: - self.xml.get_object('anonymous_checkbutton1').set_active(False) - self.xml.get_object('jid_label1').set_text(_('JID:')) - savepass = app.config.get_per('accounts', account, 'savepass') - save_password = self.xml.get_object('save_password_checkbutton1') - save_password.set_sensitive(True) - save_password.set_active(savepass) - password_entry = self.xml.get_object('password_entry1') - if savepass: - passstr = passwords.get_password(account) or '' - password_entry.set_sensitive(True) - else: - passstr = '' - password_entry.set_sensitive(False) - password_entry.set_text(passstr) - - jid = app.config.get_per('accounts', account, 'name') \ - + '@' + app.config.get_per('accounts', account, 'hostname') - self.xml.get_object('jid_entry1').set_text(jid) - self.ignore_events = False - - def init_normal_account(self): - account = self.current_account - # Account tab - self.draw_normal_jid() - self.xml.get_object('resource_entry1').set_text(app.config.get_per( - 'accounts', account, 'resource')) - - client_cert = app.config.get_per('accounts', account, 'client_cert') - self.xml.get_object('cert_entry1').set_text(client_cert) - client_cert_encrypted = app.config.get_per('accounts', account, - 'client_cert_encrypted') - self.xml.get_object('client_cert_encrypted_checkbutton1').\ - set_active(client_cert_encrypted) - - self.xml.get_object('adjust_priority_with_status_checkbutton1').\ - set_active(app.config.get_per('accounts', account, - 'adjust_priority_with_status')) - spinbutton = self.xml.get_object('priority_spinbutton1') - if app.config.get('enable_negative_priority'): - spinbutton.set_range(-128, 127) - else: - spinbutton.set_range(0, 127) - spinbutton.set_value(app.config.get_per('accounts', account, - 'priority')) - - # Connection tab - use_env_http_proxy = app.config.get_per('accounts', account, - 'use_env_http_proxy') - self.xml.get_object('use_env_http_proxy_checkbutton1').set_active( - use_env_http_proxy) - self.xml.get_object('proxy_hbox1').set_sensitive(not use_env_http_proxy) - - warn_when_insecure_ssl = app.config.get_per('accounts', account, - 'warn_when_insecure_ssl_connection') - self.xml.get_object('warn_when_insecure_connection_checkbutton1').\ - set_active(warn_when_insecure_ssl) - - self.xml.get_object('send_keepalive_checkbutton1').set_active( - app.config.get_per('accounts', account, 'keep_alives_enabled')) - - use_custom_host = app.config.get_per('accounts', account, - 'use_custom_host') - self.xml.get_object('custom_host_port_checkbutton1').set_active( - use_custom_host) - custom_host = app.config.get_per('accounts', account, 'custom_host') - if not custom_host: - custom_host = app.config.get_per('accounts', account, 'hostname') - app.config.set_per('accounts', account, 'custom_host', - custom_host) - self.xml.get_object('custom_host_entry1').set_text(custom_host) - custom_port = app.config.get_per('accounts', account, 'custom_port') - if not custom_port: - custom_port = 5222 - app.config.set_per('accounts', account, 'custom_port', - custom_port) - self.xml.get_object('custom_port_entry1').set_text(str(custom_port)) - - # Personal tab - gpg_key_label = self.xml.get_object('gpg_key_label1') - if app.HAVE_GPG: - self.xml.get_object('gpg_choose_button1').set_sensitive(True) - self.init_account_gpg() - else: - gpg_key_label.set_text(_('OpenPGP is not usable on this computer')) - self.xml.get_object('gpg_choose_button1').set_sensitive(False) - - # General tab - self.xml.get_object('autoconnect_checkbutton1').set_active( - app.config.get_per('accounts', account, 'autoconnect')) - self.xml.get_object('autoreconnect_checkbutton1').set_active(app. - config.get_per('accounts', account, 'autoreconnect')) - - list_no_log_for = app.config.get_per('accounts', account, - 'no_log_for').split() - if account in list_no_log_for: - self.xml.get_object('log_history_checkbutton1').set_active(False) - else: - self.xml.get_object('log_history_checkbutton1').set_active(True) - - self.xml.get_object('sync_logs_with_server_checkbutton1').set_active( - app.config.get_per('accounts', account, 'sync_logs_with_server')) - self.xml.get_object('sync_with_global_status_checkbutton1').set_active( - app.config.get_per('accounts', account, - 'sync_with_global_status')) - self.xml.get_object('carbons_checkbutton1').set_active( - app.config.get_per('accounts', account, 'enable_message_carbons')) - self.xml.get_object('use_ft_proxies_checkbutton1').set_active( - app.config.get_per('accounts', account, 'use_ft_proxies')) - - def on_add_button_clicked(self, widget): - """ - When add button is clicked: open an account information window - """ - if 'account_creation_wizard' in app.interface.instances: - app.interface.instances['account_creation_wizard'].window.present() - else: - app.interface.instances['account_creation_wizard'] = \ - AccountCreationWizardWindow() - - def on_remove_button_clicked(self, widget): - """ - When delete button is clicked: Remove an account from the listStore and - from the config file - """ - if not self.current_account: - return - account = self.current_account - if len(app.events.get_events(account)): - dialogs.ErrorDialog(_('Unread events'), - _('Read all pending events before removing this account.'), - transient_for=self.window) - return - - if app.config.get_per('accounts', account, 'is_zeroconf'): - # Should never happen as button is insensitive - return - - win_opened = False - if app.interface.msg_win_mgr.get_controls(acct=account): - win_opened = True - elif account in app.interface.instances: - for key in app.interface.instances[account]: - if app.interface.instances[account][key] and key != \ - 'remove_account': - win_opened = True - break - # Detect if we have opened windows for this account - def remove(account): - if account in app.interface.instances and \ - 'remove_account' in app.interface.instances[account]: - app.interface.instances[account]['remove_account'].window.\ - present() - else: - if not account in app.interface.instances: - app.interface.instances[account] = {} - app.interface.instances[account]['remove_account'] = \ - RemoveAccountWindow(account) - if win_opened: - dialogs.ConfirmationDialog( - _('You have opened chat in account %s') % account, - _('All chat and groupchat windows will be closed. Do you want to ' - 'continue?'), - on_response_ok = (remove, account)) - else: - remove(account) - - def on_rename_button_clicked(self, widget): - if not self.current_account: - return - active = app.config.get_per('accounts', self.current_account, - 'active') and self.current_account in app.connections - if active and app.connections[self.current_account].connected != 0: - dialogs.ErrorDialog( - _('You are currently connected to the server'), - _('To change the account name, you must be disconnected.'), - transient_for=self.window) - return - if len(app.events.get_events(self.current_account)): - dialogs.ErrorDialog(_('Unread events'), - _('To change the account name, you must read all pending ' - 'events.'), transient_for=self.window) - return - # Get the new name - def on_renamed(new_name, old_name): - if new_name in app.connections: - dialogs.ErrorDialog(_('Account Name Already Used'), - _('This name is already used by another of your accounts. ' - 'Please choose another name.'), transient_for=self.window) - return - if (new_name == ''): - dialogs.ErrorDialog(_('Invalid account name'), - _('Account name cannot be empty.'), - transient_for=self.window) - return - if new_name.find(' ') != -1: - dialogs.ErrorDialog(_('Invalid account name'), - _('Account name cannot contain spaces.'), - transient_for=self.window) - return - if active: - # update variables - app.interface.instances[new_name] = app.interface.instances[ - old_name] - app.interface.minimized_controls[new_name] = \ - app.interface.minimized_controls[old_name] - app.nicks[new_name] = app.nicks[old_name] - app.block_signed_in_notifications[new_name] = \ - app.block_signed_in_notifications[old_name] - app.groups[new_name] = app.groups[old_name] - app.gc_connected[new_name] = app.gc_connected[old_name] - app.automatic_rooms[new_name] = app.automatic_rooms[ - old_name] - app.newly_added[new_name] = app.newly_added[old_name] - app.to_be_removed[new_name] = app.to_be_removed[old_name] - app.sleeper_state[new_name] = app.sleeper_state[old_name] - app.encrypted_chats[new_name] = app.encrypted_chats[ - old_name] - app.last_message_time[new_name] = \ - app.last_message_time[old_name] - app.status_before_autoaway[new_name] = \ - app.status_before_autoaway[old_name] - app.transport_avatar[new_name] = app.transport_avatar[old_name] - app.gajim_optional_features[new_name] = \ - app.gajim_optional_features[old_name] - app.caps_hash[new_name] = app.caps_hash[old_name] - - app.contacts.change_account_name(old_name, new_name) - app.events.change_account_name(old_name, new_name) - - # change account variable for chat / gc controls - app.interface.msg_win_mgr.change_account_name(old_name, new_name) - # upgrade account variable in opened windows - for kind in ('infos', 'disco', 'gc_config', 'search', - 'online_dialog', 'sub_request'): - for j in app.interface.instances[new_name][kind]: - app.interface.instances[new_name][kind][j].account = \ - new_name - - # ServiceCache object keep old property account - if hasattr(app.connections[old_name], 'services_cache'): - app.connections[old_name].services_cache.account = \ - new_name - del app.interface.instances[old_name] - del app.interface.minimized_controls[old_name] - del app.nicks[old_name] - del app.block_signed_in_notifications[old_name] - del app.groups[old_name] - del app.gc_connected[old_name] - del app.automatic_rooms[old_name] - del app.newly_added[old_name] - del app.to_be_removed[old_name] - del app.sleeper_state[old_name] - del app.encrypted_chats[old_name] - del app.last_message_time[old_name] - del app.status_before_autoaway[old_name] - del app.transport_avatar[old_name] - del app.gajim_optional_features[old_name] - del app.caps_hash[old_name] - app.connections[old_name].name = new_name - app.connections[old_name].pep_change_account_name(new_name) - app.connections[old_name].caps_change_account_name(new_name) - app.connections[new_name] = app.connections[old_name] - del app.connections[old_name] - app.config.add_per('accounts', new_name) - old_config = app.config.get_per('accounts', old_name) - for opt in old_config: - app.config.set_per('accounts', new_name, opt, old_config[opt]) - app.config.del_per('accounts', old_name) - if self.current_account == old_name: - self.current_account = new_name - if old_name == app.ZEROCONF_ACC_NAME: - app.ZEROCONF_ACC_NAME = new_name - # refresh roster - app.interface.roster.setup_and_draw_roster() - self.init_accounts() - self.select_account(new_name) - app.app.remove_account_actions(old_name) - app.app.add_account_actions(new_name) - gui_menu_builder.build_accounts_menu() - - title = _('Rename Account') - message = _('Enter a new name for account %s') % self.current_account - old_text = self.current_account - dialogs.InputDialog(title, message, old_text, is_modal=False, - ok_handler=(on_renamed, self.current_account), - transient_for=self.window) - - def option_changed(self, option, value): - return app.config.get_per('accounts', self.current_account, option) \ - != value - - def on_jid_entry1_focus_out_event(self, widget, event): - if self.ignore_events: - return - jid = widget.get_text() - # check if jid is conform to RFC and stringprep it - try: - jid = helpers.parse_jid(jid) - except helpers.InvalidFormat as s: - if not widget.is_focus(): - pritext = _('Invalid JID') - dialogs.ErrorDialog(pritext, str(s), transient_for=self.window) - GLib.idle_add(lambda: widget.grab_focus()) - return True - - jid_splited = jid.split('@', 1) - if len(jid_splited) != 2 and not app.config.get_per('accounts', - self.current_account, 'anonymous_auth'): - if not widget.is_focus(): - pritext = _('Invalid JID') - sectext = \ - _('A JID must be in the form "user@servername".') - dialogs.ErrorDialog(pritext, sectext, transient_for=self.window) - GLib.idle_add(lambda: widget.grab_focus()) - return True - - - if app.config.get_per('accounts', self.current_account, - 'anonymous_auth'): - app.config.set_per('accounts', self.current_account, 'hostname', - jid_splited[0]) - if self.option_changed('hostname', jid_splited[0]): - self.need_relogin = True - else: - if self.option_changed('name', jid_splited[0]) or \ - self.option_changed('hostname', jid_splited[1]): - self.need_relogin = True - - app.config.set_per('accounts', self.current_account, 'name', - jid_splited[0]) - app.config.set_per('accounts', self.current_account, 'hostname', - jid_splited[1]) - - def on_cert_entry1_focus_out_event(self, widget, event): - if self.ignore_events: - return - client_cert = widget.get_text() - if self.option_changed('client_cert', client_cert): - self.need_relogin = True - app.config.set_per('accounts', self.current_account, 'client_cert', - client_cert) - - def on_anonymous_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - active = widget.get_active() - app.config.set_per('accounts', self.current_account, 'anonymous_auth', - active) - self.draw_normal_jid() - - def on_password_entry1_changed(self, widget): - if self.ignore_events: - return - passwords.save_password(self.current_account, widget.get_text()) - - def on_save_password_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - active = widget.get_active() - password_entry = self.xml.get_object('password_entry1') - password_entry.set_sensitive(active) - app.config.set_per('accounts', self.current_account, 'savepass', - active) - if active: - password = password_entry.get_text() - passwords.save_password(self.current_account, password) - else: - passwords.save_password(self.current_account, '') - - def on_resource_entry1_focus_out_event(self, widget, event): - if self.ignore_events: - return - resource = self.xml.get_object('resource_entry1').get_text() - try: - resource = helpers.parse_resource(resource) - except helpers.InvalidFormat as s: - if not widget.is_focus(): - pritext = _('Invalid JID') - dialogs.ErrorDialog(pritext, str(s), transient_for=self.window) - GLib.idle_add(lambda: widget.grab_focus()) - return True - - if self.option_changed('resource', resource): - self.need_relogin = True - - app.config.set_per('accounts', self.current_account, 'resource', - resource) - - def on_adjust_priority_with_status_checkbutton1_toggled(self, widget): - self.xml.get_object('priority_spinbutton1').set_sensitive( - not widget.get_active()) - self.on_checkbutton_toggled(widget, 'adjust_priority_with_status', - account = self.current_account) - - def on_priority_spinbutton1_value_changed(self, widget): - prio = widget.get_value_as_int() - - if self.option_changed('priority', prio): - self.resend_presence = True - - app.config.set_per('accounts', self.current_account, 'priority', prio) - - def on_synchronise_contacts_button1_clicked(self, widget): - try: - dialogs.SynchroniseSelectAccountDialog(self.current_account) - except GajimGeneralException: - # If we showed ErrorDialog, there will not be dialog instance - return - - def on_change_password_button1_clicked(self, widget): - def on_changed(new_password): - if new_password is not None: - app.connections[self.current_account].change_password( - new_password) - if self.xml.get_object('save_password_checkbutton1').\ - get_active(): - self.xml.get_object('password_entry1').set_text( - new_password) - - try: - dialogs.ChangePasswordDialog(self.current_account, on_changed, - self.window) - except GajimGeneralException: - # if we showed ErrorDialog, there will not be dialog instance - return - - def on_client_cert_encrypted_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'client_cert_encrypted', - account=self.current_account) - - def on_autoconnect_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'autoconnect', - account=self.current_account) - - def on_autoreconnect_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'autoreconnect', - account=self.current_account) - - def on_log_history_checkbutton_toggled(self, widget): - if self.ignore_events: - return - list_no_log_for = app.config.get_per('accounts', self.current_account, - 'no_log_for').split() - if self.current_account in list_no_log_for: - list_no_log_for.remove(self.current_account) - - if not widget.get_active(): - list_no_log_for.append(self.current_account) - app.config.set_per('accounts', self.current_account, 'no_log_for', - ' '.join(list_no_log_for)) - - def on_sync_logs_with_server_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'sync_logs_with_server', - account=self.current_account) - - def on_sync_with_global_status_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'sync_with_global_status', - account=self.current_account) - app.interface.roster.update_status_combobox() - - def on_carbons_checkbutton_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'enable_message_carbons', - account=self.current_account) - - def on_use_ft_proxies_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'use_ft_proxies', - account=self.current_account) - - def on_use_env_http_proxy_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'use_env_http_proxy', - account=self.current_account) - hbox = self.xml.get_object('proxy_hbox1') - hbox.set_sensitive(not widget.get_active()) - - def on_proxies_combobox1_changed(self, widget): - active = widget.get_active() - proxy = widget.get_model()[active][0] - if proxy == _('None'): - proxy = '' - - if self.option_changed('proxy', proxy): - self.need_relogin = True - - app.config.set_per('accounts', self.current_account, 'proxy', proxy) - - def on_manage_proxies_button1_clicked(self, widget): - if 'manage_proxies' in app.interface.instances: - app.interface.instances['manage_proxies'].window.present() - else: - app.interface.instances['manage_proxies'] = ManageProxiesWindow( - self.window) - - def on_warn_when_insecure_connection_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - - self.on_checkbutton_toggled(widget, 'warn_when_insecure_ssl_connection', - account=self.current_account) - - def on_send_keepalive_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - self.on_checkbutton_toggled(widget, 'keep_alives_enabled', - account=self.current_account) - app.config.set_per('accounts', self.current_account, - 'ping_alives_enabled', widget.get_active()) - - def on_custom_host_port_checkbutton1_toggled(self, widget): - if self.option_changed('use_custom_host', widget.get_active()): - self.need_relogin = True - - self.on_checkbutton_toggled(widget, 'use_custom_host', - account=self.current_account) - active = widget.get_active() - self.xml.get_object('custom_host_port_hbox1').set_sensitive(active) - - def on_custom_host_entry1_changed(self, widget): - if self.ignore_events: - return - host = widget.get_text() - if self.option_changed('custom_host', host): - self.need_relogin = True - app.config.set_per('accounts', self.current_account, 'custom_host', - host) - - def on_custom_port_entry_focus_out_event(self, widget, event): - if self.ignore_events: - return - custom_port = widget.get_text() - try: - custom_port = int(custom_port) - except Exception: - if not widget.is_focus(): - dialogs.ErrorDialog(_('Invalid entry'), - _('Custom port must be a port number.'), - transient_for=self.window) - GLib.idle_add(lambda: widget.grab_focus()) - return True - if self.option_changed('custom_port', custom_port): - self.need_relogin = True - app.config.set_per('accounts', self.current_account, 'custom_port', - custom_port) - - def on_gpg_choose_button_clicked(self, widget, data = None): - if self.current_account in app.connections and \ - app.connections[self.current_account].gpg: - secret_keys = app.connections[self.current_account].\ - ask_gpg_secrete_keys() - - # self.current_account is None and/or app.connections is {} - else: - if app.HAVE_GPG: - secret_keys = gpg.GnuPG().get_secret_keys() - else: - secret_keys = [] - if not secret_keys: - dialogs.ErrorDialog(_('Failed to get secret keys'), - _('There is no OpenPGP secret key available.'), - transient_for=self.window) - secret_keys[_('None')] = _('None') - - def on_key_selected(keyID): - if keyID is None: - return - if self.current_account == app.ZEROCONF_ACC_NAME: - wiget_name_ext = '2' - else: - wiget_name_ext = '1' - gpg_key_label = self.xml.get_object('gpg_key_label' + \ - wiget_name_ext) - gpg_name_label = self.xml.get_object('gpg_name_label' + \ - wiget_name_ext) - use_gpg_agent_checkbutton = self.xml.get_object( - 'use_gpg_agent_checkbutton' + wiget_name_ext) - if keyID[0] == _('None'): - gpg_key_label.set_text(_('No key selected')) - gpg_name_label.set_text('') - use_gpg_agent_checkbutton.set_sensitive(False) - if self.option_changed('keyid', ''): - self.need_relogin = True - app.config.set_per('accounts', self.current_account, - 'keyname', '') - app.config.set_per('accounts', self.current_account, 'keyid', - '') - else: - gpg_key_label.set_text(keyID[0]) - gpg_name_label.set_text(keyID[1]) - use_gpg_agent_checkbutton.set_sensitive(True) - if self.option_changed('keyid', keyID[0]): - self.need_relogin = True - app.config.set_per('accounts', self.current_account, - 'keyname', keyID[1]) - app.config.set_per('accounts', self.current_account, 'keyid', - keyID[0]) - - dialogs.ChooseGPGKeyDialog(_('OpenPGP Key Selection'), - _('Choose your OpenPGP key'), secret_keys, on_key_selected, - transient_for=self.window) - - def on_use_gpg_agent_checkbutton_toggled(self, widget): - self.on_checkbutton_toggled(widget, 'use_gpg_agent') - - def on_edit_details_button1_clicked(self, widget): - if self.current_account not in app.interface.instances: - dlg = dialogs.ErrorDialog(_('No such account available'), - _('You must create your account before editing your personal ' - 'information.'), transient_for=self.window) - return - - # show error dialog if account is newly created (not in app.connections) - if self.current_account not in app.connections or \ - app.connections[self.current_account].connected < 2: - dialogs.ErrorDialog(_('You are not connected to the server'), - _('Without a connection, you can not edit your personal ' - 'information.'), transient_for=self.window) - return - - if not app.connections[self.current_account].vcard_supported: - dialogs.ErrorDialog(_("Your server does not have vCard support"), - _("Your server can't save your personal information."), - transient_for=self.window) - return - - jid = app.get_jid_from_account(self.current_account) - if 'profile' not in app.interface.instances[self.current_account]: - app.interface.instances[self.current_account]['profile'] = \ - profile_window.ProfileWindow(self.current_account, transient_for=self.window) - app.connections[self.current_account].request_vcard(jid) - - def on_checkbutton_toggled(self, widget, config_name, - change_sensitivity_widgets = None, account = None): - if account: - app.config.set_per('accounts', account, config_name, - widget.get_active()) - else: - app.config.set(config_name, widget.get_active()) - if change_sensitivity_widgets: - for w in change_sensitivity_widgets: - w.set_sensitive(widget.get_active()) - - def on_merge_checkbutton_toggled(self, widget): - self.on_checkbutton_toggled(widget, 'mergeaccounts') - if len(app.connections) >= 2: # Do not merge accounts if only one active - app.interface.roster.regroup = app.config.get('mergeaccounts') - else: - app.interface.roster.regroup = False - app.interface.roster.setup_and_draw_roster() - - def _disable_account(self, account): - app.interface.roster.close_all(account) - if account == app.ZEROCONF_ACC_NAME: - app.connections[account].disable_account() - app.connections[account].cleanup() - del app.connections[account] - del app.interface.instances[account] - del app.interface.minimized_controls[account] - del app.nicks[account] - del app.block_signed_in_notifications[account] - del app.groups[account] - app.contacts.remove_account(account) - del app.gc_connected[account] - del app.automatic_rooms[account] - del app.to_be_removed[account] - del app.newly_added[account] - del app.sleeper_state[account] - del app.encrypted_chats[account] - del app.last_message_time[account] - del app.status_before_autoaway[account] - del app.transport_avatar[account] - del app.gajim_optional_features[account] - del app.caps_hash[account] - if len(app.connections) >= 2: - # Do not merge accounts if only one exists - app.interface.roster.regroup = app.config.get('mergeaccounts') - else: - app.interface.roster.regroup = False - app.interface.roster.setup_and_draw_roster() - app.app.remove_account_actions(account) - gui_menu_builder.build_accounts_menu() - - def _enable_account(self, account): - if account == app.ZEROCONF_ACC_NAME: - app.connections[account] = connection_zeroconf.ConnectionZeroconf( - account) - if app.connections[account].gpg: - self.xml.get_object('gpg_choose_button2').set_sensitive(True) - else: - app.connections[account] = connection.Connection(account) - if app.connections[account].gpg: - self.xml.get_object('gpg_choose_button1').set_sensitive(True) - self.init_account_gpg() - # update variables - app.interface.instances[account] = {'infos': {}, - 'disco': {}, 'gc_config': {}, 'search': {}, 'online_dialog': {}, - 'sub_request': {}} - app.interface.minimized_controls[account] = {} - app.connections[account].connected = 0 - app.groups[account] = {} - app.contacts.add_account(account) - app.gc_connected[account] = {} - app.automatic_rooms[account] = {} - app.newly_added[account] = [] - app.to_be_removed[account] = [] - if account == app.ZEROCONF_ACC_NAME: - app.nicks[account] = app.ZEROCONF_ACC_NAME - else: - app.nicks[account] = app.config.get_per('accounts', account, - 'name') - app.block_signed_in_notifications[account] = True - app.sleeper_state[account] = 'off' - app.encrypted_chats[account] = [] - app.last_message_time[account] = {} - app.status_before_autoaway[account] = '' - app.transport_avatar[account] = {} - app.gajim_optional_features[account] = [] - app.caps_hash[account] = '' - helpers.update_optional_features(account) - # refresh roster - if len(app.connections) >= 2: - # Do not merge accounts if only one exists - app.interface.roster.regroup = app.config.get('mergeaccounts') - else: - app.interface.roster.regroup = False - app.interface.roster.setup_and_draw_roster() - app.app.add_account_actions(account) - gui_menu_builder.build_accounts_menu() - - def on_enable_zeroconf_checkbutton2_toggled(self, widget): - # don't do anything if there is an account with the local name but is a - # normal account - if self.ignore_events: - return - if self.current_account in app.connections and \ - app.connections[self.current_account].connected > 0: - self.ignore_events = True - self.xml.get_object('enable_zeroconf_checkbutton2').set_active(True) - self.ignore_events = False - dialogs.ErrorDialog( - _('You are currently connected to the server'), - _('To disable the account, you must be disconnected.'), - transient_for=self.window) - return - if app.ZEROCONF_ACC_NAME in app.connections and not \ - app.connections[app.ZEROCONF_ACC_NAME].is_zeroconf: - app.nec.push_incoming_event(InformationEvent(None, - conn=app.connections[app.ZEROCONF_ACC_NAME], - level='error', pri_txt=_('Account Local already exists.'), - sec_txt=_('Please rename or remove it before enabling ' - 'link-local messaging.'))) - return - - if app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, 'active') \ - and not widget.get_active(): - self.xml.get_object('zeroconf_notebook').set_sensitive(False) - # disable - self._disable_account(app.ZEROCONF_ACC_NAME) - - elif not app.config.get_per('accounts', app.ZEROCONF_ACC_NAME, - 'active') and widget.get_active(): - self.xml.get_object('zeroconf_notebook').set_sensitive(True) - # enable (will create new account if not present) - self._enable_account(app.ZEROCONF_ACC_NAME) - - self.on_checkbutton_toggled(widget, 'active', - account=app.ZEROCONF_ACC_NAME) - - def on_enable_checkbutton1_toggled(self, widget): - if self.ignore_events: - return - if self.current_account in app.connections and \ - app.connections[self.current_account].connected > 0: - # connecting or connected - self.ignore_events = True - self.xml.get_object('enable_checkbutton1').set_active(True) - self.ignore_events = False - dialogs.ErrorDialog( - _('You are currently connected to the server'), - _('To disable the account, you must be disconnected.'), - transient_for=self.window) - return - # add/remove account in roster and all variables - if widget.get_active(): - # enable - self._enable_account(self.current_account) - else: - # disable - self._disable_account(self.current_account) - self.on_checkbutton_toggled(widget, 'active', - account=self.current_account, change_sensitivity_widgets=[ - self.xml.get_object('normal_notebook1')]) - - def on_custom_port_checkbutton2_toggled(self, widget): - self.xml.get_object('custom_port_entry2').set_sensitive( - widget.get_active()) - self.on_checkbutton_toggled(widget, 'use_custom_host', - account=self.current_account) - if not widget.get_active(): - self.xml.get_object('custom_port_entry2').set_text('5298') - - def on_first_name_entry2_changed(self, widget): - if self.ignore_events: - return - name = widget.get_text() - if self.option_changed('zeroconf_first_name', name): - self.need_relogin = True - app.config.set_per('accounts', self.current_account, - 'zeroconf_first_name', name) - - def on_last_name_entry2_changed(self, widget): - if self.ignore_events: - return - name = widget.get_text() - if self.option_changed('zeroconf_last_name', name): - self.need_relogin = True - app.config.set_per('accounts', self.current_account, - 'zeroconf_last_name', name) - - def on_jabber_id_entry2_changed(self, widget): - if self.ignore_events: - return - id_ = widget.get_text() - if self.option_changed('zeroconf_jabber_id', id_): - self.need_relogin = True - app.config.set_per('accounts', self.current_account, - 'zeroconf_jabber_id', id_) - - def on_email_entry2_changed(self, widget): - if self.ignore_events: - return - email = widget.get_text() - if self.option_changed('zeroconf_email', email): - self.need_relogin = True - app.config.set_per('accounts', self.current_account, - 'zeroconf_email', email) - class FakeDataForm(Gtk.Table, object): """ Class for forms that are in XML format value1 infos in a