Revision: 5412 Author: russblau Date: 2008-05-20 18:14:52 +0000 (Tue, 20 May 2008)
Log Message: ----------- Since we have our own text editor, we might as well give it some basic functionality. Remove duplicate code in tkinter_interface.py
Modified Paths: -------------- trunk/pywikipedia/gui.py trunk/pywikipedia/userinterfaces/tkinter_interface.py
Modified: trunk/pywikipedia/gui.py =================================================================== --- trunk/pywikipedia/gui.py 2008-05-20 13:27:38 UTC (rev 5411) +++ trunk/pywikipedia/gui.py 2008-05-20 18:14:52 UTC (rev 5412) @@ -1,5 +1,3 @@ -#coding: utf-8 - ''' A window with a unicode textfield where the user can e.g. edit the contents of an article @@ -9,64 +7,247 @@ # (C) Rob W.W. Hooft, 2003 # (C) Daniel Herding, 2004 # Wikiwichtel +# (C) the PyWikipediabot team, 2008 # # Distributed under the terms of the MIT license. # __version__='$Id$'
from Tkinter import * +from ScrolledText import ScrolledText +import tkSimpleDialog + +from idlelib.configHandler import idleConf +from idlelib import SearchDialog, ReplaceDialog + import wikipedia
-class EditBoxWindow:
- # called when user pushes the OK button. - # saves the buffer into a variable, and closes the window. - def pressedOK(self): - self.text = self.editbox.get('1.0', END) - # if the editbox contains ASCII characters only, editbox.get() will - # return string, otherwise unicode (very annoying). We only want - # it to return unicode, so we work around this. - if type(self.text) == type(''): - self.text = unicode(self.text, 'ascii') - self.myParent.destroy() +class TextEditor(ScrolledText): + """A text widget with some editing enhancements.
- def __init__(self, parent = None): + A lot of code here is copied or adapted from the idlelib/EditorWindow.py + file in the standard Python distribution. + + """ + def __init__(self, master=None, **kwargs): + # get default settings from user's IDLE configuration + currentTheme=idleConf.CurrentTheme() + textcf = dict(padx=5, wrap='word', + foreground=idleConf.GetHighlight(currentTheme, + 'normal', fgBg='fg'), + background=idleConf.GetHighlight(currentTheme, + 'normal', fgBg='bg'), + highlightcolor=idleConf.GetHighlight(currentTheme, + 'hilite', fgBg='fg'), + highlightbackground=idleConf.GetHighlight(currentTheme, + 'hilite', fgBg='bg'), + insertbackground=idleConf.GetHighlight(currentTheme, + 'cursor', fgBg='fg'), + width=idleConf.GetOption('main','EditorWindow','width'), + height=idleConf.GetOption('main','EditorWindow','height') + ) + fontWeight = 'normal' + if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'): + fontWeight='bold' + textcf['font']=(idleConf.GetOption('main', 'EditorWindow', 'font'), + idleConf.GetOption('main', 'EditorWindow', 'font-size'), + fontWeight) + # override defaults with any user-specified settings + textcf.update(kwargs) + ScrolledText.__init__(self, master, **textcf) + + self.bind("<<cut>>", self.cut) + self.bind("<<copy>>", self.copy) + self.bind("<<paste>>", self.paste) + self.bind("<<select-all>>", self.select_all) + self.bind("<<remove-selection>>", self.remove_selection) + self.bind("<<find>>", self.find_event) + self.bind("<<find-again>>", self.find_again_event) + self.bind("<<find-selection>>", self.find_selection_event) + self.bind("<<replace>>", self.replace_event) + self.bind("<<goto-line>>", self.goto_line_event) + self.bind("<<del-word-left>>", self.del_word_left) + self.bind("<<del-word-right>>", self.del_word_right) + + keydefs = {'<<copy>>': ['<Control-Key-c>', '<Control-Key-C>'], + '<<cut>>': ['<Control-Key-x>', '<Control-Key-X>'], + '<<del-word-left>>': ['<Control-Key-BackSpace>'], + '<<del-word-right>>': ['<Control-Key-Delete>'], + '<<end-of-file>>': ['<Control-Key-d>', '<Control-Key-D>'], + '<<find-again>>': ['<Control-Key-g>', '<Key-F3>'], + '<<find-selection>>': ['<Control-Key-F3>'], + '<<find>>': ['<Control-Key-f>', '<Control-Key-F>'], + '<<goto-line>>': ['<Alt-Key-g>', '<Meta-Key-g>'], + '<<paste>>': ['<Control-Key-v>', '<Control-Key-V>'], + '<<redo>>': ['<Control-Shift-Key-Z>'], + '<<remove-selection>>': ['<Key-Escape>'], + '<<replace>>': ['<Control-Key-h>', '<Control-Key-H>'], + '<<select-all>>': ['<Control-Key-a>'], + '<<undo>>': ['<Control-Key-z>', '<Control-Key-Z>'], + } + for event, keylist in keydefs.items(): + if keylist: + self.event_add(event, *keylist) + + def cut(self,event): + if self.tag_ranges("sel"): + self.event_generate("<<Cut>>") + return "break" + + def copy(self,event): + if self.tag_ranges("sel"): + self.event_generate("<<Copy>>") + return "break" + + def paste(self,event): + self.event_generate("<<Paste>>") + return "break" + + def select_all(self, event=None): + self.tag_add("sel", "1.0", "end-1c") + self.mark_set("insert", "1.0") + self.see("insert") + return "break" + + def remove_selection(self, event=None): + self.tag_remove("sel", "1.0", "end") + self.see("insert") + + def del_word_left(self, event): + self.event_generate('<Meta-Delete>') + return "break" + + def del_word_right(self, event): + self.event_generate('<Meta-d>') + return "break" + + def find_event(self, event): + if not self.tag_ranges("sel"): + found = self.tag_ranges("found") + if found: + self.tag_add("sel", found[0], found[1]) + else: + self.tag_add("sel", "1.0", "1.0+1c") + SearchDialog.find(self) + return "break" + + def find_again_event(self, event): + SearchDialog.find_again(self) + return "break" + + def find_selection_event(self, event): + SearchDialog.find_selection(self) + return "break" + + def replace_event(self, event): + ReplaceDialog.replace(self) + return "break" + + def find_all(self, s): + ''' + Highlight all occurrences of string s, and select the first one. If + the string has already been highlighted, jump to the next occurrence + after the current selection. (You cannot go backwards using the + button, but you can manually place the cursor anywhere in the + document to start searching from that point.) + + ''' + if hasattr(self, "_highlight") and self._highlight == s: + try: + if self.get(SEL_FIRST, SEL_LAST) == s: + return self.find_selection_event(None) + else: + # user must have changed the selection + found = self.tag_nextrange('found', SEL_LAST) + except TclError: + # user must have unset the selection + found = self.tag_nextrange('found', INSERT) + if not found: + # at last occurrence, scroll back to the top + found = self.tag_nextrange('found', 1.0) + if found: + self.do_highlight(found[0], found[1]) + else: + # find all occurrences of string s; + # adapted from O'Reilly's Python in a Nutshell + # remove previous uses of tag 'found', if any + self.tag_remove('found', '1.0', END) + if s: + self._highlight = s + # start from the beginning (and when we come to the end, stop) + idx = '1.0' + while True: + # find next occurence, exit loop if no more + idx = self.search(s, idx, nocase=1, stopindex=END) + if not idx: + break + # index right after the end of the occurence + lastidx = '%s+%dc' % (idx, len(s)) + # tag the whole occurence (start included, stop excluded) + self.tag_add('found', idx, lastidx) + # prepare to search for next occurence + idx = lastidx + # use a red foreground for all the tagged occurences + self.tag_config('found', foreground='red') + found = self.tag_nextrange('found', 1.0) + if found: + self.do_highlight(found[0], found[1]) + + def do_highlight(self, start, end): + """Select and show the text from index start to index end.""" + self.see(start) + self.tag_remove(SEL, '1.0', END) + self.tag_add(SEL, start, end) + self.focus_set() + + def goto_line_event(self, event): + lineno = tkSimpleDialog.askinteger("Goto", + "Go to line number:", parent=self) + if lineno is None: + return "break" + if lineno <= 0: + self.bell() + return "break" + self.mark_set("insert", "%d.0" % lineno) + self.see("insert") + + +class EditBoxWindow(Frame): + + def __init__(self, parent = None, **kwargs): if parent == None: # create a new window parent = Tk() - self.myParent = parent + self.parent = parent + Frame.__init__(self, parent) + self.editbox = TextEditor(self, **kwargs) + self.editbox.pack(side=TOP)
- self.top_frame = Frame(parent) - - scrollbar = Scrollbar(self.top_frame) - # textarea with vertical scrollbar - self.editbox = Text(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.editbox.yview) - - # put textarea into top frame, using all available space - self.editbox.pack(anchor=CENTER, fill=BOTH) - self.top_frame.pack(side=TOP) - + bottom = Frame(parent) # lower left subframe which will contain a textfield and a Search button - self.bottom_left_frame = Frame(parent) - self.textfield = Entry(self.bottom_left_frame) + bottom_left_frame = Frame(bottom) + self.textfield = Entry(bottom_left_frame) self.textfield.pack(side=LEFT, fill=X, expand=1)
- buttonSearch = Button(self.bottom_left_frame, text='Find', command=self.find) + buttonSearch = Button(bottom_left_frame, text='Find next', + command=self.find) buttonSearch.pack(side=RIGHT) - self.bottom_left_frame.pack(side=LEFT, expand=1) - + bottom_left_frame.pack(side=LEFT, expand=1) + # lower right subframe which will contain OK and Cancel buttons - self.bottom_right_frame = Frame(parent) + bottom_right_frame = Frame(bottom)
- buttonOK = Button(self.bottom_right_frame, text='OK', command=self.pressedOK) - buttonCancel = Button(self.bottom_right_frame, text='Cancel', command=parent.destroy) + buttonOK = Button(bottom_right_frame, text='OK', + command=self.pressedOK) + buttonCancel = Button(bottom_right_frame, text='Cancel', + command=parent.destroy) buttonOK.pack(side=LEFT, fill=X) buttonCancel.pack(side=RIGHT, fill=X) - self.bottom_right_frame.pack(side=RIGHT, expand=1) + bottom_right_frame.pack(side=RIGHT, expand=1)
+ bottom.pack(side=TOP) + # create a toplevel menu # menubar = Menu(root) # menubar.add_command(label="Hello!", command=self.hello) @@ -74,8 +255,8 @@
# display the menu # root.config(menu=menubar) + self.pack()
- def edit(self, text, jumpIndex = None, highlight = None): """ Parameters: @@ -93,7 +274,7 @@ # start search if required if highlight: self.textfield.insert(END, highlight) - self.find() + self.editbox.find_all(highlight) if jumpIndex: print jumpIndex line = text[:jumpIndex].count('\n') + 1 # lines are indexed starting at 1 @@ -102,55 +283,51 @@ # should already be helpful. self.editbox.see('%d.%d' % (line, column)) # wait for user to push a button which will destroy (close) the window - self.myParent.mainloop() + self.parent.mainloop() return self.text
def find(self): - ''' - Action-function for the Button: highlight all occurences of a 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) + # get text to search for s = self.textfield.get() if s: - # start from the beginning (and when we come to the end, stop) - idx = '1.0' - while True: - # find next occurence, exit loop if no more - idx =self.editbox.search(s, idx, nocase=1, stopindex=END) - if not idx: - break - # index right after the end of the occurence - lastidx = '%s+%dc' % (idx, len(s)) - # tag the whole occurence (start included, stop excluded) - self.editbox.tag_add('found', idx, lastidx) - # prepare to search for next occurence - idx = lastidx - # use a red foreground for all the tagged occurencs - self.editbox.tag_config('found', foreground='red') - + self.editbox.find_all(s)
+ def pressedOK(self): + # called when user pushes the OK button. + # saves the buffer into a variable, and closes the window. + self.text = self.editbox.get('1.0', END) + # if the editbox contains ASCII characters only, get() will + # return string, otherwise unicode (very annoying). We only want + # it to return unicode, so we work around this. + if isinstance(self.text, str): + self.text = unicode(self.text) + self.parent.destroy() + + def debug(self, event=None): + self.quit() + return "break" + + +#### the following class isn't used anywhere in the framework: #### class ListBoxWindow:
# called when user pushes the OK button. # closes the window. def pressedOK(self): #ok closes listbox - self.myParent.destroy() + self.parent.destroy()
def __init__(self, parent = None): if parent == None: # create a new window parent = Tk() - self.myParent = parent + self.parent = parent
#selectable: only one item self.listbox = Listbox(parent, selectmode=SINGLE) # put list into main frame, using all available space self.listbox.pack(anchor=CENTER, fill=BOTH) - + # lower subframe which will contain one button self.bottom_frame = Frame(parent) self.bottom_frame.pack(side=BOTTOM) @@ -178,9 +355,12 @@
if __name__=="__main__": - root = Tk() - page = wikipedia.Page(wikipedia.getSite(), u'Wiki') - content = page.get() - myapp = EditBoxWindow(root) - myapp.edit(content, highlight = page.title()) - + try: + root = Tk() + page = wikipedia.Page(wikipedia.getSite(), u'Wiki') + content = page.get() + myapp = EditBoxWindow(root) + myapp.bind("<Control-d>", myapp.debug) + v = myapp.edit(content, highlight = page.title()) + finally: + wikipedia.stopme()
Modified: trunk/pywikipedia/userinterfaces/tkinter_interface.py =================================================================== --- trunk/pywikipedia/userinterfaces/tkinter_interface.py 2008-05-20 13:27:38 UTC (rev 5411) +++ trunk/pywikipedia/userinterfaces/tkinter_interface.py 2008-05-20 18:14:52 UTC (rev 5412) @@ -6,6 +6,7 @@ import time import tkMessageBox, tkSimpleDialog from Tkinter import * +from gui import EditBoxWindow
color_pattern = re.compile(r"%s{(?P<colorname>\w+)}" % "\x03")
@@ -25,96 +26,6 @@ self.window.mainloop()
-class EditBoxWindow: - def __init__(self, text): - # create a new window if necessary - #self.parent = parent or Tk() - self.top = Toplevel() - self.top_frame = Frame(self.top) - - scrollbar = Scrollbar(self.top_frame) - # textarea with vertical scrollbar - self.editbox = Text(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.editbox.yview) - # put given text into our textarea - self.editbox.insert(END, text) - - # put textarea into top frame, using all available space - self.editbox.pack(anchor=CENTER, fill=BOTH) - self.top_frame.pack(side=TOP) - # enable word wrap - self.editbox.tag_add('all', '1.0', END) - self.editbox.tag_config('all', wrap=WORD) - - # lower left subframe which will contain a textfield and a Search button - self.bottom_left_frame = Frame(self.top) - self.textfield = Entry(self.bottom_left_frame) - self.textfield.pack(side=LEFT, fill=X, expand=1) - - buttonSearch = Button(self.bottom_left_frame, text='search', command=self.highlight) - buttonSearch.pack(side=RIGHT) - self.bottom_left_frame.pack(side=LEFT, expand=1) - - # lower right subframe which will contain OK and Cancel buttons - self.bottom_right_frame = Frame(self.top) - - buttonOK = Button(self.bottom_right_frame, text='OK', command=self.pressedOK) - buttonCancel = Button(self.bottom_right_frame, text='Cancel', command=self.top.destroy) - buttonOK.pack(side=LEFT, fill=X) - buttonCancel.pack(side=RIGHT, fill=X) - self.bottom_right_frame.pack(side=RIGHT, expand=1) - - # create a toplevel menu - # menubar = Menu(root) - # menubar.add_command(label="Hello!", command=self.hello) - # menubar.add_command(label="Quit!", command=self.hello) - - # display the menu - # root.config(menu=menubar) - - def edit(self): - 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) - s = searchkey or self.textfield.get() - if s: - # start from the beginning (and when we come to the end, stop) - idx = '1.0' - while True: - # highlight next occurence, exit loop if no more - idx =self.editbox.search(s, idx, nocase=1, stopindex=END) - if not idx: - break - # index right after the end of the occurence - lastidx = '%s+%dc' % (idx, len(s)) - # tag the whole occurence (start included, stop excluded) - self.editbox.tag_add('found', idx, lastidx) - # prepare to search for next occurence - idx = lastidx - # use a red foreground for all the tagged occurencs - self.editbox.tag_config('found', foreground='red') - - # called when user pushes the OK button. - # saves the buffer into a variable, and closes the window. - def pressedOK(self): - self.text = self.editbox.get('1.0', END) - # if the editbox contains ASCII characters only, editbox.get() will - # return string, otherwise unicode (very annoying). We only want - # it to return unicode, so we work around this. - if type(self.text) == type(''): - self.text = unicode(self.text, 'ascii') - self.top.destroy() - - class CustomMessageBox(tkSimpleDialog.Dialog): def __init__(self, master, question, options, hotkeys, default=None): self.question = question