Move the Windows from the DataFormsTab to the windows directory
This commit is contained in:
parent
2f629ee686
commit
950a864d2f
3 changed files with 481 additions and 467 deletions
|
@ -5,10 +5,8 @@ Defines the data-forms Tab and all the Windows for it.
|
|||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from windows import g_lock
|
||||
import windows
|
||||
from tabs import Tab
|
||||
from theming import to_curses_attr, get_theme
|
||||
|
||||
class DataFormsTab(Tab):
|
||||
"""
|
||||
|
@ -26,7 +24,7 @@ class DataFormsTab(Tab):
|
|||
for field in self._form:
|
||||
self.fields.append(field)
|
||||
self.topic_win = windows.Topic()
|
||||
self.form_win = FormWin(form, self.height-4, self.width, 1, 0)
|
||||
self.form_win = windows.FormWin(form, self.height-4, self.width, 1, 0)
|
||||
self.help_win = windows.HelpText("Ctrl+Y: send form, Ctrl+G: cancel")
|
||||
self.help_win_dyn = windows.HelpText()
|
||||
self.key_func['KEY_UP'] = self.form_win.go_to_previous_input
|
||||
|
@ -75,467 +73,3 @@ class DataFormsTab(Tab):
|
|||
self.help_win_dyn.refresh(self.form_win.get_help_message())
|
||||
self.form_win.refresh()
|
||||
|
||||
class FieldInput(object):
|
||||
"""
|
||||
All input type in a data form should inherite this class,
|
||||
in addition with windows.Input or any relevant class from the
|
||||
'windows' library.
|
||||
"""
|
||||
def __init__(self, field):
|
||||
self._field = field
|
||||
self.color = get_theme().COLOR_NORMAL_TEXT
|
||||
|
||||
def set_color(self, color):
|
||||
self.color = color
|
||||
self.refresh()
|
||||
|
||||
def update_field_value(self, value):
|
||||
raise NotImplementedError
|
||||
|
||||
def resize(self, height, width, y, x):
|
||||
self._resize(height, width, y, x)
|
||||
|
||||
def is_dummy(self):
|
||||
return False
|
||||
|
||||
def reply(self):
|
||||
"""
|
||||
Set the correct response value in the field
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_help_message(self):
|
||||
"""
|
||||
Should return a string explaining the keys of the input.
|
||||
Will be displayed at each refresh on a line at the bottom of the tab.
|
||||
"""
|
||||
return ''
|
||||
|
||||
class ColoredLabel(windows.Win):
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
self.color = get_theme().COLOR_NORMAL_TEXT
|
||||
windows.Win.__init__(self)
|
||||
|
||||
def resize(self, height, width, y, x):
|
||||
self._resize(height, width, y, x)
|
||||
|
||||
def set_color(self, color):
|
||||
self.color = color
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addstr(0, 0, self.text)
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
|
||||
class DummyInput(FieldInput, windows.Win):
|
||||
"""
|
||||
Used for fields that do not require any input ('fixed')
|
||||
"""
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
windows.Win.__init__(self)
|
||||
|
||||
def do_command(self):
|
||||
return
|
||||
|
||||
def refresh(self):
|
||||
return
|
||||
|
||||
def is_dummy(self):
|
||||
return True
|
||||
|
||||
class BooleanWin(FieldInput, windows.Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
windows.Win.__init__(self)
|
||||
self.last_key = 'KEY_RIGHT'
|
||||
self.value = bool(field.getValue())
|
||||
|
||||
def do_command(self, key):
|
||||
if key == 'KEY_LEFT' or key == 'KEY_RIGHT':
|
||||
self.value = not self.value
|
||||
self.last_key = key
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addnstr(0, 0, ' '*(8), self.width)
|
||||
self.addstr(0, 2, "%s"%self.value)
|
||||
self.addstr(0, 8, '→')
|
||||
self.addstr(0, 0, '←')
|
||||
if self.last_key == 'KEY_RIGHT':
|
||||
self.addstr(0, 8, '')
|
||||
else:
|
||||
self.addstr(0, 0, '')
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def reply(self):
|
||||
self._field['label'] = ''
|
||||
self._field.setAnswer(self.value)
|
||||
|
||||
def get_help_message(self):
|
||||
return '← and →: change the value between True and False'
|
||||
|
||||
class TextMultiWin(FieldInput, windows.Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
windows.Win.__init__(self)
|
||||
self.options = field.getValue()
|
||||
self.options = self.options.split('\n') if self.options else []
|
||||
self.val_pos = 0
|
||||
self.edition_input = None
|
||||
if not isinstance(self.options, list):
|
||||
if isinstance(self.options, str):
|
||||
self.options = [self.options]
|
||||
else:
|
||||
self.options = []
|
||||
self.options.append('')
|
||||
|
||||
def do_command(self, key):
|
||||
if not self.edition_input:
|
||||
if key == 'KEY_LEFT':
|
||||
if self.val_pos > 0:
|
||||
self.val_pos -= 1
|
||||
elif key == 'KEY_RIGHT':
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.val_pos += 1
|
||||
elif key == '^M':
|
||||
self.edition_input = windows.Input()
|
||||
self.edition_input.color = self.color
|
||||
self.edition_input.resize(self.height, self.width, self.y, self.x)
|
||||
self.edition_input.text = self.options[self.val_pos]
|
||||
self.edition_input.key_end()
|
||||
else:
|
||||
if key == '^M':
|
||||
self.options[self.val_pos] = self.edition_input.get_text()
|
||||
if not self.options[self.val_pos] and self.val_pos != len(self.options) -1:
|
||||
del self.options[self.val_pos]
|
||||
if self.val_pos == len(self.options) -1:
|
||||
self.val_pos -= 1
|
||||
self.edition_input = None
|
||||
if not self.options or self.options[-1] != '':
|
||||
self.options.append('')
|
||||
else:
|
||||
self.edition_input.do_command(key)
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
if not self.edition_input:
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addnstr(0, 0, ' '*self.width, self.width)
|
||||
option = self.options[self.val_pos]
|
||||
self.addstr(0, self.width//2-len(option)//2, option)
|
||||
if self.val_pos > 0:
|
||||
self.addstr(0, 0, '←')
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.addstr(0, self.width-1, '→')
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
else:
|
||||
self.edition_input.refresh()
|
||||
|
||||
def reply(self):
|
||||
values = [val for val in self.options if val]
|
||||
self._field.setAnswer(values)
|
||||
|
||||
def get_help_message(self):
|
||||
if not self.edition_input:
|
||||
help_msg = '← and →: browse the available entries. '
|
||||
if self.val_pos == len(self.options)-1:
|
||||
help_msg += 'Enter: add an entry'
|
||||
else:
|
||||
help_msg += 'Enter: edit this entry'
|
||||
else:
|
||||
help_msg = 'Enter: finish editing this entry.'
|
||||
return help_msg
|
||||
|
||||
class ListMultiWin(FieldInput, windows.Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
windows.Win.__init__(self)
|
||||
values = field.getValue() or []
|
||||
self.options = [[option, True if option['value'] in values else False]\
|
||||
for option in field.get_options()]
|
||||
self.val_pos = 0
|
||||
|
||||
def do_command(self, key):
|
||||
if key == 'KEY_LEFT':
|
||||
if self.val_pos > 0:
|
||||
self.val_pos -= 1
|
||||
elif key == 'KEY_RIGHT':
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.val_pos += 1
|
||||
elif key == ' ':
|
||||
self.options[self.val_pos][1] = not self.options[self.val_pos][1]
|
||||
else:
|
||||
return
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addnstr(0, 0, ' '*self.width, self.width)
|
||||
if self.val_pos > 0:
|
||||
self.addstr(0, 0, '←')
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.addstr(0, self.width-1, '→')
|
||||
option = self.options[self.val_pos]
|
||||
self.addstr(0, self.width//2-len(option)//2, option[0]['label'])
|
||||
self.addstr(0, 2, '✔' if option[1] else '☐')
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def reply(self):
|
||||
self._field['label'] = ''
|
||||
self._field.delOptions()
|
||||
values = [option[0]['value'] for option in self.options if option[1] is True]
|
||||
self._field.setAnswer(values)
|
||||
|
||||
def get_help_message(self):
|
||||
return '←, →: Switch between the value. Space: select or unselect a value'
|
||||
|
||||
class ListSingleWin(FieldInput, windows.Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
windows.Win.__init__(self)
|
||||
# the option list never changes
|
||||
self.options = field.getOptions()
|
||||
# val_pos is the position of the currently selected option
|
||||
self.val_pos = 0
|
||||
for i, option in enumerate(self.options):
|
||||
if field.getValue() == option['value']:
|
||||
self.val_pos = i
|
||||
|
||||
def do_command(self, key):
|
||||
if key == 'KEY_LEFT':
|
||||
if self.val_pos > 0:
|
||||
self.val_pos -= 1
|
||||
elif key == 'KEY_RIGHT':
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.val_pos += 1
|
||||
else:
|
||||
return
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addnstr(0, 0, ' '*self.width, self.width)
|
||||
if self.val_pos > 0:
|
||||
self.addstr(0, 0, '←')
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.addstr(0, self.width-1, '→')
|
||||
option = self.options[self.val_pos]['label']
|
||||
self.addstr(0, self.width//2-len(option)//2, option)
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def reply(self):
|
||||
self._field['label'] = ''
|
||||
self._field.delOptions()
|
||||
self._field.setAnswer(self.options[self.val_pos]['value'])
|
||||
|
||||
def get_help_message(self):
|
||||
return '←, →: Select a value amongst the others'
|
||||
|
||||
class TextSingleWin(FieldInput, windows.Input):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
windows.Input.__init__(self)
|
||||
self.text = field.getValue() if isinstance(field.getValue(), str)\
|
||||
else ""
|
||||
self.pos = len(self.text)
|
||||
self.color = get_theme().COLOR_NORMAL_TEXT
|
||||
|
||||
def reply(self):
|
||||
self._field['label'] = ''
|
||||
self._field.setAnswer(self.get_text())
|
||||
|
||||
def get_help_message(self):
|
||||
return 'Edit the text'
|
||||
|
||||
class TextPrivateWin(TextSingleWin):
|
||||
def __init__(self, field):
|
||||
TextSingleWin.__init__(self, field)
|
||||
|
||||
def rewrite_text(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
if self.color:
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addstr('*'*len(self.text[self.view_pos:self.view_pos+self.width-1]))
|
||||
if self.color:
|
||||
(y, x) = self._win.getyx()
|
||||
size = self.width-x
|
||||
self.addnstr(' '*size, size, to_curses_attr(self.color))
|
||||
self.addstr(0, self.pos, '')
|
||||
if self.color:
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def get_help_message(self):
|
||||
return 'Edit the secret text'
|
||||
|
||||
class FormWin(object):
|
||||
"""
|
||||
A window, with some subwins (the various inputs).
|
||||
On init, create all the subwins.
|
||||
On resize, move and resize all the subwin and define how the text will be written
|
||||
On refresh, write all the text, and refresh all the subwins
|
||||
"""
|
||||
input_classes = {'boolean': BooleanWin,
|
||||
'fixed': DummyInput,
|
||||
'jid-multi': TextMultiWin,
|
||||
'jid-single': TextSingleWin,
|
||||
'list-multi': ListMultiWin,
|
||||
'list-single': ListSingleWin,
|
||||
'text-multi': TextMultiWin,
|
||||
'text-private': TextPrivateWin,
|
||||
'text-single': TextSingleWin,
|
||||
}
|
||||
def __init__(self, form, height, width, y, x):
|
||||
self._form = form
|
||||
with g_lock:
|
||||
self._win = windows.Win._tab_win.derwin(height, width, y, x)
|
||||
self.scroll_pos = 0
|
||||
self.current_input = 0
|
||||
self.inputs = [] # dict list
|
||||
for (name, field) in self._form.getFields().items():
|
||||
if field['type'] == 'hidden':
|
||||
continue
|
||||
try:
|
||||
input_class = self.input_classes[field['type']]
|
||||
except IndexError:
|
||||
continue
|
||||
label = field['label']
|
||||
desc = field['desc']
|
||||
if field['type'] == 'fixed':
|
||||
label = field.getValue()
|
||||
inp = input_class(field)
|
||||
self.inputs.append({'label':ColoredLabel(label),
|
||||
'description': desc,
|
||||
'input':inp})
|
||||
|
||||
def resize(self, height, width, y, x):
|
||||
self.height = height
|
||||
self.width = width
|
||||
with g_lock:
|
||||
self._win = windows.Win._tab_win.derwin(height, width, y, x)
|
||||
# Adjust the scroll position, if resizing made the window too small
|
||||
# for the cursor to be visible
|
||||
while self.current_input - self.scroll_pos > self.height-1:
|
||||
self.scroll_pos += 1
|
||||
|
||||
def reply(self):
|
||||
"""
|
||||
Set the response values in the form, for each field
|
||||
from the corresponding input
|
||||
"""
|
||||
for inp in self.inputs:
|
||||
if inp['input'].is_dummy():
|
||||
continue
|
||||
else:
|
||||
inp['input'].reply()
|
||||
self._form['title'] = ''
|
||||
self._form['instructions'] = ''
|
||||
|
||||
def go_to_next_input(self):
|
||||
if not self.inputs:
|
||||
return
|
||||
if self.current_input == len(self.inputs) - 1:
|
||||
return
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.current_input += 1
|
||||
jump = 0
|
||||
while self.current_input+jump != len(self.inputs) - 1 and self.inputs[self.current_input+jump]['input'].is_dummy():
|
||||
jump += 1
|
||||
if self.inputs[self.current_input+jump]['input'].is_dummy():
|
||||
return
|
||||
self.current_input += jump
|
||||
# If moving made the current input out of the visible screen, we
|
||||
# adjust the scroll position and we redraw the whole thing. We don’t
|
||||
# call refresh() if this is not the case, because
|
||||
# refresh_current_input() is always called anyway, so this is not
|
||||
# needed
|
||||
if self.current_input - self.scroll_pos > self.height-1:
|
||||
self.scroll_pos += 1
|
||||
self.refresh()
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
|
||||
def go_to_previous_input(self):
|
||||
if not self.inputs:
|
||||
return
|
||||
if self.current_input == 0:
|
||||
return
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.current_input -= 1
|
||||
jump = 0
|
||||
while self.current_input-jump > 0 and self.inputs[self.current_input+jump]['input'].is_dummy():
|
||||
jump += 1
|
||||
if self.inputs[self.current_input+jump]['input'].is_dummy():
|
||||
return
|
||||
# Adjust the scroll position if the current_input would be outside
|
||||
# of the visible area
|
||||
if self.current_input < self.scroll_pos:
|
||||
self.scroll_pos = self.current_input
|
||||
self.refresh()
|
||||
self.current_input -= jump
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
|
||||
def on_input(self, key):
|
||||
if not self.inputs:
|
||||
return
|
||||
self.inputs[self.current_input]['input'].do_command(key)
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
y = -self.scroll_pos
|
||||
i = 0
|
||||
for name, field in self._form.getFields().items():
|
||||
if field['type'] == 'hidden':
|
||||
continue
|
||||
self.inputs[i]['label'].resize(1, self.width//2, y + 1, 0)
|
||||
self.inputs[i]['input'].resize(1, self.width//2, y+1, self.width//2)
|
||||
# TODO: display the field description
|
||||
y += 1
|
||||
i += 1
|
||||
self._win.refresh()
|
||||
for i, inp in enumerate(self.inputs):
|
||||
if i < self.scroll_pos:
|
||||
continue
|
||||
if i >= self.height + self.scroll_pos:
|
||||
break
|
||||
inp['label'].refresh()
|
||||
inp['input'].refresh()
|
||||
inp['label'].refresh()
|
||||
if self.current_input < self.height-1:
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
self.inputs[self.current_input]['input'].refresh()
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
self.inputs[self.current_input]['label'].refresh()
|
||||
|
||||
def refresh_current_input(self):
|
||||
self.inputs[self.current_input]['input'].refresh()
|
||||
|
||||
def get_help_message(self):
|
||||
if self.current_input < self.height-1 and self.inputs[self.current_input]['input']:
|
||||
return self.inputs[self.current_input]['input'].get_help_message()
|
||||
return ''
|
||||
|
|
|
@ -3,6 +3,7 @@ Module exporting all the Windows, which are wrappers around curses wins
|
|||
used to display information on the screen
|
||||
"""
|
||||
from . base_wins import Win, g_lock
|
||||
from . data_forms import FormWin
|
||||
from . info_bar import GlobalInfoBar, VerticalGlobalInfoBar
|
||||
from . info_wins import InfoWin, XMLInfoWin, PrivateInfoWin, MucListInfoWin, \
|
||||
ConversationInfoWin, DynamicConversationInfoWin, MucInfoWin, \
|
||||
|
|
479
src/windows/data_forms.py
Normal file
479
src/windows/data_forms.py
Normal file
|
@ -0,0 +1,479 @@
|
|||
"""
|
||||
Windows used by the DataFormsTab.
|
||||
|
||||
We only need to export the FormWin (which is not a real Win, as it
|
||||
does not inherit from the Win base class), as it will create the
|
||||
others when needed.
|
||||
"""
|
||||
|
||||
from . import g_lock, Win
|
||||
from . inputs import Input
|
||||
|
||||
from theming import to_curses_attr, get_theme
|
||||
|
||||
class FieldInput(object):
|
||||
"""
|
||||
All input type in a data form should inherite this class,
|
||||
in addition with windows.Input or any relevant class from the
|
||||
'windows' library.
|
||||
"""
|
||||
def __init__(self, field):
|
||||
self._field = field
|
||||
self.color = get_theme().COLOR_NORMAL_TEXT
|
||||
|
||||
def set_color(self, color):
|
||||
self.color = color
|
||||
self.refresh()
|
||||
|
||||
def update_field_value(self, value):
|
||||
raise NotImplementedError
|
||||
|
||||
def resize(self, height, width, y, x):
|
||||
self._resize(height, width, y, x)
|
||||
|
||||
def is_dummy(self):
|
||||
return False
|
||||
|
||||
def reply(self):
|
||||
"""
|
||||
Set the correct response value in the field
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_help_message(self):
|
||||
"""
|
||||
Should return a string explaining the keys of the input.
|
||||
Will be displayed at each refresh on a line at the bottom of the tab.
|
||||
"""
|
||||
return ''
|
||||
|
||||
class ColoredLabel(Win):
|
||||
def __init__(self, text):
|
||||
self.text = text
|
||||
self.color = get_theme().COLOR_NORMAL_TEXT
|
||||
Win.__init__(self)
|
||||
|
||||
def resize(self, height, width, y, x):
|
||||
self._resize(height, width, y, x)
|
||||
|
||||
def set_color(self, color):
|
||||
self.color = color
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addstr(0, 0, self.text)
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
|
||||
class DummyInput(FieldInput, Win):
|
||||
"""
|
||||
Used for fields that do not require any input ('fixed')
|
||||
"""
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Win.__init__(self)
|
||||
|
||||
def do_command(self):
|
||||
return
|
||||
|
||||
def refresh(self):
|
||||
return
|
||||
|
||||
def is_dummy(self):
|
||||
return True
|
||||
|
||||
class BooleanWin(FieldInput, Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Win.__init__(self)
|
||||
self.last_key = 'KEY_RIGHT'
|
||||
self.value = bool(field.getValue())
|
||||
|
||||
def do_command(self, key):
|
||||
if key == 'KEY_LEFT' or key == 'KEY_RIGHT':
|
||||
self.value = not self.value
|
||||
self.last_key = key
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addnstr(0, 0, ' '*(8), self.width)
|
||||
self.addstr(0, 2, "%s"%self.value)
|
||||
self.addstr(0, 8, '→')
|
||||
self.addstr(0, 0, '←')
|
||||
if self.last_key == 'KEY_RIGHT':
|
||||
self.addstr(0, 8, '')
|
||||
else:
|
||||
self.addstr(0, 0, '')
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def reply(self):
|
||||
self._field['label'] = ''
|
||||
self._field.setAnswer(self.value)
|
||||
|
||||
def get_help_message(self):
|
||||
return '← and →: change the value between True and False'
|
||||
|
||||
class TextMultiWin(FieldInput, Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Win.__init__(self)
|
||||
self.options = field.getValue()
|
||||
self.options = self.options.split('\n') if self.options else []
|
||||
self.val_pos = 0
|
||||
self.edition_input = None
|
||||
if not isinstance(self.options, list):
|
||||
if isinstance(self.options, str):
|
||||
self.options = [self.options]
|
||||
else:
|
||||
self.options = []
|
||||
self.options.append('')
|
||||
|
||||
def do_command(self, key):
|
||||
if not self.edition_input:
|
||||
if key == 'KEY_LEFT':
|
||||
if self.val_pos > 0:
|
||||
self.val_pos -= 1
|
||||
elif key == 'KEY_RIGHT':
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.val_pos += 1
|
||||
elif key == '^M':
|
||||
self.edition_input = Input()
|
||||
self.edition_input.color = self.color
|
||||
self.edition_input.resize(self.height, self.width, self.y, self.x)
|
||||
self.edition_input.text = self.options[self.val_pos]
|
||||
self.edition_input.key_end()
|
||||
else:
|
||||
if key == '^M':
|
||||
self.options[self.val_pos] = self.edition_input.get_text()
|
||||
if not self.options[self.val_pos] and self.val_pos != len(self.options) -1:
|
||||
del self.options[self.val_pos]
|
||||
if self.val_pos == len(self.options) -1:
|
||||
self.val_pos -= 1
|
||||
self.edition_input = None
|
||||
if not self.options or self.options[-1] != '':
|
||||
self.options.append('')
|
||||
else:
|
||||
self.edition_input.do_command(key)
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
if not self.edition_input:
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addnstr(0, 0, ' '*self.width, self.width)
|
||||
option = self.options[self.val_pos]
|
||||
self.addstr(0, self.width//2-len(option)//2, option)
|
||||
if self.val_pos > 0:
|
||||
self.addstr(0, 0, '←')
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.addstr(0, self.width-1, '→')
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
else:
|
||||
self.edition_input.refresh()
|
||||
|
||||
def reply(self):
|
||||
values = [val for val in self.options if val]
|
||||
self._field.setAnswer(values)
|
||||
|
||||
def get_help_message(self):
|
||||
if not self.edition_input:
|
||||
help_msg = '← and →: browse the available entries. '
|
||||
if self.val_pos == len(self.options)-1:
|
||||
help_msg += 'Enter: add an entry'
|
||||
else:
|
||||
help_msg += 'Enter: edit this entry'
|
||||
else:
|
||||
help_msg = 'Enter: finish editing this entry.'
|
||||
return help_msg
|
||||
|
||||
class ListMultiWin(FieldInput, Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Win.__init__(self)
|
||||
values = field.getValue() or []
|
||||
self.options = [[option, True if option['value'] in values else False]\
|
||||
for option in field.get_options()]
|
||||
self.val_pos = 0
|
||||
|
||||
def do_command(self, key):
|
||||
if key == 'KEY_LEFT':
|
||||
if self.val_pos > 0:
|
||||
self.val_pos -= 1
|
||||
elif key == 'KEY_RIGHT':
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.val_pos += 1
|
||||
elif key == ' ':
|
||||
self.options[self.val_pos][1] = not self.options[self.val_pos][1]
|
||||
else:
|
||||
return
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addnstr(0, 0, ' '*self.width, self.width)
|
||||
if self.val_pos > 0:
|
||||
self.addstr(0, 0, '←')
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.addstr(0, self.width-1, '→')
|
||||
option = self.options[self.val_pos]
|
||||
self.addstr(0, self.width//2-len(option)//2, option[0]['label'])
|
||||
self.addstr(0, 2, '✔' if option[1] else '☐')
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def reply(self):
|
||||
self._field['label'] = ''
|
||||
self._field.delOptions()
|
||||
values = [option[0]['value'] for option in self.options if option[1] is True]
|
||||
self._field.setAnswer(values)
|
||||
|
||||
def get_help_message(self):
|
||||
return '←, →: Switch between the value. Space: select or unselect a value'
|
||||
|
||||
class ListSingleWin(FieldInput, Win):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Win.__init__(self)
|
||||
# the option list never changes
|
||||
self.options = field.getOptions()
|
||||
# val_pos is the position of the currently selected option
|
||||
self.val_pos = 0
|
||||
for i, option in enumerate(self.options):
|
||||
if field.getValue() == option['value']:
|
||||
self.val_pos = i
|
||||
|
||||
def do_command(self, key):
|
||||
if key == 'KEY_LEFT':
|
||||
if self.val_pos > 0:
|
||||
self.val_pos -= 1
|
||||
elif key == 'KEY_RIGHT':
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.val_pos += 1
|
||||
else:
|
||||
return
|
||||
self.refresh()
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addnstr(0, 0, ' '*self.width, self.width)
|
||||
if self.val_pos > 0:
|
||||
self.addstr(0, 0, '←')
|
||||
if self.val_pos < len(self.options)-1:
|
||||
self.addstr(0, self.width-1, '→')
|
||||
option = self.options[self.val_pos]['label']
|
||||
self.addstr(0, self.width//2-len(option)//2, option)
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def reply(self):
|
||||
self._field['label'] = ''
|
||||
self._field.delOptions()
|
||||
self._field.setAnswer(self.options[self.val_pos]['value'])
|
||||
|
||||
def get_help_message(self):
|
||||
return '←, →: Select a value amongst the others'
|
||||
|
||||
class TextSingleWin(FieldInput, Input):
|
||||
def __init__(self, field):
|
||||
FieldInput.__init__(self, field)
|
||||
Input.__init__(self)
|
||||
self.text = field.getValue() if isinstance(field.getValue(), str)\
|
||||
else ""
|
||||
self.pos = len(self.text)
|
||||
self.color = get_theme().COLOR_NORMAL_TEXT
|
||||
|
||||
def reply(self):
|
||||
self._field['label'] = ''
|
||||
self._field.setAnswer(self.get_text())
|
||||
|
||||
def get_help_message(self):
|
||||
return 'Edit the text'
|
||||
|
||||
class TextPrivateWin(TextSingleWin):
|
||||
def __init__(self, field):
|
||||
TextSingleWin.__init__(self, field)
|
||||
|
||||
def rewrite_text(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
if self.color:
|
||||
self._win.attron(to_curses_attr(self.color))
|
||||
self.addstr('*'*len(self.text[self.view_pos:self.view_pos+self.width-1]))
|
||||
if self.color:
|
||||
(y, x) = self._win.getyx()
|
||||
size = self.width-x
|
||||
self.addnstr(' '*size, size, to_curses_attr(self.color))
|
||||
self.addstr(0, self.pos, '')
|
||||
if self.color:
|
||||
self._win.attroff(to_curses_attr(self.color))
|
||||
self._refresh()
|
||||
|
||||
def get_help_message(self):
|
||||
return 'Edit the secret text'
|
||||
|
||||
class FormWin(object):
|
||||
"""
|
||||
A window, with some subwins (the various inputs).
|
||||
On init, create all the subwins.
|
||||
On resize, move and resize all the subwin and define how the text will be written
|
||||
On refresh, write all the text, and refresh all the subwins
|
||||
"""
|
||||
input_classes = {'boolean': BooleanWin,
|
||||
'fixed': DummyInput,
|
||||
'jid-multi': TextMultiWin,
|
||||
'jid-single': TextSingleWin,
|
||||
'list-multi': ListMultiWin,
|
||||
'list-single': ListSingleWin,
|
||||
'text-multi': TextMultiWin,
|
||||
'text-private': TextPrivateWin,
|
||||
'text-single': TextSingleWin,
|
||||
}
|
||||
def __init__(self, form, height, width, y, x):
|
||||
self._form = form
|
||||
with g_lock:
|
||||
self._win = Win._tab_win.derwin(height, width, y, x)
|
||||
self.scroll_pos = 0
|
||||
self.current_input = 0
|
||||
self.inputs = [] # dict list
|
||||
for (name, field) in self._form.getFields().items():
|
||||
if field['type'] == 'hidden':
|
||||
continue
|
||||
try:
|
||||
input_class = self.input_classes[field['type']]
|
||||
except IndexError:
|
||||
continue
|
||||
label = field['label']
|
||||
desc = field['desc']
|
||||
if field['type'] == 'fixed':
|
||||
label = field.getValue()
|
||||
inp = input_class(field)
|
||||
self.inputs.append({'label':ColoredLabel(label),
|
||||
'description': desc,
|
||||
'input':inp})
|
||||
|
||||
def resize(self, height, width, y, x):
|
||||
self.height = height
|
||||
self.width = width
|
||||
with g_lock:
|
||||
self._win = Win._tab_win.derwin(height, width, y, x)
|
||||
# Adjust the scroll position, if resizing made the window too small
|
||||
# for the cursor to be visible
|
||||
while self.current_input - self.scroll_pos > self.height-1:
|
||||
self.scroll_pos += 1
|
||||
|
||||
def reply(self):
|
||||
"""
|
||||
Set the response values in the form, for each field
|
||||
from the corresponding input
|
||||
"""
|
||||
for inp in self.inputs:
|
||||
if inp['input'].is_dummy():
|
||||
continue
|
||||
else:
|
||||
inp['input'].reply()
|
||||
self._form['title'] = ''
|
||||
self._form['instructions'] = ''
|
||||
|
||||
def go_to_next_input(self):
|
||||
if not self.inputs:
|
||||
return
|
||||
if self.current_input == len(self.inputs) - 1:
|
||||
return
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.current_input += 1
|
||||
jump = 0
|
||||
while self.current_input+jump != len(self.inputs) - 1 and self.inputs[self.current_input+jump]['input'].is_dummy():
|
||||
jump += 1
|
||||
if self.inputs[self.current_input+jump]['input'].is_dummy():
|
||||
return
|
||||
self.current_input += jump
|
||||
# If moving made the current input out of the visible screen, we
|
||||
# adjust the scroll position and we redraw the whole thing. We don’t
|
||||
# call refresh() if this is not the case, because
|
||||
# refresh_current_input() is always called anyway, so this is not
|
||||
# needed
|
||||
if self.current_input - self.scroll_pos > self.height-1:
|
||||
self.scroll_pos += 1
|
||||
self.refresh()
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
|
||||
def go_to_previous_input(self):
|
||||
if not self.inputs:
|
||||
return
|
||||
if self.current_input == 0:
|
||||
return
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_NORMAL_TEXT)
|
||||
self.current_input -= 1
|
||||
jump = 0
|
||||
while self.current_input-jump > 0 and self.inputs[self.current_input+jump]['input'].is_dummy():
|
||||
jump += 1
|
||||
if self.inputs[self.current_input+jump]['input'].is_dummy():
|
||||
return
|
||||
# Adjust the scroll position if the current_input would be outside
|
||||
# of the visible area
|
||||
if self.current_input < self.scroll_pos:
|
||||
self.scroll_pos = self.current_input
|
||||
self.refresh()
|
||||
self.current_input -= jump
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
|
||||
def on_input(self, key):
|
||||
if not self.inputs:
|
||||
return
|
||||
self.inputs[self.current_input]['input'].do_command(key)
|
||||
|
||||
def refresh(self):
|
||||
with g_lock:
|
||||
self._win.erase()
|
||||
y = -self.scroll_pos
|
||||
i = 0
|
||||
for name, field in self._form.getFields().items():
|
||||
if field['type'] == 'hidden':
|
||||
continue
|
||||
self.inputs[i]['label'].resize(1, self.width//2, y + 1, 0)
|
||||
self.inputs[i]['input'].resize(1, self.width//2, y+1, self.width//2)
|
||||
# TODO: display the field description
|
||||
y += 1
|
||||
i += 1
|
||||
self._win.refresh()
|
||||
for i, inp in enumerate(self.inputs):
|
||||
if i < self.scroll_pos:
|
||||
continue
|
||||
if i >= self.height + self.scroll_pos:
|
||||
break
|
||||
inp['label'].refresh()
|
||||
inp['input'].refresh()
|
||||
inp['label'].refresh()
|
||||
if self.current_input < self.height-1:
|
||||
self.inputs[self.current_input]['input'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
self.inputs[self.current_input]['input'].refresh()
|
||||
self.inputs[self.current_input]['label'].set_color(get_theme().COLOR_SELECTED_ROW)
|
||||
self.inputs[self.current_input]['label'].refresh()
|
||||
|
||||
def refresh_current_input(self):
|
||||
self.inputs[self.current_input]['input'].refresh()
|
||||
|
||||
def get_help_message(self):
|
||||
if self.current_input < self.height-1 and self.inputs[self.current_input]['input']:
|
||||
return self.inputs[self.current_input]['input'].get_help_message()
|
||||
return ''
|
||||
|
||||
|
Loading…
Reference in a new issue