mirror of
https://github.com/markqvist/NomadNet.git
synced 2025-05-17 13:20:25 -04:00
283 lines
No EOL
10 KiB
Python
283 lines
No EOL
10 KiB
Python
import urwid
|
||
|
||
class DialogLineBox(urwid.LineBox):
|
||
def __init__(self, body, parent=None, title="?"):
|
||
super().__init__(body, title=title)
|
||
self.parent = parent
|
||
|
||
def keypress(self, size, key):
|
||
if key == "esc":
|
||
if self.parent and hasattr(self.parent, "dismiss_dialog"):
|
||
self.parent.dismiss_dialog()
|
||
return None
|
||
return super().keypress(size, key)
|
||
|
||
class Placeholder(urwid.Edit):
|
||
def __init__(self, caption="", edit_text="", placeholder="", **kwargs):
|
||
super().__init__(caption, edit_text, **kwargs)
|
||
self.placeholder = placeholder
|
||
|
||
def render(self, size, focus=False):
|
||
if not self.edit_text and not focus:
|
||
placeholder_widget = urwid.Text(("placeholder", self.placeholder))
|
||
return placeholder_widget.render(size, focus)
|
||
else:
|
||
return super().render(size, focus)
|
||
|
||
class Dropdown(urwid.WidgetWrap):
|
||
signals = ['change'] # emit for urwid.connect_signal fn
|
||
|
||
def __init__(self, label, options, default=None):
|
||
self.label = label
|
||
self.options = options
|
||
self.selected = default if default is not None else options[0]
|
||
|
||
self.main_text = f"{self.selected}"
|
||
self.main_button = urwid.SelectableIcon(self.main_text, 0)
|
||
self.main_button = urwid.AttrMap(self.main_button, "button_normal", "button_focus")
|
||
|
||
self.option_widgets = []
|
||
for opt in options:
|
||
icon = urwid.SelectableIcon(opt, 0)
|
||
icon = urwid.AttrMap(icon, "list_normal", "list_focus")
|
||
self.option_widgets.append(icon)
|
||
|
||
self.options_walker = urwid.SimpleFocusListWalker(self.option_widgets)
|
||
self.options_listbox = urwid.ListBox(self.options_walker)
|
||
self.dropdown_box = None # will be created on open_dropdown
|
||
|
||
self.pile = urwid.Pile([self.main_button])
|
||
self.dropdown_visible = False
|
||
|
||
super().__init__(self.pile)
|
||
|
||
def open_dropdown(self):
|
||
if not self.dropdown_visible:
|
||
height = len(self.options)
|
||
self.dropdown_box = urwid.BoxAdapter(self.options_listbox, height)
|
||
self.pile.contents.append((self.dropdown_box, self.pile.options()))
|
||
self.dropdown_visible = True
|
||
self.pile.focus_position = 1
|
||
self.options_walker.set_focus(0)
|
||
|
||
def close_dropdown(self):
|
||
if self.dropdown_visible:
|
||
self.pile.contents.pop() # remove the dropdown_box
|
||
self.dropdown_visible = False
|
||
self.pile.focus_position = 0
|
||
self.dropdown_box = None
|
||
|
||
def keypress(self, size, key):
|
||
if not self.dropdown_visible:
|
||
if key == "enter":
|
||
self.open_dropdown()
|
||
return None
|
||
return self.main_button.keypress(size, key)
|
||
else:
|
||
if key == "enter":
|
||
focus_result = self.options_walker.get_focus()
|
||
if focus_result is not None:
|
||
focus_widget = focus_result[0]
|
||
new_val = focus_widget.base_widget.text
|
||
old_val = self.selected
|
||
self.selected = new_val
|
||
self.main_button.base_widget.set_text(f"{self.selected}")
|
||
|
||
if old_val != new_val:
|
||
self._emit('change', new_val)
|
||
|
||
self.close_dropdown()
|
||
return None
|
||
return self.dropdown_box.keypress(size, key)
|
||
|
||
def get_value(self):
|
||
return self.selected
|
||
|
||
class ValidationError(urwid.Text):
|
||
def __init__(self, message=""):
|
||
super().__init__(("error", message))
|
||
|
||
class FormField:
|
||
def __init__(self, config_key, transform=None):
|
||
self.config_key = config_key
|
||
self.transform = transform or (lambda x: x)
|
||
|
||
class FormEdit(Placeholder, FormField):
|
||
def __init__(self, config_key, caption="", edit_text="", placeholder="", validation_types=None, transform=None, **kwargs):
|
||
Placeholder.__init__(self, caption, edit_text, placeholder, **kwargs)
|
||
FormField.__init__(self, config_key, transform)
|
||
self.validation_types = validation_types or []
|
||
self.error_widget = urwid.Text("")
|
||
self.error = None
|
||
|
||
def get_value(self):
|
||
return self.transform(self.edit_text.strip())
|
||
|
||
def validate(self):
|
||
value = self.edit_text.strip()
|
||
self.error = None
|
||
|
||
for validation in self.validation_types:
|
||
if validation == "required":
|
||
if not value:
|
||
self.error = "This field is required"
|
||
break
|
||
elif validation == "number":
|
||
if value and not value.replace('-', '').replace('.', '').isdigit():
|
||
self.error = "This field must be a number"
|
||
break
|
||
elif validation == "float":
|
||
try:
|
||
if value:
|
||
float(value)
|
||
except ValueError:
|
||
self.error = "This field must be decimal number"
|
||
break
|
||
|
||
self.error_widget.set_text(("error", self.error or ""))
|
||
return self.error is None
|
||
|
||
class FormCheckbox(urwid.CheckBox, FormField):
|
||
def __init__(self, config_key, label="", state=False, validation_types=None, transform=None, **kwargs):
|
||
urwid.CheckBox.__init__(self, label, state, **kwargs)
|
||
FormField.__init__(self, config_key, transform)
|
||
self.validation_types = validation_types or []
|
||
self.error_widget = urwid.Text("")
|
||
self.error = None
|
||
|
||
def get_value(self):
|
||
return self.transform(self.get_state())
|
||
|
||
def validate(self):
|
||
|
||
value = self.get_state()
|
||
self.error = None
|
||
|
||
for validation in self.validation_types:
|
||
if validation == "required":
|
||
if not value:
|
||
self.error = "This field is required"
|
||
break
|
||
|
||
self.error_widget.set_text(("error", self.error or ""))
|
||
return self.error is None
|
||
|
||
class FormDropdown(Dropdown, FormField):
|
||
signals = ['change']
|
||
|
||
def __init__(self, config_key, label, options, default=None, validation_types=None, transform=None):
|
||
self.options = [str(opt) for opt in options]
|
||
|
||
if default is not None:
|
||
default_str = str(default)
|
||
if default_str in self.options:
|
||
default = default_str
|
||
elif transform:
|
||
try:
|
||
default_transformed = transform(default_str)
|
||
for opt in self.options:
|
||
if transform(opt) == default_transformed:
|
||
default = opt
|
||
break
|
||
except:
|
||
default = self.options[0]
|
||
else:
|
||
default = self.options[0]
|
||
else:
|
||
default = self.options[0]
|
||
|
||
Dropdown.__init__(self, label, self.options, default)
|
||
FormField.__init__(self, config_key, transform)
|
||
|
||
self.validation_types = validation_types or []
|
||
self.error_widget = urwid.Text("")
|
||
self.error = None
|
||
|
||
if hasattr(self, 'main_button'):
|
||
self.main_button.base_widget.set_text(str(default))
|
||
|
||
def get_value(self):
|
||
return self.transform(self.selected)
|
||
|
||
def validate(self):
|
||
value = self.get_value()
|
||
self.error = None
|
||
|
||
for validation in self.validation_types:
|
||
if validation == "required":
|
||
if not value:
|
||
self.error = "This field is required"
|
||
break
|
||
|
||
self.error_widget.set_text(("error", self.error or ""))
|
||
return self.error is None
|
||
|
||
def open_dropdown(self):
|
||
if not self.dropdown_visible:
|
||
super().open_dropdown()
|
||
try:
|
||
current_index = self.options.index(self.selected)
|
||
self.options_walker.set_focus(current_index)
|
||
except ValueError:
|
||
pass
|
||
|
||
class FormMultiList(urwid.Pile, FormField):
|
||
def __init__(self, config_key, placeholder="", validation_types=None, transform=None, **kwargs):
|
||
self.entries = []
|
||
self.error_widget = urwid.Text("")
|
||
self.error = None
|
||
self.placeholder = placeholder
|
||
self.validation_types = validation_types or []
|
||
|
||
first_entry = self.create_entry_row()
|
||
self.entries.append(first_entry)
|
||
|
||
self.add_button = urwid.Button("+ Add Another", on_press=self.add_entry)
|
||
add_button_padded = urwid.Padding(self.add_button, left=2, right=2)
|
||
|
||
pile_widgets = [first_entry, add_button_padded]
|
||
urwid.Pile.__init__(self, pile_widgets)
|
||
FormField.__init__(self, config_key, transform)
|
||
|
||
def create_entry_row(self):
|
||
edit = urwid.Edit("", "")
|
||
entry_row = urwid.Columns([
|
||
('weight', 1, edit),
|
||
(3, urwid.Button("×", on_press=lambda button: self.remove_entry(button, entry_row))),
|
||
])
|
||
return entry_row
|
||
|
||
def remove_entry(self, button, entry_row):
|
||
if len(self.entries) > 1:
|
||
self.entries.remove(entry_row)
|
||
self.contents = [(w, self.options()) for w in self.get_pile_widgets()]
|
||
|
||
def add_entry(self, button):
|
||
new_entry = self.create_entry_row()
|
||
self.entries.append(new_entry)
|
||
|
||
self.contents = [(w, self.options()) for w in self.get_pile_widgets()]
|
||
|
||
def get_pile_widgets(self):
|
||
return self.entries + [urwid.Padding(self.add_button, left=2, right=2)]
|
||
|
||
def get_value(self):
|
||
values = []
|
||
for entry in self.entries:
|
||
edit_widget = entry.contents[0][0]
|
||
value = edit_widget.edit_text.strip()
|
||
if value:
|
||
values.append(value)
|
||
return self.transform(values)
|
||
|
||
def validate(self):
|
||
values = self.get_value()
|
||
self.error = None
|
||
|
||
for validation in self.validation_types:
|
||
if validation == "required" and not values:
|
||
self.error = "At least one entry is required"
|
||
break
|
||
|
||
self.error_widget.set_text(("error", self.error or ""))
|
||
return self.error is None |