]> code.delx.au - offlineimap/blob - offlineimap/head/offlineimap/ui/Curses.py
c51b32115ad9e81da38e7d3e271c217d7ebb0289
[offlineimap] / offlineimap / head / offlineimap / ui / Curses.py
1 # Curses-based interfaces
2 # Copyright (C) 2003 John Goerzen
3 # <jgoerzen@complete.org>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
19 from Blinkenlights import BlinkenBase
20 from UIBase import UIBase
21 from threading import *
22 from offlineimap import version, threadutil
23
24 import curses, curses.panel, curses.textpad, curses.wrapper
25 from debuglock import DebuggingLock
26
27 class CursesUtil:
28 def __init__(self):
29 self.pairs = {self._getpairindex(curses.COLOR_WHITE,
30 curses.COLOR_BLACK): 0}
31 self.start()
32 self.nextpair = 1
33 self.pairlock = Lock()
34
35 def isactive(self):
36 return hasattr(self, 'stdscr')
37
38 def _getpairindex(self, fg, bg):
39 return '%d/%d' % (fg,bg)
40
41 def getpair(self, fg, bg):
42 pindex = self._getpairindex(fg, bg)
43 self.pairlock.acquire()
44 try:
45 if self.pairs.has_key(pindex):
46 return curses.color_pair(self.pairs[pindex])
47 else:
48 self.pairs[pindex] = self.nextpair
49 curses.init_pair(self.nextpair, fg, bg)
50 self.nextpair += 1
51 return curses.color_pair(self.nextpair - 1)
52 finally:
53 self.pairlock.release()
54
55 def start(self):
56 self.stdscr = curses.initscr()
57 curses.noecho()
58 curses.cbreak()
59 self.stdscr.keypad(1)
60 try:
61 curses.start_color()
62 self.has_color = curses.has_colors()
63 except:
64 self.has_color = 0
65
66 self.stdscr.clear()
67 self.stdscr.refresh()
68 (self.height, self.width) = self.stdscr.getmaxyx()
69
70 def stop(self):
71 if not hasattr(self, 'stdscr'):
72 return
73 #self.stdscr.addstr(self.height - 1, 0, "\n",
74 # self.getpair(curses.COLOR_WHITE,
75 # curses.COLOR_BLACK))
76 self.stdscr.refresh()
77 self.stdscr.keypad(0)
78 curses.nocbreak()
79 curses.echo()
80 curses.endwin()
81 del self.stdscr
82
83 def reset(self):
84 self.stop()
85 self.start()
86
87 class CursesAccountFrame:
88 def __init__(s, master, accountname, iolock):
89 s.iolock = iolock
90 s.c = master
91 s.children = []
92 s.accountname = accountname
93
94 def setwindow(s, window, lock = 1):
95 s.window = window
96 acctstr = '%15.15s: ' % s.accountname
97 s.window.addstr(0, 0, acctstr)
98 s.location = len(acctstr)
99 for child in s.children:
100 child.update(window, 0, s.location, lock)
101 s.location += 1
102
103 def getnewthreadframe(s, lock = 1):
104 tf = CursesThreadFrame(s.c, s.window, 0, s.location, s.iolock, lock)
105 s.location += 1
106 s.children.append(tf)
107 return tf
108
109 class CursesThreadFrame:
110 def __init__(s, master, window, y, x, iolock, lock = 1):
111 """master should be a CursesUtil object."""
112 s.iolock = iolock
113 s.c = master
114 s.window = window
115 s.x = x
116 s.y = y
117 s.colors = []
118 bg = curses.COLOR_BLACK
119 s.colormap = {'black': s.c.getpair(curses.COLOR_BLACK, bg),
120 'gray': s.c.getpair(curses.COLOR_WHITE, bg),
121 'white': curses.A_BOLD | s.c.getpair(curses.COLOR_WHITE, bg),
122 'blue': s.c.getpair(curses.COLOR_BLUE, bg),
123 'red': s.c.getpair(curses.COLOR_RED, bg),
124 'purple': s.c.getpair(curses.COLOR_MAGENTA, bg),
125 'cyan': s.c.getpair(curses.COLOR_CYAN, bg),
126 'green': s.c.getpair(curses.COLOR_GREEN, bg),
127 'orange': s.c.getpair(curses.COLOR_YELLOW, bg),
128 'yellow': curses.A_BOLD | s.c.getpair(curses.COLOR_YELLOW, bg),
129 'pink': curses.A_BOLD | s.c.getpair(curses.COLOR_RED, bg)}
130 #s.setcolor('gray')
131 s.setcolor('black', lock)
132
133 def setcolor(self, color, lock = 1):
134 self.color = self.colormap[color]
135 self.display(lock)
136
137 def display(self, lock = 1):
138 if lock:
139 self.iolock.acquire()
140 try:
141 self.window.addstr(self.y, self.x, '.', self.color)
142 self.c.stdscr.move(self.c.height - 1, self.c.width - 1)
143 self.window.refresh()
144 finally:
145 if lock:
146 self.iolock.release()
147
148 def getcolor(self):
149 return self.color
150
151 def update(self, window, y, x, lock = 1):
152 self.window = window
153 self.y = y
154 self.x = x
155 self.display(lock)
156
157 def setthread(self, newthread, lock = 1):
158 self.setcolor('black', lock)
159 #if newthread:
160 # self.setcolor('gray')
161 #else:
162 # self.setcolor('black')
163
164 class InputHandler:
165 def __init__(s, util):
166 s.c = util
167 s.bgchar = None
168 s.inputlock = DebuggingLock('inputlock')
169 s.lockheld = 0
170 s.statuslock = DebuggingLock('statuslock')
171 s.startup = Event()
172 s.startthread()
173
174 def startthread(s):
175 s.thread = threadutil.ExitNotifyThread(target = s.bgreaderloop,
176 name = "InputHandler loop")
177 s.thread.setDaemon(1)
178 s.thread.start()
179
180 def bgreaderloop(s):
181 while 1:
182 s.statuslock.acquire()
183 if s.lockheld or s.bgchar == None:
184 s.statuslock.release()
185 s.startup.wait()
186 else:
187 s.statuslock.release()
188 ch = s.c.stdscr.getch()
189 s.statuslock.acquire()
190 try:
191 if s.lockheld or s.bgchar == None:
192 curses.ungetch(ch)
193 else:
194 s.bgchar(ch)
195 finally:
196 s.statuslock.release()
197
198 def set_bgchar(s, callback):
199 """Sets a "background" character handler. If a key is pressed
200 while not doing anything else, it will be passed to this handler.
201
202 callback is a function taking a single arg -- the char pressed.
203
204 If callback is None, clears the request."""
205 s.statuslock.acquire()
206 oldhandler = s.bgchar
207 newhandler = callback
208 s.bgchar = callback
209
210 if oldhandler and not newhandler:
211 pass
212 if newhandler and not oldhandler:
213 s.startup.set()
214
215 s.statuslock.release()
216
217 def input_acquire(s):
218 """Call this method when you want exclusive input control.
219 Make sure to call input_release afterwards!
220 """
221
222 s.inputlock.acquire()
223 s.statuslock.acquire()
224 s.lockheld = 1
225 s.statuslock.release()
226
227 def input_release(s):
228 """Call this method when you are done getting input."""
229 s.statuslock.acquire()
230 s.lockheld = 0
231 s.statuslock.release()
232 s.inputlock.release()
233 s.startup.set()
234
235 class Blinkenlights(BlinkenBase, UIBase):
236 def init_banner(s):
237 s.iolock = DebuggingLock('iolock')
238 s.af = {}
239 s.aflock = DebuggingLock('aflock')
240 s.c = CursesUtil()
241 s.text = []
242 BlinkenBase.init_banner(s)
243 s.setupwindows(dolock = 0)
244 s.inputhandler = InputHandler(s.c)
245 s.gettf().setcolor('red')
246 s._msg(version.banner)
247 s.inputhandler.set_bgchar(s.keypress)
248
249 def keypress(s, key):
250 s._msg("Key pressed: " + str(key))
251
252 def getpass(s, accountname, config, errmsg = None):
253 s.inputhandler.input_acquire()
254 s.iolock.acquire()
255 try:
256 s.gettf(lock = 0).setcolor('white', lock = 0)
257 s._addline_unlocked(" *** Input Required", s.gettf().getcolor())
258 s._addline_unlocked(" *** Please enter password for account %s: " % accountname,
259 s.gettf(lock = 0).getcolor())
260 s.logwindow.refresh()
261 password = s.logwindow.getstr()
262 finally:
263 s.iolock.release()
264 s.inputhandler.input_release()
265 return password
266
267 def setupwindows(s, dolock = 1):
268 if dolock:
269 s.iolock.acquire()
270 try:
271 s.bannerwindow = curses.newwin(1, s.c.width, 0, 0)
272 s.setupwindow_drawbanner()
273 s.logheight = s.c.height - 1 - len(s.af.keys())
274 s.logwindow = curses.newwin(s.logheight, s.c.width, 1, 0)
275 s.logwindow.idlok(1)
276 s.logwindow.scrollok(1)
277 s.setupwindow_drawlog()
278 accounts = s.af.keys()
279 accounts.sort()
280 accounts.reverse()
281
282 pos = s.c.height - 1
283 for account in accounts:
284 accountwindow = curses.newwin(1, s.c.width, pos, 0)
285 s.af[account].setwindow(accountwindow, lock = 0)
286 pos -= 1
287
288 curses.doupdate()
289 finally:
290 if dolock:
291 s.iolock.release()
292
293 def setupwindow_drawbanner(s):
294 s.bannerwindow.bkgd(' ', curses.A_BOLD | \
295 s.c.getpair(curses.COLOR_WHITE,
296 curses.COLOR_BLUE))
297 s.bannerwindow.addstr("%s %s" % (version.productname,
298 version.versionstr))
299 s.bannerwindow.addstr(0, s.bannerwindow.getmaxyx()[1] - len(version.copyright) - 1,
300 version.copyright)
301
302 s.bannerwindow.noutrefresh()
303
304 def setupwindow_drawlog(s):
305 s.logwindow.bkgd(' ', s.c.getpair(curses.COLOR_WHITE, curses.COLOR_BLACK))
306 for line, color in s.text:
307 s.logwindow.addstr(line + "\n", color)
308 s.logwindow.noutrefresh()
309
310 def getaccountframe(s):
311 accountname = s.getthreadaccount()
312 s.aflock.acquire()
313 try:
314 if accountname in s.af:
315 return s.af[accountname]
316
317 # New one.
318 s.af[accountname] = CursesAccountFrame(s.c, accountname, s.iolock)
319 #s.iolock.acquire()
320 s.c.reset()
321 s.setupwindows(dolock = 0)
322 #s.iolock.release()
323 finally:
324 s.aflock.release()
325 return s.af[accountname]
326
327
328 def _msg(s, msg, color = None):
329 if "\n" in msg:
330 for thisline in msg.split("\n"):
331 s._msg(thisline)
332 return
333 s.iolock.acquire()
334 try:
335 if not s.c.isactive():
336 # For dumping out exceptions and stuff.
337 print msg
338 return
339 if color:
340 s.gettf(lock = 0).setcolor(color, lock = 0)
341 s._addline_unlocked(msg, s.gettf(lock = 0).getcolor())
342 s.logwindow.refresh()
343 finally:
344 s.iolock.release()
345
346 def _addline_unlocked(s, msg, color):
347 s.logwindow.addstr(msg + "\n", color)
348 s.text.append((msg, color))
349 while len(s.text) > s.logheight:
350 s.text = s.text[1:]
351
352
353 def terminate(s, exitstatus = 0):
354 s.c.stop()
355 UIBase.terminate(s, exitstatus)
356
357 def threadException(s, thread):
358 s.c.stop()
359 UIBase.threadException(s, thread)
360
361 def mainException(s):
362 s.c.stop()
363 UIBase.mainException(s)
364
365 if __name__ == '__main__':
366 x = Blinkenlights(None)
367 x.init_banner()
368 import time
369 time.sleep(10)
370 x.c.stop()
371 fgs = {'black': curses.COLOR_BLACK, 'red': curses.COLOR_RED,
372 'green': curses.COLOR_GREEN, 'yellow': curses.COLOR_YELLOW,
373 'blue': curses.COLOR_BLUE, 'magenta': curses.COLOR_MAGENTA,
374 'cyan': curses.COLOR_CYAN, 'white': curses.COLOR_WHITE}
375
376 x = CursesUtil()
377 win1 = curses.newwin(x.height, x.width / 4 - 1, 0, 0)
378 win1.addstr("Black/normal\n")
379 for name, fg in fgs.items():
380 win1.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK))
381 win2 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1])
382 win2.addstr("Blue/normal\n")
383 for name, fg in fgs.items():
384 win2.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE))
385 win3 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] +
386 win2.getmaxyx()[1])
387 win3.addstr("Black/bright\n")
388 for name, fg in fgs.items():
389 win3.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLACK) | \
390 curses.A_BOLD)
391 win4 = curses.newwin(x.height, x.width / 4 - 1, 0, win1.getmaxyx()[1] * 3)
392 win4.addstr("Blue/bright\n")
393 for name, fg in fgs.items():
394 win4.addstr("%s\n" % name, x.getpair(fg, curses.COLOR_BLUE) | \
395 curses.A_BOLD)
396
397
398 win1.refresh()
399 win2.refresh()
400 win3.refresh()
401 win4.refresh()
402 x.stdscr.refresh()
403 import time
404 time.sleep(40)
405 x.stop()
406 print x.has_color
407 print x.height
408 print x.width
409