mirror of
https://github.com/markqvist/NomadNet.git
synced 2025-12-17 06:44:21 +01:00
277 lines
13 KiB
Python
277 lines
13 KiB
Python
#! /usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
from ..assisting_modules.modifier_key import MODIFIER_KEY # pylint: disable=unused-import
|
|
from .selectable_row import SelectableRow
|
|
|
|
import sys # pylint: disable=unused-import
|
|
import urwid
|
|
|
|
|
|
class IntegerPicker(urwid.WidgetWrap):
|
|
"""Serves as a selector for integer numbers."""
|
|
|
|
def __init__(self, value, *, min_v=(-sys.maxsize - 1), max_v=sys.maxsize, step_len=1, jump_len=100, on_selection_change=None,
|
|
initialization_is_selection_change=False, modifier_key=MODIFIER_KEY.NONE, ascending=True,
|
|
return_unused_navigation_input=True, topBar_align="center", topBar_endCovered_prop=("▲", None, None),
|
|
topBar_endExposed_prop=("───", None, None), bottomBar_align="center", bottomBar_endCovered_prop=("▼", None, None),
|
|
bottomBar_endExposed_prop=("───", None, None), display_syntax="{}", display_align="center", display_prop=(None, None)):
|
|
assert (min_v <= max_v), "'min_v' must be less than or equal to 'max_v'."
|
|
|
|
assert (min_v <= value <= max_v), "'min_v <= value <= max_v' must be True."
|
|
|
|
self._value = value
|
|
|
|
self._minimum = min_v
|
|
self._maximum = max_v
|
|
|
|
# Specifies how far to move in the respective direction when the keys 'up/down' are pressed.
|
|
self._step_len = step_len
|
|
|
|
# Specifies how far to jump in the respective direction when the keys 'page up/down' or the mouse events 'wheel up/down'
|
|
# are passed.
|
|
self._jump_len = jump_len
|
|
|
|
# A hook which is triggered when the value changes.
|
|
self.on_selection_change = on_selection_change
|
|
|
|
# 'MODIFIER_KEY' changes the behavior, so that the widget responds only to modified input. ('up' => 'ctrl up')
|
|
self._modifier_key = modifier_key
|
|
|
|
# Specifies whether moving upwards represents a decrease or an increase of the value.
|
|
self._ascending = ascending
|
|
|
|
# If the minimum has been reached and an attempt is made to select an even smaller value, the input is normally not
|
|
# swallowed by the widget, but passed on so that other widgets can interpret it. This may result in transferring the focus.
|
|
self._return_unused_navigation_input = return_unused_navigation_input
|
|
|
|
# The bars are just 'urwid.Text' widgets.
|
|
self._top_bar = urwid.AttrMap(urwid.Text("", topBar_align),
|
|
None)
|
|
|
|
self._bottom_bar = urwid.AttrMap(urwid.Text("", bottomBar_align),
|
|
None)
|
|
|
|
# During the initialization of 'urwid.AttrMap', the value can be passed as non-dict. After initializing, its value can be
|
|
# manipulated by passing a dict. The dicts I create below will be used later to change the appearance of the widgets.
|
|
self._topBar_endCovered_markup = topBar_endCovered_prop[0]
|
|
self._topBar_endCovered_focus = {None:topBar_endCovered_prop[1]}
|
|
self._topBar_endCovered_offFocus = {None:topBar_endCovered_prop[2]}
|
|
|
|
self._topBar_endExposed_markup = topBar_endExposed_prop[0]
|
|
self._topBar_endExposed_focus = {None:topBar_endExposed_prop[1]}
|
|
self._topBar_endExposed_offFocus = {None:topBar_endExposed_prop[2]}
|
|
|
|
self._bottomBar_endCovered_markup = bottomBar_endCovered_prop[0]
|
|
self._bottomBar_endCovered_focus = {None:bottomBar_endCovered_prop[1]}
|
|
self._bottomBar_endCovered_offFocus = {None:bottomBar_endCovered_prop[2]}
|
|
|
|
self._bottomBar_endExposed_markup = bottomBar_endExposed_prop[0]
|
|
self._bottomBar_endExposed_focus = {None:bottomBar_endExposed_prop[1]}
|
|
self._bottomBar_endExposed_offFocus = {None:bottomBar_endExposed_prop[2]}
|
|
|
|
# Format the number before displaying it. That way it is easier to read.
|
|
self._display_syntax = display_syntax
|
|
|
|
# The current value is displayed via this widget.
|
|
self._display = SelectableRow([display_syntax.format(value)],
|
|
align=display_align)
|
|
|
|
display_attr = urwid.AttrMap(self._display,
|
|
display_prop[1],
|
|
display_prop[0])
|
|
|
|
# wrap 'urwid.Pile'
|
|
super().__init__(urwid.Pile([self._top_bar,
|
|
display_attr,
|
|
self._bottom_bar]))
|
|
|
|
# Is 'on_selection_change' triggered during the initialization?
|
|
if initialization_is_selection_change and (on_selection_change is not None):
|
|
on_selection_change(None, value)
|
|
|
|
def __repr__(self):
|
|
return "{}(value='{}', min_v='{}', max_v='{}', ascending='{}')".format(self.__class__.__name__,
|
|
self._value,
|
|
self._minimum,
|
|
self._maximum,
|
|
self._ascending)
|
|
|
|
def render(self, size, focus=False):
|
|
# Changes the appearance of the bar at the top depending on whether the upper limit is reached.
|
|
if self._value == (self._minimum if self._ascending else self._maximum):
|
|
self._top_bar.original_widget.set_text(self._topBar_endExposed_markup)
|
|
self._top_bar.set_attr_map(self._topBar_endExposed_focus
|
|
if focus else self._topBar_endExposed_offFocus)
|
|
else:
|
|
self._top_bar.original_widget.set_text(self._topBar_endCovered_markup)
|
|
self._top_bar.set_attr_map(self._topBar_endCovered_focus
|
|
if focus else self._topBar_endCovered_offFocus)
|
|
|
|
# Changes the appearance of the bar at the bottom depending on whether the lower limit is reached.
|
|
if self._value == (self._maximum if self._ascending else self._minimum):
|
|
self._bottom_bar.original_widget.set_text(self._bottomBar_endExposed_markup)
|
|
self._bottom_bar.set_attr_map(self._bottomBar_endExposed_focus
|
|
if focus else self._bottomBar_endExposed_offFocus)
|
|
else:
|
|
self._bottom_bar.original_widget.set_text(self._bottomBar_endCovered_markup)
|
|
self._bottom_bar.set_attr_map(self._bottomBar_endCovered_focus
|
|
if focus else self._bottomBar_endCovered_offFocus)
|
|
|
|
return super().render(size, focus=focus)
|
|
|
|
def keypress(self, size, key):
|
|
# A keystroke is changed to a modified one ('up' => 'ctrl up'). This prevents the widget from responding when the arrows
|
|
# keys are used to navigate between widgets. That way it can be used in a 'urwid.Pile' or similar.
|
|
if key == self._modifier_key.prepend_to("up"):
|
|
successful = self._change_value(-self._step_len)
|
|
|
|
elif key == self._modifier_key.prepend_to("down"):
|
|
successful = self._change_value(self._step_len)
|
|
|
|
elif key == self._modifier_key.prepend_to("page up"):
|
|
successful = self._change_value(-self._jump_len)
|
|
|
|
elif key == self._modifier_key.prepend_to("page down"):
|
|
successful = self._change_value(self._jump_len)
|
|
|
|
elif key == self._modifier_key.prepend_to("home"):
|
|
successful = self._change_value(float("-inf"))
|
|
|
|
elif key == self._modifier_key.prepend_to("end"):
|
|
successful = self._change_value(float("inf"))
|
|
|
|
else:
|
|
successful = False
|
|
|
|
return key if not successful else None
|
|
|
|
def mouse_event(self, size, event, button, col, row, focus):
|
|
if focus:
|
|
# An event is changed to a modified one ('mouse press' => 'ctrl mouse press'). This prevents the original widget from
|
|
# responding when mouse buttons are also used to navigate between widgets.
|
|
if event == self._modifier_key.prepend_to("mouse press"):
|
|
# mousewheel up
|
|
if button == 4.0:
|
|
result = self._change_value(-self._jump_len)
|
|
return result if self._return_unused_navigation_input else True
|
|
|
|
# mousewheel down
|
|
elif button == 5.0:
|
|
result = self._change_value(self._jump_len)
|
|
return result if self._return_unused_navigation_input else True
|
|
|
|
return False
|
|
|
|
# This method tries to change the value depending on the desired arrangement and returns True if this change was successful.
|
|
def _change_value(self, summand):
|
|
value_before_input = self._value
|
|
|
|
if self._ascending:
|
|
new_value = self._value + summand
|
|
|
|
if summand < 0:
|
|
# If the corresponding limit has already been reached, then determine whether the unused input should be
|
|
# returned or swallowed.
|
|
if self._value == self._minimum:
|
|
return not self._return_unused_navigation_input
|
|
|
|
# If the new value stays within the permitted range, use it.
|
|
elif new_value > self._minimum:
|
|
self._value = new_value
|
|
|
|
# The permitted range would be exceeded, so the limit is set instead.
|
|
else:
|
|
self._value = self._minimum
|
|
|
|
elif summand > 0:
|
|
if self._value == self._maximum:
|
|
return not self._return_unused_navigation_input
|
|
|
|
elif new_value < self._maximum:
|
|
self._value = new_value
|
|
|
|
else:
|
|
self._value = self._maximum
|
|
else:
|
|
new_value = self._value - summand
|
|
|
|
if summand < 0:
|
|
if self._value == self._maximum:
|
|
return not self._return_unused_navigation_input
|
|
|
|
elif new_value < self._maximum:
|
|
self._value = new_value
|
|
|
|
else:
|
|
self._value = self._maximum
|
|
|
|
elif summand > 0:
|
|
if self._value == self._minimum:
|
|
return not self._return_unused_navigation_input
|
|
|
|
elif new_value > self._minimum:
|
|
self._value = new_value
|
|
|
|
else:
|
|
self._value = self._minimum
|
|
|
|
# Update the displayed value.
|
|
self._display.set_contents([self._display_syntax.format(self._value)])
|
|
|
|
# If the value has changed, execute the hook (if existing).
|
|
if (value_before_input != self._value) and (self.on_selection_change is not None):
|
|
self.on_selection_change(value_before_input,
|
|
self._value)
|
|
|
|
return True
|
|
|
|
def get_value(self):
|
|
return self._value
|
|
|
|
def set_value(self, value):
|
|
if not (self._minimum <= value <= self._maximum):
|
|
raise ValueError("'minimum <= value <= maximum' must be True.")
|
|
|
|
if value != self._value:
|
|
value_before_change = self._value
|
|
self._value = value
|
|
|
|
# Update the displayed value.
|
|
self._display.set_contents([self._display_syntax.format(self._value)])
|
|
|
|
# Execute the hook (if existing).
|
|
if (self.on_selection_change is not None):
|
|
self.on_selection_change(value_before_change, self._value)
|
|
|
|
def set_to_minimum(self):
|
|
self.set_value(self._minimum)
|
|
|
|
def set_to_maximum(self):
|
|
self.set_value(self._maximum)
|
|
|
|
def set_minimum(self, new_min):
|
|
if new_min > self._maximum:
|
|
raise ValueError("'new_min' must be less than or equal to the maximum value.")
|
|
|
|
self._minimum = new_min
|
|
|
|
if self._value < new_min:
|
|
self.set_to_minimum()
|
|
|
|
def set_maximum(self, new_max):
|
|
if new_max < self._minimum:
|
|
raise ValueError("'new_max' must be greater than or equal to the minimum value.")
|
|
|
|
self._maximum = new_max
|
|
|
|
if self._value > new_max:
|
|
self.set_to_maximum()
|
|
|
|
def minimum_is_selected(self):
|
|
return self._value == self._minimum
|
|
|
|
def maximum_is_selected(self):
|
|
return self._value == self._maximum
|
|
|