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