deluge/deluge/ui/gtkui/preferences.py
2014-07-05 19:43:07 +01:00

1296 lines
57 KiB
Python

#
# preferences.py
#
# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
#
import os
import pygtk
pygtk.require('2.0')
import gtk
import logging
from hashlib import sha1 as sha
import deluge.component as component
from deluge.ui.client import client
from deluge.ui.gtkui.path_chooser import PathChooser
import deluge.common
import common
import dialogs
from deluge.configmanager import ConfigManager
import deluge.configmanager
from deluge.error import AuthManagerError, NotAuthorizedError
log = logging.getLogger(__name__)
ACCOUNTS_USERNAME, ACCOUNTS_LEVEL, ACCOUNTS_PASSWORD = range(3)
COLOR_MISSING, COLOR_WAITING, COLOR_DOWNLOADING, COLOR_COMPLETED = 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(
"deluge.ui.gtkui", 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(common.get_deluge_icon())
self.treeview = self.builder.get_object("treeview")
self.notebook = self.builder.get_object("notebook")
self.gtkui_config = ConfigManager("gtkui.conf")
self.window_open = False
self.load_pref_dialog_state()
self.builder.get_object("image_magnet").set_from_file(
deluge.common.get_pixmap("magnet.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)
self.treeview.set_model(self.liststore)
render = gtk.CellRendererText()
column = gtk.TreeViewColumn(_("Categories"), render, text=1)
self.treeview.append_column(column)
# Add the default categories
i = 0
for category in (_("Interface"), _("Downloads"), _("Network"), _("Bandwidth"), _("Other"),
_("Daemon"), _("Queue"), _("Proxy"), _("Cache"), _("Plugins"), "_separator_"):
self.liststore.append([i, category])
i += 1
def set_separator(model, iter, data=None):
if "_separator_" == model.get_value(iter, 1):
return True
self.treeview.set_row_separator_func(set_separator)
# 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.SORT_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.SORT_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({
"on_pref_dialog_delete_event": self.on_pref_dialog_delete_event,
"on_button_ok_clicked": self.on_button_ok_clicked,
"on_button_apply_clicked": self.on_button_apply_clicked,
"on_button_cancel_clicked": self.on_button_cancel_clicked,
"on_toggle": self.on_toggle,
"on_test_port_clicked": self.on_test_port_clicked,
"on_button_plugin_install_clicked": self._on_button_plugin_install_clicked,
"on_button_rescan_plugins_clicked": self._on_button_rescan_plugins_clicked,
"on_button_find_plugins_clicked": self._on_button_find_plugins_clicked,
"on_button_cache_refresh_clicked": self._on_button_cache_refresh_clicked,
"on_combo_encryption_changed": self._on_combo_encryption_changed,
"on_combo_proxy_type_changed": self._on_combo_proxy_type_changed,
"on_button_associate_magnet_clicked": self._on_button_associate_magnet_clicked,
"on_accounts_add_clicked": self._on_accounts_add_clicked,
"on_accounts_delete_clicked": self._on_accounts_delete_clicked,
"on_accounts_edit_clicked": self._on_accounts_edit_clicked,
"on_piecesbar_toggle_toggled": self._on_piecesbar_toggle_toggled,
"on_completed_color_set": self._on_completed_color_set,
"on_revert_color_completed_clicked": self._on_revert_color_completed_clicked,
"on_downloading_color_set": self._on_downloading_color_set,
"on_revert_color_downloading_clicked": self._on_revert_color_downloading_clicked,
"on_waiting_color_set": self._on_waiting_color_set,
"on_revert_color_waiting_clicked": self._on_revert_color_waiting_clicked,
"on_missing_color_set": self._on_missing_color_set,
"on_revert_color_missing_clicked": self._on_revert_color_missing_clicked,
"on_pref_dialog_configure_event": self.on_pref_dialog_configure_event,
"on_checkbutton_language_toggled": self._on_checkbutton_language_toggled,
})
from deluge.ui.gtkui.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")
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")
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")
self.copy_torrents_to_hbox.add(self.copy_torrent_files_path_chooser)
self.copy_torrents_to_hbox.show_all()
def load_languages(self):
from deluge.ui import languages # Import here so that gettext has been setup first
translations_path = deluge.common.get_translations_path()
for root, dirs, files in os.walk(translations_path):
# Get the dirs
break
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()
index = -1
for i, lang_code in enumerate(sorted(dirs)):
name = "%s (Language name missing)" % lang_code
if lang_code in languages.LANGUAGES:
name = languages.LANGUAGES[lang_code]
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_sensitive(False)
elif 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.VBox()
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)
align = gtk.Alignment()
align.set_padding(5, 0, 0, 0)
align.set(0, 0, 1, 1)
align.add(widget)
vbox.pack_start(align, True, True, 0)
scrolled = gtk.ScrolledWindow()
viewport = gtk.Viewport()
viewport.set_shadow_type(gtk.SHADOW_NONE)
viewport.add(vbox)
scrolled.add(viewport)
scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolled.show_all()
# Add this page to the notebook
index = self.notebook.append_page(scrolled)
self.liststore.append([index, 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 check_row(model, path, iter, user_data):
row_name = 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
self.liststore.foreach(check_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 i, (index, name) in enumerate(self.liststore):
self.liststore[i][0] = i
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_cache_status().addCallback(_on_get_cache_status)
def _on_get_cache_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_port_min": ("value", lambda: self.core_config["listen_ports"][0]),
"spin_port_max": ("value", lambda: self.core_config["listen_ports"][1]),
"chk_random_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_peer_tos": ("text", "peer_tos"),
"chk_dht": ("active", "dht"),
"chk_upnp": ("active", "upnp"),
"chk_natpmp": ("active", "natpmp"),
"chk_utpex": ("active", "utpex"),
"chk_lt_tex": ("active", "lt_tex"),
"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_seed_ratio": ("active", "stop_seed_at_ratio"),
"spin_share_ratio": ("value", "stop_seed_ratio"),
"chk_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"]),
"entry_i2p_host": ("text", lambda: self.core_config["i2p_proxy"]["hostname"]),
"spin_i2p_port": ("value", lambda: self.core_config["i2p_proxy"]["port"]),
"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.keys():
modifier = core_widgets[key][0]
if type(key) is str:
widget = self.builder.get_object(key)
else:
widget = key
widget.set_sensitive(self.is_connected)
if self.is_connected:
value = core_widgets[key][1]
from types import FunctionType
if type(value) is FunctionType:
value = value()
elif type(value) is str:
value = self.core_config[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.keys():
if type(key) is str:
widget = self.builder.get_object(key)
else:
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("chk_enable_appindicator").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_classic").set_active(
self.gtkui_config["classic_mode"])
self.builder.get_object("radio_thinclient").set_active(
not self.gtkui_config["classic_mode"])
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.__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:
if plugin in enabled_plugins:
enabled = True
else:
enabled = False
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
"""
classic_mode_was_set = self.gtkui_config["classic_mode"]
# 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_port_min").get_value_as_int(),
self.builder.get_object("spin_port_max").get_value_as_int()
)
new_core_config["listen_ports"] = listen_ports
new_core_config["random_port"] = \
self.builder.get_object("chk_random_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()
if deluge.common.is_ip(incoming_address) or not incoming_address:
new_core_config["listen_interface"] = incoming_address
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["lt_tex"] = \
self.builder.get_object("chk_lt_tex").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("chk_enable_appindicator").get_active()
new_gtkui_config["lock_tray"] = \
self.builder.get_object("chk_lock_tray").get_active()
passhex = sha(self.builder.get_object("txt_tray_password").get_text()).hexdigest()
if passhex != "c07eb5a8c0dc7bb81c217b67f11c3b7a5e95ffd7":
new_gtkui_config["tray_password"] = passhex
new_gtkui_in_classic_mode = self.builder.get_object("radio_classic").get_active()
new_gtkui_config["classic_mode"] = new_gtkui_in_classic_mode
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"] = {}
new_core_config["proxy"]["type"] = self.builder.get_object("combo_proxy_type").get_active()
new_core_config["proxy"]["username"] = self.builder.get_object("entry_proxy_user").get_text()
new_core_config["proxy"]["password"] = self.builder.get_object("entry_proxy_pass").get_text()
new_core_config["proxy"]["hostname"] = self.builder.get_object("entry_proxy_host").get_text()
new_core_config["proxy"]["port"] = self.builder.get_object("spin_proxy_port").get_value_as_int()
new_core_config["proxy"]["proxy_hostnames"] = self.builder.get_object("chk_proxy_host_resolve").get_active()
new_core_config["proxy"]["proxy_peer_connections"] = self.builder.get_object(
"chk_proxy_peer_conn").get_active()
new_core_config["i2p_proxy"] = {}
new_core_config["i2p_proxy"]["hostname"] = self.builder.get_object("entry_i2p_host").get_text()
new_core_config["i2p_proxy"]["port"] = self.builder.get_object("spin_i2p_port").get_value_as_int()
## 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_seed_ratio").get_active()
new_core_config["remove_seed_at_ratio"] = \
self.builder.get_object("chk_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()
# Lanuage
if self.language_checkbox.get_active():
new_gtkui_config["language"] = None
else:
active = self.language_combo.get_active()
if active == -1:
dialog = dialogs.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 = dialogs.InformationDialog(
_("Attention"),
_("You must now restart the deluge UI for the changes to take effect.")
)
dialog.run()
# GtkUI
for key in new_gtkui_config.keys():
# 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.keys():
# 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 classic_mode_was_set and not new_gtkui_in_classic_mode:
def on_response(response):
if response == gtk.RESPONSE_NO:
# Set each changed config value in the core
self.gtkui_config["classic_mode"] = True
self.builder.get_object("radio_classic").set_active(True)
else:
client.disconnect()
component.stop()
self.builder.get_object("radio_thinclient").set_active(True)
dialog = dialogs.YesNoDialog(
_("Attention"),
_("Your current session will be stopped. Continue?")
)
dialog.run().addCallback(on_response)
elif not classic_mode_was_set and new_gtkui_in_classic_mode:
dialog = dialogs.InformationDialog(
_("Attention"),
_("You must now restart the deluge UI")
)
dialog.run()
def hide(self):
self.window_open = False
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
for widget_name in ('label_cache_blocks_written', 'label_cache_writes', 'label_cache_write_hit_ratio',
'label_cache_blocks_read', 'label_cache_blocks_read_hit', 'label_cache_read_hit_ratio',
'label_cache_reads', 'label_cache_cache_size', 'label_cache_read_cache_size'):
widget = self.builder.get_object(widget_name)
key = widget_name[len("label_cache_"):]
value = self.cache_status[key]
if type(value) == float:
value = "%.2f" % value
else:
value = str(value)
widget.set_text(value)
def _on_button_cache_refresh_clicked(self, widget):
def on_get_cache_status(status):
self.cache_status = status
self.__update_cache_status()
client.core.get_cache_status().addCallback(on_get_cache_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:
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_port": {"spin_port_min": False,
"spin_port_max": False},
"chk_random_outgoing_ports": {"spin_outgoing_port_min": False,
"spin_outgoing_port_max": False},
"chk_use_tray": {"chk_min_on_close": True,
"chk_start_in_tray": True,
"chk_enable_appindicator": 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_seed_ratio": {"spin_share_ratio": True,
"chk_remove_ratio": True}
}
def update_dependent_widgets(name, value):
dependency = dependents[name]
for dep in dependency.keys():
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 and self.is_connected)
if dep in dependents:
update_dependent_widgets(dep, depwidget.get_active() and sensitive)
for key in dependents.keys():
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):
if status:
self.builder.get_object("port_img").set_from_stock(gtk.STOCK_YES, 4)
self.builder.get_object("port_img").show()
else:
self.builder.get_object("port_img").set_from_stock(gtk.STOCK_DIALOG_WARNING, 4)
self.builder.get_object("port_img").show()
client.core.test_listen_port().addCallback(on_get_test)
# XXX: Consider using gtk.Spinner() instead of the loading gif
# It requires gtk.ver > 2.12
self.builder.get_object("port_img").set_from_file(
deluge.common.get_pixmap('loading.gif')
)
self.builder.get_object("port_img").show()
client.force_call()
def on_plugin_toggled(self, renderer, path):
log.debug("on_plugin_toggled")
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)
self.plugin_liststore.set_value(row, 1, not value)
if not value:
client.core.enable_plugin(name)
else:
client.core.disable_plugin(name)
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.FILE_CHOOSER_ACTION_OPEN,
buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN,
gtk.RESPONSE_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.RESPONSE_OK:
filepath = chooser.get_filename()
else:
chooser.destroy()
return
import base64
import shutil
import os.path
filename = os.path.split(filepath)[1]
shutil.copyfile(
filepath,
os.path.join(deluge.configmanager.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
filedump = base64.encodestring(open(filepath, "rb").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()
hides = []
shows = []
# 0:"None"
if proxy_type == 0:
hides.extend(["entry_proxy_pass", "entry_proxy_user", "entry_proxy_host", "spin_proxy_port",
"label_proxy_pass", "label_proxy_user", "label_proxy_host", "label_proxy_port",
"chk_proxy_host_resolve", "chk_proxy_peer_conn"])
# 1:"Socks4", 2:"Socks5", 4:"HTTP"
elif proxy_type in (1, 2, 4):
if proxy_type in (2, 4):
shows.extend(["chk_proxy_host_resolve"])
hides.extend(["entry_proxy_pass", "entry_proxy_user", "label_proxy_pass", "label_proxy_user"])
shows.extend(["entry_proxy_host", "spin_proxy_port", "label_proxy_host",
"label_proxy_port", "chk_proxy_peer_conn"])
# 3:"Socks5 Auth", 5:"HTTP Auth"
elif proxy_type in (3, 5):
shows.extend(["entry_proxy_pass", "entry_proxy_user", "entry_proxy_host", "spin_proxy_port",
"label_proxy_pass", "label_proxy_user", "label_proxy_host", "label_proxy_port",
"chk_proxy_host_resolve", "chk_proxy_peer_conn"])
for hide_entry in hides:
self.builder.get_object(hide_entry).hide()
for show_entry in shows:
self.builder.get_object(show_entry).show()
def _on_button_associate_magnet_clicked(self, widget):
common.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:
dialogs.ErrorDialog(
_("Server Side Error"),
_("An error ocurred 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().iteritems():
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:
iter = self.accounts_liststore.append()
self.accounts_liststore.set_value(
iter, ACCOUNTS_USERNAME, account['username']
)
self.accounts_liststore.set_value(
iter, ACCOUNTS_LEVEL, account['authlevel']
)
self.accounts_liststore.set_value(
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
username = model[itr][0]
if username:
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)
def _on_accounts_add_clicked(self, widget):
dialog = dialogs.AccountDialog(
levels_mapping=client.auth_levels_mapping,
parent=self.pref_dialog
)
def dialog_finished(response_id):
username = dialog.get_username()
password = dialog.get_password()
authlevel = dialog.get_authlevel()
def add_ok(rv):
iter = self.accounts_liststore.append()
self.accounts_liststore.set_value(
iter, ACCOUNTS_USERNAME, username
)
self.accounts_liststore.set_value(
iter, ACCOUNTS_LEVEL, authlevel
)
self.accounts_liststore.set_value(
iter, ACCOUNTS_PASSWORD, password
)
def add_fail(failure):
if failure.type == AuthManagerError:
dialogs.ErrorDialog(
_("Error Adding Account"),
_("Authentication failed"),
parent=self.pref_dialog, details=failure.getErrorMessage()
).run()
else:
dialogs.ErrorDialog(
_("Error Adding Account"),
_("An error ocurred while adding account"),
parent=self.pref_dialog, details=failure.getErrorMessage()
).run()
if response_id == gtk.RESPONSE_OK:
client.core.create_account(
username, password, authlevel
).addCallback(add_ok).addErrback(add_fail)
dialog.run().addCallback(dialog_finished)
def _on_accounts_edit_clicked(self, widget):
(model, itr) = self.accounts_listview.get_selection().get_selected()
if not itr:
return
dialog = dialogs.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):
dialogs.ErrorDialog(
_("Error Updating Account"),
_("An error ocurred while updating account"),
parent=self.pref_dialog, details=failure.getErrorMessage()
).run()
if response_id == gtk.RESPONSE_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 wan't do remove the account with the "
"username \"%(username)s\"?" % dict(username=username))
dialog = dialogs.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:
dialogs.ErrorDialog(
_("Error Removing Account"),
_("Auhentication failed"),
parent=self.pref_dialog, details=failure.getErrorMessage()
).run()
else:
dialogs.ErrorDialog(
_("Error Removing Account"),
_("An error ocurred while removing account"),
parent=self.pref_dialog, details=failure.getErrorMessage()
).run()
if response_id == gtk.RESPONSE_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_checkbutton_language_toggled(self, widget):
self.language_combo.set_sensitive(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 = gtk.gdk.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(
gtk.gdk.Color(*self.COLOR_DEFAULTS[state])
)
self.builder.get_object("revert_color_%s" % state).set_sensitive(False)
self.gtkui_config.apply_set_functions("pieces_colors")