# # preferences.py # # Copyright (C) 2007, 2008 Andrew Resch ('andar') # # 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 2 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 pygtk pygtk.require('2.0') import gtk, gtk.glade import pkg_resources import deluge.component as component from deluge.log import LOG as log from deluge.ui.client import aclient as client import deluge.common import deluge.error from deluge.configmanager import ConfigManager class Preferences(component.Component): def __init__(self): component.Component.__init__(self, "Preferences") self.window = component.get("MainWindow") self.glade = gtk.glade.XML( pkg_resources.resource_filename("deluge.ui.gtkui", "glade/preferences_dialog.glade")) self.pref_dialog = self.glade.get_widget("pref_dialog") self.pref_dialog.set_icon(deluge.common.get_logo(32)) self.treeview = self.glade.get_widget("treeview") self.notebook = self.glade.get_widget("notebook") self.gtkui_config = ConfigManager("gtkui.conf") # 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 ["Downloads", "Network", "Bandwidth", "Interface", "Other", "Daemon", "Queue", "Plugins"]: self.liststore.append([i, category]) i += 1 # Setup plugin tab listview self.plugin_liststore = gtk.ListStore(str, bool) self.plugin_listview = self.glade.get_widget("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=0)) # 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.glade.signal_autoconnect({ "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 }) # These get updated by requests done to the core self.all_plugins = [] self.enabled_plugins = [] 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 widget.unparent() vbox = gtk.VBox() label = gtk.Label() label.set_use_markup(True) label.set_markup("" + name + "") label.set_alignment(0.05, 0.50) label.set_padding(0, 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.add(widget) vbox.pack_start(align, False, False, 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 != None: self.notebook.remove_page(self.page_num_to_remove) if self.iter_to_remove != None: self.liststore.remove(self.iter_to_remove) def _on_get_config(self, config): self.core_config = config def _on_get_available_plugins(self, plugins): self.all_plugins = plugins def _on_get_enabled_plugins(self, plugins): self.enabled_plugins = plugins def _on_get_listen_port(self, port): self.active_port = port def show(self, page=None): """Page should be the string in the left list.. ie, 'Network' or 'Bandwidth'""" if page != None: for (index, string) in self.liststore: if page == string: self.treeview.get_selection().select_path(index) break # Update the preferences dialog to reflect current config settings self.core_config = {} try: client.get_config(self._on_get_config) client.get_available_plugins(self._on_get_available_plugins) client.get_enabled_plugins(self._on_get_enabled_plugins) client.get_listen_port(self._on_get_listen_port) # Force these calls and block until we've done them all client.force_call() except deluge.error.NoCoreError: log.debug("Not connected to a daemon..") if self.core_config != {} and self.core_config != None: core_widgets = { "download_path_button": \ ("filename", self.core_config["download_location"]), "chk_move_completed": \ ("active", self.core_config["move_completed"]), "move_completed_path_button": \ ("filename", self.core_config["move_completed_path"]), "chk_copy_torrent_file": \ ("active", self.core_config["copy_torrent_file"]), "torrent_files_button": \ ("filename", self.core_config["torrentfiles_location"]), "chk_autoadd": \ ("active", self.core_config["autoadd_enable"]), "folder_autoadd": \ ("filename", self.core_config["autoadd_location"]), "radio_compact_allocation": \ ("active", self.core_config["compact_allocation"]), "radio_full_allocation": \ ("not_active", self.core_config["compact_allocation"]), "chk_prioritize_first_last_pieces": \ ("active", self.core_config["prioritize_first_last_pieces"]), "spin_port_min": ("value", self.core_config["listen_ports"][0]), "spin_port_max": ("value", self.core_config["listen_ports"][1]), "active_port_label": ("text", str(self.active_port)), "chk_random_port": ("active", self.core_config["random_port"]), "chk_dht": ("active", self.core_config["dht"]), "chk_upnp": ("active", self.core_config["upnp"]), "chk_natpmp": ("active", self.core_config["natpmp"]), "chk_utpex": ("active", self.core_config["utpex"]), "chk_lsd": ("active", self.core_config["lsd"]), "combo_encin": ("active", self.core_config["enc_in_policy"]), "combo_encout": ("active", self.core_config["enc_out_policy"]), "combo_enclevel": ("active", self.core_config["enc_level"]), "chk_pref_rc4": ("active", self.core_config["enc_prefer_rc4"]), "spin_max_connections_global": \ ("value", self.core_config["max_connections_global"]), "spin_max_download": \ ("value", self.core_config["max_download_speed"]), "spin_max_upload": \ ("value", self.core_config["max_upload_speed"]), "spin_max_upload_slots_global": \ ("value", self.core_config["max_upload_slots_global"]), "spin_max_connections_per_torrent": \ ("value", self.core_config["max_connections_per_torrent"]), "spin_max_upload_slots_per_torrent": \ ("value", self.core_config["max_upload_slots_per_torrent"]), "spin_max_download_per_torrent": \ ("value", self.core_config["max_download_speed_per_torrent"]), "spin_max_upload_per_torrent": \ ("value", self.core_config["max_upload_speed_per_torrent"]), "spin_daemon_port": \ ("value", self.core_config["daemon_port"]), "chk_allow_remote_connections": \ ("active", self.core_config["allow_remote"]), "spin_active": ("value", self.core_config["max_active_limit"]), "spin_seeding": ("value", self.core_config["max_active_seeding"]), "spin_downloading": ("value", self.core_config["max_active_downloading"]), "chk_dont_count_slow_torrents": ("active", self.core_config["dont_count_slow_torrents"]), "chk_queue_new_top": ("active", self.core_config["queue_new_to_top"]), "spin_share_ratio_limit": ("value", self.core_config["share_ratio_limit"]), "spin_seed_time_ratio_limit": \ ("value", self.core_config["seed_time_ratio_limit"]), "spin_seed_time_limit": ("value", self.core_config["seed_time_limit"]), "chk_seed_ratio": ("active", self.core_config["stop_seed_at_ratio"]), "spin_share_ratio": ("value", self.core_config["stop_seed_ratio"]), "chk_remove_ratio": ("active", self.core_config["remove_seed_at_ratio"]) } # Change a few widgets if we're connected to a remote host if not client.is_localhost(): self.glade.get_widget("entry_download_path").show() self.glade.get_widget("download_path_button").hide() core_widgets.pop("download_path_button") core_widgets["entry_download_path"] = ("text", self.core_config["download_location"]) self.glade.get_widget("entry_move_completed_path").show() self.glade.get_widget("move_completed_path_button").hide() core_widgets.pop("move_completed_path_button") core_widgets["entry_move_completed_path"] = ("text", self.core_config["move_completed_path"]) self.glade.get_widget("entry_torrents_path").show() self.glade.get_widget("torrent_files_button").hide() core_widgets.pop("torrent_files_button") core_widgets["entry_torrents_path"] = ("text", self.core_config["torrentfiles_location"]) self.glade.get_widget("entry_autoadd").show() self.glade.get_widget("folder_autoadd").hide() core_widgets.pop("folder_autoadd") core_widgets["entry_autoadd"] = ("text", self.core_config["autoadd_location"]) else: self.glade.get_widget("entry_download_path").hide() self.glade.get_widget("download_path_button").show() self.glade.get_widget("entry_move_completed_path").hide() self.glade.get_widget("move_completed_path_button").show() self.glade.get_widget("entry_torrents_path").hide() self.glade.get_widget("torrent_files_button").show() self.glade.get_widget("entry_autoadd").hide() self.glade.get_widget("folder_autoadd").show() # Update the widgets accordingly for key in core_widgets.keys(): modifier = core_widgets[key][0] value = core_widgets[key][1] widget = self.glade.get_widget(key) if type(widget) == gtk.FileChooserButton: for child in widget.get_children(): child.set_sensitive(True) widget.set_sensitive(True) if modifier == "filename": if value: try: widget.set_current_folder(value) except Exception, e: log.debug("Unable to set_current_folder: %s", e) elif modifier == "active": widget.set_active(value) elif modifier == "not_active": widget.set_active(not value) elif modifier == "value": widget.set_value(value) elif modifier == "text": widget.set_text(value) else: core_widget_list = [ "download_path_button", "chk_move_completed", "move_completed_path_button", "chk_copy_torrent_file", "torrent_files_button", "chk_autoadd", "folder_autoadd", "radio_compact_allocation", "radio_full_allocation", "chk_prioritize_first_last_pieces", "spin_port_min", "spin_port_max", "active_port_label", "chk_random_port", "chk_dht", "chk_upnp", "chk_natpmp", "chk_utpex", "chk_lsd", "combo_encin", "combo_encout", "combo_enclevel", "chk_pref_rc4", "spin_max_connections_global", "spin_max_download", "spin_max_upload", "spin_max_upload_slots_global", "spin_max_connections_per_torrent", "spin_max_upload_slots_per_torrent", "spin_max_download_per_torrent", "spin_max_upload_per_torrent", "spin_daemon_port", "chk_allow_remote_connections", "spin_seeding", "spin_downloading", "spin_active", "chk_dont_count_slow_torrents", "chk_queue_new_top", "chk_seed_ratio", "spin_share_ratio", "chk_remove_ratio", "spin_share_ratio_limit", "spin_seed_time_ratio_limit", "spin_seed_time_limit" ] # We don't appear to be connected to a daemon for key in core_widget_list: widget = self.glade.get_widget(key) if type(widget) == gtk.FileChooserButton: for child in widget.get_children(): child.set_sensitive(False) widget.set_sensitive(False) ## Downloads tab ## self.glade.get_widget("chk_show_dialog").set_active( self.gtkui_config["interactive_add"]) self.glade.get_widget("chk_focus_dialog").set_active( self.gtkui_config["focus_add_dialog"]) ## Interface tab ## self.glade.get_widget("chk_use_tray").set_active( self.gtkui_config["enable_system_tray"]) self.glade.get_widget("chk_min_on_close").set_active( self.gtkui_config["close_to_tray"]) self.glade.get_widget("chk_start_in_tray").set_active( self.gtkui_config["start_in_tray"]) self.glade.get_widget("chk_lock_tray").set_active( self.gtkui_config["lock_tray"]) ## Other tab ## self.glade.get_widget("chk_new_releases").set_active( self.gtkui_config["check_new_releases"]) self.glade.get_widget("chk_send_info").set_active( self.gtkui_config["send_info"]) ## 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) # Now show the dialog self.pref_dialog.show() def set_config(self): """Sets all altered config values in the core""" import sha # Get the values from the dialog new_core_config = {} new_gtkui_config = {} ## Downloads tab ## new_gtkui_config["interactive_add"] = \ self.glade.get_widget("chk_show_dialog").get_active() new_gtkui_config["focus_add_dialog"] = \ self.glade.get_widget("chk_focus_dialog").get_active() new_core_config["copy_torrent_file"] = \ self.glade.get_widget("chk_copy_torrent_file").get_active() new_core_config["move_completed"] = \ self.glade.get_widget("chk_move_completed").get_active() if client.is_localhost(): new_core_config["download_location"] = \ self.glade.get_widget("download_path_button").get_filename() new_core_config["move_completed_path"] = \ self.glade.get_widget("move_completed_path_button").get_filename() new_core_config["torrentfiles_location"] = \ self.glade.get_widget("torrent_files_button").get_filename() else: new_core_config["download_location"] = \ self.glade.get_widget("entry_download_path").get_text() new_core_config["move_completed_path"] = \ self.glade.get_widget("entry_move_completed_path").get_text() new_core_config["torrentfiles_location"] = \ self.glade.get_widget("entry_torrents_path").get_text() new_core_config["autoadd_enable"] = \ self.glade.get_widget("chk_autoadd").get_active() if client.is_localhost(): new_core_config["autoadd_location"] = \ self.glade.get_widget("folder_autoadd").get_filename() else: new_core_config["autoadd_location"] = \ self.glade.get_widget("entry_autoadd").get_text() new_core_config["compact_allocation"] = \ self.glade.get_widget("radio_compact_allocation").get_active() new_core_config["prioritize_first_last_pieces"] = \ self.glade.get_widget( "chk_prioritize_first_last_pieces").get_active() ## Network tab ## listen_ports = [] listen_ports.append( self.glade.get_widget("spin_port_min").get_value_as_int()) listen_ports.append( self.glade.get_widget("spin_port_max").get_value_as_int()) new_core_config["listen_ports"] = listen_ports new_core_config["random_port"] = \ self.glade.get_widget("chk_random_port").get_active() new_core_config["dht"] = self.glade.get_widget("chk_dht").get_active() new_core_config["upnp"] = self.glade.get_widget("chk_upnp").get_active() new_core_config["natpmp"] = \ self.glade.get_widget("chk_natpmp").get_active() new_core_config["utpex"] = \ self.glade.get_widget("chk_utpex").get_active() new_core_config["lsd"] = \ self.glade.get_widget("chk_lsd").get_active() new_core_config["enc_in_policy"] = \ self.glade.get_widget("combo_encin").get_active() new_core_config["enc_out_policy"] = \ self.glade.get_widget("combo_encout").get_active() new_core_config["enc_level"] = \ self.glade.get_widget("combo_enclevel").get_active() new_core_config["enc_prefer_rc4"] = \ self.glade.get_widget("chk_pref_rc4").get_active() ## Bandwidth tab ## new_core_config["max_connections_global"] = \ self.glade.get_widget( "spin_max_connections_global").get_value_as_int() new_core_config["max_download_speed"] = \ self.glade.get_widget("spin_max_download").get_value() new_core_config["max_upload_speed"] = \ self.glade.get_widget("spin_max_upload").get_value() new_core_config["max_upload_slots_global"] = \ self.glade.get_widget( "spin_max_upload_slots_global").get_value_as_int() new_core_config["max_connections_per_torrent"] = \ self.glade.get_widget( "spin_max_connections_per_torrent").get_value_as_int() new_core_config["max_upload_slots_per_torrent"] = \ self.glade.get_widget( "spin_max_upload_slots_per_torrent").get_value_as_int() new_core_config["max_upload_speed_per_torrent"] = \ self.glade.get_widget( "spin_max_upload_per_torrent").get_value() new_core_config["max_download_speed_per_torrent"] = \ self.glade.get_widget( "spin_max_download_per_torrent").get_value() ## Interface tab ## new_gtkui_config["enable_system_tray"] = \ self.glade.get_widget("chk_use_tray").get_active() new_gtkui_config["close_to_tray"] = \ self.glade.get_widget("chk_min_on_close").get_active() new_gtkui_config["start_in_tray"] = \ self.glade.get_widget("chk_start_in_tray").get_active() new_gtkui_config["lock_tray"] = \ self.glade.get_widget("chk_lock_tray").get_active() passhex = sha.new(self.glade.get_widget("txt_tray_password").get_text())\ .hexdigest() if passhex != "c07eb5a8c0dc7bb81c217b67f11c3b7a5e95ffd7": new_gtkui_config["tray_password"] = passhex ## Other tab ## new_gtkui_config["check_new_releases"] = \ self.glade.get_widget("chk_new_releases").get_active() new_gtkui_config["send_info"] = \ self.glade.get_widget("chk_send_info").get_active() ## Daemon tab ## new_core_config["daemon_port"] = \ self.glade.get_widget("spin_daemon_port").get_value_as_int() new_core_config["allow_remote"] = \ self.glade.get_widget("chk_allow_remote_connections").get_active() ## Queue tab ## new_core_config["queue_new_to_top"] = \ self.glade.get_widget("chk_queue_new_top").get_active() new_core_config["max_active_seeding"] = \ self.glade.get_widget("spin_seeding").get_value_as_int() new_core_config["max_active_downloading"] = \ self.glade.get_widget("spin_downloading").get_value_as_int() new_core_config["max_active_limit"] = \ self.glade.get_widget("spin_active").get_value_as_int() new_core_config["dont_count_slow_torrents"] = \ self.glade.get_widget("chk_dont_count_slow_torrents").get_active() new_core_config["stop_seed_at_ratio"] = \ self.glade.get_widget("chk_seed_ratio").get_active() new_core_config["remove_seed_at_ratio"] = \ self.glade.get_widget("chk_remove_ratio").get_active() new_core_config["stop_seed_ratio"] = \ self.glade.get_widget("spin_share_ratio").get_value() new_core_config["share_ratio_limit"] = \ self.glade.get_widget("spin_share_ratio_limit").get_value() new_core_config["seed_time_ratio_limit"] = \ self.glade.get_widget("spin_seed_time_ratio_limit").get_value() new_core_config["seed_time_limit"] = \ self.glade.get_widget("spin_seed_time_limit").get_value() # 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.get_core_uri() != None: # 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] # Set each changed config value in the core client.set_config(config_to_set) client.force_call(True) # Update the configuration self.core_config.update(config_to_set) # Re-show the dialog to make sure everything has been updated self.show() def hide(self): self.pref_dialog.hide() def on_pref_dialog_delete_event(self, widget, event): self.hide() return True def on_toggle(self, widget): """Handles widget sensitivity based on radio/check button values.""" value = widget.get_active() # Disable the focus dialog checkbox if the show dialog isn't active. if widget == self.glade.get_widget("chk_show_dialog"): self.glade.get_widget("chk_focus_dialog").set_sensitive(value) # Disable the port spinners if random ports is selected. if widget == self.glade.get_widget("chk_random_port"): log.debug("chk_random_port set to: %s", value) self.glade.get_widget("spin_port_min").set_sensitive(not value) self.glade.get_widget("spin_port_max").set_sensitive(not value) # Disable all the tray options if tray is not used. if widget == self.glade.get_widget("chk_use_tray"): self.glade.get_widget("chk_min_on_close").set_sensitive(value) self.glade.get_widget("chk_start_in_tray").set_sensitive(value) self.glade.get_widget("chk_lock_tray").set_sensitive(value) if value == True: lock = self.glade.get_widget("chk_lock_tray").get_active() self.glade.get_widget("txt_tray_password").set_sensitive(lock) self.glade.get_widget("password_label").set_sensitive(lock) else: self.glade.get_widget("txt_tray_password").set_sensitive(value) self.glade.get_widget("password_label").set_sensitive(value) if widget == self.glade.get_widget("chk_lock_tray"): self.glade.get_widget("txt_tray_password").set_sensitive(value) self.glade.get_widget("password_label").set_sensitive(value) # Disable the file manager combo box if custom is selected. if widget == self.glade.get_widget("radio_open_folder_custom"): self.glade.get_widget("combo_file_manager").set_sensitive(not value) self.glade.get_widget("txt_open_folder_location").set_sensitive( value) def on_button_ok_clicked(self, data): log.debug("on_button_ok_clicked") self.set_config() component.get("PluginManager").run_on_apply_prefs() self.hide() return True def on_button_apply_clicked(self, data): log.debug("on_button_apply_clicked") self.set_config() component.get("PluginManager").run_on_apply_prefs() 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() self.notebook.set_current_page(model.get_value(row, 0)) def on_test_port_clicked(self, data): log.debug("on_test_port_clicked") def on_get_listen_port(port): deluge.common.open_url_in_browser( "http://deluge-torrent.org/test-port.php?port=%s" % port) client.get_listen_port(on_get_listen_port) 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.enable_plugin(name) component.get("PluginManager").enable_plugin(name) else: client.disable_plugin(name) component.get("PluginManager").disable_plugin(name) def on_plugin_selection_changed(self, treeselection): log.debug("on_plugin_selection_changed")