]>
code.delx.au - offlineimap/blob - 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 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: " % accountname
42 self
.label
= Label(self
.top
, text
= text
)
45 self
.entry
= Entry(self
.top
, show
='*')
46 self
.entry
.bind("<Return>", self
.ok
)
48 self
.entry
.focus_force()
50 self
.button
= Button(self
.top
, text
= "OK", command
=self
.ok
)
53 self
.entry
.focus_force()
54 self
.top
.wait_window(self
.label
)
56 def ok(self
, args
= None):
57 self
.password
= self
.entry
.get()
60 def getpassword(self
):
64 def __init__(self
, title
, message
, blocking
= 1, master
= None):
68 self
.top
= Toplevel(master
)
70 self
.text
= ScrolledText(self
.top
, font
= "Courier 10")
72 self
.text
.insert(END
, message
)
73 self
.text
['state'] = DISABLED
74 self
.button
= Button(self
.top
, text
= "OK", command
=self
.ok
)
78 self
.top
.wait_window(self
.button
)
85 class ThreadFrame(Frame
):
86 def __init__(self
, master
=None):
87 self
.threadextraframe
= None
88 self
.thread
= currentThread()
89 self
.threadid
= thread
.get_ident()
90 Frame
.__init
__(self
, master
, relief
= RIDGE
, borderwidth
= 2)
92 self
.threadlabel
= Label(self
, foreground
= '#FF0000',
93 text
="Thread %d (%s)" % (self
.threadid
,
94 self
.thread
.getName()))
95 self
.threadlabel
.pack()
96 self
.setthread(currentThread())
98 self
.account
= "Unknown"
99 self
.mailbox
= "Unknown"
100 self
.loclabel
= Label(self
,
101 text
= "Account/mailbox information unknown")
102 #self.loclabel.pack()
104 self
.updateloclabel()
106 self
.message
= Label(self
, text
="Messages will appear here.\n",
107 foreground
= '#0000FF')
108 self
.message
.pack(fill
= 'x')
110 def setthread(self
, newthread
):
112 self
.threadlabel
['text'] = newthread
.getName()
114 self
.threadlabel
['text'] = "No thread"
115 self
.destroythreadextraframe()
117 def destroythreadextraframe(self
):
118 if self
.threadextraframe
:
119 self
.threadextraframe
.destroy()
120 self
.threadextraframe
= None
122 def getthreadextraframe(self
):
123 if self
.threadextraframe
:
124 return self
.threadextraframe
125 self
.threadextraframe
= Frame(self
)
126 self
.threadextraframe
.pack(fill
= 'x')
127 return self
.threadextraframe
129 def setaccount(self
, account
):
130 self
.account
= account
131 self
.mailbox
= "Unknown"
132 self
.updateloclabel()
134 def setmailbox(self
, mailbox
):
135 self
.mailbox
= mailbox
136 self
.updateloclabel()
138 def updateloclabel(self
):
139 self
.loclabel
['text'] = "Processing %s: %s" % (self
.account
,
142 def appendmessage(self
, newtext
):
143 self
.message
['text'] += "\n" + newtext
145 def setmessage(self
, newtext
):
146 self
.message
['text'] = newtext
149 class VerboseUI(UIBase
):
152 if usabletest
!= None:
162 def _createTopWindow(self
, doidlevac
= 1):
164 self
.created
= threading
.Event()
169 t
= threadutil
.ExitNotifyThread(target
= self
._runmainloop
,
170 name
= "Tk Mainloop")
178 t
= threadutil
.ExitNotifyThread(target
= self
.idlevacuum
,
179 name
= "Tk idle vacuum")
185 s
.top
.title(version
.productname
+ " " + version
.versionstr
)
186 s
.top
.after_idle(s
.created
.set)
190 def getaccountframe(s
):
191 accountname
= s
.getthreadaccount()
194 if accountname
in s
.af
:
195 return s
.af
[accountname
]
197 s
.af
[accountname
] = LEDAccountFrame(s
.top
, accountname
,
198 s
.fontfamily
, s
.fontsize
)
201 return s
.af
[accountname
]
203 def getpass(s
, accountname
, config
, errmsg
= None):
204 pd
= PasswordDialog(accountname
, config
, errmsg
= errmsg
)
205 return pd
.getpassword()
207 def gettf(s
, newtype
=ThreadFrame
, master
= None):
210 threadid
= thread
.get_ident()
213 if threadid
in s
.threadframes
:
214 return s
.threadframes
[threadid
]
215 if len(s
.availablethreadframes
):
216 tf
= s
.availablethreadframes
.pop(0)
217 tf
.setthread(currentThread())
220 s
.threadframes
[threadid
] = tf
225 def _display(s
, msg
):
226 s
.gettf().setmessage(msg
)
228 def threadExited(s
, thread
):
229 threadid
= thread
.threadid
231 if threadid
in s
.threadframes
:
232 tf
= s
.threadframes
[threadid
]
234 tf
.setaccount("Unknown")
235 tf
.setmessage("Idle")
236 s
.availablethreadframes
.append(tf
)
237 del s
.threadframes
[threadid
]
239 UIBase
.threadExited(s
, thread
)
245 while len(s
.availablethreadframes
):
246 tf
= s
.availablethreadframes
.pop()
250 def threadException(s
, thread
):
251 exceptionstr
= s
.getThreadExceptionString(thread
)
256 TextOKDialog("Thread Exception", exceptionstr
)
257 s
.delThreadDebugLog(thread
)
260 def mainException(s
):
261 exceptionstr
= s
.getMainExceptionString()
266 TextOKDialog("Main Program Exception", exceptionstr
)
268 def warn(s
, msg
, minor
= 0):
270 # Just let the default handler catch it
271 UIBase
.warn(s
, msg
, minor
)
273 TextOKDialog("OfflineIMAP Warning", msg
)
276 TextOKDialog(version
.productname
+ " License",
277 version
.bigcopyright
+ "\n" +
278 version
.homepage
+ "\n\n" + version
.license
,
279 blocking
= 0, master
= s
.top
)
284 s
.availablethreadframes
= []
287 s
._msg
(version
.productname
+ " " + version
.versionstr
+ ", " +\
289 tf
= s
.gettf().getthreadextraframe()
291 b
= Button(tf
, text
= "About", command
= s
.showlicense
)
294 b
= Button(tf
, text
= "Exit", command
= s
.terminate
)
296 s
.sleeping_abort
= {}
298 def deletingmessages(s
, uidlist
, destlist
):
299 ds
= s
.folderlist(destlist
)
300 s
._msg
("Deleting %d messages in %s" % (len(uidlist
), ds
))
302 def _sleep_cancel(s
, args
= None):
303 s
.sleeping_abort
[thread
.get_ident()] = 1
305 def sleep(s
, sleepsecs
):
306 threadid
= thread
.get_ident()
307 s
.sleeping_abort
[threadid
] = 0
308 tf
= s
.gettf().getthreadextraframe()
311 s
.sleeping_abort
[threadid
] = 1
313 sleepbut
= Button(tf
, text
= 'Sync immediately',
314 command
= sleep_cancel
)
316 UIBase
.sleep(s
, sleepsecs
)
318 def sleeping(s
, sleepsecs
, remainingsecs
):
319 retval
= s
.sleeping_abort
[thread
.get_ident()]
321 s
._msg
("Next sync in %d:%02d" % (remainingsecs
/ 60,
324 s
._msg
("Wait done; synchronizing now.")
325 s
.gettf().destroythreadextraframe()
326 del s
.sleeping_abort
[thread
.get_ident()]
327 time
.sleep(sleepsecs
)
332 ################################################## Blinkenlights
334 class LEDAccountFrame
:
335 def __init__(self
, top
, accountname
, fontfamily
, fontsize
):
337 self
.accountname
= accountname
338 self
.fontfamily
= fontfamily
339 self
.fontsize
= fontsize
340 self
.frame
= Frame(self
.top
, background
= 'black')
341 self
.frame
.pack(side
= BOTTOM
, expand
= 1, fill
= X
)
342 self
._createcanvas
(self
.frame
)
344 self
.label
= Label(self
.frame
, text
= accountname
,
345 background
= "black", foreground
= "blue",
346 font
= (self
.fontfamily
, self
.fontsize
))
347 self
.label
.grid(sticky
= E
, row
= 0, column
= 1)
349 def getnewthreadframe(s
):
350 return LEDThreadFrame(s
.canvas
)
352 def _createcanvas(self
, parent
):
355 c
.grid(sticky
= E
, row
= 0, column
= 0)
356 parent
.grid_columnconfigure(1, weight
= 1)
357 #c.pack(side = LEFT, expand = 0, fill = X)
359 def startsleep(s
, sleepsecs
):
361 s
.button
= Button(s
.frame
, text
= "Sync now", command
= s
.syncnow
,
362 background
= "black", activebackground
= "black",
363 activeforeground
= "white",
364 foreground
= "blue", highlightthickness
= 0,
366 font
= (s
.fontfamily
, s
.fontsize
), borderwidth
= 0,
368 s
.button
.grid(sticky
= E
, row
= 0, column
= 2)
373 def sleeping(s
, sleepsecs
, remainingsecs
):
375 s
.button
.config(text
= 'Sync now (%d:%02d remain)' % \
376 (remainingsecs
/ 60, remainingsecs
% 60))
377 time
.sleep(sleepsecs
)
381 return s
.sleeping_abort
383 class LEDFrame(Frame
):
384 """This holds the different lights."""
386 retval
= Canvas(self
, background
= 'black', height
= 20, bd
= 0,
387 highlightthickness
= 0, width
= 10)
388 retval
.pack(side
= LEFT
, padx
= 0, pady
= 0, ipadx
= 0, ipady
= 0)
391 class LEDThreadFrame
:
392 """There is one of these for each little light."""
393 def __init__(self
, master
):
394 self
.canvas
= master
.getnewobj()
396 self
.ovalid
= self
.canvas
.create_oval(4, 4, 9,
400 def setcolor(self
, newcolor
):
401 if newcolor
!= self
.color
:
402 self
.canvas
.itemconfigure(self
.ovalid
, fill
= newcolor
)
403 self
.color
= newcolor
408 def setthread(self
, newthread
):
410 self
.setcolor('gray')
412 self
.setcolor('black')
415 class Blinkenlights(BlinkenBase
, VerboseUI
):
416 def __init__(s
, config
, verbose
= 0):
417 VerboseUI
.__init
__(s
, config
, verbose
)
418 s
.fontfamily
= 'Helvetica'
420 if config
.has_option('ui.Tk.Blinkenlights', 'fontfamily'):
421 s
.fontfamily
= config
.get('ui.Tk.Blinkenlights', 'fontfamily')
422 if config
.has_option('ui.Tk.Blinkenlights', 'fontsize'):
423 s
.fontsize
= config
.getint('ui.Tk.Blinkenlights', 'fontsize')
426 return VerboseUI
.isusable(s
)
428 def _createTopWindow(self
):
429 VerboseUI
._createTopWindow
(self
, 0)
430 #self.top.resizable(width = 0, height = 0)
431 self
.top
.configure(background
= 'black', bd
= 0)
433 widthmetric
= tkFont
.Font(family
= self
.fontfamily
, size
= self
.fontsize
).measure("0")
434 self
.loglines
= self
.config
.getdefaultint("ui.Tk.Blinkenlights",
436 self
.bufferlines
= self
.config
.getdefaultint("ui.Tk.Blinkenlights",
438 self
.text
= ScrolledText(self
.top
, bg
= 'black', #scrollbar = 'y',
439 font
= (self
.fontfamily
, self
.fontsize
),
440 bd
= 0, highlightthickness
= 0, setgrid
= 0,
441 state
= DISABLED
, height
= self
.loglines
,
442 wrap
= NONE
, width
= 60)
443 self
.text
.vbar
.configure(background
= '#000050',
444 activebackground
= 'blue',
445 highlightbackground
= 'black',
446 troughcolor
= "black", bd
= 0,
447 elementborderwidth
= 2)
451 self
.textlock
= Lock()
454 BlinkenBase
.init_banner(s
)
456 menubar
= Menu(s
.top
, activebackground
= "black",
457 activeforeground
= "white",
458 activeborderwidth
= 0,
459 background
= "black", foreground
= "blue",
460 font
= (s
.fontfamily
, s
.fontsize
), bd
= 0)
461 menubar
.add_command(label
= "About", command
= s
.showlicense
)
462 menubar
.add_command(label
= "Show Log", command
= s
._togglelog
)
463 menubar
.add_command(label
= "Exit", command
= s
.terminate
)
464 s
.top
.config(menu
= menubar
)
467 if s
.config
.getdefaultboolean("ui.Tk.Blinkenlights", "showlog", 1):
469 s
.gettf().setcolor('red')
470 s
.top
.resizable(width
= 0, height
= 0)
471 s
._msg
(version
.banner
)
475 s
.oldtextheight
= s
.text
.winfo_height()
478 s
.menubar
.entryconfig('Hide Log', label
= 'Show Log')
482 s
.top
.resizable(width
= 0, height
= 0)
486 s
.text
.pack(side
= TOP
, expand
= 1, fill
= BOTH
)
490 s
.menubar
.entryconfig('Show Log', label
= 'Hide Log')
492 s
.top
.resizable(width
= 1, height
= 1)
494 def sleep(s
, sleepsecs
):
495 s
.gettf().setcolor('red')
496 s
._msg
("Next sync in %d:%02d" % (sleepsecs
/ 60, sleepsecs
% 60))
497 BlinkenBase
.sleep(s
, sleepsecs
)
499 def sleeping(s
, sleepsecs
, remainingsecs
):
500 return BlinkenBase
.sleeping(s
, sleepsecs
, remainingsecs
)
504 lo
, hi
= s
.text
.vbar
.get()
505 s
.text
.vbar
.set(1.0 - (hi
- lo
), 1.0)
507 def _display(s
, msg
):
509 for thisline
in msg
.split("\n"):
512 #VerboseUI._msg(s, msg)
513 color
= s
.gettf().getcolor()
517 if s
.text
.vbar
.get()[1] != 1.0:
519 s
.text
.config(state
= NORMAL
)
520 if not color
in s
.tags
:
521 s
.text
.tag_config(color
, foreground
= color
)
523 s
.text
.insert(END
, "\n" + msg
, color
)
525 # Trim down. Not quite sure why I have to say 7 instead of 5,
527 while float(s
.text
.index(END
)) > s
.bufferlines
+ 2.0:
528 s
.text
.delete(1.0, 2.0)
533 s
.text
.config(state
= DISABLED
)