deluge/deluge/ui/console/modes/input_popup.py

913 lines
34 KiB
Python

#
# input_popup.py
#
# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
#
# Complete function from commands/add.py:
# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
#
# Deluge is free software.
#
# You may redistribute it and/or modify it under the terms of the
# GNU General Public License, as published by the Free Software
# Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# deluge is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with deluge. If not, write to:
# The Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301, USA.
#
# In addition, as a special exception, the copyright holders give
# permission to link the code of portions of this program with the OpenSSL
# library.
# You must obey the GNU General Public License in all respects for all of
# the code used other than OpenSSL. If you modify file(s) with this
# exception, you may extend this exception to your version of the file(s),
# but you are not obligated to do so. If you do not wish to do so, delete
# this exception statement from your version. If you delete this exception
# statement from all source files in the program, then also delete it here.
#
#
try:
import curses
except ImportError:
pass
import logging,os,os.path
from popup import Popup, ALIGN
from deluge.ui.console import colors
log = logging.getLogger(__name__)
class InputField:
depend = None
# render the input. return number of rows taken up
def get_height(self):
return 0
def render(self,screen,row,width,selected,col=1):
return 0
def handle_read(self, c):
if c in [curses.KEY_ENTER, 10, 127, 113]:
return True
return False
def get_value(self):
return None
def set_value(self, value):
pass
def set_depend(self,i,inverse=False):
if not isinstance(i,CheckedInput):
raise Exception("Can only depend on CheckedInputs")
self.depend = i
self.inverse = inverse
def depend_skip(self):
if not self.depend:
return False
if self.inverse:
return self.depend.checked
else:
return not self.depend.checked
class CheckedInput(InputField):
def __init__(self, parent, message, name, checked=False, additional_formatting=False):
self.parent = parent
self.additional_formatting = additional_formatting
self.chkd_inact = "[X] %s"%message
self.unchkd_inact = "[ ] %s"%message
self.chkd_act = "[{!black,white,bold!}X{!white,black!}] %s"%message
self.unchkd_act = "[{!black,white,bold!} {!white,black!}] %s"%message
self.name = name
self.checked = checked
def get_height(self):
return 1
def render(self, screen, row, width, active, col=1):
if self.checked and active:
self.parent.add_string(row,self.chkd_act,screen,col,False,True)
elif self.checked:
self.parent.add_string(row,self.chkd_inact,screen,col,False,True)
elif active:
self.parent.add_string(row,self.unchkd_act,screen,col,False,True)
else:
self.parent.add_string(row,self.unchkd_inact,screen,col,False,True)
return 1
def handle_read(self, c):
if c == 32:
self.checked = not self.checked
def get_value(self):
return self.checked
def set_value(self, c):
self.checked = c
class CheckedPlusInput(InputField):
def __init__(self, parent, message, name, child, checked=False, additional_formatting=False):
self.parent = parent
self.additional_formatting = additional_formatting
self.chkd_inact = "[X] %s"%message
self.unchkd_inact = "[ ] %s"%message
self.chkd_act = "[{!black,white,bold!}X{!white,black!}] %s"%message
self.unchkd_act = "[{!black,white,bold!} {!white,black!}] %s"%message
self.name = name
self.checked = checked
self.msglen = len(self.chkd_inact)+1
self.child = child
self.child_active = False
def get_height():
return max(2, self.child.height)
def render(self, screen, row, width, active, col=1):
isact = active and not self.child_active
if self.checked and isact:
self.parent.add_string(row,self.chkd_act,screen,col,False,True)
elif self.checked:
self.parent.add_string(row,self.chkd_inact,screen,col,False,True)
elif isact:
self.parent.add_string(row,self.unchkd_act,screen,col,False,True)
else:
self.parent.add_string(row,self.unchkd_inact,screen,col,False,True)
if active and self.checked and self.child_active:
self.parent.add_string(row+1,"(esc to leave)",screen,col,False,True)
elif active and self.checked:
self.parent.add_string(row+1,"(right arrow to edit)",screen,col,False,True)
rows = 2
# show child
if self.checked:
if isinstance(self.child,(TextInput,IntSpinInput,FloatSpinInput)):
crows = self.child.render(screen,row,width-self.msglen,self.child_active and active,col+self.msglen,self.msglen)
else:
crows = self.child.render(screen,row,width-self.msglen,self.child_active and active,col+self.msglen)
rows = max(rows,crows)
else:
self.parent.add_string(row,"(enable to view/edit value)",screen,col+self.msglen,False,True)
return rows
def handle_read(self, c):
if self.child_active:
if c == 27: # leave child on esc
self.child_active = False
return
# pass keys through to child
self.child.handle_read(c)
else:
if c == 32:
self.checked = not self.checked
if c == curses.KEY_RIGHT:
self.child_active = True
def get_value(self):
return self.checked
def set_value(self, c):
self.checked = c
def get_child(self):
return self.child
class IntSpinInput(InputField):
def __init__(self, parent, message, name, move_func, value, min_val=None, max_val=None, additional_formatting=False):
self.parent = parent
self.message = message
self.name = name
self.additional_formatting = additional_formatting
self.default_str = str(value)
self.set_value( value)
self.default_value = self.value
self.cursor = len(self.valstr)
self.cursoff = colors.get_line_width(self.message)+4 # + 4 for the " [ " in the rendered string
self.move_func = move_func
self.min_val = min_val
self.max_val = max_val
self.need_update = False
def get_height(self):
return 1
def __limit_value(self):
if (self.min_val != None) and self.value < self.min_val:
self.value = self.min_val
if (self.max_val != None) and self.value > self.max_val:
self.value = self.max_val
def render(self, screen, row, width, active, col=1, cursor_offset=0):
if not active and self.need_update:
if not self.valstr or self.valstr == '-':
self.value = self.default_value
self.valstr = self.default_str
try:
int(self.value)
except:
self.real_value = False
else:
self.value = int(self.valstr)
self.__limit_value()
self.valstr = "%d"%self.value
self.cursor = len(self.valstr)
self.cursor = colors.get_line_width(self.valstr)
self.need_update = False
elif self.need_update and self.valstr != '-':
self.real_value = True
try:
self.value = int(self.valstr)
except:
self.value = self.default_value
try:
int(self.value)
except:
self.real_value = False
if not self.valstr:
self.parent.add_string(row,"%s {!input!}[ ]"%self.message,screen,col,False,True)
elif active:
self.parent.add_string(row,"%s {!input!}[ {!black,white,bold!}%s{!input!} ]"%(self.message,self.valstr),screen,col,False,True)
elif self.additional_formatting and self.valstr == self.default_str:
self.parent.add_string(row,"%s {!input!}[ {!magenta,black!}%s{!input!} ]"%(self.message,self.valstr),screen,col,False,True)
else:
self.parent.add_string(row,"%s {!input!}[ %s ]"%(self.message,self.valstr),screen,col,False,True)
if active:
self.move_func(row,self.cursor+self.cursoff+cursor_offset)
return 1
def handle_read(self, c):
if c == curses.KEY_PPAGE and self.value < self.max_val:
if not self.real_value:
self.value = self.min_val
self.valstr = "%d"%self.value
self.real_value = True
else:
self.value+=1
self.valstr = "%d"%self.value
self.cursor = len(self.valstr)
elif c == curses.KEY_NPAGE and self.value > self.min_val:
if not self.real_value:
self.value = self.min_val
self.valstr = "%d"%self.value
self.real_value = True
else:
self.value-=1
self.valstr = "%d"%self.value
self.cursor = len(self.valstr)
elif c == curses.KEY_LEFT:
if not self.real_value: return None
self.cursor = max(0,self.cursor-1)
elif c == curses.KEY_RIGHT:
if not self.real_value: return None
self.cursor = min(len(self.valstr),self.cursor+1)
elif c == curses.KEY_HOME:
if not self.real_value: return None
self.cursor = 0
elif c == curses.KEY_END:
if not self.real_value: return None
self.cursor = len(self.valstr)
elif c == curses.KEY_BACKSPACE or c == 127:
if not self.real_value:
self.valstr = ""
self.cursor = 0
self.real_value = True
self.need_update = True
elif self.valstr and self.cursor > 0:
self.valstr = self.valstr[:self.cursor - 1] + self.valstr[self.cursor:]
self.cursor-=1
self.need_update = True
elif c == curses.KEY_DC:
if not self.real_value: return None
if self.valstr and self.cursor < len(self.valstr):
self.valstr = self.valstr[:self.cursor] + self.valstr[self.cursor+1:]
self.need_update = True
elif c == 45 and self.min_val < 0:
if not self.real_value:
self.valstr = "-"
self.cursor = 1
self.real_value = True
self.need_update = True
if self.cursor != 0: return
minus_place = self.valstr.find('-')
if minus_place >= 0: return
self.valstr = chr(c)+self.valstr
self.cursor += 1
self.need_update = True
elif c > 47 and c < 58:
if (not self.real_value) and self.valstr:
self.valstr = ""
self.cursor = 0
self.real_value = True
self.need_update = True
if c == 48 and self.cursor == 0: return
minus_place = self.valstr.find('-')
if self.cursor <= minus_place: return
if self.cursor == len(self.valstr):
self.valstr += chr(c)
else:
# Insert into string
self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:]
self.need_update = True
# Move the cursor forward
self.cursor+=1
def get_value(self):
if self.real_value:
self.__limit_value()
return self.value
else:
return None
def set_value(self, val):
try:
self.value = int(val)
self.valstr = "%d" % self.value
self.real_value = True
except ValueError:
self.value = None
self.real_value = False
self.valstr = val
self.cursor = len(self.valstr)
#TODO: This vvvvv
class FloatSpinInput(InputField):
def __init__(self, parent, message, name, move_func, value, inc_amt, precision, min_val=None, max_val=None, additional_formatting = False):
self.parent = parent
self.message = message
self.name = name
self.precision = precision
self.inc_amt = inc_amt
self.additional_formatting = additional_formatting
self.fmt = "%%.%df"%precision
self.default_str = str(value)
self.set_value(value)
self.default_value = self.value
self.cursor = len(self.valstr)
self.cursoff = colors.get_line_width(self.message)+4 # + 4 for the " [ " in the rendered string
self.move_func = move_func
self.min_val = min_val
self.max_val = max_val
self.need_update = False
def get_height(self):
return 1
def __limit_value(self):
if (self.min_val != None) and self.value < self.min_val:
self.value = self.min_val
if (self.max_val != None) and self.value > self.max_val:
self.value = self.max_val
self.valstr = self.fmt % self.value
def render(self, screen, row, width, active, col=1, cursor_offset=0):
if not active and self.need_update:
if not self.valstr or self.valstr == '-':
self.value = self.default_value
self.valstr = self.default_str
try:
float(self.value)
except:
self.real_value = False
else:
self.set_value(self.valstr)
self.__limit_value()
self.valstr = self.fmt % self.value
self.cursor = len(self.valstr)
self.cursor = colors.get_line_width(self.valstr)
self.need_update = False
elif self.need_update and self.valstr != '-':
self.real_value = True
try:
self.value = round(float(self.valstr), self.precision)
except:
self.value = self.default_value
try:
float(self.value)
except:
self.real_value = False
if not self.valstr:
self.parent.add_string(row,"%s {!input!}[ ]"%self.message,screen,col,False,True)
elif active:
self.parent.add_string(row,"%s {!input!}[ {!black,white,bold!}%s{!white,black!} ]"%(self.message,self.valstr),screen,col,False,True)
elif self.additional_formatting and self.valstr == self.default_str:
self.parent.add_string(row,"%s {!input!}[ {!magenta,black!}%s{!input!} ]"%(self.message,self.valstr),screen,col,False,True)
else:
self.parent.add_string(row,"%s {!input!}[ %s ]"%(self.message,self.valstr),screen,col,False,True)
if active:
self.move_func(row,self.cursor+self.cursoff+cursor_offset)
return 1
def handle_read(self, c):
if c == curses.KEY_PPAGE:
if not self.real_value:
self.value = self.min_val
self.valstr = "%d"%self.value
self.real_value = True
else:
self.value+=self.inc_amt
self.__limit_value()
self.valstr = self.fmt%self.value
self.cursor = len(self.valstr)
elif c == curses.KEY_NPAGE:
if not self.real_value:
self.value = self.min_val
self.valstr = "%d"%self.value
self.real_value = True
else:
self.value-=self.inc_amt
self.__limit_value()
self.valstr = self.fmt%self.value
self.cursor = len(self.valstr)
elif c == curses.KEY_LEFT:
if not self.real_value: return None
self.cursor = max(0,self.cursor-1)
elif c == curses.KEY_RIGHT:
if not self.real_value: return None
self.cursor = min(len(self.valstr),self.cursor+1)
elif c == curses.KEY_HOME:
if not self.real_value: return None
self.cursor = 0
elif c == curses.KEY_END:
if not self.real_value: return None
self.cursor = len(self.valstr)
elif c == curses.KEY_BACKSPACE or c == 127:
if not self.real_value:
self.valstr = ""
self.cursor = 0
self.real_value = True
self.need_update = True
elif self.valstr and self.cursor > 0:
self.valstr = self.valstr[:self.cursor - 1] + self.valstr[self.cursor:]
self.cursor-=1
self.need_update = True
elif c == curses.KEY_DC:
if not self.real_value: return None
if self.valstr and self.cursor < len(self.valstr):
self.valstr = self.valstr[:self.cursor] + self.valstr[self.cursor+1:]
self.need_update = True
elif c == 45 and self.min_val < 0:
if not self.real_value:
self.valstr = "-"
self.cursor = 1
self.need_update = True
self.real_value = True
if self.cursor != 0: return
minus_place = self.valstr.find('-')
if minus_place >= 0: return
self.valstr = chr(c)+self.valstr
self.cursor += 1
self.need_update = True
elif c == 46:
if (not self.real_value) and self.valstr:
self.valstr = "0."
self.cursor = 2
self.real_value = True
self.need_update = True
minus_place = self.valstr.find('-')
if self.cursor <= minus_place: return
point_place = self.valstr.find('.')
if point_place >= 0: return
if self.cursor == len(self.valstr):
self.valstr += chr(c)
else:
# Insert into string
self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:]
self.need_update = True
# Move the cursor forward
self.cursor+=1
elif (c > 47 and c < 58):
if (not self.real_value) and self.valstr:
self.valstr = ""
self.cursor = 0
self.real_value = True
self.need_update = True
if self.value == "mixed":
self.value = ""
minus_place = self.valstr.find('-')
if self.cursor <= minus_place: return
if self.cursor == len(self.valstr):
self.valstr += chr(c)
else:
# Insert into string
self.valstr = self.valstr[:self.cursor] + chr(c) + self.valstr[self.cursor:]
self.need_update = True
# Move the cursor forward
self.cursor+=1
def get_value(self):
if self.real_value:
self.__limit_value()
return self.value
else:
return None
def set_value(self, val):
try:
self.value = round(float(val), self.precision)
self.valstr = self.fmt % self.value
self.real_value = True
except ValueError:
self.value = None
self.real_value = False
self.valstr = val
self.cursor = len(self.valstr)
class SelectInput(InputField):
def __init__(self, parent, message, name, opts, vals, selidx, additional_formatting = False):
self.parent = parent
self.message = message
self.additional_formatting = additional_formatting
self.name = name
self.opts = opts
self.vals = vals
self.selidx = selidx
self.default_option = selidx
def get_height(self):
return 1 + bool(self.message)
def render(self, screen, row, width, selected, col=1):
if self.message:
self.parent.add_string(row,self.message,screen,col,False,True)
row += 1
off = col+1
for i,opt in enumerate(self.opts):
if selected and i == self.selidx:
self.parent.add_string(row,"{!black,white,bold!}[%s]"%opt,screen,off,False,True)
elif i == self.selidx:
if self.additional_formatting and i == self.default_option:
self.parent.add_string(row,"[{!magenta,black!}%s{!white,black!}]"%opt,screen,off,False,True)
elif self.additional_formatting:
self.parent.add_string(row,"[{!white,blue!}%s{!white,black!}]"%opt,screen,off,False,True)
else:
self.parent.add_string(row,"[{!white,black,underline!}%s{!white,black!}]"%opt,screen,off,False,True)
else:
self.parent.add_string(row,"[%s]"%opt,screen,off,False,True)
off += len(opt)+3
if self.message:
return 2
else:
return 1
def handle_read(self, c):
if c == curses.KEY_LEFT:
self.selidx = max(0,self.selidx-1)
if c == curses.KEY_RIGHT:
self.selidx = min(len(self.opts)-1,self.selidx+1)
def get_value(self):
return self.vals[self.selidx]
def set_value(self, nv):
for i,val in enumerate(self.vals):
if nv == val:
self.selidx = i
return
raise Exception("Invalid value for SelectInput")
class TextInput(InputField):
def __init__(self, parent, move_func, width, message, name, value, docmp, additional_formatting=False):
self.parent = parent
self.move_func = move_func
self.width = width
self.additional_formatting = additional_formatting
self.message = message
self.name = name
self.value = value
self.default_value = value
self.docmp = docmp
self.tab_count = 0
self.cursor = len(self.value)
self.opts = None
self.opt_off = 0
def get_height(self):
return 2 + bool(self.message)
def render(self,screen,row,width,selected,col=1,cursor_offset=0):
if not self.value and not selected and len(self.default_value) != 0:
self.value = self.default_value
self.cursor = len(self.value)
if self.message:
self.parent.add_string(row,self.message,screen,col,False,True)
row += 1
if selected:
if self.opts:
self.parent.add_string(row+1,self.opts[self.opt_off:],screen,col,False,True)
if self.cursor > (width-3):
self.move_func(row,width-2)
else:
self.move_func(row,self.cursor+1+cursor_offset)
slen = len(self.value)+3
if slen > width:
vstr = self.value[(slen-width):]
else:
vstr = self.value.ljust(width-2)
if self.additional_formatting and len(self.value) != 0 and self.value == self.default_value:
self.parent.add_string(row,"{!magenta,white!}%s"%vstr,screen,col,False,False)
else:
self.parent.add_string(row,"{!black,white,bold!}%s"%vstr,screen,col,False,False)
if self.message:
return 3
else:
return 2
def get_value(self):
return self.value
def set_value(self,val):
self.value = val
self.cursor = len(self.value)
# most of the cursor,input stuff here taken from ui/console/screen.py
def handle_read(self,c):
if c == 9 and self.docmp:
# Keep track of tab hit count to know when it's double-hit
self.tab_count += 1
if self.tab_count > 1:
second_hit = True
self.tab_count = 0
else:
second_hit = False
# We only call the tab completer function if we're at the end of
# the input string on the cursor is on a space
if self.cursor == len(self.value) or self.value[self.cursor] == " ":
if self.opts:
prev = self.opt_off
self.opt_off += self.width-3
# now find previous double space, best guess at a split point
# in future could keep opts unjoined to get this really right
self.opt_off = self.opts.rfind(" ",0,self.opt_off)+2
if second_hit and self.opt_off == prev: # double tap and we're at the end
self.opt_off = 0
else:
opts = self.complete(self.value)
if len(opts) == 1: # only one option, just complete it
self.value = opts[0]
self.cursor = len(opts[0])
self.tab_count = 0
elif len(opts) > 1:
prefix = os.path.commonprefix(opts)
if prefix:
self.value = prefix
self.cursor = len(prefix)
if len(opts) > 1 and second_hit: # display multiple options on second tab hit
sp = self.value.rfind(os.sep)+1
self.opts = " ".join([o[sp:] for o in opts])
# Cursor movement
elif c == curses.KEY_LEFT:
self.cursor = max(0,self.cursor-1)
elif c == curses.KEY_RIGHT:
self.cursor = min(len(self.value),self.cursor+1)
elif c == curses.KEY_HOME:
self.cursor = 0
elif c == curses.KEY_END:
self.cursor = len(self.value)
if c != 9:
self.opts = None
self.opt_off = 0
self.tab_count = 0
# Delete a character in the input string based on cursor position
if c == curses.KEY_BACKSPACE or c == 127:
if self.value and self.cursor > 0:
self.value = self.value[:self.cursor - 1] + self.value[self.cursor:]
self.cursor-=1
elif c == curses.KEY_DC:
if self.value and self.cursor < len(self.value):
self.value = self.value[:self.cursor] + self.value[self.cursor+1:]
elif c > 31 and c < 256:
# Emulate getwch
stroke = chr(c)
uchar = ""
while not uchar:
try:
uchar = stroke.decode(self.parent.encoding)
except UnicodeDecodeError:
c = self.parent.parent.stdscr.getch()
stroke += chr(c)
if uchar:
if self.cursor == len(self.value):
self.value += uchar
else:
# Insert into string
self.value = self.value[:self.cursor] + uchar + self.value[self.cursor:]
# Move the cursor forward
self.cursor+=1
def complete(self,line):
line = os.path.abspath(os.path.expanduser(line))
ret = []
if os.path.exists(line):
# This is a correct path, check to see if it's a directory
if os.path.isdir(line):
# Directory, so we need to show contents of directory
#ret.extend(os.listdir(line))
for f in os.listdir(line):
# Skip hidden
if f.startswith("."):
continue
f = os.path.join(line, f)
if os.path.isdir(f):
f += os.sep
ret.append(f)
else:
# This is a file, but we could be looking for another file that
# shares a common prefix.
for f in os.listdir(os.path.dirname(line)):
if f.startswith(os.path.split(line)[1]):
ret.append(os.path.join( os.path.dirname(line), f))
else:
# This path does not exist, so lets do a listdir on it's parent
# and find any matches.
ret = []
if os.path.isdir(os.path.dirname(line)):
for f in os.listdir(os.path.dirname(line)):
if f.startswith(os.path.split(line)[1]):
p = os.path.join(os.path.dirname(line), f)
if os.path.isdir(p):
p += os.sep
ret.append(p)
return ret
class InputPopup(Popup):
def __init__(self,parent_mode,title,width_req=0,height_req=0,
align=ALIGN.DEFAULT,
close_cb=None,
additional_formatting=True,
immediate_action=False):
Popup.__init__(self,parent_mode,title, width_req=width_req, height_req=height_req, align=align, close_cb=close_cb)
self.inputs = []
self.lines = []
self.current_input = 0
self.additional_formatting = additional_formatting
self.immediate_action = immediate_action
#We need to replicate some things in order to wrap our inputs
self.encoding = parent_mode.encoding
def move(self,r,c):
self._cursor_row = r
self._cursor_col = c
def add_text_input(self, message, name, value="", complete=True):
"""
Add a text input field to the popup.
: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
"""
self.inputs.append(TextInput(self, self.move, self.width, message,
name, value, complete,
additional_formatting = self.additional_formatting))
def getmaxyx(self):
return self.screen.getmaxyx()
def add_string(self, row, string, scr=None, col = 0, pad=True, trim=True):
if row <= 0:
return False
elif row >= self.height -1:
return False
self.parent.add_string(row, string, scr, col, pad, trim)
return True
def add_spaces(self, num):
for i in range(num):
self.lines.append((len(self.inputs), ""))
def add_text(self, string):
lines = string.split("\n")
for line in lines:
self.lines.append( (len(self.inputs), line) )
def add_select_input(self, message, name, opts, vals, default_index=0):
self.inputs.append(SelectInput(self, message, name, opts, vals, default_index,
additional_formatting = self.additional_formatting))
def add_checked_input(self, message, name, checked=False):
self.inputs.append(CheckedInput(self,message,name,checked,
additional_formatting = self.additional_formatting))
#def add_checked_plus_input(self, message, name, child)
def add_float_spin_input(self, message, name, value=0.0, inc_amt = 1.0, precision = 1, min_val = None, max_val = None):
i = FloatSpinInput(self, message, name, self.move, value, inc_amt, precision, min_val, max_val,
additional_formatting = self.additional_formatting)
self.inputs.append(i)
def add_int_spin_input(self, message, name, value = 0, min_val = None, max_val = None):
i = IntSpinInput(self, message, name, self.move, value, min_val, max_val,
additional_formatting = self.additional_formatting)
self.inputs.append(i)
def _refresh_lines(self):
self._cursor_row = -1
self._cursor_col = -1
curses.curs_set(0)
start_row = 0
end_row = 0
spos = 0
for i, ipt in enumerate(self.inputs):
for line in self.lines:
if line[0] == i:
end_row += 1
start_row = end_row
end_row += ipt.get_height()
active = (i == self.current_input)
if active:
if end_row + 1 >= self.height + self.lineoff :
self.lineoff += ipt.get_height()
elif start_row < self.lineoff:
self.lineoff -= ipt.get_height()
self.content_height = end_row
crow = 1 - self.lineoff
spos = 0
for i,ipt in enumerate(self.inputs):
for line in self.lines:
if line[0] == i:
self.add_string(crow, line[1], self.screen, 1, pad=False)
crow += 1
crow += ipt.render(self.screen,crow,self.width,i==self.current_input)
if (self.content_height > (self.height-2)):
lts = self.content_height-(self.height-3)
perc_sc = float(self.lineoff)/lts
sb_pos = int((self.height-2)*perc_sc)+1
if (sb_pos == 1) and (self.lineoff != 0):
sb_pos += 1
self.add_string(sb_pos, "{!red,black,bold!}#",self.screen,col=(self.width-1),pad=False,trim=False)
if self._cursor_row >= 0:
curses.curs_set(2)
self.screen.move(self._cursor_row,self._cursor_col)
def handle_read(self, c):
if c == curses.KEY_UP:
self.current_input = max(0,self.current_input-1)
elif c == curses.KEY_DOWN:
self.current_input = min(len(self.inputs)-1,self.current_input+1)
elif c == curses.KEY_ENTER or c == 10:
if self.close_cb:
vals = {}
for ipt in self.inputs:
vals[ipt.name] = ipt.get_value()
curses.curs_set(0)
self.close_cb(vals)
return True # close the popup
elif c == 27: # close on esc, no action
return True
elif self.inputs:
self.inputs[self.current_input].handle_read(c)
if self.immediate_action:
vals = {}
for ipt in self.inputs:
vals[ipt.name] = ipt.get_value()
self.close_cb(vals)
self.refresh()
return False