]>
code.delx.au - offlineimap/blob - offlineimap/head/offlineimap/ui/Curses.py
c51b32115ad9e81da38e7d3e271c217d7ebb0289
1 # Curses-based interfaces
2 # Copyright (C) 2003 John Goerzen
3 # <jgoerzen@complete.org>
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.
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.
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
19 from Blinkenlights
import BlinkenBase
20 from UIBase
import UIBase
21 from threading
import *
22 from offlineimap
import version
, threadutil
24 import curses
, curses
.panel
, curses
.textpad
, curses
.wrapper
25 from debuglock
import DebuggingLock
29 self
.pairs
= {self
._getpairindex
(curses
.COLOR_WHITE
,
30 curses
.COLOR_BLACK
): 0}
33 self
.pairlock
= Lock()
36 return hasattr(self
, 'stdscr')
38 def _getpairindex(self
, fg
, bg
):
39 return '%d/%d' % (fg
,bg
)
41 def getpair(self
, fg
, bg
):
42 pindex
= self
._getpairindex
(fg
, bg
)
43 self
.pairlock
.acquire()
45 if self
.pairs
.has_key(pindex
):
46 return curses
.color_pair(self
.pairs
[pindex
])
48 self
.pairs
[pindex
] = self
.nextpair
49 curses
.init_pair(self
.nextpair
, fg
, bg
)
51 return curses
.color_pair(self
.nextpair
- 1)
53 self
.pairlock
.release()
56 self
.stdscr
= curses
.initscr()
62 self
.has_color
= curses
.has_colors()
68 (self
.height
, self
.width
) = self
.stdscr
.getmaxyx()
71 if not hasattr(self
, 'stdscr'):
73 #self.stdscr.addstr(self.height - 1, 0, "\n",
74 # self.getpair(curses.COLOR_WHITE,
75 # curses.COLOR_BLACK))
87 class CursesAccountFrame
:
88 def __init__(s
, master
, accountname
, iolock
):
92 s
.accountname
= accountname
94 def setwindow(s
, window
, lock
= 1):
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
)
103 def getnewthreadframe(s
, lock
= 1):
104 tf
= CursesThreadFrame(s
.c
, s
.window
, 0, s
.location
, s
.iolock
, lock
)
106 s
.children
.append(tf
)
109 class CursesThreadFrame
:
110 def __init__(s
, master
, window
, y
, x
, iolock
, lock
= 1):
111 """master should be a CursesUtil object."""
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
)}
131 s
.setcolor('black', lock
)
133 def setcolor(self
, color
, lock
= 1):
134 self
.color
= self
.colormap
[color
]
137 def display(self
, lock
= 1):
139 self
.iolock
.acquire()
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()
146 self
.iolock
.release()
151 def update(self
, window
, y
, x
, lock
= 1):
157 def setthread(self
, newthread
, lock
= 1):
158 self
.setcolor('black', lock
)
160 # self.setcolor('gray')
162 # self.setcolor('black')
165 def __init__(s
, util
):
168 s
.inputlock
= DebuggingLock('inputlock')
170 s
.statuslock
= DebuggingLock('statuslock')
175 s
.thread
= threadutil
.ExitNotifyThread(target
= s
.bgreaderloop
,
176 name
= "InputHandler loop")
177 s
.thread
.setDaemon(1)
182 s
.statuslock
.acquire()
183 if s
.lockheld
or s
.bgchar
== None:
184 s
.statuslock
.release()
187 s
.statuslock
.release()
188 ch
= s
.c
.stdscr
.getch()
189 s
.statuslock
.acquire()
191 if s
.lockheld
or s
.bgchar
== None:
196 s
.statuslock
.release()
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.
202 callback is a function taking a single arg -- the char pressed.
204 If callback is None, clears the request."""
205 s
.statuslock
.acquire()
206 oldhandler
= s
.bgchar
207 newhandler
= callback
210 if oldhandler
and not newhandler
:
212 if newhandler
and not oldhandler
:
215 s
.statuslock
.release()
217 def input_acquire(s
):
218 """Call this method when you want exclusive input control.
219 Make sure to call input_release afterwards!
222 s
.inputlock
.acquire()
223 s
.statuslock
.acquire()
225 s
.statuslock
.release()
227 def input_release(s
):
228 """Call this method when you are done getting input."""
229 s
.statuslock
.acquire()
231 s
.statuslock
.release()
232 s
.inputlock
.release()
235 class Blinkenlights(BlinkenBase
, UIBase
):
237 s
.iolock
= DebuggingLock('iolock')
239 s
.aflock
= DebuggingLock('aflock')
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
)
249 def keypress(s
, key
):
250 s
._msg
("Key pressed: " + str(key
))
252 def getpass(s
, accountname
, config
, errmsg
= None):
253 s
.inputhandler
.input_acquire()
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()
264 s
.inputhandler
.input_release()
267 def setupwindows(s
, dolock
= 1):
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)
276 s
.logwindow
.scrollok(1)
277 s
.setupwindow_drawlog()
278 accounts
= s
.af
.keys()
283 for account
in accounts
:
284 accountwindow
= curses
.newwin(1, s
.c
.width
, pos
, 0)
285 s
.af
[account
].setwindow(accountwindow
, lock
= 0)
293 def setupwindow_drawbanner(s
):
294 s
.bannerwindow
.bkgd(' ', curses
.A_BOLD | \
295 s
.c
.getpair(curses
.COLOR_WHITE
,
297 s
.bannerwindow
.addstr("%s %s" % (version
.productname
,
299 s
.bannerwindow
.addstr(0, s
.bannerwindow
.getmaxyx()[1] - len(version
.copyright
) - 1,
302 s
.bannerwindow
.noutrefresh()
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()
310 def getaccountframe(s
):
311 accountname
= s
.getthreadaccount()
314 if accountname
in s
.af
:
315 return s
.af
[accountname
]
318 s
.af
[accountname
] = CursesAccountFrame(s
.c
, accountname
, s
.iolock
)
321 s
.setupwindows(dolock
= 0)
325 return s
.af
[accountname
]
328 def _msg(s
, msg
, color
= None):
330 for thisline
in msg
.split("\n"):
335 if not s
.c
.isactive():
336 # For dumping out exceptions and stuff.
340 s
.gettf(lock
= 0).setcolor(color
, lock
= 0)
341 s
._addline
_unlocked
(msg
, s
.gettf(lock
= 0).getcolor())
342 s
.logwindow
.refresh()
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
:
353 def terminate(s
, exitstatus
= 0):
355 UIBase
.terminate(s
, exitstatus
)
357 def threadException(s
, thread
):
359 UIBase
.threadException(s
, thread
)
361 def mainException(s
):
363 UIBase
.mainException(s
)
365 if __name__
== '__main__':
366 x
= Blinkenlights(None)
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
}
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] +
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
) | \
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
) | \