From 1e90e0fd78f2b05e4b857c902fd881e3d2d0eea5 Mon Sep 17 00:00:00 2001 From: John Goerzen Date: Wed, 4 Jul 2007 17:57:09 +0100 Subject: [PATCH 1/1] Remove the Tk interfaces These were a constant source of trouble. Tkinter likely has multiple memory leaks that OfflineIMAP was tickling. I never used these, so poof, goodbye. --- offlineimap.conf | 28 +- offlineimap.sgml | 48 +--- offlineimap/ui/Tk.py | 543 ------------------------------------- offlineimap/ui/__init__.py | 7 - offlineimap/ui/detector.py | 3 +- 5 files changed, 10 insertions(+), 619 deletions(-) delete mode 100644 offlineimap/ui/Tk.py diff --git a/offlineimap.conf b/offlineimap.conf index 06f883b..01169eb 100644 --- a/offlineimap.conf +++ b/offlineimap.conf @@ -51,8 +51,6 @@ maxsyncaccounts = 1 # fails, the second, and so forth. # # The pre-defined options are: -# Tk.Blinkenlights -- A graphical interface, shows LEDs and a single log -# Tk.VerboseUI -- A graphical interface, shows logs per thread # Curses.Blinkenlights -- A text-based (terminal) interface similar to # Tk.Blinkenlights # TTY.TTYUI -- a text-based (terminal) interface @@ -62,7 +60,7 @@ maxsyncaccounts = 1 # # You can override this with a command-line option -u. -ui = Tk.Blinkenlights, Tk.VerboseUI, Curses.Blinkenlights, TTY.TTYUI, +ui = Curses.Blinkenlights, TTY.TTYUI, Noninteractive.Basic, Noninteractive.Quiet # If you try to synchronize messages to a read-only folder, @@ -116,30 +114,6 @@ footer = "\n" # Note that this filter can be used only to further restrict mbnames # to a subset of folders that pass the account's folderfilter. -################################################## -# Blinkenlights configuration -################################################## - -[ui.Tk.Blinkenlights] - -# Specifies the default number of lines in the log. - -loglines = 5 - -# Specifies how many lines are in the scrollback log buffer. - -bufferlines = 500 - -# If true, says that the log should be enabled by default. -# Otherwise, you have to click "Show Log" to enable the log. - -showlog = false - -# Sets the font information. - -fontfamily = Helvetica -fontsize = 8 - ################################################## # Accounts ################################################## diff --git a/offlineimap.sgml b/offlineimap.sgml index 0bd0ce3..f0481f7 100644 --- a/offlineimap.sgml +++ b/offlineimap.sgml @@ -214,8 +214,7 @@ remoteuser = jgoerzen taken care of for you. If you do not have Python already, check with your system administrator or operating system vendor; or, download it from the Python website. - If you intend to use the Tk interface, you must have Tkinter - (python-tk) installed. If you intend to use the SSL interface, your + If you intend to use the SSL interface, your Python must have been built with SSL support. @@ -412,27 +411,22 @@ cd offlineimap-x.y.z option can override the configuration file setting. The available values for the configuration file or command-line are described in this section. + - Tk.Blinkenlights - Tk.Blinkenlights is an interface designed to be sleek, fun to watch, and + Curses.Blinkenlights + + Curses.Blinkenlights is an interface designed to be sleek, fun to watch, and informative of the overall picture of what &OfflineIMAP; is doing. I consider it to be the best general-purpose interface in &OfflineIMAP;. - Tk.Blinkenlights contains, by default, a small window with a row of - LEDs, a small log, and a row of command buttons. - The total size of the window is - very small, so it uses little desktop space, yet it is quite - functional. The optional, toggleable, log shows more + Curses.Blinkenlights contains a row of + "LEDs" with command buttons and a log. + The log shows more detail about what is happening and is color-coded to match the color of the lights. - - Tk.Blinkenlights is the only user interface that has configurable - parameters; see the example offlineimap.conf - for more details. - Each light in the Blinkenlights interface represents a thread of execution -- that is, a particular task that &OfflineIMAP; @@ -538,32 +532,6 @@ cd offlineimap-x.y.z - - Curses.Blinkenlights - - Curses.Blinkenlights is an interface very similar to Tk.Blinkenlights, - but is designed to be run in a console window (an xterm, Linux virtual - terminal, etc.) Since it doesn't have access to graphics, it isn't - quite as pretty, but it still gets the job done. - - Please see the Tk.Blinkenlights section above for more - information about the colors used in this interface. - - - - - Tk.VerboseUI - - Tk.VerboseUI (formerly known as Tk.TkUI) is a graphical interface - that presents a variable-sized window. In the window, each - currently-executing thread has a section where its name and current - status are displayed. This interface is best suited to people running - on slower connections, as you get a lot of detail, but for fast - connections, the detail may go by too quickly to be useful. People - with fast connections may wish to use Tk.Blinkenlights instead. - - - TTY.TTYUI diff --git a/offlineimap/ui/Tk.py b/offlineimap/ui/Tk.py deleted file mode 100644 index 2dc586e..0000000 --- a/offlineimap/ui/Tk.py +++ /dev/null @@ -1,543 +0,0 @@ -# Tk UI -# Copyright (C) 2002, 2003 John Goerzen -# -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -from __future__ import nested_scopes - -from Tkinter import * -import tkFont -from threading import * -import thread, traceback, time, threading -from StringIO import StringIO -from ScrolledText import ScrolledText -from offlineimap import threadutil, version -from Queue import Queue -from UIBase import UIBase -from offlineimap.ui.Blinkenlights import BlinkenBase - -usabletest = None - -class PasswordDialog: - def __init__(self, accountname, config, master=None, errmsg = None): - self.top = Toplevel(master) - self.top.title(version.productname + " Password Entry") - text = '' - if errmsg: - text = '%s: %s\n' % (accountname, errmsg) - text += "%s: Enter password: " % accountname - self.label = Label(self.top, text = text) - self.label.pack() - - self.entry = Entry(self.top, show='*') - self.entry.bind("", self.ok) - self.entry.pack() - self.entry.focus_force() - - self.button = Button(self.top, text = "OK", command=self.ok) - self.button.pack() - - self.entry.focus_force() - self.top.wait_window(self.label) - - def ok(self, args = None): - self.password = self.entry.get() - self.top.destroy() - - def getpassword(self): - return self.password - -class TextOKDialog: - def __init__(self, title, message, blocking = 1, master = None): - if not master: - self.top = Tk() - else: - self.top = Toplevel(master) - self.top.title(title) - self.text = ScrolledText(self.top, font = "Courier 10") - self.text.pack() - self.text.insert(END, message) - self.text['state'] = DISABLED - self.button = Button(self.top, text = "OK", command=self.ok) - self.button.pack() - - if blocking: - self.top.wait_window(self.button) - - def ok(self): - self.top.destroy() - - - -class ThreadFrame(Frame): - def __init__(self, master=None): - self.threadextraframe = None - self.thread = currentThread() - self.threadid = thread.get_ident() - Frame.__init__(self, master, relief = RIDGE, borderwidth = 2) - self.pack(fill = 'x') - self.threadlabel = Label(self, foreground = '#FF0000', - text ="Thread %d (%s)" % (self.threadid, - self.thread.getName())) - self.threadlabel.pack() - self.setthread(currentThread()) - - self.account = "Unknown" - self.mailbox = "Unknown" - self.loclabel = Label(self, - text = "Account/mailbox information unknown") - #self.loclabel.pack() - - self.updateloclabel() - - self.message = Label(self, text="Messages will appear here.\n", - foreground = '#0000FF') - self.message.pack(fill = 'x') - - def setthread(self, newthread): - if newthread: - self.threadlabel['text'] = newthread.getName() - else: - self.threadlabel['text'] = "No thread" - self.destroythreadextraframe() - - def destroythreadextraframe(self): - if self.threadextraframe: - self.threadextraframe.destroy() - self.threadextraframe = None - - def getthreadextraframe(self): - if self.threadextraframe: - return self.threadextraframe - self.threadextraframe = Frame(self) - self.threadextraframe.pack(fill = 'x') - return self.threadextraframe - - def setaccount(self, account): - self.account = account - self.mailbox = "Unknown" - self.updateloclabel() - - def setmailbox(self, mailbox): - self.mailbox = mailbox - self.updateloclabel() - - def updateloclabel(self): - self.loclabel['text'] = "Processing %s: %s" % (self.account, - self.mailbox) - - def appendmessage(self, newtext): - self.message['text'] += "\n" + newtext - - def setmessage(self, newtext): - self.message['text'] = newtext - - -class VerboseUI(UIBase): - def isusable(s): - global usabletest - if usabletest != None: - return usabletest - - try: - Tk().destroy() - usabletest = 1 - except TclError: - usabletest = 0 - return usabletest - - def _createTopWindow(self, doidlevac = 1): - self.notdeleted = 1 - self.created = threading.Event() - - self.af = {} - self.aflock = Lock() - - t = threadutil.ExitNotifyThread(target = self._runmainloop, - name = "Tk Mainloop") - t.setDaemon(1) - t.start() - - self.created.wait() - del self.created - - if doidlevac: - t = threadutil.ExitNotifyThread(target = self.idlevacuum, - name = "Tk idle vacuum") - t.setDaemon(1) - t.start() - - def _runmainloop(s): - s.top = Tk() - s.top.title(version.productname + " " + version.versionstr) - s.top.after_idle(s.created.set) - s.top.mainloop() - s.notdeleted = 0 - - def getaccountframe(s): - accountname = s.getthreadaccount() - s.aflock.acquire() - try: - if accountname in s.af: - return s.af[accountname] - - s.af[accountname] = LEDAccountFrame(s.top, accountname, - s.fontfamily, s.fontsize) - finally: - s.aflock.release() - return s.af[accountname] - - def getpass(s, accountname, config, errmsg = None): - pd = PasswordDialog(accountname, config, errmsg = errmsg) - return pd.getpassword() - - def gettf(s, newtype=ThreadFrame, master = None): - if master == None: - master = s.top - threadid = thread.get_ident() - s.tflock.acquire() - try: - if threadid in s.threadframes: - return s.threadframes[threadid] - if len(s.availablethreadframes): - tf = s.availablethreadframes.pop(0) - tf.setthread(currentThread()) - else: - tf = newtype(master) - s.threadframes[threadid] = tf - return tf - finally: - s.tflock.release() - - def _display(s, msg): - s.gettf().setmessage(msg) - - def threadExited(s, thread): - threadid = thread.threadid - s.tflock.acquire() - if threadid in s.threadframes: - tf = s.threadframes[threadid] - tf.setthread(None) - tf.setaccount("Unknown") - tf.setmessage("Idle") - s.availablethreadframes.append(tf) - del s.threadframes[threadid] - s.tflock.release() - UIBase.threadExited(s, thread) - - def idlevacuum(s): - while s.notdeleted: - time.sleep(10) - s.tflock.acquire() - while len(s.availablethreadframes): - tf = s.availablethreadframes.pop() - tf.destroy() - s.tflock.release() - - def terminate(s, exitstatus = 0, errortitle = None, errormsg = None): - if errormsg <> None: - if errortitle == None: - errortitle = "Error" - TextOKDialog(errortitle, errormsg) - UIBase.terminate(s, exitstatus = exitstatus, errortitle = None, errormsg = None) - - def threadException(s, thread): - exceptionstr = s.getThreadExceptionString(thread) - print exceptionstr - - s.top.destroy() - s.top = None - TextOKDialog("Thread Exception", exceptionstr) - s.delThreadDebugLog(thread) - s.terminate(100) - - def mainException(s): - exceptionstr = s.getMainExceptionString() - print exceptionstr - - s.top.destroy() - s.top = None - TextOKDialog("Main Program Exception", exceptionstr) - - def warn(s, msg, minor = 0): - if minor: - # Just let the default handler catch it - UIBase.warn(s, msg, minor) - else: - TextOKDialog("OfflineIMAP Warning", msg) - - def showlicense(s): - TextOKDialog(version.productname + " License", - version.bigcopyright + "\n" + - version.homepage + "\n\n" + version.license, - blocking = 0, master = s.top) - - - def init_banner(s): - s.threadframes = {} - s.availablethreadframes = [] - s.tflock = Lock() - s._createTopWindow() - s._msg(version.productname + " " + version.versionstr + ", " +\ - version.copyright) - tf = s.gettf().getthreadextraframe() - - b = Button(tf, text = "About", command = s.showlicense) - b.pack(side = LEFT) - - b = Button(tf, text = "Exit", command = s.terminate) - b.pack(side = RIGHT) - s.sleeping_abort = {} - - def deletingmessages(s, uidlist, destlist): - ds = s.folderlist(destlist) - s._msg("Deleting %d messages in %s" % (len(uidlist), ds)) - - def _sleep_cancel(s, args = None): - s.sleeping_abort[thread.get_ident()] = 1 - - def sleep(s, sleepsecs): - threadid = thread.get_ident() - s.sleeping_abort[threadid] = 0 - tf = s.gettf().getthreadextraframe() - - def sleep_cancel(): - s.sleeping_abort[threadid] = 1 - - sleepbut = Button(tf, text = 'Sync immediately', - command = sleep_cancel) - sleepbut.pack() - UIBase.sleep(s, sleepsecs) - - def sleeping(s, sleepsecs, remainingsecs): - retval = s.sleeping_abort[thread.get_ident()] - if remainingsecs: - s._msg("Next sync in %d:%02d" % (remainingsecs / 60, - remainingsecs % 60)) - else: - s._msg("Wait done; synchronizing now.") - s.gettf().destroythreadextraframe() - del s.sleeping_abort[thread.get_ident()] - time.sleep(sleepsecs) - return retval - -TkUI = VerboseUI - -################################################## Blinkenlights - -class LEDAccountFrame: - def __init__(self, top, accountname, fontfamily, fontsize): - self.top = top - self.accountname = accountname - self.fontfamily = fontfamily - self.fontsize = fontsize - self.frame = Frame(self.top, background = 'black') - self.frame.pack(side = BOTTOM, expand = 1, fill = X) - self._createcanvas(self.frame) - - self.label = Label(self.frame, text = accountname, - background = "black", foreground = "blue", - font = (self.fontfamily, self.fontsize)) - self.label.grid(sticky = E, row = 0, column = 1) - - def getnewthreadframe(s): - return LEDThreadFrame(s.canvas) - - def _createcanvas(self, parent): - c = LEDFrame(parent) - self.canvas = c - c.grid(sticky = E, row = 0, column = 0) - parent.grid_columnconfigure(1, weight = 1) - #c.pack(side = LEFT, expand = 0, fill = X) - - def startsleep(s, sleepsecs): - s.sleeping_abort = 0 - s.button = Button(s.frame, text = "Sync now", command = s.syncnow, - background = "black", activebackground = "black", - activeforeground = "white", - foreground = "blue", highlightthickness = 0, - padx = 0, pady = 0, - font = (s.fontfamily, s.fontsize), borderwidth = 0, - relief = 'solid') - s.button.grid(sticky = E, row = 0, column = 2) - - def syncnow(s): - s.sleeping_abort = 1 - - def sleeping(s, sleepsecs, remainingsecs): - if remainingsecs: - s.button.config(text = 'Sync now (%d:%02d remain)' % \ - (remainingsecs / 60, remainingsecs % 60)) - time.sleep(sleepsecs) - else: - s.button.destroy() - del s.button - return s.sleeping_abort - -class LEDFrame(Frame): - """This holds the different lights.""" - def getnewobj(self): - retval = Canvas(self, background = 'black', height = 20, bd = 0, - highlightthickness = 0, width = 10) - retval.pack(side = LEFT, padx = 0, pady = 0, ipadx = 0, ipady = 0) - return retval - -class LEDThreadFrame: - """There is one of these for each little light.""" - def __init__(self, master): - self.canvas = master.getnewobj() - self.color = '' - self.ovalid = self.canvas.create_oval(4, 4, 9, - 9, fill = 'gray', - outline = '#303030') - - def setcolor(self, newcolor): - if newcolor != self.color: - self.canvas.itemconfigure(self.ovalid, fill = newcolor) - self.color = newcolor - - def getcolor(self): - return self.color - - def setthread(self, newthread): - if newthread: - self.setcolor('gray') - else: - self.setcolor('black') - - -class Blinkenlights(BlinkenBase, VerboseUI): - def __init__(s, config, verbose = 0): - VerboseUI.__init__(s, config, verbose) - s.fontfamily = 'Helvetica' - s.fontsize = 8 - if config.has_option('ui.Tk.Blinkenlights', 'fontfamily'): - s.fontfamily = config.get('ui.Tk.Blinkenlights', 'fontfamily') - if config.has_option('ui.Tk.Blinkenlights', 'fontsize'): - s.fontsize = config.getint('ui.Tk.Blinkenlights', 'fontsize') - - def isusable(s): - return VerboseUI.isusable(s) - - def _createTopWindow(self): - VerboseUI._createTopWindow(self, 0) - #self.top.resizable(width = 0, height = 0) - self.top.configure(background = 'black', bd = 0) - - widthmetric = tkFont.Font(family = self.fontfamily, size = self.fontsize).measure("0") - self.loglines = self.config.getdefaultint("ui.Tk.Blinkenlights", - "loglines", 5) - self.bufferlines = self.config.getdefaultint("ui.Tk.Blinkenlights", - "bufferlines", 500) - self.text = ScrolledText(self.top, bg = 'black', #scrollbar = 'y', - font = (self.fontfamily, self.fontsize), - bd = 0, highlightthickness = 0, setgrid = 0, - state = DISABLED, height = self.loglines, - wrap = NONE, width = 60) - self.text.vbar.configure(background = '#000050', - activebackground = 'blue', - highlightbackground = 'black', - troughcolor = "black", bd = 0, - elementborderwidth = 2) - - self.textenabled = 0 - self.tags = [] - self.textlock = Lock() - - def init_banner(s): - BlinkenBase.init_banner(s) - s._createTopWindow() - menubar = Menu(s.top, activebackground = "black", - activeforeground = "white", - activeborderwidth = 0, - background = "black", foreground = "blue", - font = (s.fontfamily, s.fontsize), bd = 0) - menubar.add_command(label = "About", command = s.showlicense) - menubar.add_command(label = "Show Log", command = s._togglelog) - menubar.add_command(label = "Exit", command = s.terminate) - s.top.config(menu = menubar) - s.menubar = menubar - s.text.see(END) - if s.config.getdefaultboolean("ui.Tk.Blinkenlights", "showlog", 1): - s._togglelog() - s.gettf().setcolor('red') - s.top.resizable(width = 0, height = 0) - s._msg(version.banner) - - def _togglelog(s): - if s.textenabled: - s.oldtextheight = s.text.winfo_height() - s.text.pack_forget() - s.textenabled = 0 - s.menubar.entryconfig('Hide Log', label = 'Show Log') - s.top.update() - s.top.geometry("") - s.top.update() - s.top.resizable(width = 0, height = 0) - s.top.update() - - else: - s.text.pack(side = TOP, expand = 1, fill = BOTH) - s.textenabled = 1 - s.top.update() - s.top.geometry("") - s.menubar.entryconfig('Show Log', label = 'Hide Log') - s._rescroll() - s.top.resizable(width = 1, height = 1) - - def sleep(s, sleepsecs): - s.gettf().setcolor('red') - s._msg("Next sync in %d:%02d" % (sleepsecs / 60, sleepsecs % 60)) - BlinkenBase.sleep(s, sleepsecs) - - def sleeping(s, sleepsecs, remainingsecs): - return BlinkenBase.sleeping(s, sleepsecs, remainingsecs) - - def _rescroll(s): - s.text.see(END) - lo, hi = s.text.vbar.get() - s.text.vbar.set(1.0 - (hi - lo), 1.0) - - def _display(s, msg): - if "\n" in msg: - for thisline in msg.split("\n"): - s._msg(thisline) - return - #VerboseUI._msg(s, msg) - color = s.gettf().getcolor() - rescroll = 1 - s.textlock.acquire() - try: - if s.text.vbar.get()[1] != 1.0: - rescroll = 0 - s.text.config(state = NORMAL) - if not color in s.tags: - s.text.tag_config(color, foreground = color) - s.tags.append(color) - s.text.insert(END, "\n" + msg, color) - - # Trim down. Not quite sure why I have to say 7 instead of 5, - # but so it is. - while float(s.text.index(END)) > s.bufferlines + 2.0: - s.text.delete(1.0, 2.0) - - if rescroll: - s._rescroll() - finally: - s.text.config(state = DISABLED) - s.textlock.release() - - diff --git a/offlineimap/ui/__init__.py b/offlineimap/ui/__init__.py index c1bdb2e..d351093 100644 --- a/offlineimap/ui/__init__.py +++ b/offlineimap/ui/__init__.py @@ -23,13 +23,6 @@ try: except ImportError: pass -try: - import Tkinter -except ImportError: - pass -else: - import Tk - try: import curses except ImportError: diff --git a/offlineimap/ui/detector.py b/offlineimap/ui/detector.py index a320d1e..48d3bfe 100644 --- a/offlineimap/ui/detector.py +++ b/offlineimap/ui/detector.py @@ -19,8 +19,7 @@ import offlineimap.ui import sys -DEFAULT_UI_LIST = ('Tk.Blinkenlights', 'Tk.VerboseUI', - 'Curses.Blinkenlights', 'TTY.TTYUI', +DEFAULT_UI_LIST = ('Curses.Blinkenlights', 'TTY.TTYUI', 'Noninteractive.Basic', 'Noninteractive.Quiet') def findUI(config, chosenUI=None): -- 2.39.2