Step 2 of SVN to arch tree conversion
This commit is contained in:
589
offlineimap/ui/Curses.py
Normal file
589
offlineimap/ui/Curses.py
Normal file
@ -0,0 +1,589 @@
|
||||
# Curses-based interfaces
|
||||
# Copyright (C) 2003 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from Blinkenlights import BlinkenBase
|
||||
from UIBase import UIBase
|
||||
from threading import *
|
||||
import thread, time, sys, os, signal, time
|
||||
from offlineimap import version, threadutil
|
||||
from offlineimap.threadutil import MultiLock
|
||||
|
||||
import curses, curses.panel, curses.textpad, curses.wrapper
|
||||
|
||||
acctkeys = '1234567890abcdefghijklmnoprstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-=;/.,'
|
||||
|
||||
class CursesUtil:
|
||||
def __init__(self):
|
||||
self.pairlock = Lock()
|
||||
self.iolock = MultiLock()
|
||||
self.start()
|
||||
|
||||
def initpairs(self):
|
||||
self.pairlock.acquire()
|
||||
try:
|
||||
self.pairs = {self._getpairindex(curses.COLOR_WHITE,
|
||||
curses.COLOR_BLACK): 0}
|
||||
self.nextpair = 1
|
||||
finally:
|
||||
self.pairlock.release()
|
||||
|
||||
def lock(self):
|
||||
self.iolock.acquire()
|
||||
|
||||
def unlock(self):
|
||||
self.iolock.release()
|
||||
|
||||
def locked(self, target, *args, **kwargs):
|
||||
"""Perform an operation with full locking."""
|
||||
self.lock()
|
||||
try:
|
||||
apply(target, args, kwargs)
|
||||
finally:
|
||||
self.unlock()
|
||||
|
||||
def refresh(self):
|
||||
def lockedstuff():
|
||||
curses.panel.update_panels()
|
||||
curses.doupdate()
|
||||
self.locked(lockedstuff)
|
||||
|
||||
def isactive(self):
|
||||
return hasattr(self, 'stdscr')
|
||||
|
||||
def _getpairindex(self, fg, bg):
|
||||
return '%d/%d' % (fg,bg)
|
||||
|
||||
def getpair(self, fg, bg):
|
||||
if not self.has_color:
|
||||
return 0
|
||||
pindex = self._getpairindex(fg, bg)
|
||||
self.pairlock.acquire()
|
||||
try:
|
||||
if self.pairs.has_key(pindex):
|
||||
return curses.color_pair(self.pairs[pindex])
|
||||
else:
|
||||
self.pairs[pindex] = self.nextpair
|
||||
curses.init_pair(self.nextpair, fg, bg)
|
||||
self.nextpair += 1
|
||||
return curses.color_pair(self.nextpair - 1)
|
||||
finally:
|
||||
self.pairlock.release()
|
||||
|
||||
def start(self):
|
||||
self.stdscr = curses.initscr()
|
||||
curses.noecho()
|
||||
curses.cbreak()
|
||||
self.stdscr.keypad(1)
|
||||
try:
|
||||
curses.start_color()
|
||||
self.has_color = curses.has_colors()
|
||||
except:
|
||||
self.has_color = 0
|
||||
|
||||
self.oldcursor = None
|
||||
try:
|
||||
self.oldcursor = curses.curs_set(0)
|
||||
except:
|
||||
pass
|
||||
|
||||
self.stdscr.clear()
|
||||
self.stdscr.refresh()
|
||||
(self.height, self.width) = self.stdscr.getmaxyx()
|
||||
self.initpairs()
|
||||
|
||||
def stop(self):
|
||||
if not hasattr(self, 'stdscr'):
|
||||
return
|
||||
#self.stdscr.addstr(self.height - 1, 0, "\n",
|
||||
# self.getpair(curses.COLOR_WHITE,
|
||||
# curses.COLOR_BLACK))
|
||||
if self.oldcursor != None:
|
||||
curses.curs_set(self.oldcursor)
|
||||
self.stdscr.refresh()
|
||||
self.stdscr.keypad(0)
|
||||
curses.nocbreak()
|
||||
curses.echo()
|
||||
curses.endwin()
|
||||
del self.stdscr
|
||||
|
||||
def reset(self):
|
||||
self.stop()
|
||||
self.start()
|
||||
|
||||
class CursesAccountFrame:
|
||||
def __init__(s, master, accountname):
|
||||
s.c = master
|
||||
s.children = []
|
||||
s.accountname = accountname
|
||||
|
||||
def drawleadstr(s, secs = None):
|
||||
if secs == None:
|
||||
acctstr = '%s: [active] %13.13s: ' % (s.key, s.accountname)
|
||||
else:
|
||||
acctstr = '%s: [%3d:%02d] %13.13s: ' % (s.key,
|
||||
secs / 60, secs % 60,
|
||||
s.accountname)
|
||||
s.c.locked(s.window.addstr, 0, 0, acctstr)
|
||||
s.location = len(acctstr)
|
||||
|
||||
def setwindow(s, window, key):
|
||||
s.window = window
|
||||
s.key = key
|
||||
s.drawleadstr()
|
||||
for child in s.children:
|
||||
child.update(window, 0, s.location)
|
||||
s.location += 1
|
||||
|
||||
def getnewthreadframe(s):
|
||||
tf = CursesThreadFrame(s.c, s.window, 0, s.location)
|
||||
s.location += 1
|
||||
s.children.append(tf)
|
||||
return tf
|
||||
|
||||
def startsleep(s, sleepsecs):
|
||||
s.sleeping_abort = 0
|
||||
|
||||
def sleeping(s, sleepsecs, remainingsecs):
|
||||
if remainingsecs:
|
||||
s.c.lock()
|
||||
try:
|
||||
s.drawleadstr(remainingsecs)
|
||||
s.window.refresh()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
time.sleep(sleepsecs)
|
||||
else:
|
||||
s.c.lock()
|
||||
try:
|
||||
s.drawleadstr()
|
||||
s.window.refresh()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
return s.sleeping_abort
|
||||
|
||||
def syncnow(s):
|
||||
s.sleeping_abort = 1
|
||||
|
||||
class CursesThreadFrame:
|
||||
def __init__(s, master, window, y, x):
|
||||
"""master should be a CursesUtil object."""
|
||||
s.c = master
|
||||
s.window = window
|
||||
s.x = x
|
||||
s.y = y
|
||||
s.colors = []
|
||||
bg = curses.COLOR_BLACK
|
||||
s.colormap = {'black': s.c.getpair(curses.COLOR_BLACK, bg),
|
||||
'gray': s.c.getpair(curses.COLOR_WHITE, bg),
|
||||
'white': curses.A_BOLD | s.c.getpair(curses.COLOR_WHITE, bg),
|
||||
'blue': s.c.getpair(curses.COLOR_BLUE, bg),
|
||||
'red': s.c.getpair(curses.COLOR_RED, bg),
|
||||
'purple': s.c.getpair(curses.COLOR_MAGENTA, bg),
|
||||
'cyan': s.c.getpair(curses.COLOR_CYAN, bg),
|
||||
'green': s.c.getpair(curses.COLOR_GREEN, bg),
|
||||
'orange': s.c.getpair(curses.COLOR_YELLOW, bg),
|
||||
'yellow': curses.A_BOLD | s.c.getpair(curses.COLOR_YELLOW, bg),
|
||||
'pink': curses.A_BOLD | s.c.getpair(curses.COLOR_RED, bg)}
|
||||
#s.setcolor('gray')
|
||||
s.setcolor('black')
|
||||
|
||||
def setcolor(self, color):
|
||||
self.color = self.colormap[color]
|
||||
self.colorname = color
|
||||
self.display()
|
||||
|
||||
def display(self):
|
||||
def lockedstuff():
|
||||
if self.getcolor() == 'black':
|
||||
self.window.addstr(self.y, self.x, ' ', self.color)
|
||||
else:
|
||||
self.window.addstr(self.y, self.x, '.', self.color)
|
||||
self.c.stdscr.move(self.c.height - 1, self.c.width - 1)
|
||||
self.window.refresh()
|
||||
self.c.locked(lockedstuff)
|
||||
|
||||
def getcolor(self):
|
||||
return self.colorname
|
||||
|
||||
def getcolorpair(self):
|
||||
return self.color
|
||||
|
||||
def update(self, window, y, x):
|
||||
self.window = window
|
||||
self.y = y
|
||||
self.x = x
|
||||
self.display()
|
||||
|
||||
def setthread(self, newthread):
|
||||
self.setcolor('black')
|
||||
#if newthread:
|
||||
# self.setcolor('gray')
|
||||
#else:
|
||||
# self.setcolor('black')
|
||||
|
||||
class InputHandler:
|
||||
def __init__(s, util):
|
||||
s.c = util
|
||||
s.bgchar = None
|
||||
s.inputlock = Lock()
|
||||
s.lockheld = 0
|
||||
s.statuslock = Lock()
|
||||
s.startup = Event()
|
||||
s.startthread()
|
||||
|
||||
def startthread(s):
|
||||
s.thread = threadutil.ExitNotifyThread(target = s.bgreaderloop,
|
||||
name = "InputHandler loop")
|
||||
s.thread.setDaemon(1)
|
||||
s.thread.start()
|
||||
|
||||
def bgreaderloop(s):
|
||||
while 1:
|
||||
s.statuslock.acquire()
|
||||
if s.lockheld or s.bgchar == None:
|
||||
s.statuslock.release()
|
||||
s.startup.wait()
|
||||
else:
|
||||
s.statuslock.release()
|
||||
ch = s.c.stdscr.getch()
|
||||
s.statuslock.acquire()
|
||||
try:
|
||||
if s.lockheld or s.bgchar == None:
|
||||
curses.ungetch(ch)
|
||||
else:
|
||||
s.bgchar(ch)
|
||||
finally:
|
||||
s.statuslock.release()
|
||||
|
||||
def set_bgchar(s, callback):
|
||||
"""Sets a "background" character handler. If a key is pressed
|
||||
while not doing anything else, it will be passed to this handler.
|
||||
|
||||
callback is a function taking a single arg -- the char pressed.
|
||||
|
||||
If callback is None, clears the request."""
|
||||
s.statuslock.acquire()
|
||||
oldhandler = s.bgchar
|
||||
newhandler = callback
|
||||
s.bgchar = callback
|
||||
|
||||
if oldhandler and not newhandler:
|
||||
pass
|
||||
if newhandler and not oldhandler:
|
||||
s.startup.set()
|
||||
|
||||
s.statuslock.release()
|
||||
|
||||
def input_acquire(s):
|
||||
"""Call this method when you want exclusive input control.
|
||||
Make sure to call input_release afterwards!
|
||||
"""
|
||||
|
||||
s.inputlock.acquire()
|
||||
s.statuslock.acquire()
|
||||
s.lockheld = 1
|
||||
s.statuslock.release()
|
||||
|
||||
def input_release(s):
|
||||
"""Call this method when you are done getting input."""
|
||||
s.statuslock.acquire()
|
||||
s.lockheld = 0
|
||||
s.statuslock.release()
|
||||
s.inputlock.release()
|
||||
s.startup.set()
|
||||
|
||||
class Blinkenlights(BlinkenBase, UIBase):
|
||||
def init_banner(s):
|
||||
s.af = {}
|
||||
s.aflock = Lock()
|
||||
s.c = CursesUtil()
|
||||
s.text = []
|
||||
BlinkenBase.init_banner(s)
|
||||
s.setupwindows()
|
||||
s.inputhandler = InputHandler(s.c)
|
||||
s.gettf().setcolor('red')
|
||||
s._msg(version.banner)
|
||||
s.inputhandler.set_bgchar(s.keypress)
|
||||
signal.signal(signal.SIGWINCH, s.resizehandler)
|
||||
s.resizelock = Lock()
|
||||
s.resizecount = 0
|
||||
|
||||
def resizehandler(s, signum, frame):
|
||||
s.resizeterm()
|
||||
|
||||
def resizeterm(s, dosleep = 1):
|
||||
if not s.resizelock.acquire(0):
|
||||
s.resizecount += 1
|
||||
return
|
||||
signal.signal(signal.SIGWINCH, signal.SIG_IGN)
|
||||
s.aflock.acquire()
|
||||
s.c.lock()
|
||||
s.resizecount += 1
|
||||
while s.resizecount:
|
||||
s.c.reset()
|
||||
s.setupwindows()
|
||||
s.resizecount -= 1
|
||||
s.c.unlock()
|
||||
s.aflock.release()
|
||||
s.resizelock.release()
|
||||
signal.signal(signal.SIGWINCH, s.resizehandler)
|
||||
if dosleep:
|
||||
time.sleep(1)
|
||||
s.resizeterm(0)
|
||||
|
||||
def isusable(s):
|
||||
# Not a terminal? Can't use curses.
|
||||
if not sys.stdout.isatty() and sys.stdin.isatty():
|
||||
return 0
|
||||
|
||||
# No TERM specified? Can't use curses.
|
||||
try:
|
||||
if not len(os.environ['TERM']):
|
||||
return 0
|
||||
except: return 0
|
||||
|
||||
# ncurses doesn't want to start? Can't use curses.
|
||||
# This test is nasty because initscr() actually EXITS on error.
|
||||
# grr.
|
||||
|
||||
pid = os.fork()
|
||||
if pid:
|
||||
# parent
|
||||
return not os.WEXITSTATUS(os.waitpid(pid, 0)[1])
|
||||
else:
|
||||
# child
|
||||
curses.initscr()
|
||||
curses.endwin()
|
||||
# If we didn't die by here, indicate success.
|
||||
sys.exit(0)
|
||||
|
||||
def keypress(s, key):
|
||||
if key > 255:
|
||||
return
|
||||
|
||||
if chr(key) == 'q':
|
||||
# Request to quit.
|
||||
s.terminate()
|
||||
|
||||
try:
|
||||
index = acctkeys.index(chr(key))
|
||||
except ValueError:
|
||||
# Key not a valid one: exit.
|
||||
return
|
||||
|
||||
if index >= len(s.hotkeys):
|
||||
# Not in our list of valid hotkeys.
|
||||
return
|
||||
|
||||
# Trying to end sleep somewhere.
|
||||
|
||||
s.getaccountframe(s.hotkeys[index]).syncnow()
|
||||
|
||||
def getpass(s, accountname, config, errmsg = None):
|
||||
s.inputhandler.input_acquire()
|
||||
|
||||
# See comment on _msg for info on why both locks are obtained.
|
||||
|
||||
s.tflock.acquire()
|
||||
s.c.lock()
|
||||
try:
|
||||
s.gettf().setcolor('white')
|
||||
s._addline(" *** Input Required", s.gettf().getcolorpair())
|
||||
s._addline(" *** Please enter password for account %s: " % accountname,
|
||||
s.gettf().getcolorpair())
|
||||
s.logwindow.refresh()
|
||||
password = s.logwindow.getstr()
|
||||
finally:
|
||||
s.tflock.release()
|
||||
s.c.unlock()
|
||||
s.inputhandler.input_release()
|
||||
return password
|
||||
|
||||
def setupwindows(s):
|
||||
s.c.lock()
|
||||
try:
|
||||
s.bannerwindow = curses.newwin(1, s.c.width, 0, 0)
|
||||
s.setupwindow_drawbanner()
|
||||
s.logheight = s.c.height - 1 - len(s.af.keys())
|
||||
s.logwindow = curses.newwin(s.logheight, s.c.width, 1, 0)
|
||||
s.logwindow.idlok(1)
|
||||
s.logwindow.scrollok(1)
|
||||
s.logwindow.move(s.logheight - 1, 0)
|
||||
s.setupwindow_drawlog()
|
||||
accounts = s.af.keys()
|
||||
accounts.sort()
|
||||
accounts.reverse()
|
||||
|
||||
pos = s.c.height - 1
|
||||
index = 0
|
||||
s.hotkeys = []
|
||||
for account in accounts:
|
||||
accountwindow = curses.newwin(1, s.c.width, pos, 0)
|
||||
s.af[account].setwindow(accountwindow, acctkeys[index])
|
||||
s.hotkeys.append(account)
|
||||
index += 1
|
||||
pos -= 1
|
||||
|
||||
curses.doupdate()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
|
||||
def setupwindow_drawbanner(s):
|
||||
if s.c.has_color:
|
||||
color = s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLUE) | \
|
||||
curses.A_BOLD
|
||||
else:
|
||||
color = curses.A_REVERSE
|
||||
s.bannerwindow.bkgd(' ', color) # Fill background with that color
|
||||
s.bannerwindow.addstr("%s %s" % (version.productname,
|
||||
version.versionstr))
|
||||
s.bannerwindow.addstr(0, s.bannerwindow.getmaxyx()[1] - len(version.copyright) - 1,
|
||||
version.copyright)
|
||||
|
||||
s.bannerwindow.noutrefresh()
|
||||
|
||||
def setupwindow_drawlog(s):
|
||||
if s.c.has_color:
|
||||
color = s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLACK)
|
||||
else:
|
||||
color = curses.A_NORMAL
|
||||
s.logwindow.bkgd(' ', color)
|
||||
for line, color in s.text:
|
||||
s.logwindow.addstr("\n" + line, color)
|
||||
s.logwindow.noutrefresh()
|
||||
|
||||
def getaccountframe(s, accountname = None):
|
||||
if accountname == None:
|
||||
accountname = s.getthreadaccount()
|
||||
s.aflock.acquire()
|
||||
try:
|
||||
if accountname in s.af:
|
||||
return s.af[accountname]
|
||||
|
||||
# New one.
|
||||
s.af[accountname] = CursesAccountFrame(s.c, accountname)
|
||||
s.c.lock()
|
||||
try:
|
||||
s.c.reset()
|
||||
s.setupwindows()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
finally:
|
||||
s.aflock.release()
|
||||
return s.af[accountname]
|
||||
|
||||
|
||||
def _display(s, msg, color = None):
|
||||
if "\n" in msg:
|
||||
for thisline in msg.split("\n"):
|
||||
s._msg(thisline)
|
||||
return
|
||||
|
||||
# We must acquire both locks. Otherwise, deadlock can result.
|
||||
# This can happen if one thread calls _msg (locking curses, then
|
||||
# tf) and another tries to set the color (locking tf, then curses)
|
||||
#
|
||||
# By locking both up-front here, in this order, we prevent deadlock.
|
||||
|
||||
s.tflock.acquire()
|
||||
s.c.lock()
|
||||
try:
|
||||
if not s.c.isactive():
|
||||
# For dumping out exceptions and stuff.
|
||||
print msg
|
||||
return
|
||||
if color:
|
||||
s.gettf().setcolor(color)
|
||||
s._addline(msg, s.gettf().getcolorpair())
|
||||
s.logwindow.refresh()
|
||||
finally:
|
||||
s.c.unlock()
|
||||
s.tflock.release()
|
||||
|
||||
def _addline(s, msg, color):
|
||||
s.c.lock()
|
||||
try:
|
||||
s.logwindow.addstr("\n" + msg, color)
|
||||
s.text.append((msg, color))
|
||||
while len(s.text) > s.logheight:
|
||||
s.text = s.text[1:]
|
||||
finally:
|
||||
s.c.unlock()
|
||||
|
||||
def terminate(s, exitstatus = 0):
|
||||
s.c.stop()
|
||||
UIBase.terminate(s, exitstatus)
|
||||
|
||||
def threadException(s, thread):
|
||||
s.c.stop()
|
||||
UIBase.threadException(s, thread)
|
||||
|
||||
def mainException(s):
|
||||
s.c.stop()
|
||||
UIBase.mainException(s)
|
||||
|
||||
def sleep(s, sleepsecs):
|
||||
s.gettf().setcolor('red')
|
||||
s._msg("Next sync in %d:%02d" % (sleepsecs / 60, sleepsecs % 60))
|
||||
BlinkenBase.sleep(s, sleepsecs)
|
||||
|
||||
if __name__ == '__main__':
|
||||
x = Blinkenlights(None)
|
||||
x.init_banner()
|
||||
import time
|
||||
time.sleep(5)
|
||||
x.c.stop()
|
||||
fgs = {'black': curses.COLOR_BLACK, 'red': curses.COLOR_RED,
|
||||
'green': curses.COLOR_GREEN, 'yellow': curses.COLOR_YELLOW,
|
||||
'blue': curses.COLOR_BLUE, 'magenta': curses.COLOR_MAGENTA,
|
||||
'cyan': curses.COLOR_CYAN, 'white': curses.COLOR_WHITE}
|
||||
|
||||
x = CursesUtil()
|
||||
win1 = curses.newwin(x.height, x.width / 4 - 1, 0, 0)
|
||||
win1.addstr("Black/normal\n")
|
||||
for name, fg in fgs.items():
|
||||
win1.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK))
|
||||
win2 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1])
|
||||
win2.addstr("Blue/normal\n")
|
||||
for name, fg in fgs.items():
|
||||
win2.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE))
|
||||
win3 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] +
|
||||
win2.getmaxyx()[1])
|
||||
win3.addstr("Black/bright\n")
|
||||
for name, fg in fgs.items():
|
||||
win3.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK) | \
|
||||
curses.A_BOLD)
|
||||
win4 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] * 3)
|
||||
win4.addstr("Blue/bright\n")
|
||||
for name, fg in fgs.items():
|
||||
win4.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE) | \
|
||||
curses.A_BOLD)
|
||||
|
||||
|
||||
win1.refresh()
|
||||
win2.refresh()
|
||||
win3.refresh()
|
||||
win4.refresh()
|
||||
x.stdscr.refresh()
|
||||
import time
|
||||
time.sleep(5)
|
||||
x.stop()
|
||||
print x.has_color
|
||||
print x.height
|
||||
print x.width
|
||||
|
Reference in New Issue
Block a user