Initial commit
This commit is contained in:
commit
72d05bc6df
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
*.pyc
|
1
README
Normal file
1
README
Normal file
@ -0,0 +1 @@
|
|||||||
|
A set of utilities to develop applications using ncurses and python.
|
0
guicurses/__init__.py
Normal file
0
guicurses/__init__.py
Normal file
709
guicurses/widgets.py
Normal file
709
guicurses/widgets.py
Normal file
@ -0,0 +1,709 @@
|
|||||||
|
#holds basic GUI structures for use in curses (treeview, listbox, checkbox, ETC)
|
||||||
|
# Taken and modified from http://bmcginty.us/clifox.git
|
||||||
|
import curses, time, os, os.path, string, sys
|
||||||
|
from curses import ascii
|
||||||
|
|
||||||
|
class GuiObject(object):
|
||||||
|
done = 0
|
||||||
|
|
||||||
|
def beepIfNeeded(self):
|
||||||
|
# if self.base.config.beeps:
|
||||||
|
curses.beep()
|
||||||
|
|
||||||
|
def setStatus(self,*a,**kw):
|
||||||
|
return self.base.setStatus(*a,**kw)
|
||||||
|
|
||||||
|
def onFocus(self, *args, **kwargs): self.screen.move(self.y, self.x)
|
||||||
|
|
||||||
|
class Dialog(GuiObject):
|
||||||
|
"""control holder
|
||||||
|
down and up arrows move through controls
|
||||||
|
enter selects default button or displays error if not one
|
||||||
|
"""
|
||||||
|
@property
|
||||||
|
def controlIndex(self):
|
||||||
|
return self._controlIndex
|
||||||
|
|
||||||
|
@controlIndex.setter
|
||||||
|
def controlIndex(self, i):
|
||||||
|
self._controlIndex = i
|
||||||
|
self.controls[self._controlIndex].onFocus()
|
||||||
|
return i
|
||||||
|
|
||||||
|
def __init__(self, screen = None, base = None, y = 0, x = 0, controls = [], can_go_back=True):
|
||||||
|
self.base = base
|
||||||
|
self.screen = screen
|
||||||
|
self.y, self.x = y, x
|
||||||
|
self._controls = controls
|
||||||
|
self.controls = []
|
||||||
|
self.can_go_back = can_go_back
|
||||||
|
self.initialDraw()
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
def initialDraw(self):
|
||||||
|
for i in xrange(0, len(self._controls)):
|
||||||
|
co = self._controls[i]
|
||||||
|
c = co[0](screen=self.screen, base=self.base, y=i, can_go_back=self.can_go_back, **co[1])
|
||||||
|
self.controls.append(c)
|
||||||
|
self.controlIndex = 0
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
for i in self.controls:
|
||||||
|
i.draw()
|
||||||
|
|
||||||
|
def handleKey(self, c):
|
||||||
|
ret = 1
|
||||||
|
if c == curses.KEY_DOWN:
|
||||||
|
if self.controlIndex >= len(self.controls) -1:
|
||||||
|
self.beepIfNeeded()
|
||||||
|
self.setStatus("No more controls in this dialog. Please up arrow to the first control.")
|
||||||
|
self.controlIndex = len(self.controls) -1
|
||||||
|
else:
|
||||||
|
self.controlIndex += 1
|
||||||
|
elif c == curses.KEY_UP:
|
||||||
|
if self.controlIndex <= 0:
|
||||||
|
self.beepIfNeeded()
|
||||||
|
self.setStatus("This is the first control in this dialog.")
|
||||||
|
self.controlIndex = 0
|
||||||
|
else:
|
||||||
|
self.controlIndex -= 1
|
||||||
|
else:
|
||||||
|
ret = self.controls[self.controlIndex].handleKey(c)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
class Button(GuiObject):
|
||||||
|
def __init__(self, screen=None, base=None, y=1, x=0, can_go_back=True, prompt="Button", action="", help_string=""):
|
||||||
|
self.base = base
|
||||||
|
self.screen = screen
|
||||||
|
self.y, self.x = y, x
|
||||||
|
self.prompt = prompt
|
||||||
|
self.selected = 0
|
||||||
|
self.draw()
|
||||||
|
self.action = action
|
||||||
|
self.help_string = help_string
|
||||||
|
self.can_go_back = can_go_back
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
s = self.prompt
|
||||||
|
self.screen.addstr(self.y, self.x, s.encode("utf-8"))
|
||||||
|
self.screen.refresh()
|
||||||
|
|
||||||
|
def handleKey(self, k):
|
||||||
|
if k == 10 or k == curses.KEY_RIGHT: # Enter key or right for easier use
|
||||||
|
self.done = 1
|
||||||
|
self.selected = 1
|
||||||
|
return 1
|
||||||
|
elif k == curses.KEY_F1:
|
||||||
|
if hasattr(self, "help_string"):
|
||||||
|
self.setStatus(self.help_string)
|
||||||
|
return 1
|
||||||
|
elif k == curses.KEY_BACKSPACE or k == curses.KEY_LEFT and self.can_go_back: # Let's go back
|
||||||
|
self.base.go_back()
|
||||||
|
return 1
|
||||||
|
return None
|
||||||
|
|
||||||
|
class Editbox(object):
|
||||||
|
"""Editing widget using the interior of a window object.
|
||||||
|
Supports the following Emacs-like key bindings:
|
||||||
|
Ctrl-A Go to left edge of window.
|
||||||
|
Ctrl-B Cursor left, wrapping to previous line if appropriate.
|
||||||
|
Ctrl-D Delete character under cursor.
|
||||||
|
Ctrl-E Go to right edge (stripspaces off) or end of line (stripspaces on).
|
||||||
|
Ctrl-F Cursor right, wrapping to next line when appropriate.
|
||||||
|
Ctrl-G Terminate, returning the window contents.
|
||||||
|
Ctrl-H Delete character backward.
|
||||||
|
Ctrl-J Terminate if the window is 1 line, otherwise insert newline.
|
||||||
|
Ctrl-K If line is blank, delete it, otherwise clear to end of line.
|
||||||
|
Ctrl-L Refresh screen.
|
||||||
|
Ctrl-N Cursor down; move down one line.
|
||||||
|
Ctrl-O Insert a blank line at cursor location.
|
||||||
|
Ctrl-P Cursor up; move up one line.
|
||||||
|
Move operations do nothing if the cursor is at an edge where the movement is not possible.
|
||||||
|
The following synonyms are supported where possible:
|
||||||
|
KEY_LEFT = Ctrl-B, KEY_RIGHT = Ctrl-F, KEY_UP = Ctrl-P, KEY_DOWN = Ctrl-N, KEY_BACKSPACE = Ctrl-h
|
||||||
|
"""
|
||||||
|
def __init__(self, screen=None, base=None, y=1, x=0, default="edit field"):
|
||||||
|
self.base = base
|
||||||
|
self.value = default
|
||||||
|
self.win = screen
|
||||||
|
self.loop = self.edit
|
||||||
|
(self.maxy, self.maxx) = self.win.getmaxyx()
|
||||||
|
self.maxy -= 2
|
||||||
|
self.maxx -= 1
|
||||||
|
self.stripspaces = 1
|
||||||
|
self.lastcmd = None
|
||||||
|
self.text = [[] for y in xrange(self.maxy+1)]
|
||||||
|
self.win.keypad(1)
|
||||||
|
self.win.move(0,0)
|
||||||
|
|
||||||
|
def text_insert(self, y, x, ch):
|
||||||
|
if len(self.text[y]) > x:
|
||||||
|
self.text[y].insert(x, ch)
|
||||||
|
else: # < = x
|
||||||
|
# self.text[y] + = [curses.ascii.SP] * (x - len(self.text[y]))
|
||||||
|
self.text[y].append(ch)
|
||||||
|
|
||||||
|
def text_delete(self, y, x):
|
||||||
|
if y < 0 or x < 0 or y >= len(self.text) or x >= len(self.text[y]): return
|
||||||
|
del self.text[y][x]
|
||||||
|
|
||||||
|
def _end_of_line(self, y):
|
||||||
|
"""Go to the location of the first blank on the given line."""
|
||||||
|
last = self.maxx
|
||||||
|
while 1:
|
||||||
|
if curses.ascii.ascii(self.win.inch(y, last)) != curses.ascii.SP:
|
||||||
|
last = min(self.maxx, last+1)
|
||||||
|
break
|
||||||
|
elif last == 0:
|
||||||
|
break
|
||||||
|
last = last - 1
|
||||||
|
return last
|
||||||
|
|
||||||
|
def do_command(self, ch):
|
||||||
|
"Process a single editing command."
|
||||||
|
(y, x) = self.win.getyx()
|
||||||
|
self.lastcmd = ch
|
||||||
|
if ch == curses.ascii.SOH: # ^a
|
||||||
|
x = 0
|
||||||
|
self.win.move(y, x)
|
||||||
|
elif ch in (curses.ascii.STX,curses.KEY_LEFT, curses.ascii.BS, curses.KEY_BACKSPACE,127):
|
||||||
|
if x > 0:
|
||||||
|
x -= 1
|
||||||
|
self.win.move(y, x)
|
||||||
|
elif y == 0:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
y -= 1
|
||||||
|
x = len(self.text[y])-1 #if len(self.text[y])<self.maxx else self.maxx
|
||||||
|
self.win.move(y, x)
|
||||||
|
if ch in (curses.ascii.BS, curses.KEY_BACKSPACE, 127):
|
||||||
|
self.win.delch()
|
||||||
|
y, x = self.win.getyx()
|
||||||
|
self.text_delete(y, x)
|
||||||
|
elif ch in (curses.ascii.EOT, curses.KEY_DC): # ^d
|
||||||
|
self.win.delch()
|
||||||
|
self.text_delete(y, x)
|
||||||
|
elif ch == curses.ascii.ENQ: # ^e
|
||||||
|
x = len(self.text[y]) if len(self.text[y])<self.maxx else self.maxx
|
||||||
|
self.win.move(y, x)
|
||||||
|
elif ch in (curses.ascii.ACK, curses.KEY_RIGHT): # ^f
|
||||||
|
if x < self.maxx and x < len(self.text[y]):
|
||||||
|
x += 1
|
||||||
|
self.win.move(y, x)
|
||||||
|
elif y == self.maxy:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
y += 1
|
||||||
|
x = 0
|
||||||
|
self.win.move(y, x)
|
||||||
|
elif ch == curses.ascii.BEL: # ^g
|
||||||
|
return True
|
||||||
|
elif ch in (10, 13): # ^j ^m
|
||||||
|
if y < self.maxy:
|
||||||
|
y += 1
|
||||||
|
x = 0
|
||||||
|
self.win.move(y, x)
|
||||||
|
elif ch == curses.ascii.VT: # ^k
|
||||||
|
if x < len(self.text[y]):
|
||||||
|
self.win.clrtoeol()
|
||||||
|
del self.text[y][x:]
|
||||||
|
else:
|
||||||
|
self.win.deleteln()
|
||||||
|
del self.text[y]
|
||||||
|
# self.win.move(y, x)
|
||||||
|
elif ch == curses.ascii.FF: # ^l
|
||||||
|
self.win.refresh()
|
||||||
|
elif ch in (curses.ascii.SO, curses.KEY_DOWN): # ^n
|
||||||
|
if y < self.maxy:
|
||||||
|
y += 1
|
||||||
|
x = len(self.text[y]) if x > len(self.text[y]) else x
|
||||||
|
self.win.move(y, x)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
elif ch == curses.ascii.SI: # ^o
|
||||||
|
self.win.insertln()
|
||||||
|
self.text.insert(y, [])
|
||||||
|
elif ch in (curses.ascii.DLE, curses.KEY_UP): # ^p
|
||||||
|
if y > 0:
|
||||||
|
y -= 1
|
||||||
|
x = len(self.text[y]) if x > len(self.text[y]) else x
|
||||||
|
self.win.move(y, x)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
elif ch == curses.KEY_HOME:
|
||||||
|
y = 0
|
||||||
|
x = len(self.text[y]) if x > len(self.text[y]) else x
|
||||||
|
self.win.move(y, x)
|
||||||
|
elif ch == curses.KEY_END:
|
||||||
|
y = len(self.text)
|
||||||
|
# x = len(self.text[y]) if x > len(self.text[y]) else x
|
||||||
|
self.win.move(y,x)
|
||||||
|
elif ch == curses.KEY_F2:
|
||||||
|
if self.externalEdit() == None:
|
||||||
|
self.setStatus("No external editor found.")
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
elif ch>31 and ch<256:
|
||||||
|
ch = self.getunicode(ch)
|
||||||
|
if y < self.maxy or x < self.maxx:
|
||||||
|
self.text_insert(y, x, ch)
|
||||||
|
self.win.addstr(y, 0, ''.join(self.text[y]))
|
||||||
|
if x<self.maxx:
|
||||||
|
x += 1
|
||||||
|
else:
|
||||||
|
x = 0
|
||||||
|
y += 1
|
||||||
|
self.win.move(y, x)
|
||||||
|
self.draw()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def gather(self):
|
||||||
|
tmp = [''] * len(self.text)
|
||||||
|
for y in xrange(len(self.text)):
|
||||||
|
tmp[y] = ''.join(self.text[y])
|
||||||
|
tmp[y] = tmp[y].rstrip()
|
||||||
|
return '\n'.join(tmp).strip()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.text = [[] for y in xrange(self.maxy+1)]
|
||||||
|
for y in xrange(self.maxy+1):
|
||||||
|
self.win.move(y, 0)
|
||||||
|
self.win.clrtoeol()
|
||||||
|
self.win.move(0, 0)
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
y, x = self.win.getyx()
|
||||||
|
l = len(self.text)
|
||||||
|
if l < self.maxy:
|
||||||
|
for i in xrange(l):
|
||||||
|
self.win.move(i, 0)
|
||||||
|
self.win.clrtoeol()
|
||||||
|
self.win.addstr(i, 0, ''.join(self.text[i]))
|
||||||
|
self.win.move(y, x)
|
||||||
|
else:
|
||||||
|
for i in xrange(l-self.maxy):
|
||||||
|
self.win.move(i, 0)
|
||||||
|
self.win.clrtoeol()
|
||||||
|
self.win.addstr(i, 0, ''.join(self.text[i]))
|
||||||
|
self.win.move(y, x)
|
||||||
|
self.win.refresh()
|
||||||
|
|
||||||
|
def externalEdit(self):
|
||||||
|
# if self.base.config.editor:
|
||||||
|
# e = self.base.config.editor
|
||||||
|
if os.environment.get("nano"):
|
||||||
|
e = os.environment.get("EDITOR")
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
tempfile = "/tmp/squigglitz"
|
||||||
|
open(tempfile,"wb").write(self.gather())
|
||||||
|
cmd = "%s %s" % (e,tempfile)
|
||||||
|
os.system(cmd)
|
||||||
|
fh = open(tempfile,"rb")
|
||||||
|
self.text = fh.readlines()
|
||||||
|
fh.close()
|
||||||
|
os.remove(tempfile)
|
||||||
|
return self.text
|
||||||
|
|
||||||
|
def getunicode(self, c):
|
||||||
|
tc = u' '
|
||||||
|
buf = ''
|
||||||
|
done = False
|
||||||
|
nc = chr(c)
|
||||||
|
buf += nc
|
||||||
|
if ord(nc) in (194, 195):
|
||||||
|
nc = chr(self.win.getch())
|
||||||
|
buf += nc
|
||||||
|
try:
|
||||||
|
tc = buf.decode()
|
||||||
|
done = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return tc
|
||||||
|
|
||||||
|
def edit(self):
|
||||||
|
self.win.clear()
|
||||||
|
text = list(self.value)
|
||||||
|
while 1:
|
||||||
|
if text != None and len(text)>0:
|
||||||
|
ch = ord(text.pop(0))
|
||||||
|
if len(text) == 0:
|
||||||
|
text = -1
|
||||||
|
else:
|
||||||
|
ch = self.win.getch()
|
||||||
|
if ch == -1:
|
||||||
|
time.sleep(0.02)
|
||||||
|
continue
|
||||||
|
o_ch = ch
|
||||||
|
if self.do_command(ch):
|
||||||
|
break
|
||||||
|
if text == -1:
|
||||||
|
self.win.move(0,0)
|
||||||
|
self.win.refresh()
|
||||||
|
text = None
|
||||||
|
return self.gather()
|
||||||
|
|
||||||
|
class Readline(GuiObject):
|
||||||
|
"""
|
||||||
|
prompt for user input, with bindings to that of the default readline implementation
|
||||||
|
prompt: prompt displayed before the users text
|
||||||
|
history: a list of strings which constitutes the previously entered set of strings given to the caller of this function during previous calls
|
||||||
|
text: the default text, entered as if the user had typed it directly
|
||||||
|
echo: acts as a mask for passwords (set to ' ' in order to not echo any visible character for passwords)
|
||||||
|
length: the maximum length for this text entry
|
||||||
|
delimiter: the delimiter between prompt and text
|
||||||
|
readonly: whether to accept new text
|
||||||
|
"""
|
||||||
|
def __init__(self, screen=None, base=None, y=0, x=0, history=[], prompt=u"input", default=u"", echo=None, maxLength=0, delimiter=u": ", readonly=0, action=""):
|
||||||
|
self.value = default
|
||||||
|
self.done = 0
|
||||||
|
self.base = base
|
||||||
|
self.screen = screen
|
||||||
|
self.y, self.x = y, x
|
||||||
|
self.history = history
|
||||||
|
self.historyPos = len(self.history) if self.history else 0
|
||||||
|
self.prompt = prompt
|
||||||
|
self.delimiter = delimiter
|
||||||
|
self.echo = echo
|
||||||
|
self.readonly = readonly
|
||||||
|
self.maxLength = maxLength
|
||||||
|
#prompt and delimiter
|
||||||
|
self.s = u"%s%s" % (self.prompt,self.delimiter,) if self.prompt else ""
|
||||||
|
#position in the currently-being-editted text
|
||||||
|
self.ptr = 0
|
||||||
|
#start of text entry "on-screen", should be greater than self.ptr unless there is absolutely no prompt, (in other words, a completely blank line)
|
||||||
|
#if there's a prompt, startX should be right after the prompt and the delimiter
|
||||||
|
#if not, startX is going to be wherever self.x is, as that's where our text is going to appear
|
||||||
|
self.startX = len(self.s) if self.s else self.x
|
||||||
|
#put ptr at the end of the current bit of text
|
||||||
|
self.currentLine = self.value
|
||||||
|
self.ptr = len(self.currentLine)
|
||||||
|
self.insertMode = True
|
||||||
|
self.lastDraw = None
|
||||||
|
self.draw()
|
||||||
|
self.action = action
|
||||||
|
|
||||||
|
def externalEdit(self): return None
|
||||||
|
|
||||||
|
def getunicode(self, c):
|
||||||
|
tc = u' '
|
||||||
|
buf = ''
|
||||||
|
done = False
|
||||||
|
nc = chr(c)
|
||||||
|
buf += nc
|
||||||
|
if ord(nc) in (194, 195):
|
||||||
|
nc = chr(self.screen.getch())
|
||||||
|
buf += nc
|
||||||
|
try:
|
||||||
|
tc = buf.decode()
|
||||||
|
done = True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return tc
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
d = self.ptr, self.currentLine
|
||||||
|
if self.lastDraw and d == self.lastDraw:
|
||||||
|
return
|
||||||
|
loc = self.x
|
||||||
|
t = self.s
|
||||||
|
self.screen.move(self.y, self.x)
|
||||||
|
self.screen.clrtoeol()
|
||||||
|
if self.s:
|
||||||
|
self.screen.addstr(self.y, self.x, self.s)
|
||||||
|
t = self.currentLine
|
||||||
|
cnt = 0
|
||||||
|
if self.echo:
|
||||||
|
t = str(self.echo)[:1]*len(t)
|
||||||
|
self.screen.addstr(self.y, self.startX, "".join(t))
|
||||||
|
self.screen.move(self.y, self.startX+self.ptr)
|
||||||
|
self.screen.refresh()
|
||||||
|
self.lastDraw = self.ptr, self.currentLine
|
||||||
|
|
||||||
|
def handleKey(self, c):
|
||||||
|
if c == -1:
|
||||||
|
return None
|
||||||
|
if c == 3: # ^C
|
||||||
|
self.setStatus("Input aborted!")
|
||||||
|
self.currentLine = u''
|
||||||
|
elif c == 10: # ^J newline
|
||||||
|
if self.history != None and self.currentLine:
|
||||||
|
self.history.append(self.currentLine)
|
||||||
|
self.done = 1
|
||||||
|
elif c in (1, 262): # ^A, Home key
|
||||||
|
self.ptr = 0
|
||||||
|
elif c in (5, 360): # ^E, End key
|
||||||
|
self.ptr = len(self.currentLine)
|
||||||
|
elif c in (2, 260): # ^B, left arrow
|
||||||
|
if self.ptr>0:
|
||||||
|
self.ptr -= 1
|
||||||
|
else:
|
||||||
|
self.beepIfNeeded()
|
||||||
|
elif c in (6, 261): # ^f, right arrow
|
||||||
|
if self.ptr<len(self.currentLine):
|
||||||
|
self.ptr += 1
|
||||||
|
else:
|
||||||
|
self.beepIfNeeded()
|
||||||
|
self.ptr = len(self.currentLine)
|
||||||
|
elif c == 259: # Up arrow
|
||||||
|
if not self.history or self.historyPos == 0: #history will return non-zero if it has content
|
||||||
|
self.beepIfNeeded()
|
||||||
|
msg = "No history to move up through." if not self.history else "No previous history to move up through."
|
||||||
|
self.setStatus(msg)
|
||||||
|
elif self.history and self.historyPos>0:
|
||||||
|
self.tempLine = self.currentLine
|
||||||
|
self.historyPos -= 1
|
||||||
|
self.currentLine = self.history[self.historyPos]
|
||||||
|
self.ptr = len(self.currentLine)
|
||||||
|
else:
|
||||||
|
self.setStatus("Something odd occured, readLine, up arrow")
|
||||||
|
elif c == 258: # Down arrow
|
||||||
|
#if there is no history, or we're off the end of the history list (therefore using tempLine), show an error
|
||||||
|
if not self.history or self.historyPos>= len(self.history):
|
||||||
|
self.beepIfNeeded()
|
||||||
|
msg = "No history to move down through." if not self.history else "No more history to move down through."
|
||||||
|
self.setStatus(msg)
|
||||||
|
#otherwise, we've got more history, or tempLine left to view
|
||||||
|
elif self.history:
|
||||||
|
#go ahead and move down
|
||||||
|
self.historyPos += 1
|
||||||
|
#if we're now off the end of the history, pull up tempLine
|
||||||
|
#maybe user thought they'd typed something and they hadn't, so they can get back to their pre-history command
|
||||||
|
if self.historyPos == len(self.history):
|
||||||
|
self.currentLine = self.tempLine
|
||||||
|
#normal history item
|
||||||
|
else:
|
||||||
|
self.currentLine = self.history[self.historyPos]
|
||||||
|
#move to the end of this line, history or tempLine
|
||||||
|
self.ptr = len(self.currentLine)
|
||||||
|
else:
|
||||||
|
self.setStatus("Something odd occured, readLine, down arrow")
|
||||||
|
elif c in (8, 263): # ^H, backSpace
|
||||||
|
if self.ptr>0:
|
||||||
|
self.currentLine = u"%s%s" % (self.currentLine[:self.ptr-1],self.currentLine[self.ptr:])
|
||||||
|
self.ptr -= 1
|
||||||
|
else:
|
||||||
|
self.beepIfNeeded()
|
||||||
|
elif c in (4, 330): # ^D, delete
|
||||||
|
if self.ptr<len(self.currentLine):
|
||||||
|
self.currentLine = u"%s%s" % (self.currentLine[:self.ptr],self.currentLine[self.ptr+1:])
|
||||||
|
else:
|
||||||
|
self.beepIfNeeded()
|
||||||
|
elif c == 331: # insert
|
||||||
|
self.insertMode = False if self.insertMode == True else False
|
||||||
|
self.setStatus("insert mode "+"on" if self.insertMode else "off")
|
||||||
|
elif c == 21: # ^U
|
||||||
|
self.ptr = 0
|
||||||
|
self.currentLine = u''
|
||||||
|
elif c == 11: # ^K
|
||||||
|
self.currentLine = self.currentLine[:self.ptr]
|
||||||
|
else:
|
||||||
|
if self.readonly:
|
||||||
|
self.beepIfNeeded()
|
||||||
|
self.setStatus("This is a read only line. Text can not be modified.")
|
||||||
|
else:
|
||||||
|
uchar = self.getunicode(c)
|
||||||
|
if not self.insertMode:
|
||||||
|
self.currentLine[self.ptr] = uchar
|
||||||
|
else:
|
||||||
|
self.currentLine = u"%s%s%s" % (self.currentLine[:self.ptr],uchar,self.currentLine[self.ptr:])
|
||||||
|
self.ptr += 1
|
||||||
|
if self.maxLength>0 and self.ptr >= self.maxLength:
|
||||||
|
if self.history != None and self.currentLine:
|
||||||
|
self.history.append(self.currentLine)
|
||||||
|
self.done = True
|
||||||
|
self.setStatus("Maximum field length reached.")
|
||||||
|
#handled keystroke
|
||||||
|
self.draw()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
class Listbox(GuiObject):
|
||||||
|
"""Listbox
|
||||||
|
render a listbox to the screen in `title \n separator \n items` format
|
||||||
|
y,x: y and x coordinates where to draw this window on the screen
|
||||||
|
height: maximum height of this window on the screen, including title and separator (defaults to the length of the list, or the height of the window)
|
||||||
|
base: base clifox object for accessing settings and other clifox state
|
||||||
|
default: the index of the currently selected item (or a list of selected items, if multiple is true)
|
||||||
|
title: the title of this select box (might be taken from the element on the webpage)
|
||||||
|
keysWaitTime: maximum amount of time the system will consider a consecutive set of key-presses as a single search
|
||||||
|
items: a list of options in string form, or a list of (id,option) tuples
|
||||||
|
multiple: whether to allow selecting multiple options
|
||||||
|
"""
|
||||||
|
def __init__(self, screen=None, base=None, y=0, x=0, height=None, title=None, items=[], keysWaitTime=0.4, default=0, multiple=False):
|
||||||
|
self.screen = screen
|
||||||
|
self.base = base
|
||||||
|
self.multiple = multiple
|
||||||
|
self.y, self.x = y, x
|
||||||
|
self.title = title
|
||||||
|
#if we've got a list of strings or a list of non-list objects, turn them into itemIndex,item
|
||||||
|
#so ["a","b","c"] would become [[0,"a"],[1,"b"],[2,"c"]]
|
||||||
|
if items and type(items[0]) not in (tuple, list):
|
||||||
|
items = zip(xrange(len(items)),items)
|
||||||
|
else:
|
||||||
|
items = items
|
||||||
|
items = [(i,str(j)) for i, j in items]
|
||||||
|
self.items = items
|
||||||
|
self.keys = []
|
||||||
|
self.lastKeyTime = -1
|
||||||
|
self.keysWaitTime = keysWaitTime
|
||||||
|
if height == None:
|
||||||
|
height = (len(self.items)+2) if (len(self.items)+2)<self.base.maxy-2 else self.base.maxy-2
|
||||||
|
self.height = height
|
||||||
|
if type(default) not in (list, tuple):
|
||||||
|
default = [default]
|
||||||
|
self.selections = default
|
||||||
|
self.pos = self.selections[0]
|
||||||
|
self.lastDraw = None
|
||||||
|
# self.draw()
|
||||||
|
|
||||||
|
def draw(self):
|
||||||
|
title = self.title
|
||||||
|
windowY = self.y
|
||||||
|
listHeight = self.height-2
|
||||||
|
start = self.pos//listHeight
|
||||||
|
startL = (start*listHeight)
|
||||||
|
#if startL%lsitHiehgt = = 0 then we can clear the screen
|
||||||
|
endL = (start*listHeight)+listHeight
|
||||||
|
sw = windowY+2
|
||||||
|
if self.lastDraw != (startL, endL,[i for i in self.selections]):
|
||||||
|
show = self.items[startL:endL]
|
||||||
|
self.screen.move(windowY, 0)
|
||||||
|
self.screen.clrtoeol()
|
||||||
|
self.screen.addstr(windowY, 0, title)
|
||||||
|
sep = '-'*len(title)
|
||||||
|
self.screen.move(windowY+1, 0)
|
||||||
|
self.screen.clrtoeol()
|
||||||
|
self.screen.addstr(windowY+1, 0, sep)
|
||||||
|
for idx, itm in enumerate(show):
|
||||||
|
self.screen.move(idx+sw, 0)
|
||||||
|
self.screen.clrtoeol()
|
||||||
|
if self.multiple:
|
||||||
|
s = "[%s] %s" % (("+" if startL+idx in self.selections else "-"),itm[1],)
|
||||||
|
else:
|
||||||
|
s = "%s" % (itm[1],)
|
||||||
|
self.screen.addstr(idx+sw, 0, s)
|
||||||
|
self.lastDraw = (startL, endL,[i for i in self.selections])
|
||||||
|
self.screen.move(sw+(self.pos-startL), 0)
|
||||||
|
# self.setStatus("height = %d:startL = %d:endL = %d:pos = %d" % (self.height,startL,endL,sw+self.pos-startL))
|
||||||
|
self.screen.refresh()
|
||||||
|
|
||||||
|
def search(self, key):
|
||||||
|
t = time.time()
|
||||||
|
if key in string.printable and (self.keys and t-self.lastKeyTime<self.keysWaitTime):
|
||||||
|
self.keys.append(key)
|
||||||
|
self.lastKeyTime = t
|
||||||
|
else:
|
||||||
|
self.keys = [key]
|
||||||
|
keys = "".join(self.keys)
|
||||||
|
keys = keys.lower()
|
||||||
|
j = -1
|
||||||
|
for i in self.items:
|
||||||
|
j += 1
|
||||||
|
if str(i[1]).lower().startswith(keys):
|
||||||
|
self.pos = j
|
||||||
|
break
|
||||||
|
|
||||||
|
def handleKey(self, c):
|
||||||
|
if c == -1:
|
||||||
|
return None
|
||||||
|
if self.multiple and c == 32:
|
||||||
|
if self.pos in self.selections:
|
||||||
|
self.selections.remove(self.pos)
|
||||||
|
else:
|
||||||
|
self.selections.append(self.pos)
|
||||||
|
elif curses.ascii.isprint(c):
|
||||||
|
self.search(chr(c))
|
||||||
|
elif c == curses.KEY_UP:
|
||||||
|
if self.pos == 0:
|
||||||
|
#we don't want to wrap around to the top
|
||||||
|
pass #self.pos = len(self.items)-1
|
||||||
|
self.beepIfNeeded()
|
||||||
|
else:
|
||||||
|
self.pos -= 1
|
||||||
|
elif c == curses.KEY_DOWN:
|
||||||
|
if self.pos == len(self.items)-1:
|
||||||
|
pass #self.pos = 0
|
||||||
|
self.beepIfNeeded()
|
||||||
|
else:
|
||||||
|
self.pos += 1
|
||||||
|
elif c in (10, 261): # newline or right arrow
|
||||||
|
self.done = 1
|
||||||
|
return self.selections if self.multiple else self.pos
|
||||||
|
elif c == 260: # left arrow quietly back out
|
||||||
|
self.done = 1
|
||||||
|
return self.selections if self.multiple else self.pos
|
||||||
|
self.draw()
|
||||||
|
|
||||||
|
class fileBrowser(Listbox):
|
||||||
|
|
||||||
|
def __init__(self, dir="./", select_type="file", action="", *args, **kwargs):
|
||||||
|
self.select_type = select_type
|
||||||
|
self.selected_action = action
|
||||||
|
if dir:
|
||||||
|
self.dir = dir if dir else os.environ.get("HOME","/")
|
||||||
|
items = self.make_list()
|
||||||
|
super(fileBrowser, self).__init__(items=items, title="File browser", *args, **kwargs)
|
||||||
|
|
||||||
|
def make_list(self):
|
||||||
|
self.pos = 0
|
||||||
|
files = []
|
||||||
|
folders = []
|
||||||
|
folders.append((os.path.abspath(".."), "Back"))
|
||||||
|
os.chdir(self.dir)
|
||||||
|
for i in sorted(os.listdir(self.dir)):
|
||||||
|
if os.path.isdir(i):
|
||||||
|
folders.append((os.path.abspath(i), i))
|
||||||
|
else:
|
||||||
|
files.append((os.path.abspath(i), i))
|
||||||
|
folders.extend(files)
|
||||||
|
return folders
|
||||||
|
|
||||||
|
def getDir(self):
|
||||||
|
return self.items[self.pos][0]
|
||||||
|
|
||||||
|
def expand(self):
|
||||||
|
self.dir = self.getDir()
|
||||||
|
self.items = self.make_list()
|
||||||
|
self.lastDraw = None
|
||||||
|
|
||||||
|
def collapse(self):
|
||||||
|
self.dir = self.items[0][0]
|
||||||
|
self.items = self.make_list()
|
||||||
|
self.lastDraw = None
|
||||||
|
|
||||||
|
def handleKey(self, c):
|
||||||
|
if c == -1:
|
||||||
|
return None
|
||||||
|
if self.multiple and c == 32:
|
||||||
|
if self.pos in self.selections:
|
||||||
|
self.selections.remove(self.pos)
|
||||||
|
else:
|
||||||
|
self.selections.append(self.pos)
|
||||||
|
elif curses.ascii.isprint(c):
|
||||||
|
self.search(chr(c))
|
||||||
|
elif c == curses.KEY_UP:
|
||||||
|
if self.pos == 0:
|
||||||
|
#we don't want to wrap around to the top
|
||||||
|
pass #self.pos = len(self.items)-1
|
||||||
|
self.beepIfNeeded()
|
||||||
|
else:
|
||||||
|
self.pos -= 1
|
||||||
|
elif c == curses.KEY_DOWN:
|
||||||
|
if self.pos == len(self.items)-1:
|
||||||
|
pass #self.pos = 0
|
||||||
|
self.beepIfNeeded()
|
||||||
|
else:
|
||||||
|
self.pos += 1
|
||||||
|
elif c in (10, 261, curses.KEY_RIGHT): # newline or right arrow
|
||||||
|
if os.path.isfile(self.getDir()) and self.select_type == "file":
|
||||||
|
self.done = 1
|
||||||
|
return 1
|
||||||
|
if os.path.isdir(self.getDir()) and self.select_type != "file":
|
||||||
|
self.done = 1
|
||||||
|
return 1
|
||||||
|
self.expand()
|
||||||
|
return 1
|
||||||
|
elif c == curses.KEY_LEFT or c == curses.KEY_BACKSPACE: # left arrow quietly back out
|
||||||
|
self.collapse()
|
||||||
|
self.draw()
|
||||||
|
|
110
guicurses/window.py
Normal file
110
guicurses/window.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import curses
|
||||||
|
import widgets
|
||||||
|
|
||||||
|
class Window(object):
|
||||||
|
|
||||||
|
def exit(self,*a,**kw):
|
||||||
|
try:
|
||||||
|
curses.echo(1)
|
||||||
|
self.screen.keypad(0)
|
||||||
|
self.screen.nodelay(0)
|
||||||
|
curses.nocbreak()
|
||||||
|
except Exception,e:
|
||||||
|
print "error"
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
quit=exit
|
||||||
|
|
||||||
|
def __init__(self, screen):
|
||||||
|
self.handlers=[self]
|
||||||
|
self.keys = {}
|
||||||
|
self.statusbar=1
|
||||||
|
self.lastMessage=""
|
||||||
|
self.daemon=1
|
||||||
|
self.windows=[]
|
||||||
|
self.windowVars={}
|
||||||
|
self.windex=0
|
||||||
|
self.screen = screen
|
||||||
|
self.screen.nodelay(1)
|
||||||
|
self.screen.keypad(1)
|
||||||
|
curses.raw(1)
|
||||||
|
curses.noecho()
|
||||||
|
curses.cbreak()
|
||||||
|
self.initVars()
|
||||||
|
|
||||||
|
def initVars(self):
|
||||||
|
self.maxy,self.maxx=self.screen.getmaxyx()
|
||||||
|
self.curPos=[0,0]
|
||||||
|
self.status=self.maxy-1
|
||||||
|
self.entry=self.status-1
|
||||||
|
self.maxy=self.entry-1
|
||||||
|
self.screenPos=0
|
||||||
|
self.screenPosX=0
|
||||||
|
self.screenNum=0
|
||||||
|
|
||||||
|
def setStatus(self,txt,*l):
|
||||||
|
if not self.statusbar: return
|
||||||
|
if isinstance(txt,list): txt=",".join([str(i).strip() for i in txt])
|
||||||
|
if l: txt+=" "+" ".join(l)
|
||||||
|
if not isinstance(txt,str): txt=str(txt)
|
||||||
|
txt=txt.encode('utf-8')
|
||||||
|
cur=self.screen.getyx()
|
||||||
|
self.screen.move(self.status,0)
|
||||||
|
self.screen.clrtoeol()
|
||||||
|
s=txt[:self.maxx-1]
|
||||||
|
try:
|
||||||
|
self.screen.addstr(self.status,0,s)
|
||||||
|
except:
|
||||||
|
generate_error_report()
|
||||||
|
self.setStatus("error")
|
||||||
|
self.screen.move(cur[0],cur[1])
|
||||||
|
self.screen.refresh()
|
||||||
|
|
||||||
|
self.screen.refresh()
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while 1:
|
||||||
|
time.sleep(0.01)
|
||||||
|
c=self.screen.getch()
|
||||||
|
if c!=-1:
|
||||||
|
for handler in self.handlers[:]:
|
||||||
|
if handler.handleKey(c):
|
||||||
|
self.check_changes(handler)
|
||||||
|
break
|
||||||
|
|
||||||
|
def check_changes(self, handler):
|
||||||
|
if hasattr(handler, "selected_action"):
|
||||||
|
self.handlers.remove(handler)
|
||||||
|
getattr(self, handler.selected_action)(handler.dir)
|
||||||
|
elif not hasattr(handler, "controls"): return
|
||||||
|
for control in handler.controls:
|
||||||
|
if control.selected == 1 or control.done == 1:
|
||||||
|
self.handlers.remove(handler)
|
||||||
|
if "." in control.action:
|
||||||
|
self.open_file(control.action)
|
||||||
|
else:
|
||||||
|
getattr(self, control.action)()
|
||||||
|
|
||||||
|
def handleKey(self,k,*args):
|
||||||
|
pass
|
||||||
|
# if k in self.keys:
|
||||||
|
# try:
|
||||||
|
# exec(self.keys[k])
|
||||||
|
# return 1
|
||||||
|
# except Exception,e:
|
||||||
|
# self.setStatus("KeyboardError:%s" % (e,))
|
||||||
|
# return None
|
||||||
|
|
||||||
|
def parse_menu(self, items, is_submenu=False):
|
||||||
|
controls = []
|
||||||
|
for i in items:
|
||||||
|
if len(i) > 2:
|
||||||
|
controls.append((widgets.Button, dict(prompt=i[1], action=i[0], help_string=i[2])))
|
||||||
|
else:
|
||||||
|
controls.append((widgets.Button, dict(prompt=i[1], action=i[0])))
|
||||||
|
if is_submenu:
|
||||||
|
controls.append((widgets.Button, "Back"))
|
||||||
|
return controls
|
18
setup.py
Normal file
18
setup.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import os
|
||||||
|
from setuptools import setup
|
||||||
|
|
||||||
|
def read(fname):
|
||||||
|
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name = "guicurses",
|
||||||
|
version = "0.3",
|
||||||
|
author = "Manuel Cortez",
|
||||||
|
author_email = "manuel@manuelcortez.net",
|
||||||
|
description = "A set of utilities for building accessible applications with curses",
|
||||||
|
license = "GPL V2",
|
||||||
|
keywords = "Gui curses accessibility speakup",
|
||||||
|
url = "http://manuelcortez.net:1337/pi/guicurses",
|
||||||
|
long_description=read('README'),
|
||||||
|
packages = ["guicurses"],
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user