Revision: 4362
Author: russblau
Date: 2007-09-25 14:08:05 +0000 (Tue, 25 Sep 2007)
Log Message:
-----------
Refactored for maintainability; separated UI object from
Tkinter objects in mainloop
Modified Paths:
--------------
trunk/pywikipedia/userinterfaces/tkinter_interface.py
Modified: trunk/pywikipedia/userinterfaces/tkinter_interface.py
===================================================================
--- trunk/pywikipedia/userinterfaces/tkinter_interface.py 2007-09-25 13:54:29 UTC (rev 4361)
+++ trunk/pywikipedia/userinterfaces/tkinter_interface.py 2007-09-25 14:08:05 UTC (rev 4362)
@@ -1,9 +1,10 @@
__version__ = '$Id$'
+import re
+import sys
+import threading
import time
-import re, sys, threading
import tkMessageBox, tkSimpleDialog
-import config
from Tkinter import *
color_pattern = re.compile(r"%s\{(?P<colorname>\w+)\}" % "\x03")
@@ -11,9 +12,8 @@
# we run the Tkinter mainloop in a separate thread so as not to block
# the main bot code; however, this means that all communication with
# the Tkinter interface has to be done through events that will be processed
-# by the mainloop in the separate thread. It is not possible for the
-# interface code to call any of the Tkinter objects directly, except to
-# put events on their queue (e.g., .after_idle()).
+# by the mainloop in the separate thread. Code outside this module must not
+# call any of the Tkinter objects directly.
class MainloopThread(threading.Thread):
@@ -22,10 +22,7 @@
self.window = window
def run(self):
- try:
- self.window.mainloop()
- except SystemExit:
- return
+ self.window.mainloop()
class EditBoxWindow:
@@ -81,10 +78,10 @@
return self.text
def highlight(self, searchkey = None):
- '''
+ """
Action-function for the Button: highlight all occurences of string.
Taken from O'Reilly's Python in a Nutshell.
- '''
+ """
#remove previous uses of tag 'found', if any
self.editbox.tag_remove('found', '1.0', END)
# get string to look for (if empty, no searching)
@@ -141,7 +138,7 @@
b = Button(self, text=self.options[i],
underline=pos,
command=lambda h=self.hotkeys[i]: self.select(h))
- self.bind("<Control-%s" % self.hotkeys[i],
+ self.bind("<Control-%s>" % self.hotkeys[i],
lambda h=self.hotkeys[i]: self.select(h))
b.grid(row = 1, column = i)
btns.append(b)
@@ -174,7 +171,7 @@
('lightgreen', 'Green'),
('lightaqua', 'DarkSeaGreen'),
('lightred', 'Red'),
- ('lightpurple', 'Violet'),
+ ('lightpurple', 'DarkViolet'),
('lightyellow', 'DarkOrange')
):
self.tag_config(ucolor, foreground=tcolor)
@@ -193,16 +190,67 @@
self.yview(END)
-class UI:
+class TkController(Frame):
+ """
+ Tkinter user interface controller.
+
+ This object receives and processes events dispatched to it by the UI
+ object. Do not call this object's methods directly. Methods of this
+ object cannot return values to the main thread; they must store them
+ in attributes to be retrieved by the main thread.
+
+ """
+ # TODO: use Event for inter-thread communication instead of wait loops
+ def __init__(self, parent, **kwargs):
+ Frame.__init__(self, parent, **kwargs)
+
+ def showinfo(self, text, wait=True):
+ """
+ Show a pop-up message.
+
+ Set wait to False to allow the pop-up to remain on screen while the
+ bot continues to work.
+
+ """
+ box = tkMessageBox.showinfo("Bot Message", text)
+ box.display()
+ if wait:
+ self.wait_window(box.top)
+
+ def ask(self, question, password=False):
+ """
+ Show a question in a dialog window and store the user's answer.
+ """
+ self.answer = tkSimpleDialog.askstring('Question', question)
+
+ def ask_choice(self, question, options, hotkeys, default):
+ d = CustomMessageBox(self, question, options, hotkeys)
+ self.wait_window(d.top)
+ self.selection = d.selection or d.default
+
+ def edit_text(self, text, jumpIndex, highlight):
+ editBoxWindow = EditBoxWindow(text)
+ editBoxWindow.highlight(highlight)
+ self.wait_window(editBoxWindow.top)
+ self.edited_text = editBoxWindow.text
+
+
+class UI(object):
+ """
+ Tkinter user interface.
+
+ This object serves only to dispatch event calls to the TkController
+ object, to be run in that object's separate mainloop thread; and,
+ when necessary, to wait for the user's response.
+
+ """
def __init__(self, parent = None):
# create a new window if necessary
- self.parent = parent or Tk()
+ self.control = TkController(parent or Tk())
- self.top_frame = Frame(parent)
- scrollbar = Scrollbar(self.top_frame)
-
# textarea with vertical scrollbar
- self.logBox = OutputBox(self.top_frame, yscrollcommand=scrollbar.set)
+ scrollbar = Scrollbar(self.control)
+ self.logBox = OutputBox(self.control, yscrollcommand=scrollbar.set)
# add scrollbar to main frame, associate it with our editbox
scrollbar.pack(side=RIGHT, fill=Y)
@@ -210,11 +258,11 @@
# put textarea into top frame, using all available space
self.logBox.pack(anchor=CENTER, fill=BOTH)
- self.top_frame.pack(side=TOP)
+ self.control.pack(side=TOP)
- MainloopThread(self.parent).start()
+ MainloopThread(self.control).start()
- def output(self, text, urgency = 1, toStdout = False):
+ def output(self, text, urgency=1, toStdout=False, wait=True):
"""
urgency levels: (NOT IMPLEMENTED)
0 - Debug output. Won't be shown in normal mode.
@@ -222,39 +270,43 @@
2 - Will be shown in error box.
TODO: introduce constants
+
+ wait: block bot until user dismisses pop-up window (level 2 only)
"""
if urgency >= 2:
- box = CustomMessageBox(self.parent)
- self.parent.after_idle(box.display, text, ['OK'], ['O'], 'O')
- self.parent.wait_window(box.top)
+ self.control.after_idle(self.control.showinfo, text, wait)
elif urgency >= 1:
- self.parent.after_idle(self.logBox.show, text)
+ self.control.after_idle(self.logBox.show, text)
def input(self, question, password = False):
"""
Returns a unicode string.
"""
# TODO: hide input if password = True
- self.parent.after_idle(self.ask, question)
+ self.control.after_idle(self.control.ask, question)
# wait until the answer has been given
- while not hasattr(self, "answer"):
+ while not hasattr(self.control, "answer"):
time.sleep(1)
- answer = self.answer
- del self.answer
+ # answer needs to be deleted so that it won't be reused the
+ # next time this method is called
+ answer = self.control.answer
+ del self.control.answer
return answer
- def ask(self, question, password=False):
- # this method is called from the mainloopThread
- self.answer = tkSimpleDialog.askstring('Question', question)
-
def editText(self, text, jumpIndex = None, highlight = None):
- editBoxWindow = EditBoxWindow(text)
- editBoxWindow.highlight(highlight)
- self.parent.wait_window(editBoxWindow.top)
- return editBoxWindow.text
+ self.control.after_idle(self.control.edit_text,
+ text, jumpIndex, highlight)
+ while not hasattr(self.control, "edited_text"):
+ time.sleep(1)
+ result = self.control.edited_text
+ del self.control.edited_text
+ return result
def inputChoice(self, question, options, hotkeys, default = None):
- d = CustomMessageBox(self.parent, question, options, hotkeys)
- self.parent.wait_window(d.top)
- answer = d.ask()
- return answer
+ self.control.after_idle(self.control.ask_choice, question,
+ options, hotkeys, default)
+ while not hasattr(self.control, "selection"):
+ time.sleep(1)
+ selection = self.control.selection
+ del self.control.selection
+ return selection