]>
code.delx.au - offlineimap/blob - offlineimap/ui/Tk.py
2dc586eb9e0fc09e4d4a14fd7e9dca4fd7bd480e
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 terminate(s
, exitstatus
= 0, errortitle
= None, errormsg
= None):
252 if errortitle
== None:
254 TextOKDialog(errortitle
, errormsg
)
255 UIBase
.terminate(s
, exitstatus
= exitstatus
, errortitle
= None, errormsg
= None)
257 def threadException(s
, thread
):
258 exceptionstr
= s
.getThreadExceptionString(thread
)
263 TextOKDialog("Thread Exception", exceptionstr
)
264 s
.delThreadDebugLog(thread
)
267 def mainException(s
):
268 exceptionstr
= s
.getMainExceptionString()
273 TextOKDialog("Main Program Exception", exceptionstr
)
275 def warn(s
, msg
, minor
= 0):
277 # Just let the default handler catch it
278 UIBase
.warn(s
, msg
, minor
)
280 TextOKDialog("OfflineIMAP Warning", msg
)
283 TextOKDialog(version
.productname
+ " License",
284 version
.bigcopyright
+ "\n" +
285 version
.homepage
+ "\n\n" + version
.license
,
286 blocking
= 0, master
= s
.top
)
291 s
.availablethreadframes
= []
294 s
._msg
(version
.productname
+ " " + version
.versionstr
+ ", " +\
296 tf
= s
.gettf().getthreadextraframe()
298 b
= Button(tf
, text
= "About", command
= s
.showlicense
)
301 b
= Button(tf
, text
= "Exit", command
= s
.terminate
)
303 s
.sleeping_abort
= {}
305 def deletingmessages(s
, uidlist
, destlist
):
306 ds
= s
.folderlist(destlist
)
307 s
._msg
("Deleting %d messages in %s" % (len(uidlist
), ds
))
309 def _sleep_cancel(s
, args
= None):
310 s
.sleeping_abort
[thread
.get_ident()] = 1
312 def sleep(s
, sleepsecs
):
313 threadid
= thread
.get_ident()
314 s
.sleeping_abort
[threadid
] = 0
315 tf
= s
.gettf().getthreadextraframe()
318 s
.sleeping_abort
[threadid
] = 1
320 sleepbut
= Button(tf
, text
= 'Sync immediately',
321 command
= sleep_cancel
)
323 UIBase
.sleep(s
, sleepsecs
)
325 def sleeping(s
, sleepsecs
, remainingsecs
):
326 retval
= s
.sleeping_abort
[thread
.get_ident()]
328 s
._msg
("Next sync in %d:%02d" % (remainingsecs
/ 60,
331 s
._msg
("Wait done; synchronizing now.")
332 s
.gettf().destroythreadextraframe()
333 del s
.sleeping_abort
[thread
.get_ident()]
334 time
.sleep(sleepsecs
)
339 ################################################## Blinkenlights
341 class LEDAccountFrame
:
342 def __init__(self
, top
, accountname
, fontfamily
, fontsize
):
344 self
.accountname
= accountname
345 self
.fontfamily
= fontfamily
346 self
.fontsize
= fontsize
347 self
.frame
= Frame(self
.top
, background
= 'black')
348 self
.frame
.pack(side
= BOTTOM
, expand
= 1, fill
= X
)
349 self
._createcanvas
(self
.frame
)
351 self
.label
= Label(self
.frame
, text
= accountname
,
352 background
= "black", foreground
= "blue",
353 font
= (self
.fontfamily
, self
.fontsize
))
354 self
.label
.grid(sticky
= E
, row
= 0, column
= 1)
356 def getnewthreadframe(s
):
357 return LEDThreadFrame(s
.canvas
)
359 def _createcanvas(self
, parent
):
362 c
.grid(sticky
= E
, row
= 0, column
= 0)
363 parent
.grid_columnconfigure(1, weight
= 1)
364 #c.pack(side = LEFT, expand = 0, fill = X)
366 def startsleep(s
, sleepsecs
):
368 s
.button
= Button(s
.frame
, text
= "Sync now", command
= s
.syncnow
,
369 background
= "black", activebackground
= "black",
370 activeforeground
= "white",
371 foreground
= "blue", highlightthickness
= 0,
373 font
= (s
.fontfamily
, s
.fontsize
), borderwidth
= 0,
375 s
.button
.grid(sticky
= E
, row
= 0, column
= 2)
380 def sleeping(s
, sleepsecs
, remainingsecs
):
382 s
.button
.config(text
= 'Sync now (%d:%02d remain)' % \
383 (remainingsecs
/ 60, remainingsecs
% 60))
384 time
.sleep(sleepsecs
)
388 return s
.sleeping_abort
390 class LEDFrame(Frame
):
391 """This holds the different lights."""
393 retval
= Canvas(self
, background
= 'black', height
= 20, bd
= 0,
394 highlightthickness
= 0, width
= 10)
395 retval
.pack(side
= LEFT
, padx
= 0, pady
= 0, ipadx
= 0, ipady
= 0)
398 class LEDThreadFrame
:
399 """There is one of these for each little light."""
400 def __init__(self
, master
):
401 self
.canvas
= master
.getnewobj()
403 self
.ovalid
= self
.canvas
.create_oval(4, 4, 9,
407 def setcolor(self
, newcolor
):
408 if newcolor
!= self
.color
:
409 self
.canvas
.itemconfigure(self
.ovalid
, fill
= newcolor
)
410 self
.color
= newcolor
415 def setthread(self
, newthread
):
417 self
.setcolor('gray')
419 self
.setcolor('black')
422 class Blinkenlights(BlinkenBase
, VerboseUI
):
423 def __init__(s
, config
, verbose
= 0):
424 VerboseUI
.__init
__(s
, config
, verbose
)
425 s
.fontfamily
= 'Helvetica'
427 if config
.has_option('ui.Tk.Blinkenlights', 'fontfamily'):
428 s
.fontfamily
= config
.get('ui.Tk.Blinkenlights', 'fontfamily')
429 if config
.has_option('ui.Tk.Blinkenlights', 'fontsize'):
430 s
.fontsize
= config
.getint('ui.Tk.Blinkenlights', 'fontsize')
433 return VerboseUI
.isusable(s
)
435 def _createTopWindow(self
):
436 VerboseUI
._createTopWindow
(self
, 0)
437 #self.top.resizable(width = 0, height = 0)
438 self
.top
.configure(background
= 'black', bd
= 0)
440 widthmetric
= tkFont
.Font(family
= self
.fontfamily
, size
= self
.fontsize
).measure("0")
441 self
.loglines
= self
.config
.getdefaultint("ui.Tk.Blinkenlights",
443 self
.bufferlines
= self
.config
.getdefaultint("ui.Tk.Blinkenlights",
445 self
.text
= ScrolledText(self
.top
, bg
= 'black', #scrollbar = 'y',
446 font
= (self
.fontfamily
, self
.fontsize
),
447 bd
= 0, highlightthickness
= 0, setgrid
= 0,
448 state
= DISABLED
, height
= self
.loglines
,
449 wrap
= NONE
, width
= 60)
450 self
.text
.vbar
.configure(background
= '#000050',
451 activebackground
= 'blue',
452 highlightbackground
= 'black',
453 troughcolor
= "black", bd
= 0,
454 elementborderwidth
= 2)
458 self
.textlock
= Lock()
461 BlinkenBase
.init_banner(s
)
463 menubar
= Menu(s
.top
, activebackground
= "black",
464 activeforeground
= "white",
465 activeborderwidth
= 0,
466 background
= "black", foreground
= "blue",
467 font
= (s
.fontfamily
, s
.fontsize
), bd
= 0)
468 menubar
.add_command(label
= "About", command
= s
.showlicense
)
469 menubar
.add_command(label
= "Show Log", command
= s
._togglelog
)
470 menubar
.add_command(label
= "Exit", command
= s
.terminate
)
471 s
.top
.config(menu
= menubar
)
474 if s
.config
.getdefaultboolean("ui.Tk.Blinkenlights", "showlog", 1):
476 s
.gettf().setcolor('red')
477 s
.top
.resizable(width
= 0, height
= 0)
478 s
._msg
(version
.banner
)
482 s
.oldtextheight
= s
.text
.winfo_height()
485 s
.menubar
.entryconfig('Hide Log', label
= 'Show Log')
489 s
.top
.resizable(width
= 0, height
= 0)
493 s
.text
.pack(side
= TOP
, expand
= 1, fill
= BOTH
)
497 s
.menubar
.entryconfig('Show Log', label
= 'Hide Log')
499 s
.top
.resizable(width
= 1, height
= 1)
501 def sleep(s
, sleepsecs
):
502 s
.gettf().setcolor('red')
503 s
._msg
("Next sync in %d:%02d" % (sleepsecs
/ 60, sleepsecs
% 60))
504 BlinkenBase
.sleep(s
, sleepsecs
)
506 def sleeping(s
, sleepsecs
, remainingsecs
):
507 return BlinkenBase
.sleeping(s
, sleepsecs
, remainingsecs
)
511 lo
, hi
= s
.text
.vbar
.get()
512 s
.text
.vbar
.set(1.0 - (hi
- lo
), 1.0)
514 def _display(s
, msg
):
516 for thisline
in msg
.split("\n"):
519 #VerboseUI._msg(s, msg)
520 color
= s
.gettf().getcolor()
524 if s
.text
.vbar
.get()[1] != 1.0:
526 s
.text
.config(state
= NORMAL
)
527 if not color
in s
.tags
:
528 s
.text
.tag_config(color
, foreground
= color
)
530 s
.text
.insert(END
, "\n" + msg
, color
)
532 # Trim down. Not quite sure why I have to say 7 instead of 5,
534 while float(s
.text
.index(END
)) > s
.bufferlines
+ 2.0:
535 s
.text
.delete(1.0, 2.0)
540 s
.text
.config(state
= DISABLED
)