#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
# the additional special exception to link portions of this program with the OpenSSL library.
# See LICENSE for more details.
#

import logging
import os
from hashlib import sha1 as sha
from urllib.parse import urlparse

from gi import require_version
from gi.repository import Gtk
from gi.repository.Gdk import Color

import deluge.common
import deluge.component as component
from deluge.configmanager import ConfigManager, get_config_dir
from deluge.decorators import maybe_coroutine
from deluge.error import AuthManagerError, NotAuthorizedError
from deluge.i18n import get_languages
from deluge.ui.client import client
from deluge.ui.common import DISK_CACHE_KEYS, PREFS_CATOG_TRANS

from .common import associate_magnet_links, get_clipboard_text, get_deluge_icon
from .dialogs import AccountDialog, ErrorDialog, InformationDialog, YesNoDialog
from .path_chooser import PathChooser

try:
    require_version('AppIndicator3', '0.1')
    from gi.repository import AppIndicator3  # noqa: F401
except (ImportError, ValueError):
    appindicator = False
else:
    appindicator = True

log = logging.getLogger(__name__)

ACCOUNTS_USERNAME, ACCOUNTS_LEVEL, ACCOUNTS_PASSWORD = list(range(3))
COLOR_MISSING, COLOR_WAITING, COLOR_DOWNLOADING, COLOR_COMPLETED = list(range(4))

COLOR_STATES = {
    'missing': COLOR_MISSING,
    'waiting': COLOR_WAITING,
    'downloading': COLOR_DOWNLOADING,
    'completed': COLOR_COMPLETED,
}


class Preferences(component.Component):
    def __init__(self):
        component.Component.__init__(self, 'Preferences')
        self.builder = Gtk.Builder()
        self.builder.add_from_file(
            deluge.common.resource_filename(
                __package__, os.path.join('glade', 'preferences_dialog.ui')
            )
        )
        self.pref_dialog = self.builder.get_object('pref_dialog')
        self.pref_dialog.set_transient_for(component.get('MainWindow').window)
        self.pref_dialog.set_icon(get_deluge_icon())
        self.treeview = self.builder.get_object('treeview')
        self.notebook = self.builder.get_object('notebook')
        self.gtkui_config = ConfigManager('gtk3ui.conf')
        self.window_open = False

        self.load_pref_dialog_state()

        self.builder.get_object('image_magnet').set_from_file(
            deluge.common.get_pixmap('magnet16.png')
        )

        # Hide the unused associate magnet button on OSX see: #2420
        if deluge.common.osx_check():
            self.builder.get_object('button_associate_magnet').hide()

        # Setup the liststore for the categories (tab pages)
        self.liststore = Gtk.ListStore(int, str, str)
        self.treeview.set_model(self.liststore)
        render = Gtk.CellRendererText()
        column = Gtk.TreeViewColumn(None, render, text=2)
        self.treeview.append_column(column)

        # Add the default categories
        prefs_categories = (
            'interface',
            'downloads',
            'bandwidth',
            'queue',
            'network',
            'proxy',
            'cache',
            'other',
            'daemon',
            'plugins',
        )
        for idx, category in enumerate(prefs_categories):
            self.liststore.append([idx, category, PREFS_CATOG_TRANS[category]])

        # Add and set separator after Plugins.
        def set_separator(model, _iter, data=None):
            entry = deluge.common.decode_bytes(model.get_value(_iter, 1))
            if entry == '_separator_':
                return True

        self.treeview.set_row_separator_func(set_separator, None)
        self.liststore.append([len(self.liststore), '_separator_', ''])
        # Add a dummy notebook page to keep indexing synced with liststore.
        self.notebook.append_page(Gtk.HSeparator())

        # Setup accounts tab lisview
        self.accounts_levels_mapping = None
        self.accounts_authlevel = self.builder.get_object('accounts_authlevel')
        self.accounts_liststore = Gtk.ListStore(str, str, str, int)
        self.accounts_liststore.set_sort_column_id(
            ACCOUNTS_USERNAME, Gtk.SortType.ASCENDING
        )
        self.accounts_listview = self.builder.get_object('accounts_listview')
        self.accounts_listview.append_column(
            Gtk.TreeViewColumn(
                _('Username'), Gtk.CellRendererText(), text=ACCOUNTS_USERNAME
            )
        )
        self.accounts_listview.append_column(
            Gtk.TreeViewColumn(_('Level'), Gtk.CellRendererText(), text=ACCOUNTS_LEVEL)
        )
        password_column = Gtk.TreeViewColumn(
            'password', Gtk.CellRendererText(), text=ACCOUNTS_PASSWORD
        )
        self.accounts_listview.append_column(password_column)
        password_column.set_visible(False)
        self.accounts_listview.set_model(self.accounts_liststore)

        self.accounts_listview.get_selection().connect(
            'changed', self.on_accounts_selection_changed
        )
        self.accounts_frame = self.builder.get_object('AccountsFrame')

        # Setup plugin tab listview
        # The third entry is for holding translated plugin names
        self.plugin_liststore = Gtk.ListStore(str, bool, str)
        self.plugin_liststore.set_sort_column_id(0, Gtk.SortType.ASCENDING)
        self.plugin_listview = self.builder.get_object('plugin_listview')
        self.plugin_listview.set_model(self.plugin_liststore)
        render = Gtk.CellRendererToggle()
        render.connect('toggled', self.on_plugin_toggled)
        render.set_property('activatable', True)
        self.plugin_listview.append_column(
            Gtk.TreeViewColumn(_('Enabled'), render, active=1)
        )
        self.plugin_listview.append_column(
            Gtk.TreeViewColumn(_('Plugin'), Gtk.CellRendererText(), text=2)
        )

        # Connect to the 'changed' event of TreeViewSelection to get selection
        # changes.
        self.treeview.get_selection().connect('changed', self.on_selection_changed)

        self.plugin_listview.get_selection().connect(
            'changed', self.on_plugin_selection_changed
        )

        self.builder.connect_signals(self)

        # Radio buttons to choose between systray and appindicator
        self.builder.get_object('alignment_tray_type').set_visible(appindicator)

        from .gtkui import DEFAULT_PREFS

        self.COLOR_DEFAULTS = {}
        for key in ('missing', 'waiting', 'downloading', 'completed'):
            self.COLOR_DEFAULTS[key] = DEFAULT_PREFS['pieces_color_%s' % key][:]
        del DEFAULT_PREFS

        # These get updated by requests done to the core
        self.all_plugins = []
        self.enabled_plugins = []

        self.setup_path_choosers()
        self.load_languages()

    def setup_path_choosers(self):
        self.download_location_hbox = self.builder.get_object(
            'hbox_download_to_path_chooser'
        )
        self.download_location_path_chooser = PathChooser(
            'download_location_paths_list', parent=self.pref_dialog
        )
        self.download_location_hbox.add(self.download_location_path_chooser)
        self.download_location_hbox.show_all()

        self.move_completed_hbox = self.builder.get_object(
            'hbox_move_completed_to_path_chooser'
        )
        self.move_completed_path_chooser = PathChooser(
            'move_completed_paths_list', parent=self.pref_dialog
        )
        self.move_completed_hbox.add(self.move_completed_path_chooser)
        self.move_completed_hbox.show_all()

        self.copy_torrents_to_hbox = self.builder.get_object(
            'hbox_copy_torrent_files_path_chooser'
        )
        self.copy_torrent_files_path_chooser = PathChooser(
            'copy_torrent_files_to_paths_list', parent=self.pref_dialog
        )
        self.copy_torrents_to_hbox.add(self.copy_torrent_files_path_chooser)
        self.copy_torrents_to_hbox.show_all()

    def load_languages(self):
        self.language_combo = self.builder.get_object('combobox_language')
        self.language_checkbox = self.builder.get_object('checkbutton_language')
        lang_model = self.language_combo.get_model()
        langs = get_languages()
        index = -1
        for i, l in enumerate(langs):
            lang_code, name = l
            lang_model.append([lang_code, name])
            if self.gtkui_config['language'] == lang_code:
                index = i

        if self.gtkui_config['language'] is None:
            self.language_checkbox.set_active(True)
            self.language_combo.set_visible(False)
        else:
            self.language_combo.set_visible(True)
            if index != -1:
                self.language_combo.set_active(index)

    def __del__(self):
        del self.gtkui_config

    def add_page(self, name, widget):
        """Add a another page to the notebook"""
        # Create a header and scrolled window for the preferences tab
        parent = widget.get_parent()
        if parent:
            parent.remove(widget)
        vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, spacing=0)
        label = Gtk.Label()
        label.set_use_markup(True)
        label.set_markup('<b><i><big>' + name + '</big></i></b>')
        label.set_alignment(0.00, 0.50)
        label.set_padding(10, 10)
        vbox.pack_start(label, False, True, 0)
        sep = Gtk.HSeparator()
        vbox.pack_start(sep, False, True, 0)
        widget.set_margin_top(7)
        widget.set_vexpand(True)
        widget.set_hexpand(True)
        vbox.pack_start(widget, True, True, 0)
        scrolled = Gtk.ScrolledWindow()
        viewport = Gtk.Viewport()
        viewport.set_shadow_type(Gtk.ShadowType.NONE)
        viewport.add(vbox)
        scrolled.add(viewport)
        scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
        scrolled.show_all()
        # Add this page to the notebook
        index = self.notebook.append_page(scrolled, None)
        self.liststore.append([index, name, _(name)])
        return name

    def remove_page(self, name):
        """Removes a page from the notebook"""
        self.page_num_to_remove = None
        self.iter_to_remove = None

        def on_foreach_row(model, path, _iter, user_data):
            row_name = deluge.common.decode_bytes(model.get_value(_iter, 1))
            if row_name == user_data:
                # This is the row we need to remove
                self.page_num_to_remove = model.get_value(_iter, 0)
                self.iter_to_remove = _iter
                # Return True to stop foreach iterating
                return True

        self.liststore.foreach(on_foreach_row, name)

        # Remove the page and row
        if self.page_num_to_remove is not None:
            self.notebook.remove_page(self.page_num_to_remove)
        if self.iter_to_remove is not None:
            self.liststore.remove(self.iter_to_remove)

        # We need to re-adjust the index values for the remaining pages
        for idx, __ in enumerate(self.liststore):
            self.liststore[idx][0] = idx

    def show(self, page=None):
        """Page should be the string in the left list.. ie, 'Network' or
        'Bandwidth'"""
        self.window_open = True
        if page is not None:
            for (index, string, __) in self.liststore:
                if page == string:
                    self.treeview.get_selection().select_path(index)
                    break

        component.get('PluginManager').run_on_show_prefs()

        # Update the preferences dialog to reflect current config settings
        self.core_config = {}
        if client.connected():
            self._get_accounts_tab_data()

            def on_get_config(config):
                self.core_config = config
                client.core.get_available_plugins().addCallback(
                    on_get_available_plugins
                )

            def on_get_available_plugins(plugins):
                self.all_plugins = plugins
                client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins)

            def on_get_enabled_plugins(plugins):
                self.enabled_plugins = plugins
                client.core.get_listen_port().addCallback(on_get_listen_port)

            def on_get_listen_port(port):
                self.active_port = port
                client.core.get_session_status(DISK_CACHE_KEYS).addCallback(
                    on_get_session_status
                )

            def on_get_session_status(status):
                self.cache_status = status
                self._show()

            # This starts a series of client.core requests prior to showing the window
            client.core.get_config().addCallback(on_get_config)
        else:
            self._show()

    def start(self):
        if self.window_open:
            self.show()

    def stop(self):
        self.core_config = None
        if self.window_open:
            self._show()

    def _show(self):
        self.is_connected = self.core_config != {} and self.core_config is not None
        core_widgets = {
            'chk_move_completed': ('active', 'move_completed'),
            'chk_copy_torrent_file': ('active', 'copy_torrent_file'),
            'chk_del_copy_torrent_file': ('active', 'del_copy_torrent_file'),
            'chk_pre_allocation': ('active', 'pre_allocate_storage'),
            'chk_prioritize_first_last_pieces': (
                'active',
                'prioritize_first_last_pieces',
            ),
            'chk_sequential_download': ('active', 'sequential_download'),
            'chk_add_paused': ('active', 'add_paused'),
            'active_port_label': ('text', lambda: str(self.active_port)),
            'spin_incoming_port': (
                'value',
                lambda: self.core_config['listen_ports'][0],
            ),
            'chk_random_incoming_port': ('active', 'random_port'),
            'spin_outgoing_port_min': (
                'value',
                lambda: self.core_config['outgoing_ports'][0],
            ),
            'spin_outgoing_port_max': (
                'value',
                lambda: self.core_config['outgoing_ports'][1],
            ),
            'chk_random_outgoing_ports': ('active', 'random_outgoing_ports'),
            'entry_interface': ('text', 'listen_interface'),
            'entry_outgoing_interface': ('text', 'outgoing_interface'),
            'entry_peer_tos': ('text', 'peer_tos'),
            'chk_dht': ('active', 'dht'),
            'chk_upnp': ('active', 'upnp'),
            'chk_natpmp': ('active', 'natpmp'),
            'chk_utpex': ('active', 'utpex'),
            'chk_lsd': ('active', 'lsd'),
            'chk_new_releases': ('active', 'new_release_check'),
            'chk_send_info': ('active', 'send_info'),
            'entry_geoip': ('text', 'geoip_db_location'),
            'combo_encin': ('active', 'enc_in_policy'),
            'combo_encout': ('active', 'enc_out_policy'),
            'combo_enclevel': ('active', 'enc_level'),
            'spin_max_connections_global': ('value', 'max_connections_global'),
            'spin_max_download': ('value', 'max_download_speed'),
            'spin_max_upload': ('value', 'max_upload_speed'),
            'spin_max_upload_slots_global': ('value', 'max_upload_slots_global'),
            'spin_max_half_open_connections': ('value', 'max_connections_per_second'),
            'spin_max_connections_per_second': ('value', 'max_connections_per_second'),
            'chk_ignore_limits_on_local_network': (
                'active',
                'ignore_limits_on_local_network',
            ),
            'chk_rate_limit_ip_overhead': ('active', 'rate_limit_ip_overhead'),
            'spin_max_connections_per_torrent': (
                'value',
                'max_connections_per_torrent',
            ),
            'spin_max_upload_slots_per_torrent': (
                'value',
                'max_upload_slots_per_torrent',
            ),
            'spin_max_download_per_torrent': (
                'value',
                'max_download_speed_per_torrent',
            ),
            'spin_max_upload_per_torrent': ('value', 'max_upload_speed_per_torrent'),
            'spin_daemon_port': ('value', 'daemon_port'),
            'chk_allow_remote_connections': ('active', 'allow_remote'),
            'spin_active': ('value', 'max_active_limit'),
            'spin_seeding': ('value', 'max_active_seeding'),
            'spin_downloading': ('value', 'max_active_downloading'),
            'chk_dont_count_slow_torrents': ('active', 'dont_count_slow_torrents'),
            'chk_auto_manage_prefer_seeds': ('active', 'auto_manage_prefer_seeds'),
            'chk_queue_new_top': ('active', 'queue_new_to_top'),
            'spin_share_ratio_limit': ('value', 'share_ratio_limit'),
            'spin_seed_time_ratio_limit': ('value', 'seed_time_ratio_limit'),
            'spin_seed_time_limit': ('value', 'seed_time_limit'),
            'chk_share_ratio': ('active', 'stop_seed_at_ratio'),
            'spin_share_ratio': ('value', 'stop_seed_ratio'),
            'radio_pause_ratio': ('active', 'stop_seed_at_ratio'),
            'radio_remove_ratio': ('active', 'remove_seed_at_ratio'),
            'spin_cache_size': ('value', 'cache_size'),
            'spin_cache_expiry': ('value', 'cache_expiry'),
            'combo_proxy_type': ('active', lambda: self.core_config['proxy']['type']),
            'entry_proxy_user': ('text', lambda: self.core_config['proxy']['username']),
            'entry_proxy_pass': ('text', lambda: self.core_config['proxy']['password']),
            'entry_proxy_host': ('text', lambda: self.core_config['proxy']['hostname']),
            'spin_proxy_port': ('value', lambda: self.core_config['proxy']['port']),
            'chk_proxy_host_resolve': (
                'active',
                lambda: self.core_config['proxy']['proxy_hostnames'],
            ),
            'chk_proxy_peer_conn': (
                'active',
                lambda: self.core_config['proxy']['proxy_peer_connections'],
            ),
            'chk_proxy_tracker_conn': (
                'active',
                lambda: self.core_config['proxy']['proxy_tracker_connections'],
            ),
            'chk_force_proxy': (
                'active',
                lambda: self.core_config['proxy']['force_proxy'],
            ),
            'chk_anonymous_mode': (
                'active',
                lambda: self.core_config['proxy']['anonymous_mode'],
            ),
            'accounts_add': (None, None),
            'accounts_listview': (None, None),
            'button_cache_refresh': (None, None),
            'button_plugin_install': (None, None),
            'button_rescan_plugins': (None, None),
            'button_find_plugins': (None, None),
            'button_testport': (None, None),
            'plugin_listview': (None, None),
        }

        core_widgets[self.download_location_path_chooser] = (
            'path_chooser',
            'download_location',
        )
        core_widgets[self.move_completed_path_chooser] = (
            'path_chooser',
            'move_completed_path',
        )
        core_widgets[self.copy_torrent_files_path_chooser] = (
            'path_chooser',
            'torrentfiles_location',
        )

        # Update the widgets accordingly
        for key in core_widgets:
            modifier = core_widgets[key][0]
            try:
                widget = self.builder.get_object(key)
            except TypeError:
                widget = key

            widget.set_sensitive(self.is_connected)

            if self.is_connected:
                value = core_widgets[key][1]
                try:
                    value = self.core_config[value]
                except KeyError:
                    if callable(value):
                        value = value()
            elif modifier:
                value = {
                    'active': False,
                    'not_active': False,
                    'value': 0,
                    'text': '',
                    'path_chooser': '',
                }[modifier]

            if modifier == 'active':
                widget.set_active(value)
            elif modifier == 'not_active':
                widget.set_active(not value)
            elif modifier == 'value':
                widget.set_value(float(value))
            elif modifier == 'text':
                if value is None:
                    value = ''
                widget.set_text(value)
            elif modifier == 'path_chooser':
                widget.set_text(value, cursor_end=False, default_text=True)

        if self.is_connected:
            for key in core_widgets:
                try:
                    widget = self.builder.get_object(key)
                except TypeError:
                    widget = key
                # Update the toggle status if necessary
                self.on_toggle(widget)

        # Downloads tab #
        self.builder.get_object('chk_show_dialog').set_active(
            self.gtkui_config['interactive_add']
        )
        self.builder.get_object('chk_focus_dialog').set_active(
            self.gtkui_config['focus_add_dialog']
        )

        # Interface tab #
        self.builder.get_object('chk_use_tray').set_active(
            self.gtkui_config['enable_system_tray']
        )
        self.builder.get_object('chk_min_on_close').set_active(
            self.gtkui_config['close_to_tray']
        )
        self.builder.get_object('chk_start_in_tray').set_active(
            self.gtkui_config['start_in_tray']
        )
        self.builder.get_object('radio_appind').set_active(
            self.gtkui_config['enable_appindicator']
        )
        self.builder.get_object('chk_lock_tray').set_active(
            self.gtkui_config['lock_tray']
        )
        self.builder.get_object('radio_standalone').set_active(
            self.gtkui_config['standalone']
        )
        self.builder.get_object('radio_thinclient').set_active(
            not self.gtkui_config['standalone']
        )
        self.builder.get_object('chk_show_rate_in_title').set_active(
            self.gtkui_config['show_rate_in_title']
        )
        self.builder.get_object('chk_focus_main_window_on_add').set_active(
            self.gtkui_config['focus_main_window_on_add']
        )
        self.builder.get_object('piecesbar_toggle').set_active(
            self.gtkui_config['show_piecesbar']
        )
        self.builder.get_object('urldetect_toggle').set_active(
            self.gtkui_config['detect_urls']
        )
        self.__set_color('completed', from_config=True)
        self.__set_color('downloading', from_config=True)
        self.__set_color('waiting', from_config=True)
        self.__set_color('missing', from_config=True)

        # Other tab #
        self.builder.get_object('chk_show_new_releases').set_active(
            self.gtkui_config['show_new_releases']
        )

        # Cache tab #
        if client.connected():
            self.__update_cache_status()

        # Plugins tab #
        all_plugins = self.all_plugins
        enabled_plugins = self.enabled_plugins
        # Clear the existing list so we don't duplicate entries.
        self.plugin_liststore.clear()
        # Iterate through the lists and add them to the liststore
        for plugin in all_plugins:
            enabled = plugin in enabled_plugins
            row = self.plugin_liststore.append()
            self.plugin_liststore.set_value(row, 0, plugin)
            self.plugin_liststore.set_value(row, 1, enabled)
            self.plugin_liststore.set_value(row, 2, _(plugin))

        # Now show the dialog
        self.pref_dialog.show()

    def set_config(self, hide=False):
        """
        Sets all altered config values in the core.

        :param hide: bool, if True, will not re-show the dialog and will hide it instead
        """

        # Get the values from the dialog
        new_core_config = {}
        new_gtkui_config = {}

        # Downloads tab #
        new_gtkui_config['interactive_add'] = self.builder.get_object(
            'chk_show_dialog'
        ).get_active()
        new_gtkui_config['focus_add_dialog'] = self.builder.get_object(
            'chk_focus_dialog'
        ).get_active()

        for state in ('missing', 'waiting', 'downloading', 'completed'):
            color = self.builder.get_object('%s_color' % state).get_color()
            new_gtkui_config['pieces_color_%s' % state] = [
                color.red,
                color.green,
                color.blue,
            ]

        new_core_config['copy_torrent_file'] = self.builder.get_object(
            'chk_copy_torrent_file'
        ).get_active()
        new_core_config['del_copy_torrent_file'] = self.builder.get_object(
            'chk_del_copy_torrent_file'
        ).get_active()
        new_core_config['move_completed'] = self.builder.get_object(
            'chk_move_completed'
        ).get_active()

        new_core_config[
            'download_location'
        ] = self.download_location_path_chooser.get_text()
        new_core_config[
            'move_completed_path'
        ] = self.move_completed_path_chooser.get_text()
        new_core_config[
            'torrentfiles_location'
        ] = self.copy_torrent_files_path_chooser.get_text()
        new_core_config['prioritize_first_last_pieces'] = self.builder.get_object(
            'chk_prioritize_first_last_pieces'
        ).get_active()
        new_core_config['sequential_download'] = self.builder.get_object(
            'chk_sequential_download'
        ).get_active()
        new_core_config['add_paused'] = self.builder.get_object(
            'chk_add_paused'
        ).get_active()
        new_core_config['pre_allocate_storage'] = self.builder.get_object(
            'chk_pre_allocation'
        ).get_active()

        # Network tab #
        listen_ports = [
            self.builder.get_object('spin_incoming_port').get_value_as_int()
        ] * 2
        new_core_config['listen_ports'] = listen_ports
        new_core_config['random_port'] = self.builder.get_object(
            'chk_random_incoming_port'
        ).get_active()
        outgoing_ports = (
            self.builder.get_object('spin_outgoing_port_min').get_value_as_int(),
            self.builder.get_object('spin_outgoing_port_max').get_value_as_int(),
        )
        new_core_config['outgoing_ports'] = outgoing_ports
        new_core_config['random_outgoing_ports'] = self.builder.get_object(
            'chk_random_outgoing_ports'
        ).get_active()
        incoming_address = self.builder.get_object('entry_interface').get_text().strip()
        if deluge.common.is_interface(incoming_address) or not incoming_address:
            new_core_config['listen_interface'] = incoming_address
        outgoing_address = (
            self.builder.get_object('entry_outgoing_interface').get_text().strip()
        )
        if deluge.common.is_interface(outgoing_address) or not outgoing_address:
            new_core_config['outgoing_interface'] = (
                self.builder.get_object('entry_outgoing_interface').get_text().strip()
            )
        new_core_config['peer_tos'] = self.builder.get_object(
            'entry_peer_tos'
        ).get_text()
        new_core_config['dht'] = self.builder.get_object('chk_dht').get_active()
        new_core_config['upnp'] = self.builder.get_object('chk_upnp').get_active()
        new_core_config['natpmp'] = self.builder.get_object('chk_natpmp').get_active()
        new_core_config['utpex'] = self.builder.get_object('chk_utpex').get_active()
        new_core_config['lsd'] = self.builder.get_object('chk_lsd').get_active()
        new_core_config['enc_in_policy'] = self.builder.get_object(
            'combo_encin'
        ).get_active()
        new_core_config['enc_out_policy'] = self.builder.get_object(
            'combo_encout'
        ).get_active()
        new_core_config['enc_level'] = self.builder.get_object(
            'combo_enclevel'
        ).get_active()

        # Bandwidth tab #
        new_core_config['max_connections_global'] = self.builder.get_object(
            'spin_max_connections_global'
        ).get_value_as_int()
        new_core_config['max_download_speed'] = self.builder.get_object(
            'spin_max_download'
        ).get_value()
        new_core_config['max_upload_speed'] = self.builder.get_object(
            'spin_max_upload'
        ).get_value()
        new_core_config['max_upload_slots_global'] = self.builder.get_object(
            'spin_max_upload_slots_global'
        ).get_value_as_int()
        new_core_config['max_half_open_connections'] = self.builder.get_object(
            'spin_max_half_open_connections'
        ).get_value_as_int()
        new_core_config['max_connections_per_second'] = self.builder.get_object(
            'spin_max_connections_per_second'
        ).get_value_as_int()
        new_core_config['max_connections_per_torrent'] = self.builder.get_object(
            'spin_max_connections_per_torrent'
        ).get_value_as_int()
        new_core_config['max_upload_slots_per_torrent'] = self.builder.get_object(
            'spin_max_upload_slots_per_torrent'
        ).get_value_as_int()
        new_core_config['max_upload_speed_per_torrent'] = self.builder.get_object(
            'spin_max_upload_per_torrent'
        ).get_value()
        new_core_config['max_download_speed_per_torrent'] = self.builder.get_object(
            'spin_max_download_per_torrent'
        ).get_value()
        new_core_config['ignore_limits_on_local_network'] = self.builder.get_object(
            'chk_ignore_limits_on_local_network'
        ).get_active()
        new_core_config['rate_limit_ip_overhead'] = self.builder.get_object(
            'chk_rate_limit_ip_overhead'
        ).get_active()

        # Interface tab #
        new_gtkui_config['enable_system_tray'] = self.builder.get_object(
            'chk_use_tray'
        ).get_active()
        new_gtkui_config['close_to_tray'] = self.builder.get_object(
            'chk_min_on_close'
        ).get_active()
        new_gtkui_config['start_in_tray'] = self.builder.get_object(
            'chk_start_in_tray'
        ).get_active()
        new_gtkui_config['enable_appindicator'] = self.builder.get_object(
            'radio_appind'
        ).get_active()
        new_gtkui_config['lock_tray'] = self.builder.get_object(
            'chk_lock_tray'
        ).get_active()
        passhex = sha(
            deluge.common.decode_bytes(
                self.builder.get_object('txt_tray_password').get_text()
            ).encode()
        ).hexdigest()
        if passhex != 'c07eb5a8c0dc7bb81c217b67f11c3b7a5e95ffd7':
            new_gtkui_config['tray_password'] = passhex

        was_standalone = self.gtkui_config['standalone']
        new_gtkui_standalone = self.builder.get_object('radio_standalone').get_active()
        new_gtkui_config['standalone'] = new_gtkui_standalone

        new_gtkui_config['show_rate_in_title'] = self.builder.get_object(
            'chk_show_rate_in_title'
        ).get_active()
        new_gtkui_config['focus_main_window_on_add'] = self.builder.get_object(
            'chk_focus_main_window_on_add'
        ).get_active()

        # Other tab #
        new_gtkui_config['show_new_releases'] = self.builder.get_object(
            'chk_show_new_releases'
        ).get_active()
        new_core_config['send_info'] = self.builder.get_object(
            'chk_send_info'
        ).get_active()
        new_core_config['geoip_db_location'] = self.builder.get_object(
            'entry_geoip'
        ).get_text()

        # Daemon tab #
        new_core_config['daemon_port'] = self.builder.get_object(
            'spin_daemon_port'
        ).get_value_as_int()
        new_core_config['allow_remote'] = self.builder.get_object(
            'chk_allow_remote_connections'
        ).get_active()
        new_core_config['new_release_check'] = self.builder.get_object(
            'chk_new_releases'
        ).get_active()

        # Proxy tab #
        new_core_config['proxy'] = {
            'type': self.builder.get_object('combo_proxy_type').get_active(),
            'username': self.builder.get_object('entry_proxy_user').get_text(),
            'password': self.builder.get_object('entry_proxy_pass').get_text(),
            'hostname': self.builder.get_object('entry_proxy_host').get_text(),
            'port': self.builder.get_object('spin_proxy_port').get_value_as_int(),
            'proxy_hostnames': self.builder.get_object(
                'chk_proxy_host_resolve'
            ).get_active(),
            'proxy_peer_connections': self.builder.get_object(
                'chk_proxy_peer_conn'
            ).get_active(),
            'proxy_tracker_connections': self.builder.get_object(
                'chk_proxy_tracker_conn'
            ).get_active(),
            'force_proxy': self.builder.get_object('chk_force_proxy').get_active(),
            'anonymous_mode': self.builder.get_object(
                'chk_anonymous_mode'
            ).get_active(),
        }

        # Queue tab #
        new_core_config['queue_new_to_top'] = self.builder.get_object(
            'chk_queue_new_top'
        ).get_active()
        new_core_config['max_active_seeding'] = self.builder.get_object(
            'spin_seeding'
        ).get_value_as_int()
        new_core_config['max_active_downloading'] = self.builder.get_object(
            'spin_downloading'
        ).get_value_as_int()
        new_core_config['max_active_limit'] = self.builder.get_object(
            'spin_active'
        ).get_value_as_int()
        new_core_config['dont_count_slow_torrents'] = self.builder.get_object(
            'chk_dont_count_slow_torrents'
        ).get_active()
        new_core_config['auto_manage_prefer_seeds'] = self.builder.get_object(
            'chk_auto_manage_prefer_seeds'
        ).get_active()
        new_core_config['stop_seed_at_ratio'] = self.builder.get_object(
            'chk_share_ratio'
        ).get_active()
        new_core_config['remove_seed_at_ratio'] = self.builder.get_object(
            'radio_remove_ratio'
        ).get_active()
        new_core_config['stop_seed_ratio'] = self.builder.get_object(
            'spin_share_ratio'
        ).get_value()
        new_core_config['share_ratio_limit'] = self.builder.get_object(
            'spin_share_ratio_limit'
        ).get_value()
        new_core_config['seed_time_ratio_limit'] = self.builder.get_object(
            'spin_seed_time_ratio_limit'
        ).get_value()
        new_core_config['seed_time_limit'] = self.builder.get_object(
            'spin_seed_time_limit'
        ).get_value()

        # Cache tab #
        new_core_config['cache_size'] = self.builder.get_object(
            'spin_cache_size'
        ).get_value_as_int()
        new_core_config['cache_expiry'] = self.builder.get_object(
            'spin_cache_expiry'
        ).get_value_as_int()

        # Run plugin hook to apply preferences
        component.get('PluginManager').run_on_apply_prefs()

        # Language
        if self.language_checkbox.get_active():
            new_gtkui_config['language'] = None
        else:
            active = self.language_combo.get_active()
            if active == -1:
                dialog = InformationDialog(
                    _('Attention'), _('You must choose a language')
                )
                dialog.run()
                return
            else:
                model = self.language_combo.get_model()
                new_gtkui_config['language'] = model.get(model.get_iter(active), 0)[0]

        if new_gtkui_config['language'] != self.gtkui_config['language']:
            dialog = InformationDialog(
                _('Attention'),
                _('You must now restart the deluge UI for the changes to take effect.'),
            )
            dialog.run()

        # GtkUI
        for key in new_gtkui_config:
            # The values do not match so this needs to be updated
            if self.gtkui_config[key] != new_gtkui_config[key]:
                self.gtkui_config[key] = new_gtkui_config[key]

        # Core
        if client.connected():
            # Only do this if we're connected to a daemon
            config_to_set = {}
            for key in new_core_config:
                # The values do not match so this needs to be updated
                if self.core_config[key] != new_core_config[key]:
                    config_to_set[key] = new_core_config[key]

            if config_to_set:
                # Set each changed config value in the core
                client.core.set_config(config_to_set)
                client.force_call(True)
                # Update the configuration
                self.core_config.update(config_to_set)

        if hide:
            self.hide()
        else:
            # Re-show the dialog to make sure everything has been updated
            self.show()

        if was_standalone != new_gtkui_standalone:

            def on_response(response):
                if response == Gtk.ResponseType.YES:
                    shutdown_daemon = (
                        not client.is_standalone()
                        and client.connected()
                        and client.is_localhost()
                    )
                    component.get('MainWindow').quit(
                        shutdown=shutdown_daemon, restart=True
                    )
                else:
                    self.gtkui_config['standalone'] = not new_gtkui_standalone
                    self.builder.get_object('radio_standalone').set_active(
                        self.gtkui_config['standalone']
                    )
                    self.builder.get_object('radio_thinclient').set_active(
                        not self.gtkui_config['standalone']
                    )

            mode = _('Thinclient') if was_standalone else _('Standalone')
            dialog = YesNoDialog(
                _('Switching Deluge Client Mode...'),
                _('Do you want to restart to use %s mode?' % mode),
            )
            dialog.run().addCallback(on_response)

    def hide(self):
        self.window_open = False
        self.builder.get_object('port_spinner').stop()
        self.builder.get_object('port_img').hide()
        self.pref_dialog.hide()

    def __update_cache_status(self):
        # Updates the cache status labels with the info in the dict
        cache_labels = (
            'label_cache_read_ops',
            'label_cache_write_ops',
            'label_cache_num_blocks_read',
            'label_cache_num_blocks_written',
            'label_cache_read_hit_ratio',
            'label_cache_write_hit_ratio',
            'label_cache_disk_blocks_in_use',
            'label_cache_read_cache_blocks',
        )

        for widget_name in cache_labels:
            widget = self.builder.get_object(widget_name)
            key = widget_name[len('label_cache_') :]
            if not widget_name.endswith('ratio'):
                key = 'disk.' + key
            value = self.cache_status.get(key, 0)
            if isinstance(value, float):
                value = '%.2f' % value
            else:
                value = str(value)

            widget.set_text(value)

    def on_button_cache_refresh_clicked(self, widget):
        def on_get_session_status(status):
            self.cache_status = status
            self.__update_cache_status()

        client.core.get_session_status(DISK_CACHE_KEYS).addCallback(
            on_get_session_status
        )

    def on_pref_dialog_delete_event(self, widget, event):
        self.hide()
        return True

    def load_pref_dialog_state(self):
        w = self.gtkui_config['pref_dialog_width']
        h = self.gtkui_config['pref_dialog_height']
        if w is not None and h is not None:
            self.pref_dialog.resize(w, h)

    def on_pref_dialog_configure_event(self, widget, event):
        self.gtkui_config['pref_dialog_width'] = event.width
        self.gtkui_config['pref_dialog_height'] = event.height

    def on_toggle(self, widget):
        """Handles widget sensitivity based on radio/check button values."""
        try:
            value = widget.get_active()
        except Exception:
            return

        path_choosers = {
            'download_location_path_chooser': self.download_location_path_chooser,
            'move_completed_path_chooser': self.move_completed_path_chooser,
            'torrentfiles_location_path_chooser': self.copy_torrent_files_path_chooser,
        }

        dependents = {
            'chk_show_dialog': {'chk_focus_dialog': True},
            'chk_random_incoming_port': {'spin_incoming_port': False},
            'chk_random_outgoing_ports': {
                'spin_outgoing_port_min': False,
                'spin_outgoing_port_max': False,
            },
            'chk_use_tray': {
                'radio_appind': True,
                'radio_systray': True,
                'chk_min_on_close': True,
                'chk_start_in_tray': True,
                'alignment_tray_type': True,
                'chk_lock_tray': True,
            },
            'chk_lock_tray': {'txt_tray_password': True, 'password_label': True},
            'radio_open_folder_custom': {
                'combo_file_manager': False,
                'txt_open_folder_location': True,
            },
            'chk_move_completed': {'move_completed_path_chooser': True},
            'chk_copy_torrent_file': {
                'torrentfiles_location_path_chooser': True,
                'chk_del_copy_torrent_file': True,
            },
            'chk_share_ratio': {
                'spin_share_ratio': True,
                'radio_pause_ratio': True,
                'radio_remove_ratio': True,
            },
        }

        def update_dependent_widgets(name, value):
            dependency = dependents[name]
            for dep in dependency:
                if dep in path_choosers:
                    depwidget = path_choosers[dep]
                else:
                    depwidget = self.builder.get_object(dep)
                sensitive = [not value, value][dependency[dep]]
                depwidget.set_sensitive(sensitive)
                if dep in dependents:
                    update_dependent_widgets(dep, depwidget.get_active() and sensitive)

        for key in dependents:
            if widget != self.builder.get_object(key):
                continue
            update_dependent_widgets(key, value)

    def on_button_ok_clicked(self, data):
        log.debug('on_button_ok_clicked')
        self.set_config(hide=True)
        return True

    def on_button_apply_clicked(self, data):
        log.debug('on_button_apply_clicked')
        self.set_config()

    def on_button_cancel_clicked(self, data):
        log.debug('on_button_cancel_clicked')
        self.hide()
        return True

    def on_selection_changed(self, treeselection):
        # Show the correct notebook page based on what row is selected.
        (model, row) = treeselection.get_selected()
        try:
            if model.get_value(row, 1) == 'daemon':
                # Let's see update the accounts related stuff
                if client.connected():
                    self._get_accounts_tab_data()
            self.notebook.set_current_page(model.get_value(row, 0))
        except TypeError:
            pass

    def on_test_port_clicked(self, data):
        log.debug('on_test_port_clicked')

        def on_get_test(status):
            self.builder.get_object('port_spinner').stop()
            self.builder.get_object('port_spinner').hide()
            if status:
                self.builder.get_object('port_img').set_from_icon_name(
                    'emblem-ok-symbolic', Gtk.IconSize.MENU
                )
                self.builder.get_object('port_img').show()
            else:
                self.builder.get_object('port_img').set_from_icon_name(
                    'dialog-warning-symbolic', Gtk.IconSize.MENU
                )
                self.builder.get_object('port_img').show()

        client.core.test_listen_port().addCallback(on_get_test)
        self.builder.get_object('port_spinner').start()
        self.builder.get_object('port_spinner').show()
        self.builder.get_object('port_img').hide()
        client.force_call()

    def on_plugin_toggled(self, renderer, path):
        row = self.plugin_liststore.get_iter_from_string(path)
        name = self.plugin_liststore.get_value(row, 0)
        value = self.plugin_liststore.get_value(row, 1)
        log.debug('on_plugin_toggled - %s: %s', name, value)
        self.plugin_liststore.set_value(row, 1, not value)
        if not value:
            d = client.core.enable_plugin(name)
        else:
            d = client.core.disable_plugin(name)

        def on_plugin_action(arg):
            if not value and arg is False:
                log.warning('Failed to enable plugin: %s', name)
                self.plugin_liststore.set_value(row, 1, False)

        d.addBoth(on_plugin_action)

    def on_plugin_selection_changed(self, treeselection):
        log.debug('on_plugin_selection_changed')
        (model, itr) = treeselection.get_selected()
        if not itr:
            return
        name = model[itr][0]
        plugin_info = component.get('PluginManager').get_plugin_info(name)
        self.builder.get_object('label_plugin_author').set_text(plugin_info['Author'])
        self.builder.get_object('label_plugin_version').set_text(plugin_info['Version'])
        self.builder.get_object('label_plugin_email').set_text(
            plugin_info['Author-email']
        )
        self.builder.get_object('label_plugin_homepage').set_text(
            plugin_info['Home-page']
        )
        self.builder.get_object('label_plugin_details').set_text(
            plugin_info['Description']
        )

    def on_button_plugin_install_clicked(self, widget):
        log.debug('on_button_plugin_install_clicked')
        chooser = Gtk.FileChooserDialog(
            _('Select the Plugin'),
            self.pref_dialog,
            Gtk.FileChooserAction.OPEN,
            buttons=(
                _('_Cancel'),
                Gtk.ResponseType.CANCEL,
                _('_Open'),
                Gtk.ResponseType.OK,
            ),
        )

        chooser.set_transient_for(self.pref_dialog)
        chooser.set_select_multiple(False)
        chooser.set_property('skip-taskbar-hint', True)

        file_filter = Gtk.FileFilter()
        file_filter.set_name(_('Plugin Eggs'))
        file_filter.add_pattern('*.' + 'egg')
        chooser.add_filter(file_filter)

        # Run the dialog
        response = chooser.run()

        if response == Gtk.ResponseType.OK:
            filepath = deluge.common.decode_bytes(chooser.get_filename())
        else:
            chooser.destroy()
            return

        import shutil
        from base64 import b64encode

        filename = os.path.split(filepath)[1]
        shutil.copyfile(filepath, os.path.join(get_config_dir(), 'plugins', filename))

        component.get('PluginManager').scan_for_plugins()

        if not client.is_localhost():
            # We need to send this plugin to the daemon
            with open(filepath, 'rb') as _file:
                filedump = b64encode(_file.read())
            client.core.upload_plugin(filename, filedump)

        client.core.rescan_plugins()
        chooser.destroy()
        # We need to re-show the preferences dialog to show the new plugins
        self.show()

    def on_button_rescan_plugins_clicked(self, widget):
        component.get('PluginManager').scan_for_plugins()
        if client.connected():
            client.core.rescan_plugins()
        self.show()

    def on_button_find_plugins_clicked(self, widget):
        deluge.common.open_url_in_browser('http://dev.deluge-torrent.org/wiki/Plugins')

    def on_combo_encryption_changed(self, widget):
        combo_encin = self.builder.get_object('combo_encin').get_active()
        combo_encout = self.builder.get_object('combo_encout').get_active()
        combo_enclevel = self.builder.get_object('combo_enclevel')

        # If incoming and outgoing both set to disabled, disable level combobox
        if combo_encin == 2 and combo_encout == 2:
            combo_enclevel.set_sensitive(False)
        elif self.is_connected:
            combo_enclevel.set_sensitive(True)

    def on_combo_proxy_type_changed(self, widget):
        proxy_type = self.builder.get_object('combo_proxy_type').get_active()
        proxy_entries = [
            'label_proxy_host',
            'entry_proxy_host',
            'label_proxy_port',
            'spin_proxy_port',
            'label_proxy_pass',
            'entry_proxy_pass',
            'label_proxy_user',
            'entry_proxy_user',
            'chk_proxy_host_resolve',
            'chk_proxy_peer_conn',
            'chk_proxy_tracker_conn',
        ]

        # 0: None, 1: Socks4, 2: Socks5, 3: Socks5 Auth, 4: HTTP, 5: HTTP Auth, 6: I2P
        show_entries = []
        if proxy_type > 0:
            show_entries.extend(
                [
                    'label_proxy_host',
                    'entry_proxy_host',
                    'label_proxy_port',
                    'spin_proxy_port',
                    'chk_proxy_peer_conn',
                    'chk_proxy_tracker_conn',
                ]
            )
            if proxy_type in (3, 5):
                show_entries.extend(
                    [
                        'label_proxy_pass',
                        'entry_proxy_pass',
                        'label_proxy_user',
                        'entry_proxy_user',
                    ]
                )
            if proxy_type in (2, 3, 4, 5):
                show_entries.extend(['chk_proxy_host_resolve'])

        for entry in proxy_entries:
            if entry in show_entries:
                self.builder.get_object(entry).show()
            else:
                self.builder.get_object(entry).hide()

    def on_entry_proxy_host_paste_clipboard(self, widget):
        text = get_clipboard_text()
        log.debug('on_entry_proxy_host_paste-clipboard: got paste: %s', text)
        text = text if '//' in text else '//' + text
        parsed = urlparse(text)
        if parsed.hostname:
            widget.set_text(parsed.hostname)
            widget.emit_stop_by_name('paste-clipboard')
        if parsed.port:
            self.builder.get_object('spin_proxy_port').set_value(parsed.port)
        if parsed.username:
            self.builder.get_object('entry_proxy_user').set_text(parsed.username)
        if parsed.password:
            self.builder.get_object('entry_proxy_pass').set_text(parsed.password)

    def on_button_associate_magnet_clicked(self, widget):
        associate_magnet_links(True)

    def _get_accounts_tab_data(self):
        def on_ok(accounts):
            self.accounts_frame.show()
            self.on_get_known_accounts(accounts)

        def on_fail(failure):
            if failure.type == NotAuthorizedError:
                self.accounts_frame.hide()
            else:
                ErrorDialog(
                    _('Server Side Error'),
                    _('An error occurred on the server'),
                    parent=self.pref_dialog,
                    details=failure.getErrorMessage(),
                ).run()

        client.core.get_known_accounts().addCallback(on_ok).addErrback(on_fail)

    def on_get_known_accounts(self, known_accounts):
        known_accounts_to_log = []
        for account in known_accounts:
            account_to_log = {}
            for key, value in account.copy().items():
                if key == 'password':
                    value = '*' * len(value)
                account_to_log[key] = value
            known_accounts_to_log.append(account_to_log)
        log.debug('on_known_accounts: %s', known_accounts_to_log)

        self.accounts_liststore.clear()

        for account in known_accounts:
            accounts_iter = self.accounts_liststore.append()
            self.accounts_liststore.set_value(
                accounts_iter, ACCOUNTS_USERNAME, account['username']
            )
            self.accounts_liststore.set_value(
                accounts_iter, ACCOUNTS_LEVEL, account['authlevel']
            )
            self.accounts_liststore.set_value(
                accounts_iter, ACCOUNTS_PASSWORD, account['password']
            )

    def on_accounts_selection_changed(self, treeselection):
        log.debug('on_accounts_selection_changed')
        (model, itr) = treeselection.get_selected()
        if not itr:
            return
        level = model[itr][1]
        if level:
            self.builder.get_object('accounts_edit').set_sensitive(True)
            self.builder.get_object('accounts_delete').set_sensitive(True)
        else:
            self.builder.get_object('accounts_edit').set_sensitive(False)
            self.builder.get_object('accounts_delete').set_sensitive(False)

    @maybe_coroutine
    async def on_accounts_add_clicked(self, widget):
        dialog = AccountDialog(
            levels_mapping=client.auth_levels_mapping, parent=self.pref_dialog
        )
        response = await dialog.run()
        if response != Gtk.ResponseType.OK:
            return

        account = dialog.account
        try:
            await client.core.create_account(*account)
        except AuthManagerError as ex:
            return ErrorDialog(
                _('Error Adding Account'),
                _('Authentication failed'),
                parent=self.pref_dialog,
                details=ex,
            ).run()
        except Exception as ex:
            return ErrorDialog(
                _('Error Adding Account'),
                _(f'An error occurred while adding account: {account}'),
                parent=self.pref_dialog,
                details=ex,
            ).run()

        self.accounts_liststore.set(
            self.accounts_liststore.append(),
            [ACCOUNTS_USERNAME, ACCOUNTS_LEVEL, ACCOUNTS_PASSWORD],
            [account.username, account.authlevel, account.password],
        )

    def on_accounts_edit_clicked(self, widget):
        (model, itr) = self.accounts_listview.get_selection().get_selected()
        if not itr:
            return

        dialog = AccountDialog(
            model[itr][ACCOUNTS_USERNAME],
            model[itr][ACCOUNTS_PASSWORD],
            model[itr][ACCOUNTS_LEVEL],
            levels_mapping=client.auth_levels_mapping,
            parent=self.pref_dialog,
        )

        def dialog_finished(response_id):
            def update_ok(rc):
                model.set_value(itr, ACCOUNTS_PASSWORD, dialog.get_username())
                model.set_value(itr, ACCOUNTS_LEVEL, dialog.get_authlevel())

            def update_fail(failure):
                ErrorDialog(
                    _('Error Updating Account'),
                    _('An error occurred while updating account'),
                    parent=self.pref_dialog,
                    details=failure.getErrorMessage(),
                ).run()

            if response_id == Gtk.ResponseType.OK:
                client.core.update_account(
                    dialog.get_username(), dialog.get_password(), dialog.get_authlevel()
                ).addCallback(update_ok).addErrback(update_fail)

        dialog.run().addCallback(dialog_finished)

    def on_accounts_delete_clicked(self, widget):
        (model, itr) = self.accounts_listview.get_selection().get_selected()
        if not itr:
            return

        username = model[itr][0]
        header = _('Remove Account')
        text = _(
            'Are you sure you want to remove the account with the '
            'username "%(username)s"?' % {'username': username}
        )
        dialog = YesNoDialog(header, text, parent=self.pref_dialog)

        def dialog_finished(response_id):
            def remove_ok(rc):
                model.remove(itr)

            def remove_fail(failure):
                if failure.type == AuthManagerError:
                    ErrorDialog(
                        _('Error Removing Account'),
                        _('Auhentication failed'),
                        parent=self.pref_dialog,
                        details=failure.getErrorMessage(),
                    ).run()
                else:
                    ErrorDialog(
                        _('Error Removing Account'),
                        _('An error occurred while removing account'),
                        parent=self.pref_dialog,
                        details=failure.getErrorMessage(),
                    ).run()

            if response_id == Gtk.ResponseType.YES:
                client.core.remove_account(username).addCallback(remove_ok).addErrback(
                    remove_fail
                )

        dialog.run().addCallback(dialog_finished)

    def on_piecesbar_toggle_toggled(self, widget):
        self.gtkui_config['show_piecesbar'] = widget.get_active()
        colors_widget = self.builder.get_object('piecebar_colors_expander')
        colors_widget.set_visible(widget.get_active())

    def on_urldetect_toggle_toggled(self, widget):
        self.gtkui_config['detect_urls'] = widget.get_active()

    def on_checkbutton_language_toggled(self, widget):
        self.language_combo.set_visible(not self.language_checkbox.get_active())

    def on_completed_color_set(self, widget):
        self.__set_color('completed')

    def on_revert_color_completed_clicked(self, widget):
        self.__revert_color('completed')

    def on_downloading_color_set(self, widget):
        self.__set_color('downloading')

    def on_revert_color_downloading_clicked(self, widget):
        self.__revert_color('downloading')

    def on_waiting_color_set(self, widget):
        self.__set_color('waiting')

    def on_revert_color_waiting_clicked(self, widget):
        self.__revert_color('waiting')

    def on_missing_color_set(self, widget):
        self.__set_color('missing')

    def on_revert_color_missing_clicked(self, widget):
        self.__revert_color('missing')

    def __set_color(self, state, from_config=False):
        if from_config:
            color = Color(*self.gtkui_config['pieces_color_%s' % state])
            log.debug(
                'Setting %r color state from config to %s',
                state,
                (color.red, color.green, color.blue),
            )
            self.builder.get_object('%s_color' % state).set_color(color)
        else:
            color = self.builder.get_object('%s_color' % state).get_color()
            log.debug(
                'Setting %r color state to %s',
                state,
                (color.red, color.green, color.blue),
            )
            self.gtkui_config['pieces_color_%s' % state] = [
                color.red,
                color.green,
                color.blue,
            ]
            self.gtkui_config.save()
            self.gtkui_config.apply_set_functions('pieces_colors')

        self.builder.get_object('revert_color_%s' % state).set_sensitive(
            [color.red, color.green, color.blue] != self.COLOR_DEFAULTS[state]
        )

    def __revert_color(self, state, from_config=False):
        log.debug('Reverting %r color state', state)
        self.builder.get_object('%s_color' % state).set_color(
            Color(*self.COLOR_DEFAULTS[state])
        )
        self.builder.get_object('revert_color_%s' % state).set_sensitive(False)
        self.gtkui_config.apply_set_functions('pieces_colors')
