The Python code for this GUI console is:
class ConsoleUIError(Exception):
def __init__(self, msg):
self.value = msg
def __repr__(self):
return ("ConsoleUIError(%r)" % self.value)
class ConsoleUI(object):
class TkUI(object):
def __init__(self, kill_event, stop_update_event, msg_queue, status_queue, title=None):
self.kill_event = kill_event
self.stop_update_event = stop_update_event
self.msg_queue = msg_queue
self.status_queue = status_queue
import Tkinter as tk1
import ttk as ttk1
self.win = tk1.Toplevel()
self.status_msg = tk1.StringVar()
self.status_msg.set('')
self.win.title(title if title else "execsql console")
console_frame = ttk1.Frame(master=self.win, padding="2 2 2 2")
console_frame.grid(column=0, row=0, sticky=tk1.NSEW)
self.textarea = tk1.Text(console_frame, width=100, height=25, wrap='none')
# Status bar
statusframe = ttk1.Frame(master=self.win)
statusbar = ttk1.Label(statusframe, text='', textvariable=self.status_msg,
relief=tk1.RIDGE, anchor=tk1.W)
statusbar.pack(side=tk1.BOTTOM, fill=tk1.X)
statusframe.grid(column=0, row=1, sticky=tk1.EW)
# Scrollbars
vscroll = tk1.Scrollbar(console_frame, orient="vertical", command=self.textarea.yview)
hscroll = tk1.Scrollbar(console_frame, orient="horizontal", command=self.textarea.xview)
self.textarea.configure(yscrollcommand=vscroll.set)
self.textarea.configure(xscrollcommand=hscroll.set)
self.textarea.grid(column=0, row=0, sticky=tk1.NSEW)
vscroll.grid(column=1, row=0, sticky=tk1.NS)
hscroll.grid(column=0, row=2, sticky=tk1.EW)
# Allow resizing
self.win.columnconfigure(0, weight=1)
self.win.rowconfigure(0, weight=1)
console_frame.columnconfigure(0, weight=1)
console_frame.rowconfigure(0, weight=1)
# Kill on window close
self.win.protocol("WM_DELETE_WINDOW", self.kill)
# Display and center the window
self.win.update_idletasks()
m = re.match("(\d+)x(\d+)([-+]\d+)([-+]\d+)", self.win.geometry())
wwd = int(m.group(1))
wht = int(m.group(2))
swd = self.win.winfo_screenwidth()
sht = self.win.winfo_screenheight()
xpos = (swd/2) - (wwd/2)
ypos = (sht/2) - (wht/2)
self.win.geometry("%dx%d+%d+%d" % (wwd, wht, xpos, ypos))
self.win.grab_set()
self.win._root().withdraw()
self.update_id = self.win.after(200, self.update)
self.win.wait_window(self.win)
def kill(self):
if self.update_id:
self.win.after_cancel(self.update_id)
self.update_id = None
self.win.destroy()
self.win.update_idletasks()
def update(self):
self.update_id = None
while not self.msg_queue.empty():
msg = self.msg_queue.get(False)
self.textarea.insert('end', msg)
self.textarea.see('end')
self.msg_queue.task_done()
while not self.status_queue.empty():
msg = self.status_queue.get(False)
self.status_msg.set(msg)
if self.kill_event.is_set():
self.kill()
else:
if not self.stop_update_event.is_set():
self.update_id = self.win.after(200, self.update)
def __init__(self, title=None):
self.title = title
self.msg_queue = Queue.Queue()
self.status_queue = Queue.Queue()
self.kill_event = threading.Event()
self.stop_update_event = threading.Event()
self.consolethread = None
self.update_id = None
# Start the local event loop in a thread.
def openconsole():
self.active = True
self.ui = self.TkUI(self.kill_event, self.stop_update_event, self.msg_queue,
self.status_queue, self.title)
# Deallocate the Tk object here to avoid the "main thread is not in main loop" error.
self.ui = None
self.consolethread = threading.Thread(target=openconsole)
self.consolethread.start()
def write(self, msg):
self.active = self.consolethread and self.consolethread.is_alive()
if not self.active:
raise ConsoleUIError(msg)
self.msg_queue.put(msg)
def write_status(self, msg):
self.active = self.consolethread and self.consolethread.is_alive()
if not self.active:
raise ConsoleUIError(msg)
self.status_queue.put(msg)
def deactivate(self):
self.kill_event.set()
if self.consolethread and self.consolethread.is_alive():
self.consolethread.join()
self.active = False
def wait_for_user_quit(self):
self.stop_update_event.set()
if self.consolethread and self.consolethread.is_alive():
self.consolethread.join()
self.active = False
Because it runs in its own thread, this console can be used to display information produced by the main program or even several other separate processes.
The 'write()' method of the ConsoleUI object will write the given text at the end of the console display. A status message can also be written, separately from the stream of text that is written in the main part of the window. The console window can be closed directly from the program with the 'deactivate()' method, or the program can pause until the user closes the window by using the 'wait_for_user_quit()' method.
Other Tkinter widgets should not be activated in other threads while this GUI console window is open.
