]>
code.delx.au - offlineimap/blob - offlineimap/head/offlineimap/ui/Tk.py
2 # Copyright (C) 2002 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
21 from threading
import *
22 import thread
, traceback
, time
23 from StringIO
import StringIO
24 from ScrolledText
import ScrolledText
25 from offlineimap
import threadutil
, version
26 from Queue
import Queue
27 from UIBase
import UIBase
30 def __init__(self
, accountname
, config
, master
=None):
31 self
.top
= Toplevel(master
)
32 self
.top
.title(version
.productname
+ " Password Entry")
33 self
.label
= Label(self
.top
,
34 text
= "%s: Enter password for %s on %s: " % \
35 (accountname
, config
.get(accountname
, "remoteuser"),
36 config
.get(accountname
, "remotehost")))
39 self
.entry
= Entry(self
.top
, show
='*')
40 self
.entry
.bind("<Return>", self
.ok
)
42 self
.entry
.focus_force()
44 self
.button
= Button(self
.top
, text
= "OK", command
=self
.ok
)
47 self
.entry
.focus_force()
48 self
.top
.wait_window(self
.label
)
50 def ok(self
, args
= None):
51 self
.password
= self
.entry
.get()
54 def getpassword(self
):
58 def __init__(self
, title
, message
, blocking
= 1, master
= None):
62 self
.top
= Toplevel(master
)
64 self
.text
= ScrolledText(self
.top
, font
= "Courier 10")
66 self
.text
.insert(END
, message
)
67 self
.text
['state'] = DISABLED
68 self
.button
= Button(self
.top
, text
= "OK", command
=self
.ok
)
72 self
.top
.wait_window(self
.button
)
79 class ThreadFrame(Frame
):
80 def __init__(self
, master
=None):
81 self
.threadextraframe
= None
82 self
.thread
= currentThread()
83 self
.threadid
= thread
.get_ident()
84 Frame
.__init
__(self
, master
, relief
= RIDGE
, borderwidth
= 2)
86 self
.threadlabel
= Label(self
, foreground
= '#FF0000',
87 text
="Thread %d (%s)" % (self
.threadid
,
88 self
.thread
.getName()))
89 self
.threadlabel
.pack()
90 self
.setthread(currentThread())
92 self
.account
= "Unknown"
93 self
.mailbox
= "Unknown"
94 self
.loclabel
= Label(self
,
95 text
= "Account/mailbox information unknown")
100 self
.message
= Label(self
, text
="Messages will appear here.\n",
101 foreground
= '#0000FF')
102 self
.message
.pack(fill
= 'x')
104 def setthread(self
, newthread
):
106 self
.threadlabel
['text'] = newthread
.getName()
108 self
.threadlabel
['text'] = "No thread"
109 self
.destroythreadextraframe()
111 def destroythreadextraframe(self
):
112 if self
.threadextraframe
:
113 self
.threadextraframe
.destroy()
114 self
.threadextraframe
= None
116 def getthreadextraframe(self
):
117 if self
.threadextraframe
:
118 return self
.threadextraframe
119 self
.threadextraframe
= Frame(self
)
120 self
.threadextraframe
.pack(fill
= 'x')
121 return self
.threadextraframe
123 def setaccount(self
, account
):
124 self
.account
= account
125 self
.mailbox
= "Unknown"
126 self
.updateloclabel()
128 def setmailbox(self
, mailbox
):
129 self
.mailbox
= mailbox
130 self
.updateloclabel()
132 def updateloclabel(self
):
133 self
.loclabel
['text'] = "Processing %s: %s" % (self
.account
,
136 def appendmessage(self
, newtext
):
137 self
.message
['text'] += "\n" + newtext
139 def setmessage(self
, newtext
):
140 self
.message
['text'] = newtext
143 class VerboseUI(UIBase
):
151 def _createTopWindow(self
, doidlevac
= 1):
153 self
.top
.title(version
.productname
+ " " + version
.versionstr
)
154 self
.threadframes
= {}
155 self
.availablethreadframes
= []
159 t
= threadutil
.ExitNotifyThread(target
= self
._runmainloop
,
160 name
= "Tk Mainloop")
165 t
= threadutil
.ExitNotifyThread(target
= self
.idlevacuum
,
166 name
= "Tk idle vacuum")
174 def getpass(s
, accountname
, config
):
175 pd
= PasswordDialog(accountname
, config
)
176 return pd
.getpassword()
178 def gettf(s
, newtype
=ThreadFrame
, master
= None):
181 threadid
= thread
.get_ident()
184 if threadid
in s
.threadframes
:
185 return s
.threadframes
[threadid
]
186 if len(s
.availablethreadframes
):
187 tf
= s
.availablethreadframes
.pop(0)
188 tf
.setthread(currentThread())
191 s
.threadframes
[threadid
] = tf
197 s
.gettf().setmessage(msg
)
199 def threadExited(s
, thread
):
200 threadid
= thread
.threadid
202 if threadid
in s
.threadframes
:
203 tf
= s
.threadframes
[threadid
]
205 tf
.setaccount("Unknown")
206 tf
.setmessage("Idle")
207 s
.availablethreadframes
.append(tf
)
208 del s
.threadframes
[threadid
]
215 while len(s
.availablethreadframes
):
216 tf
= s
.availablethreadframes
.pop()
220 def threadException(s
, thread
):
221 msg
= "Thread '%s' terminated with exception:\n%s" % \
222 (thread
.getName(), thread
.getExitStackTrace())
227 TextOKDialog("Thread Exception", msg
)
230 def mainException(s
):
232 traceback
.print_exc(file = sbuf
)
233 msg
= "Main program terminated with exception:\n" + sbuf
.getvalue()
238 TextOKDialog("Main Program Exception", msg
)
241 TextOKDialog("OfflineIMAP Warning", msg
)
244 TextOKDialog(version
.productname
+ " License",
245 version
.bigcopyright
+ "\n" +
246 version
.homepage
+ "\n\n" + version
.license
,
247 blocking
= 0, master
= s
.top
)
252 s
._msg
(version
.productname
+ " " + version
.versionstr
+ ", " +\
254 tf
= s
.gettf().getthreadextraframe()
256 b
= Button(tf
, text
= "About", command
= s
.showlicense
)
259 b
= Button(tf
, text
= "Exit", command
= s
.terminate
)
262 def deletingmessages(s
, uidlist
, destlist
):
263 ds
= s
.folderlist(destlist
)
264 s
._msg
("Deleting %d messages in %s" % (len(uidlist
), ds
))
266 def _sleep_cancel(s
, args
= None):
269 def sleep(s
, sleepsecs
):
271 tf
= s
.gettf().getthreadextraframe()
273 sleepbut
= Button(tf
, text
= 'Sync immediately',
274 command
= s
._sleep
_cancel
)
276 UIBase
.sleep(s
, sleepsecs
)
278 def sleeping(s
, sleepsecs
, remainingsecs
):
280 s
._msg
("Next sync in %d:%02d" % (remainingsecs
/ 60,
283 s
._msg
("Wait done; synchronizing now.")
284 s
.gettf().destroythreadextraframe()
285 time
.sleep(sleepsecs
)
286 return s
.sleeping_abort
290 class LEDCanvas(Canvas
):
291 def createLEDLock(self
):
292 self
.ledlock
= Lock()
293 def acquireLEDLock(self
):
294 self
.ledlock
.acquire()
295 def releaseLEDLock(self
):
296 self
.ledlock
.release()
297 def setLEDCount(self
, arg
):
299 def getLEDCount(self
):
301 def incLEDCount(self
):
304 class LEDThreadFrame
:
305 def __init__(self
, master
):
309 self
.canvas
.acquireLEDLock()
310 startpos
= 5 + self
.canvas
.getLEDCount() * 10
311 self
.canvas
.incLEDCount()
313 self
.canvas
.releaseLEDLock()
314 self
.ovalid
= self
.canvas
.create_oval(startpos
, 5, startpos
+ 5,
318 def setcolor(self
, newcolor
):
319 if newcolor
!= self
.color
:
320 self
.canvas
.itemconfigure(self
.ovalid
, fill
= newcolor
)
321 self
.color
= newcolor
326 def setthread(self
, newthread
):
328 self
.setcolor('gray')
330 self
.setcolor('black')
332 def destroythreadextraframe(self
):
335 def getthreadextraframe(self
):
336 raise NotImplementedError
338 def setaccount(self
, account
):
340 def setmailbox(self
, mailbox
):
342 def updateloclabel(self
):
344 def appendmessage(self
, newtext
):
346 def setmessage(self
, newtext
):
350 class Blinkenlights(VerboseUI
):
351 def _createTopWindow(self
):
352 VerboseUI
._createTopWindow
(self
, 0)
353 #self.top.resizable(width = 0, height = 0)
354 self
.top
.configure(background
= 'black', bd
= 0)
355 c
= LEDCanvas(self
.top
, background
= 'black', height
= 20, bd
= 0,
356 highlightthickness
= 0)
360 c
.pack(side
= BOTTOM
, expand
= 0, fill
= X
)
361 widthmetric
= tkFont
.Font(family
= 'Helvetica', size
= 8).measure("0")
363 if self
.config
.has_option("ui.Tk.Blinkenlights", "loglines"):
364 self
.loglines
= self
.config
.getint("ui.Tk.Blinkenlights",
366 self
.bufferlines
= 500
367 if self
.config
.has_option("ui.Tk.Blinkenlights", "bufferlines"):
368 self
.bufferlines
= self
.config
.getint("ui.tk.Blinkenlights",
370 self
.text
= ScrolledText(self
.top
, bg
= 'black', #scrollbar = 'y',
371 font
= ("Helvetica", 8),
372 bd
= 0, highlightthickness
= 0, setgrid
= 0,
373 state
= DISABLED
, height
= self
.loglines
,
374 wrap
= NONE
, width
= 10)
375 self
.text
.vbar
.configure(background
= '#000050',
376 activebackground
= 'blue',
377 highlightbackground
= 'black',
378 troughcolor
= "black", bd
= 0,
379 elementborderwidth
= 2)
383 self
.textlock
= Lock()
385 def gettf(s
, newtype
=LEDThreadFrame
):
386 return VerboseUI
.gettf(s
, newtype
, s
.canvas
)
390 menubar
= Menu(s
.top
, activebackground
= "black",
391 activeforeground
= "white",
392 activeborderwidth
= 0,
393 background
= "black", foreground
= "blue",
394 font
= ("Helvetica", 8), bd
= 0)
395 menubar
.add_command(label
= "About", command
= s
.showlicense
)
396 menubar
.add_command(label
= "Show Log", command
= s
._togglelog
)
397 menubar
.add_command(label
= "Exit", command
= s
.terminate
)
398 s
.top
.config(menu
= menubar
)
400 s
.gettf().setcolor('red')
401 s
._msg
(version
.banner
)
403 s
.top
.resizable(width
= 0, height
= 0)
404 if s
.config
.has_option("ui.Tk.Blinkenlights", "showlog") and \
405 s
.config
.getboolean("ui.Tk.Blinkenlights", "showlog"):
410 s
.oldtextheight
= s
.text
.winfo_height()
413 s
.menubar
.entryconfig('Hide Log', label
= 'Show Log')
417 s
.top
.resizable(width
= 0, height
= 0)
421 s
.text
.pack(side
= BOTTOM
, expand
= 1, fill
= BOTH
)
425 s
.menubar
.entryconfig('Show Log', label
= 'Hide Log')
427 s
.top
.resizable(width
= 1, height
= 1)
430 def acct(s
, accountname
):
431 s
.gettf().setcolor('purple')
432 VerboseUI
.acct(s
, accountname
)
434 def connecting(s
, hostname
, port
):
435 s
.gettf().setcolor('gray')
436 VerboseUI
.connecting(s
, hostname
, port
)
438 def syncfolders(s
, srcrepos
, destrepos
):
439 s
.gettf().setcolor('blue')
440 VerboseUI
.syncfolders(s
, srcrepos
, destrepos
)
442 def syncingfolder(s
, srcrepos
, srcfolder
, destrepos
, destfolder
):
443 s
.gettf().setcolor('cyan')
444 VerboseUI
.syncingfolder(s
, srcrepos
, srcfolder
, destrepos
, destfolder
)
446 def loadmessagelist(s
, repos
, folder
):
447 s
.gettf().setcolor('green')
448 s
._msg
("Scanning folder [%s/%s]" % (s
.getnicename(repos
),
449 folder
.getvisiblename()))
451 def syncingmessages(s
, sr
, sf
, dr
, df
):
452 s
.gettf().setcolor('blue')
453 VerboseUI
.syncingmessages(s
, sr
, sf
, dr
, df
)
455 def copyingmessage(s
, uid
, src
, destlist
):
456 s
.gettf().setcolor('orange')
457 VerboseUI
.copyingmessage(s
, uid
, src
, destlist
)
459 def deletingmessages(s
, uidlist
, destlist
):
460 s
.gettf().setcolor('red')
461 VerboseUI
.deletingmessages(s
, uidlist
, destlist
)
463 def deletingmessage(s
, uid
, destlist
):
464 s
.gettf().setcolor('red')
465 VerboseUI
.deletingmessage(s
, uid
, destlist
)
467 def addingflags(s
, uid
, flags
, destlist
):
468 s
.gettf().setcolor('yellow')
469 VerboseUI
.addingflags(s
, uid
, flags
, destlist
)
471 def deletingflags(s
, uid
, flags
, destlist
):
472 s
.gettf().setcolor('pink')
473 VerboseUI
.deletingflags(s
, uid
, flags
, destlist
)
475 def threadExited(s
, thread
):
476 threadid
= thread
.threadid
479 if threadid
in s
.threadframes
:
480 tf
= s
.threadframes
[threadid
]
481 del s
.threadframes
[threadid
]
482 s
.availablethreadframes
.append(tf
)
487 def sleep(s
, sleepsecs
):
489 s
.menubar
.add_command(label
= "Sync now", command
= s
._sleep
_cancel
)
490 s
.gettf().setcolor('red')
491 s
._msg
("Next sync in %d:%02d" % (sleepsecs
/ 60, sleepsecs
% 60))
492 UIBase
.sleep(s
, sleepsecs
)
496 lo
, hi
= s
.text
.vbar
.get()
497 s
.text
.vbar
.set(1.0 - (hi
- lo
), 1.0)
501 for thisline
in msg
.split("\n"):
504 VerboseUI
._msg
(s
, msg
)
505 color
= s
.gettf().getcolor()
507 #print s.text.vbar.get()[1]
508 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
)
530 def sleeping(s
, sleepsecs
, remainingsecs
):
532 s
.menubar
.entryconfig('end', label
= "Sync now (%d:%02d remain)" % \
533 (remainingsecs
/ 60, remainingsecs
% 60))
535 s
.menubar
.delete('end')
536 s
.gettf().setcolor('black')
537 if s
.gettf().getcolor() == 'red':
538 s
.gettf().setcolor('black')
540 s
.gettf().setcolor('red')
541 time
.sleep(sleepsecs
)
542 return s
.sleeping_abort