summaryrefslogtreecommitdiffstats
path: root/deluge/ui/console/widgets/inputpane.py
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/ui/console/widgets/inputpane.py')
-rw-r--r--deluge/ui/console/widgets/inputpane.py394
1 files changed, 394 insertions, 0 deletions
diff --git a/deluge/ui/console/widgets/inputpane.py b/deluge/ui/console/widgets/inputpane.py
new file mode 100644
index 0000000..d8d2175
--- /dev/null
+++ b/deluge/ui/console/widgets/inputpane.py
@@ -0,0 +1,394 @@
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# 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 deluge.decorators import overrides
+from deluge.ui.console.modes.basemode import InputKeyHandler, move_cursor
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.widgets.fields import (
+ CheckedInput,
+ CheckedPlusInput,
+ ComboInput,
+ DividerField,
+ FloatSpinInput,
+ Header,
+ InfoField,
+ IntSpinInput,
+ NoInputField,
+ SelectInput,
+ TextArea,
+ TextField,
+ TextInput,
+)
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+
+class BaseInputPane(InputKeyHandler):
+ def __init__(
+ self,
+ mode,
+ allow_rearrange=False,
+ immediate_action=False,
+ set_first_input_active=True,
+ border_off_west=0,
+ border_off_north=0,
+ border_off_east=0,
+ border_off_south=0,
+ active_wrap=False,
+ **kwargs
+ ):
+ InputKeyHandler.__init__(self)
+ self.inputs = []
+ self.mode = mode
+ self.active_input = 0
+ self.set_first_input_active = set_first_input_active
+ self.allow_rearrange = allow_rearrange
+ self.immediate_action = immediate_action
+ self.move_active_many = 4
+ self.active_wrap = active_wrap
+ self.lineoff = 0
+ self.border_off_west = border_off_west
+ self.border_off_north = border_off_north
+ self.border_off_east = border_off_east
+ self.border_off_south = border_off_south
+ self.last_lineoff_move = 0
+
+ if not hasattr(self, 'visible_content_pane_height'):
+ log.error(
+ 'The class "%s" does not have the attribute "%s" required by super class "%s"',
+ self.__class__.__name__,
+ 'visible_content_pane_height',
+ BaseInputPane.__name__,
+ )
+ raise AttributeError('visible_content_pane_height')
+
+ @property
+ def visible_content_pane_width(self):
+ return self.mode.width
+
+ def add_spaces(self, num):
+ string = ''
+ for i in range(num):
+ string += '\n'
+
+ self.add_text_area('space %d' % len(self.inputs), string)
+
+ def add_text(self, string):
+ self.add_text_area('', string)
+
+ def move(self, r, c):
+ self._cursor_row = r
+ self._cursor_col = c
+
+ def get_input(self, name):
+ for e in self.inputs:
+ if e.name == name:
+ return e
+
+ def _add_input(self, input_element):
+ for e in self.inputs:
+ if isinstance(e, NoInputField):
+ continue
+ if e.name == input_element.name:
+ import traceback
+
+ log.warning(
+ 'Input element with name "%s" already exists in input pane (%s):\n%s',
+ input_element.name,
+ e,
+ ''.join(traceback.format_stack(limit=5)),
+ )
+ return
+
+ self.inputs.append(input_element)
+ if self.set_first_input_active and input_element.selectable():
+ self.active_input = len(self.inputs) - 1
+ self.set_first_input_active = False
+ return input_element
+
+ def add_header(self, header, space_above=False, space_below=False, **kwargs):
+ return self._add_input(Header(self, header, space_above, space_below, **kwargs))
+
+ def add_info_field(self, name, label, value):
+ return self._add_input(InfoField(self, name, label, value))
+
+ def add_text_field(self, name, message, selectable=True, col='+1', **kwargs):
+ return self._add_input(
+ TextField(self, name, message, selectable=selectable, col=col, **kwargs)
+ )
+
+ def add_text_area(self, name, message, **kwargs):
+ return self._add_input(TextArea(self, name, message, **kwargs))
+
+ def add_divider_field(self, name, message, **kwargs):
+ return self._add_input(DividerField(self, name, message, **kwargs))
+
+ def add_text_input(self, name, message, value='', col='+1', **kwargs):
+ """
+ Add a text input field
+
+ :param message: string to display above the input field
+ :param name: name of the field, for the return callback
+ :param value: initial value of the field
+ :param complete: should completion be run when tab is hit and this field is active
+ """
+ return self._add_input(
+ TextInput(
+ self,
+ name,
+ message,
+ self.move,
+ self.visible_content_pane_width,
+ value,
+ col=col,
+ **kwargs
+ )
+ )
+
+ def add_select_input(self, name, message, opts, vals, default_index=0, **kwargs):
+ return self._add_input(
+ SelectInput(self, name, message, opts, vals, default_index, **kwargs)
+ )
+
+ def add_checked_input(self, name, message, checked=False, col='+1', **kwargs):
+ return self._add_input(
+ CheckedInput(self, name, message, checked=checked, col=col, **kwargs)
+ )
+
+ def add_checkedplus_input(
+ self, name, message, child, checked=False, col='+1', **kwargs
+ ):
+ return self._add_input(
+ CheckedPlusInput(
+ self, name, message, child, checked=checked, col=col, **kwargs
+ )
+ )
+
+ def add_float_spin_input(self, name, message, value=0.0, col='+1', **kwargs):
+ return self._add_input(
+ FloatSpinInput(self, name, message, self.move, value, col=col, **kwargs)
+ )
+
+ def add_int_spin_input(self, name, message, value=0, col='+1', **kwargs):
+ return self._add_input(
+ IntSpinInput(self, name, message, self.move, value, col=col, **kwargs)
+ )
+
+ def add_combo_input(self, name, message, choices, col='+1', **kwargs):
+ return self._add_input(
+ ComboInput(self, name, message, choices, col=col, **kwargs)
+ )
+
+ @overrides(InputKeyHandler)
+ def handle_read(self, c):
+ if not self.inputs: # no inputs added yet
+ return util.ReadState.IGNORED
+ ret = self.inputs[self.active_input].handle_read(c)
+ if ret != util.ReadState.IGNORED:
+ if self.immediate_action:
+ self.immediate_action_cb(
+ state_changed=False if ret == util.ReadState.READ else True
+ )
+ return ret
+
+ ret = util.ReadState.READ
+
+ if c == curses.KEY_UP:
+ self.move_active_up(1)
+ elif c == curses.KEY_DOWN:
+ self.move_active_down(1)
+ elif c == curses.KEY_HOME:
+ self.move_active_up(len(self.inputs))
+ elif c == curses.KEY_END:
+ self.move_active_down(len(self.inputs))
+ elif c == curses.KEY_PPAGE:
+ self.move_active_up(self.move_active_many)
+ elif c == curses.KEY_NPAGE:
+ self.move_active_down(self.move_active_many)
+ elif c == util.KEY_ALT_AND_ARROW_UP:
+ self.lineoff = max(self.lineoff - 1, 0)
+ elif c == util.KEY_ALT_AND_ARROW_DOWN:
+ tot_height = self.get_content_height()
+ self.lineoff = min(
+ self.lineoff + 1, tot_height - self.visible_content_pane_height
+ )
+ elif c == util.KEY_CTRL_AND_ARROW_UP:
+ if not self.allow_rearrange:
+ return ret
+ val = self.inputs.pop(self.active_input)
+ self.active_input -= 1
+ self.inputs.insert(self.active_input, val)
+ if self.immediate_action:
+ self.immediate_action_cb(state_changed=True)
+ elif c == util.KEY_CTRL_AND_ARROW_DOWN:
+ if not self.allow_rearrange:
+ return ret
+ val = self.inputs.pop(self.active_input)
+ self.active_input += 1
+ self.inputs.insert(self.active_input, val)
+ if self.immediate_action:
+ self.immediate_action_cb(state_changed=True)
+ else:
+ ret = util.ReadState.IGNORED
+ return ret
+
+ def get_values(self):
+ vals = {}
+ for i, ipt in enumerate(self.inputs):
+ if not ipt.has_input():
+ continue
+ vals[ipt.name] = {
+ 'value': ipt.get_value(),
+ 'order': i,
+ 'active': self.active_input == i,
+ }
+ return vals
+
+ def immediate_action_cb(self, state_changed=True):
+ pass
+
+ def move_active(self, direction, amount):
+ """
+ direction == -1: Up
+ direction == 1: Down
+
+ """
+ self.last_lineoff_move = direction * amount
+
+ if direction > 0:
+ if self.active_wrap:
+ limit = self.active_input - 1
+ if limit < 0:
+ limit = len(self.inputs) + limit
+ else:
+ limit = len(self.inputs) - 1
+ else:
+ limit = 0
+ if self.active_wrap:
+ limit = self.active_input + 1
+
+ def next_move(nc, direction, limit):
+ next_index = nc
+ while next_index != limit:
+ next_index += direction
+ if direction > 0:
+ next_index %= len(self.inputs)
+ elif next_index < 0:
+ next_index = len(self.inputs) + next_index
+
+ if self.inputs[next_index].selectable():
+ return next_index
+ if next_index == limit:
+ return nc
+ return nc
+
+ next_sel = self.active_input
+ for a in range(amount):
+ cur_sel = next_sel
+ next_sel = next_move(next_sel, direction, limit)
+ if cur_sel == next_sel:
+ tot_height = (
+ self.get_content_height()
+ + self.border_off_north
+ + self.border_off_south
+ )
+ if direction > 0:
+ self.lineoff = min(
+ self.lineoff + 1, tot_height - self.visible_content_pane_height
+ )
+ else:
+ self.lineoff = max(self.lineoff - 1, 0)
+
+ if next_sel is not None:
+ self.active_input = next_sel
+
+ def move_active_up(self, amount):
+ self.move_active(-1, amount)
+ if self.immediate_action:
+ self.immediate_action_cb(state_changed=False)
+
+ def move_active_down(self, amount):
+ self.move_active(1, amount)
+ if self.immediate_action:
+ self.immediate_action_cb(state_changed=False)
+
+ def get_content_height(self):
+ height = 0
+ for i, ipt in enumerate(self.inputs):
+ if ipt.depend_skip():
+ continue
+ height += ipt.height
+ return height
+
+ def ensure_active_visible(self):
+ start_row = 0
+ end_row = self.border_off_north
+ for i, ipt in enumerate(self.inputs):
+ if ipt.depend_skip():
+ continue
+ start_row = end_row
+ end_row += ipt.height
+ if i != self.active_input or not ipt.has_input():
+ continue
+ height = self.visible_content_pane_height
+ if end_row > height + self.lineoff:
+ self.lineoff += end_row - (
+ height + self.lineoff
+ ) # Correct result depends on paranthesis
+ elif start_row < self.lineoff:
+ self.lineoff -= self.lineoff - start_row
+ break
+
+ def render_inputs(self, focused=False):
+ self._cursor_row = -1
+ self._cursor_col = -1
+ util.safe_curs_set(util.Curser.INVISIBLE)
+
+ self.ensure_active_visible()
+
+ crow = self.border_off_north
+ for i, ipt in enumerate(self.inputs):
+ if ipt.depend_skip():
+ continue
+ col = self.border_off_west
+ field_width = self.width - self.border_off_east - self.border_off_west
+ cursor_offset = self.border_off_west
+
+ if ipt.default_col != -1:
+ default_col = int(ipt.default_col)
+ if isinstance(ipt.default_col, ''.__class__) and ipt.default_col[0] in [
+ '+',
+ '-',
+ ]:
+ col += default_col
+ cursor_offset += default_col
+ field_width -= default_col # Increase to col must be reflected here
+ else:
+ col = default_col
+ crow += ipt.render(
+ self.screen,
+ crow,
+ width=field_width,
+ active=i == self.active_input,
+ focused=focused,
+ col=col,
+ cursor_offset=cursor_offset,
+ )
+
+ if self._cursor_row >= 0:
+ util.safe_curs_set(util.Curser.VERY_VISIBLE)
+ move_cursor(self.screen, self._cursor_row, self._cursor_col)