Revision: 4356
Author: russblau
Date: 2007-09-24 17:55:32 +0000 (Mon, 24 Sep 2007)
Log Message:
-----------
Still very primitive, but now it works.
Modified Paths:
--------------
trunk/pywikipedia/userinterfaces/tkinter_interface.py
Modified: trunk/pywikipedia/userinterfaces/tkinter_interface.py
===================================================================
--- trunk/pywikipedia/userinterfaces/tkinter_interface.py 2007-09-24 11:29:36 UTC (rev 4355)
+++ trunk/pywikipedia/userinterfaces/tkinter_interface.py 2007-09-24 17:55:32 UTC (rev 4356)
@@ -1,4 +1,3 @@
-
__version__ = '$Id$'
import time
@@ -6,13 +5,23 @@
import tkMessageBox, tkSimpleDialog
from Tkinter import *
+# 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()).
+
class MainloopThread(threading.Thread):
def __init__(self, window):
threading.Thread.__init__(self)
self.window = window
def run(self):
- self.window.mainloop()
+ try:
+ self.window.mainloop()
+ except SystemExit:
+ return
class EditBoxWindow:
@@ -105,54 +114,82 @@
self.text = unicode(self.text, 'ascii')
self.top.destroy()
-class CustomMessageBox(tkMessageBox.Message):
- def __init__(self, parent, question, options, hotkeys, default = None):
- self.selection = None
+
+class CustomMessageBox(tkSimpleDialog.Dialog):
+ def __init__(self, master, question, options, hotkeys, default=None):
+ self.question = question
+ self.options = options
self.hotkeys = hotkeys
self.default = default
-
- self.top = Toplevel(parent)
- Label(self.top, text=question).grid(columnspan = len(options))
- for i in range(len(options)):
+ tkSimpleDialog.Dialog.__init__(self, master)
+
+ def body(self, master):
+ Label(self, text=self.question).grid(columnspan = len(self.options))
+ btns = []
+ for i in xrange(len(self.options)):
# mark hotkey with underline
- m = re.search('[%s%s]' % (hotkeys[i].lower(), hotkeys[i].upper()), options[i])
+ m = re.search('[%s%s]' % (self.hotkeys[i].lower(),
+ self.hotkeys[i].upper()), self.options[i])
if m:
pos = m.start()
else:
- options[i] += ' (%s)' % hotkeys[i]
- pos = len(options[i]) - 2
- b = Button(self.top, text = options[i], underline = pos, command = lambda h=hotkeys[i]: self.select(h))
+ self.options[i] += ' (%s)' % self.hotkeys[i]
+ pos = len(self.options[i]) - 2
+ 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],
+ lambda h=self.hotkeys[i]: self.select(h))
b.grid(row = 1, column = i)
+ btns.append(b)
+ if self.default and self.default in self.hotkeys:
+ return btns[self.hotkeys.index(self.default)]
+ else:
+ return btns[0]
- def select(self, i):
+ def buttonbox(self):
+ return
+
+ def select(self, i, event=None):
self.selection = i
- self.top.destroy()
+ self.ok()
- def ask(self):
+ def apply(self):
if self.default and not self.selection:
- return self.default
- else:
- return self.selection
+ self.selection = self.default
+
+class OutputBox(Text):
+ def __init__(self, parent, *args, **kwargs):
+ Text.__init__(self, parent, *args, **kwargs)
+
+ def show(self, text):
+ self.insert(END, text)
+ self.yview(END)
+
+
class UI:
def __init__(self, parent = None):
# create a new window if necessary
self.parent = parent or Tk()
self.top_frame = Frame(parent)
+ scrollbar = Scrollbar(self.top_frame)
- scrollbar = Scrollbar(self.top_frame)
# textarea with vertical scrollbar
- self.logBox = Text(self.top_frame, yscrollcommand=scrollbar.set)
+ self.logBox = OutputBox(self.top_frame, yscrollcommand=scrollbar.set)
+
# add scrollbar to main frame, associate it with our editbox
scrollbar.pack(side=RIGHT, fill=Y)
scrollbar.config(command=self.logBox.yview)
+
# put textarea into top frame, using all available space
self.logBox.pack(anchor=CENTER, fill=BOTH)
self.top_frame.pack(side=TOP)
self.logBox.tag_config(12, foreground='red')
self.logBox.tag_config(10, foreground='green')
+# self.parent.mainloop()
MainloopThread(self.parent).start()
def output(self, text, urgency = 1, toStdout = False):
@@ -165,44 +202,29 @@
TODO: introduce constants
"""
if urgency >= 2:
- box = CustomMessageBox(self.parent, text, ['OK'], ['O'], 'O')
- box.ask()
+ box = CustomMessageBox(self.parent)
+ self.parent.after_idle(box.display, text, ['OK'], ['O'], 'O')
self.parent.wait_window(box.top)
- # this alternative uses a too large font
- # d = tkMessageBox.showinfo('title', text)
elif urgency >= 1:
- # Save the line number before appending text
- lineCount = float(self.logBox.index(END).split('.')[0]) - 1
- self.logBox.insert(END, text)
- # colors support currently broken, sorry.
- #if colors:
- ## How many characters we already added in this line
- #offset = 0
- ## We create a tag region for each colored character.
- ## It would be more efficient to try to use longer
- ## regions.
- #for i in range(len(colors)):
- #if text[i] == '\n':
- #lineCount += 1
- #offset = i + 1
- #if colors[i]:
- #startidx = '%i.%i' % (lineCount, i - offset)
- #endidx = '%i.%i' % (lineCount, i + 1 - offset)
- ## tag the whole occurence (start included, stop excluded)
- #self.logBox.tag_add(colors[i], startidx , endidx)
-
-
- # auto-scroll down
- self.logBox.see(END)
+ self.parent.after_idle(self.logBox.show, text)
def input(self, question, password = False):
"""
Returns a unicode string.
"""
# TODO: hide input if password = True
- answer = tkSimpleDialog.askstring('title', question)
+ self.parent.after_idle(self.ask, question)
+ # wait until the answer has been given
+ while not hasattr(self, "answer"):
+ time.sleep(1)
+ answer = self.answer
+ del self.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)
@@ -210,7 +232,6 @@
return editBoxWindow.text
def inputChoice(self, question, options, hotkeys, default = None):
-
d = CustomMessageBox(self.parent, question, options, hotkeys)
self.parent.wait_window(d.top)
answer = d.ask()