]>
code.delx.au - offlineimap/blob - offlineimap/head/offlineimap/ui/Tk.py
2 # Copyright (C) 2002, 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 __future__
import nested_scopes
23 from threading
import *
24 import thread
, traceback
, time
, threading
25 from StringIO
import StringIO
26 from ScrolledText
import ScrolledText
27 from offlineimap
import threadutil
, version
28 from Queue
import Queue
29 from UIBase
import UIBase
30 from offlineimap
.ui
.Blinkenlights
import BlinkenBase
35 def __init__(self
, accountname
, config
, master
=None, errmsg
= None):
36 self
.top
= Toplevel(master
)
37 self
.top
.title(version
.productname
+ " Password Entry")
40 text
= '%s: %s\n' % (accountname
, errmsg
)
41 text
+= "%s: Enter password for %s on %s: " % \
42 (accountname
, config
.get(accountname
, "remoteuser"),
43 config
.get(accountname
, "remotehost"))
44 self
.label
= Label(self
.top
, text
= text
)
47 self
.entry
= Entry(self
.top
, show
='*')
48 self
.entry
.bind("<Return>", self
.ok
)
50 self
.entry
.focus_force()
52 self
.button
= Button(self
.top
, text
= "OK", command
=self
.ok
)
55 self
.entry
.focus_force()
56 self
.top
.wait_window(self
.label
)
58 def ok(self
, args
= None):
59 self
.password
= self
.entry
.get()
62 def getpassword(self
):
66 def __init__(self
, title
, message
, blocking
= 1, master
= None):
70 self
.top
= Toplevel(master
)
72 self
.text
= ScrolledText(self
.top
, font
= "Courier 10")
74 self
.text
.insert(END
, message
)
75 self
.text
['state'] = DISABLED
76 self
.button
= Button(self
.top
, text
= "OK", command
=self
.ok
)
80 self
.top
.wait_window(self
.button
)
87 class ThreadFrame(Frame
):
88 def __init__(self
, master
=None):
89 self
.threadextraframe
= None
90 self
.thread
= currentThread()
91 self
.threadid
= thread
.get_ident()
92 Frame
.__init
__(self
, master
, relief
= RIDGE
, borderwidth
= 2)
94 self
.threadlabel
= Label(self
, foreground
= '#FF0000',
95 text
="Thread %d (%s)" % (self
.threadid
,
96 self
.thread
.getName()))
97 self
.threadlabel
.pack()
98 self
.setthread(currentThread())
100 self
.account
= "Unknown"
101 self
.mailbox
= "Unknown"
102 self
.loclabel
= Label(self
,
103 text
= "Account/mailbox information unknown")
104 #self.loclabel.pack()
106 self
.updateloclabel()
108 self
.message
= Label(self
, text
="Messages will appear here.\n",
109 foreground
= '#0000FF')
110 self
.message
.pack(fill
= 'x')
112 def setthread(self
, newthread
):
114 self
.threadlabel
['text'] = newthread
.getName()
116 self
.threadlabel
['text'] = "No thread"
117 self
.destroythreadextraframe()
119 def destroythreadextraframe(self
):
120 if self
.threadextraframe
:
121 self
.threadextraframe
.destroy()
122 self
.threadextraframe
= None
124 def getthreadextraframe(self
):
125 if self
.threadextraframe
:
126 return self
.threadextraframe
127 self
.threadextraframe
= Frame(self
)
128 self
.threadextraframe
.pack(fill
= 'x')
129 return self
.threadextraframe
131 def setaccount(self
, account
):
132 self
.account
= account
133 self
.mailbox
= "Unknown"
134 self
.updateloclabel()
136 def setmailbox(self
, mailbox
):
137 self
.mailbox
= mailbox
138 self
.updateloclabel()
140 def updateloclabel(self
):
141 self
.loclabel
['text'] = "Processing %s: %s" % (self
.account
,
144 def appendmessage(self
, newtext
):
145 self
.message
['text'] += "\n" + newtext
147 def setmessage(self
, newtext
):
148 self
.message
['text'] = newtext
151 class VerboseUI(UIBase
):
154 if usabletest
!= None:
164 def _createTopWindow(self
, doidlevac
= 1):
166 self
.created
= threading
.Event()
171 t
= threadutil
.ExitNotifyThread(target
= self
._runmainloop
,
172 name
= "Tk Mainloop")
180 t
= threadutil
.ExitNotifyThread(target
= self
.idlevacuum
,
181 name
= "Tk idle vacuum")
187 s
.top
.title(version
.productname
+ " " + version
.versionstr
)
188 s
.top
.after_idle(s
.created
.set)
192 def getaccountframe(s
):
193 accountname
= s
.getthreadaccount()
196 if accountname
in s
.af
:
197 return s
.af
[accountname
]
199 s
.af
[accountname
] = LEDAccountFrame(s
.top
, accountname
,
200 s
.fontfamily
, s
.fontsize
)
203 return s
.af
[accountname
]
205 def getpass(s
, accountname
, config
, errmsg
= None):
206 pd
= PasswordDialog(accountname
, config
, errmsg
= errmsg
)
207 return pd
.getpassword()
209 def gettf(s
, newtype
=ThreadFrame
, master
= None):
212 threadid
= thread
.get_ident()
215 if threadid
in s
.threadframes
:
216 return s
.threadframes
[threadid
]
217 if len(s
.availablethreadframes
):
218 tf
= s
.availablethreadframes
.pop(0)
219 tf
.setthread(currentThread())
222 s
.threadframes
[threadid
] = tf
228 s
.gettf().setmessage(msg
)
230 def threadExited(s
, thread
):
231 threadid
= thread
.threadid
233 if threadid
in s
.threadframes
:
234 tf
= s
.threadframes
[threadid
]
236 tf
.setaccount("Unknown")
237 tf
.setmessage("Idle")
238 s
.availablethreadframes
.append(tf
)
239 del s
.threadframes
[threadid
]
241 UIBase
.threadExited(s
, thread
)
247 while len(s
.availablethreadframes
):
248 tf
= s
.availablethreadframes
.pop()
252 def threadException(s
, thread
):
253 exceptionstr
= s
.getThreadExceptionString(thread
)
258 TextOKDialog("Thread Exception", exceptionstr
)
259 s
.delThreadDebugLog(thread
)
262 def mainException(s
):
263 exceptionstr
= s
.getMainExceptionString()
268 TextOKDialog("Main Program Exception", exceptionstr
)
270 def warn(s
, msg
, minor
):
272 # Just let the default handler catch it
273 UIBase
.warn(s
, msg
, minor
)
275 TextOKDialog("OfflineIMAP Warning", msg
)
278 TextOKDialog(version
.productname
+ " License",
279 version
.bigcopyright
+ "\n" +
280 version
.homepage
+ "\n\n" + version
.license
,
281 blocking
= 0, master
= s
.top
)
286 s
.availablethreadframes
= []
289 s
._msg
(version
.productname
+ " " + version
.versionstr
+ ", " +\
291 tf
= s
.gettf().getthreadextraframe()
293 b
= Button(tf
, text
= "About", command
= s
.showlicense
)
296 b
= Button(tf
, text
= "Exit", command
= s
.terminate
)
298 s
.sleeping_abort
= {}
300 def deletingmessages(s
, uidlist
, destlist
):
301 ds
= s
.folderlist(destlist
)
302 s
._msg
("Deleting %d messages in %s" % (len(uidlist
), ds
))
304 def _sleep_cancel(s
, args
= None):
305 s
.sleeping_abort
[thread
.get_ident()] = 1
307 def sleep(s
, sleepsecs
):
308 threadid
= thread
.get_ident()
309 s
.sleeping_abort
[threadid
] = 0
310 tf
= s
.gettf().getthreadextraframe()
313 s
.sleeping_abort
[threadid
] = 1
315 sleepbut
= Button(tf
, text
= 'Sync immediately',
316 command
= sleep_cancel
)
318 UIBase
.sleep(s
, sleepsecs
)
320 def sleeping(s
, sleepsecs
, remainingsecs
):
321 retval
= s
.sleeping_abort
[thread
.get_ident()]
323 s
._msg
("Next sync in %d:%02d" % (remainingsecs
/ 60,
326 s
._msg
("Wait done; synchronizing now.")
327 s
.gettf().destroythreadextraframe()
328 del s
.sleeping_abort
[thread
.get_ident()]
329 time
.sleep(sleepsecs
)
334 ################################################## Blinkenlights
336 class LEDAccountFrame
:
337 def __init__(self
, top
, accountname
, fontfamily
, fontsize
):
339 self
.accountname
= accountname
340 self
.fontfamily
= fontfamily
341 self
.fontsize
= fontsize
342 self
.frame
= Frame(self
.top
, background
= 'black')
343 self
.frame
.pack(side
= BOTTOM
, expand
= 1, fill
= X
)
344 self
._createcanvas
(self
.frame
)
346 self
.label
= Label(self
.frame
, text
= accountname
,
347 background
= "black", foreground
= "blue",
348 font
= (self
.fontfamily
, self
.fontsize
))
349 self
.label
.grid(sticky
= E
, row
= 0, column
= 1)
351 def getnewthreadframe(s
):
352 return LEDThreadFrame(s
.canvas
)
354 def _createcanvas(self
, parent
):
357 c
.grid(sticky
= E
, row
= 0, column
= 0)
358 parent
.grid_columnconfigure(1, weight
= 1)
359 #c.pack(side = LEFT, expand = 0, fill = X)
361 def startsleep(s
, sleepsecs
):
363 s
.button
= Button(s
.frame
, text
= "Sync now", command
= s
.syncnow
,
364 background
= "black", activebackground
= "black",
365 activeforeground
= "white",
366 foreground
= "blue", highlightthickness
= 0,
368 font
= (s
.fontfamily
, s
.fontsize
), borderwidth
= 0,
370 s
.button
.grid(sticky
= E
, row
= 0, column
= 2)
375 def sleeping(s
, sleepsecs
, remainingsecs
):
377 s
.button
.config(text
= 'Sync now (%d:%02d remain)' % \
378 (remainingsecs
/ 60, remainingsecs
% 60))
379 time
.sleep(sleepsecs
)
383 return s
.sleeping_abort
385 class LEDFrame(Frame
):
386 """This holds the different lights."""
388 retval
= Canvas(self
, background
= 'black', height
= 20, bd
= 0,
389 highlightthickness
= 0, width
= 10)
390 retval
.pack(side
= LEFT
, padx
= 0, pady
= 0, ipadx
= 0, ipady
= 0)
393 class LEDThreadFrame
:
394 """There is one of these for each little light."""
395 def __init__(self
, master
):
396 self
.canvas
= master
.getnewobj()
398 self
.ovalid
= self
.canvas
.create_oval(4, 4, 9,
402 def setcolor(self
, newcolor
):
403 if newcolor
!= self
.color
:
404 self
.canvas
.itemconfigure(self
.ovalid
, fill
= newcolor
)
405 self
.color
= newcolor
410 def setthread(self
, newthread
):
412 self
.setcolor('gray')
414 self
.setcolor('black')
417 class Blinkenlights(BlinkenBase
, VerboseUI
):
418 def __init__(s
, config
, verbose
= 0):
419 VerboseUI
.__init
__(s
, config
, verbose
)
420 s
.fontfamily
= 'Helvetica'
422 if config
.has_option('ui.Tk.Blinkenlights', 'fontfamily'):
423 s
.fontfamily
= config
.get('ui.Tk.Blinkenlights', 'fontfamily')
424 if config
.has_option('ui.Tk.Blinkenlights', 'fontsize'):
425 s
.fontsize
= config
.getint('ui.Tk.Blinkenlights', 'fontsize')
428 return VerboseUI
.isusable(s
)
430 def _createTopWindow(self
):
431 VerboseUI
._createTopWindow
(self
, 0)
432 #self.top.resizable(width = 0, height = 0)
433 self
.top
.configure(background
= 'black', bd
= 0)
435 widthmetric
= tkFont
.Font(family
= self
.fontfamily
, size
= self
.fontsize
).measure("0")
436 self
.loglines
= self
.config
.getdefaultint("ui.Tk.Blinkenlights",
438 self
.bufferlines
= self
.config
.getdefaultint("ui.Tk.Blinkenlights",
440 self
.text
= ScrolledText(self
.top
, bg
= 'black', #scrollbar = 'y',
441 font
= (self
.fontfamily
, self
.fontsize
),
442 bd
= 0, highlightthickness
= 0, setgrid
= 0,
443 state
= DISABLED
, height
= self
.loglines
,
444 wrap
= NONE
, width
= 60)
445 self
.text
.vbar
.configure(background
= '#000050',
446 activebackground
= 'blue',
447 highlightbackground
= 'black',
448 troughcolor
= "black", bd
= 0,
449 elementborderwidth
= 2)
453 self
.textlock
= Lock()
456 BlinkenBase
.init_banner(s
)
458 menubar
= Menu(s
.top
, activebackground
= "black",
459 activeforeground
= "white",
460 activeborderwidth
= 0,
461 background
= "black", foreground
= "blue",
462 font
= (s
.fontfamily
, s
.fontsize
), bd
= 0)
463 menubar
.add_command(label
= "About", command
= s
.showlicense
)
464 menubar
.add_command(label
= "Show Log", command
= s
._togglelog
)
465 menubar
.add_command(label
= "Exit", command
= s
.terminate
)
466 s
.top
.config(menu
= menubar
)
469 if s
.config
.getdefaultboolean("ui.Tk.Blinkenlights", "showlog", 1):
471 s
.gettf().setcolor('red')
472 s
.top
.resizable(width
= 0, height
= 0)
473 s
._msg
(version
.banner
)
477 s
.oldtextheight
= s
.text
.winfo_height()
480 s
.menubar
.entryconfig('Hide Log', label
= 'Show Log')
484 s
.top
.resizable(width
= 0, height
= 0)
488 s
.text
.pack(side
= TOP
, expand
= 1, fill
= BOTH
)
492 s
.menubar
.entryconfig('Show Log', label
= 'Hide Log')
494 s
.top
.resizable(width
= 1, height
= 1)
496 def sleep(s
, sleepsecs
):
497 s
.gettf().setcolor('red')
498 s
._msg
("Next sync in %d:%02d" % (sleepsecs
/ 60, sleepsecs
% 60))
499 BlinkenBase
.sleep(s
, sleepsecs
)
501 def sleeping(s
, sleepsecs
, remainingsecs
):
502 return BlinkenBase
.sleeping(s
, sleepsecs
, remainingsecs
)
506 lo
, hi
= s
.text
.vbar
.get()
507 s
.text
.vbar
.set(1.0 - (hi
- lo
), 1.0)
511 for thisline
in msg
.split("\n"):
514 #VerboseUI._msg(s, msg)
515 color
= s
.gettf().getcolor()
519 if s
.text
.vbar
.get()[1] != 1.0:
521 s
.text
.config(state
= NORMAL
)
522 if not color
in s
.tags
:
523 s
.text
.tag_config(color
, foreground
= color
)
525 s
.text
.insert(END
, "\n" + msg
, color
)
527 # Trim down. Not quite sure why I have to say 7 instead of 5,
529 while float(s
.text
.index(END
)) > s
.bufferlines
+ 2.0:
530 s
.text
.delete(1.0, 2.0)
535 s
.text
.config(state
= DISABLED
)