]>
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
33 def __init__(self
, accountname
, config
, master
=None, errmsg
= None):
34 self
.top
= Toplevel(master
)
35 self
.top
.title(version
.productname
+ " Password Entry")
38 text
= '%s: %s\n' % (accountname
, errmsg
)
39 text
+= "%s: Enter password for %s on %s: " % \
40 (accountname
, config
.get(accountname
, "remoteuser"),
41 config
.get(accountname
, "remotehost"))
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
):
157 def _createTopWindow(self
, doidlevac
= 1):
159 self
.created
= threading
.Event()
164 t
= threadutil
.ExitNotifyThread(target
= self
._runmainloop
,
165 name
= "Tk Mainloop")
173 t
= threadutil
.ExitNotifyThread(target
= self
.idlevacuum
,
174 name
= "Tk idle vacuum")
180 s
.top
.title(version
.productname
+ " " + version
.versionstr
)
181 s
.top
.after_idle(s
.created
.set)
185 def getaccountframe(s
):
186 accountname
= s
.getthreadaccount()
189 if accountname
in s
.af
:
190 return s
.af
[accountname
]
192 s
.af
[accountname
] = LEDAccountFrame(s
.top
, accountname
,
193 s
.fontfamily
, s
.fontsize
)
196 return s
.af
[accountname
]
198 def getpass(s
, accountname
, config
, errmsg
= None):
199 pd
= PasswordDialog(accountname
, config
, errmsg
= errmsg
)
200 return pd
.getpassword()
202 def gettf(s
, newtype
=ThreadFrame
, master
= None):
205 threadid
= thread
.get_ident()
208 if threadid
in s
.threadframes
:
209 return s
.threadframes
[threadid
]
210 if len(s
.availablethreadframes
):
211 tf
= s
.availablethreadframes
.pop(0)
212 tf
.setthread(currentThread())
215 s
.threadframes
[threadid
] = tf
221 s
.gettf().setmessage(msg
)
223 def threadExited(s
, thread
):
224 threadid
= thread
.threadid
226 if threadid
in s
.threadframes
:
227 tf
= s
.threadframes
[threadid
]
229 tf
.setaccount("Unknown")
230 tf
.setmessage("Idle")
231 s
.availablethreadframes
.append(tf
)
232 del s
.threadframes
[threadid
]
234 UIBase
.threadExited(s
, thread
)
240 while len(s
.availablethreadframes
):
241 tf
= s
.availablethreadframes
.pop()
245 def threadException(s
, thread
):
246 exceptionstr
= s
.getThreadExceptionString(thread
)
251 TextOKDialog("Thread Exception", exceptionstr
)
252 s
.delThreadDebugLog(thread
)
255 def mainException(s
):
256 exceptionstr
= s
.getMainExceptionString()
261 TextOKDialog("Main Program Exception", exceptionstr
)
263 def warn(s
, msg
, minor
):
265 # Just let the default handler catch it
266 UIBase
.warn(s
, msg
, minor
)
268 TextOKDialog("OfflineIMAP Warning", msg
)
271 TextOKDialog(version
.productname
+ " License",
272 version
.bigcopyright
+ "\n" +
273 version
.homepage
+ "\n\n" + version
.license
,
274 blocking
= 0, master
= s
.top
)
279 s
._msg
(version
.productname
+ " " + version
.versionstr
+ ", " +\
281 tf
= s
.gettf().getthreadextraframe()
283 b
= Button(tf
, text
= "About", command
= s
.showlicense
)
286 b
= Button(tf
, text
= "Exit", command
= s
.terminate
)
288 s
.sleeping_abort
= {}
290 def deletingmessages(s
, uidlist
, destlist
):
291 ds
= s
.folderlist(destlist
)
292 s
._msg
("Deleting %d messages in %s" % (len(uidlist
), ds
))
294 def _sleep_cancel(s
, args
= None):
295 s
.sleeping_abort
[thread
.get_ident()] = 1
297 def sleep(s
, sleepsecs
):
298 threadid
= thread
.get_ident()
299 s
.sleeping_abort
[threadid
] = 0
300 tf
= s
.gettf().getthreadextraframe()
303 s
.sleeping_abort
[threadid
] = 1
305 sleepbut
= Button(tf
, text
= 'Sync immediately',
306 command
= sleep_cancel
)
308 UIBase
.sleep(s
, sleepsecs
)
310 def sleeping(s
, sleepsecs
, remainingsecs
):
311 retval
= s
.sleeping_abort
[thread
.get_ident()]
313 s
._msg
("Next sync in %d:%02d" % (remainingsecs
/ 60,
316 s
._msg
("Wait done; synchronizing now.")
317 s
.gettf().destroythreadextraframe()
318 del s
.sleeping_abort
[thread
.get_ident()]
319 time
.sleep(sleepsecs
)
324 ################################################## Blinkenlights
326 class LEDAccountFrame
:
327 def __init__(self
, top
, accountname
, fontfamily
, fontsize
):
329 self
.accountname
= accountname
330 self
.fontfamily
= fontfamily
331 self
.fontsize
= fontsize
332 self
.frame
= Frame(self
.top
, background
= 'black')
333 self
.frame
.pack(side
= BOTTOM
, expand
= 1, fill
= X
)
334 self
._createcanvas
(self
.frame
)
336 self
.label
= Label(self
.frame
, text
= accountname
,
337 background
= "black", foreground
= "blue",
338 font
= (self
.fontfamily
, self
.fontsize
))
339 self
.label
.grid(sticky
= E
, row
= 0, column
= 1)
341 def getnewthreadframe(s
):
342 return LEDThreadFrame(s
.canvas
)
344 def _createcanvas(self
, parent
):
347 c
.grid(sticky
= E
, row
= 0, column
= 0)
348 parent
.grid_columnconfigure(1, weight
= 1)
349 #c.pack(side = LEFT, expand = 0, fill = X)
351 def startsleep(s
, sleepsecs
):
353 s
.button
= Button(s
.frame
, text
= "Sync now", command
= s
.syncnow
,
354 background
= "black", activebackground
= "black",
355 activeforeground
= "white",
356 foreground
= "blue", highlightthickness
= 0,
358 font
= (s
.fontfamily
, s
.fontsize
), borderwidth
= 0,
360 s
.button
.grid(sticky
= E
, row
= 0, column
= 2)
365 def sleeping(s
, sleepsecs
, remainingsecs
):
367 s
.button
.config(text
= 'Sync now (%d:%02d remain)' % \
368 (remainingsecs
/ 60, remainingsecs
% 60))
369 time
.sleep(sleepsecs
)
373 return s
.sleeping_abort
375 class LEDFrame(Frame
):
376 """This holds the different lights."""
378 retval
= Canvas(self
, background
= 'black', height
= 20, bd
= 0,
379 highlightthickness
= 0, width
= 10)
380 retval
.pack(side
= LEFT
, padx
= 0, pady
= 0, ipadx
= 0, ipady
= 0)
383 class LEDThreadFrame
:
384 """There is one of these for each little light."""
385 def __init__(self
, master
):
386 self
.canvas
= master
.getnewobj()
388 self
.ovalid
= self
.canvas
.create_oval(5, 5, 10,
392 def setcolor(self
, newcolor
):
393 if newcolor
!= self
.color
:
394 self
.canvas
.itemconfigure(self
.ovalid
, fill
= newcolor
)
395 self
.color
= newcolor
400 def setthread(self
, newthread
):
402 self
.setcolor('gray')
404 self
.setcolor('black')
407 class Blinkenlights(BlinkenBase
, VerboseUI
):
408 def __init__(s
, config
, verbose
= 0):
409 VerboseUI
.__init
__(s
, config
, verbose
)
410 s
.fontfamily
= 'Helvetica'
412 if config
.has_option('ui.Tk.Blinkenlights', 'fontfamily'):
413 s
.fontfamily
= config
.get('ui.Tk.Blinkenlights', 'fontfamily')
414 if config
.has_option('ui.Tk.Blinkenlights', 'fontsize'):
415 s
.fontsize
= config
.getint('ui.Tk.Blinkenlights', 'fontsize')
417 def _createTopWindow(self
):
418 VerboseUI
._createTopWindow
(self
, 0)
419 #self.top.resizable(width = 0, height = 0)
420 self
.top
.configure(background
= 'black', bd
= 0)
422 widthmetric
= tkFont
.Font(family
= self
.fontfamily
, size
= self
.fontsize
).measure("0")
424 if self
.config
.has_option("ui.Tk.Blinkenlights", "loglines"):
425 self
.loglines
= self
.config
.getint("ui.Tk.Blinkenlights",
427 self
.bufferlines
= 500
428 if self
.config
.has_option("ui.Tk.Blinkenlights", "bufferlines"):
429 self
.bufferlines
= self
.config
.getint("ui.Tk.Blinkenlights",
431 self
.text
= ScrolledText(self
.top
, bg
= 'black', #scrollbar = 'y',
432 font
= (self
.fontfamily
, self
.fontsize
),
433 bd
= 0, highlightthickness
= 0, setgrid
= 0,
434 state
= DISABLED
, height
= self
.loglines
,
435 wrap
= NONE
, width
= 60)
436 self
.text
.vbar
.configure(background
= '#000050',
437 activebackground
= 'blue',
438 highlightbackground
= 'black',
439 troughcolor
= "black", bd
= 0,
440 elementborderwidth
= 2)
444 self
.textlock
= Lock()
447 BlinkenBase
.init_banner(s
)
449 menubar
= Menu(s
.top
, activebackground
= "black",
450 activeforeground
= "white",
451 activeborderwidth
= 0,
452 background
= "black", foreground
= "blue",
453 font
= (s
.fontfamily
, s
.fontsize
), bd
= 0)
454 menubar
.add_command(label
= "About", command
= s
.showlicense
)
455 menubar
.add_command(label
= "Show Log", command
= s
._togglelog
)
456 menubar
.add_command(label
= "Exit", command
= s
.terminate
)
457 s
.top
.config(menu
= menubar
)
460 if s
.config
.has_option("ui.Tk.Blinkenlights", "showlog") and \
461 s
.config
.getboolean("ui.Tk.Blinkenlights", "showlog"):
463 s
.gettf().setcolor('red')
464 s
.top
.resizable(width
= 0, height
= 0)
465 s
._msg
(version
.banner
)
469 s
.oldtextheight
= s
.text
.winfo_height()
472 s
.menubar
.entryconfig('Hide Log', label
= 'Show Log')
476 s
.top
.resizable(width
= 0, height
= 0)
480 s
.text
.pack(side
= TOP
, expand
= 1, fill
= BOTH
)
484 s
.menubar
.entryconfig('Show Log', label
= 'Hide Log')
486 s
.top
.resizable(width
= 1, height
= 1)
488 def sleep(s
, sleepsecs
):
489 s
.gettf().setcolor('red')
490 s
._msg
("Next sync in %d:%02d" % (sleepsecs
/ 60, sleepsecs
% 60))
491 BlinkenBase
.sleep(s
, sleepsecs
)
493 def sleeping(s
, sleepsecs
, remainingsecs
):
494 return BlinkenBase
.sleeping(s
, sleepsecs
, remainingsecs
)
498 lo
, hi
= s
.text
.vbar
.get()
499 s
.text
.vbar
.set(1.0 - (hi
- lo
), 1.0)
503 for thisline
in msg
.split("\n"):
506 #VerboseUI._msg(s, msg)
507 color
= s
.gettf().getcolor()
511 if s
.text
.vbar
.get()[1] != 1.0:
513 s
.text
.config(state
= NORMAL
)
514 if not color
in s
.tags
:
515 s
.text
.tag_config(color
, foreground
= color
)
517 s
.text
.insert(END
, "\n" + msg
, color
)
519 # Trim down. Not quite sure why I have to say 7 instead of 5,
521 while float(s
.text
.index(END
)) > s
.bufferlines
+ 2.0:
522 s
.text
.delete(1.0, 2.0)
527 s
.text
.config(state
= DISABLED
)