diff options
Diffstat (limited to 'deluge/ui/console/modes/preferences/preferences.py')
-rw-r--r-- | deluge/ui/console/modes/preferences/preferences.py | 376 |
1 files changed, 376 insertions, 0 deletions
diff --git a/deluge/ui/console/modes/preferences/preferences.py b/deluge/ui/console/modes/preferences/preferences.py new file mode 100644 index 0000000..2c95323 --- /dev/null +++ b/deluge/ui/console/modes/preferences/preferences.py @@ -0,0 +1,376 @@ +# +# Copyright (C) 2011 Nick Lanham <nick@afternight.org> +# +# 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 +from collections import deque + +import deluge.component as component +from deluge.decorators import overrides +from deluge.ui.client import client +from deluge.ui.console.modes.basemode import BaseMode +from deluge.ui.console.modes.preferences.preference_panes import ( + BandwidthPane, + CachePane, + DaemonPane, + DownloadsPane, + InterfacePane, + NetworkPane, + OtherPane, + ProxyPane, + QueuePane, +) +from deluge.ui.console.utils import curses_util as util +from deluge.ui.console.widgets.fields import SelectInput +from deluge.ui.console.widgets.popup import MessagePopup, PopupsHandler +from deluge.ui.console.widgets.sidebar import Sidebar + +try: + import curses +except ImportError: + pass + + +log = logging.getLogger(__name__) + + +# Big help string that gets displayed when the user hits 'h' +HELP_STR = """This screen lets you view and configure various options in deluge. + +There are three main sections to this screen. Only one section is active at a time. \ +You can switch the active section by hitting TAB (or Shift-TAB to go back one) + +The section on the left displays the various categories that the settings fall in. \ +You can navigate the list using the up/down arrows + +The section on the right shows the settings for the selected category. When this \ +section is active you can navigate the various settings with the up/down arrows. \ +Special keys for each input type are described below. + +The final section is at the bottom right, the: [Cancel] [Apply] [OK] buttons. +When this section is active, simply select the option you want using the arrow +keys and press Enter to confim. + + +Special keys for various input types are as follows: +- For text inputs you can simply type in the value. + +{|indent: |}- For numeric inputs (indicated by the value being in []s), you can type a value, \ +or use PageUp and PageDown to increment/decrement the value. + +- For checkbox inputs use the spacebar to toggle + +{|indent: |}- For checkbox plus something else inputs (the something else being only visible \ +when you check the box) you can toggle the check with space, use the right \ +arrow to edit the other value, and escape to get back to the check box. + +""" + + +class ZONE: + length = 3 + CATEGORIES, PREFRENCES, ACTIONS = list(range(length)) + + +class PreferenceSidebar(Sidebar): + def __init__(self, torrentview, width): + height = curses.LINES - 2 + Sidebar.__init__( + self, torrentview, width, height, title=None, border_off_north=1 + ) + self.categories = [ + _('Interface'), + _('Downloads'), + _('Network'), + _('Bandwidth'), + _('Other'), + _('Daemon'), + _('Queue'), + _('Proxy'), + _('Cache'), + ] + for name in self.categories: + self.add_text_field( + name, + name, + selectable=True, + font_unfocused_active='bold', + color_unfocused_active='white,black', + ) + + def on_resize(self): + self.resize_window(curses.LINES - 2, self.width) + + +class Preferences(BaseMode, PopupsHandler): + def __init__(self, parent_mode, stdscr, console_config, encoding=None): + BaseMode.__init__(self, stdscr, encoding=encoding, do_refresh=False) + PopupsHandler.__init__(self) + self.parent_mode = parent_mode + self.cur_cat = 0 + self.messages = deque() + self.action_input = None + self.config_loaded = False + self.console_config = console_config + self.active_port = -1 + self.active_zone = ZONE.CATEGORIES + self.sidebar_width = 15 # Width of the categories pane + + self.sidebar = PreferenceSidebar(parent_mode, self.sidebar_width) + self.sidebar.set_focused(True) + self.sidebar.active_input = 0 + + self._calc_sizes(resize=False) + + self.panes = [ + InterfacePane(self), + DownloadsPane(self), + NetworkPane(self), + BandwidthPane(self), + OtherPane(self), + DaemonPane(self), + QueuePane(self), + ProxyPane(self), + CachePane(self), + ] + + self.action_input = SelectInput( + self, None, None, [_('Cancel'), _('Apply'), _('OK')], [0, 1, 2], 0 + ) + + def load_config(self): + if self.config_loaded: + return + + def on_get_config(core_config): + self.core_config = core_config + self.config_loaded = True + for p in self.panes: + p.create_pane(core_config, self.console_config) + self.refresh() + + client.core.get_config().addCallback(on_get_config) + + def on_get_listen_port(port): + self.active_port = port + + client.core.get_listen_port().addCallback(on_get_listen_port) + + @property + def height(self): + # top/bottom bars: 2, Action buttons (Cancel/Apply/OK): 1 + return self.rows - 3 + + @property + def width(self): + return self.prefs_width + + def _calc_sizes(self, resize=True): + self.prefs_width = self.cols - self.sidebar_width + + if not resize: + return + + for p in self.panes: + p.resize_window(self.height, p.pane_width) + + def _draw_preferences(self): + self.cur_cat = self.sidebar.active_input + self.panes[self.cur_cat].render( + self, self.stdscr, self.prefs_width, self.active_zone == ZONE.PREFRENCES + ) + self.panes[self.cur_cat].refresh() + + def _draw_actions(self): + selected = self.active_zone == ZONE.ACTIONS + self.stdscr.hline(self.rows - 3, self.sidebar_width, b'_', self.cols) + self.action_input.render( + self.stdscr, + self.rows - 2, + width=self.cols, + active=selected, + focus=True, + col=self.cols - 22, + ) + + @overrides(BaseMode) + def on_resize(self, rows, cols): + BaseMode.on_resize(self, rows, cols) + self._calc_sizes() + + if self.popup: + self.popup.handle_resize() + + self.sidebar.on_resize() + self.refresh() + + @overrides(component.Component) + def update(self): + for i, p in enumerate(self.panes): + self.panes[i].update(i == self.cur_cat) + + @overrides(BaseMode) + def resume(self): + BaseMode.resume(self) + self.sidebar.show() + + @overrides(BaseMode) + def refresh(self): + if ( + not component.get('ConsoleUI').is_active_mode(self) + or not self.config_loaded + ): + return + + if self.popup is None and self.messages: + title, msg = self.messages.popleft() + self.push_popup(MessagePopup(self, title, msg)) + + self.stdscr.erase() + self.draw_statusbars() + self._draw_actions() + # Necessary to force updating the stdscr + self.stdscr.noutrefresh() + + self.sidebar.refresh() + + # do this last since it moves the cursor + self._draw_preferences() + + if self.popup: + self.popup.refresh() + + curses.doupdate() + + def _apply_prefs(self): + if self.core_config is None: + return + + def update_conf_value(key, source_dict, dest_dict, updated): + if dest_dict[key] != source_dict[key]: + dest_dict[key] = source_dict[key] + updated = True + return updated + + new_core_config = {} + for pane in self.panes: + if not isinstance(pane, InterfacePane): + pane.add_config_values(new_core_config) + # Apply Core Prefs + 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) + + # Update Interface Prefs + new_console_config = {} + didupdate = False + for pane in self.panes: + # could just access panes by index, but that would break if panes + # are ever reordered, so do it the slightly slower but safer way + if isinstance(pane, InterfacePane): + pane.add_config_values(new_console_config) + for k in ['ring_bell', 'language']: + didupdate = update_conf_value( + k, new_console_config, self.console_config, didupdate + ) + for k in ['separate_complete', 'move_selection']: + didupdate = update_conf_value( + k, + new_console_config, + self.console_config['torrentview'], + didupdate, + ) + for k in [ + 'ignore_duplicate_lines', + 'save_command_history', + 'third_tab_lists_all', + 'torrents_per_tab_press', + ]: + didupdate = update_conf_value( + k, new_console_config, self.console_config['cmdline'], didupdate + ) + + if didupdate: + self.parent_mode.on_config_changed() + + def _update_preferences(self, core_config): + self.core_config = core_config + for pane in self.panes: + pane.update_values(core_config) + + def _actions_read(self, c): + self.action_input.handle_read(c) + if c in [curses.KEY_ENTER, util.KEY_ENTER2]: + # take action + if self.action_input.selected_index == 0: # Cancel + self.back_to_parent() + elif self.action_input.selected_index == 1: # Apply + self._apply_prefs() + client.core.get_config().addCallback(self._update_preferences) + elif self.action_input.selected_index == 2: # OK + self._apply_prefs() + self.back_to_parent() + + def back_to_parent(self): + component.get('ConsoleUI').set_mode(self.parent_mode.mode_name) + + @overrides(BaseMode) + def read_input(self): + c = self.stdscr.getch() + + if self.popup: + if self.popup.handle_read(c): + self.pop_popup() + self.refresh() + return + + if util.is_printable_chr(c): + char = chr(c) + if char == 'Q': + component.get('ConsoleUI').quit() + elif char == 'h': + self.push_popup(MessagePopup(self, 'Preferences Help', HELP_STR)) + + if self.sidebar.has_focus() and c == util.KEY_ESC: + self.back_to_parent() + return + + def update_active_zone(val): + self.active_zone += val + if self.active_zone == -1: + self.active_zone = ZONE.length - 1 + else: + self.active_zone %= ZONE.length + self.sidebar.set_focused(self.active_zone == ZONE.CATEGORIES) + + if c == util.KEY_TAB: + update_active_zone(1) + elif c == curses.KEY_BTAB: + update_active_zone(-1) + else: + if self.active_zone == ZONE.CATEGORIES: + self.sidebar.handle_read(c) + elif self.active_zone == ZONE.PREFRENCES: + self.panes[self.cur_cat].handle_read(c) + elif self.active_zone == ZONE.ACTIONS: + self._actions_read(c) + + self.refresh() + + def is_active_pane(self, pane): + return pane == self.panes[self.cur_cat] |