jenkins-bot has submitted this change and it was merged.
Change subject: Bug 68645: ported gui.py to core
......................................................................
Bug 68645: ported gui.py to core
Change-Id: Ib9371bbb21e688f0679664b3723aad3514263a28
---
A pywikibot/userinterfaces/gui.py
1 file changed, 436 insertions(+), 0 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/userinterfaces/gui.py b/pywikibot/userinterfaces/gui.py
new file mode 100644
index 0000000..25f68d4
--- /dev/null
+++ b/pywikibot/userinterfaces/gui.py
@@ -0,0 +1,436 @@
+# -*- coding: utf-8 -*-
+"""
+A window with a unicode textfield where the user can edit.
+
+Useful for editing the contents of an article.
+"""
+
+#
+# (C) Rob W.W. Hooft, 2003
+# (C) Daniel Herding, 2004
+# Wikiwichtel
+# (C) pywikibot team, 2008-2014
+#
+# Distributed under the terms of the MIT license.
+#
+__version__ = '$Id$'
+#
+
+import Tkinter
+from ScrolledText import ScrolledText
+import tkSimpleDialog
+
+from idlelib import SearchDialog, ReplaceDialog, configDialog
+from idlelib.configHandler import idleConf
+from idlelib.MultiCall import MultiCallCreator
+
+
+class TextEditor(ScrolledText):
+ """A text widget with some editing enhancements.
+
+ 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', undo='True',
+ 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)
+
+ def add_bindings(self):
+ # due to IDLE dependencies, this can't be called from __init__
+ # add key and event bindings
+ 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.iteritems():
+ 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=None):
+ self.event_generate('<Meta-d>')
+ return "break"
+
+ def find_event(self, event=None):
+ 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=None):
+ SearchDialog.find_again(self)
+ return "break"
+
+ def find_selection_event(self, event=None):
+ SearchDialog.find_selection(self)
+ return "break"
+
+ def replace_event(self, event=None):
+ 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(Tkinter.SEL_FIRST, Tkinter.SEL_LAST) == s:
+ return self.find_selection_event(None)
+ else:
+ # user must have changed the selection
+ found = self.tag_nextrange('found', Tkinter.SEL_LAST)
+ except Tkinter.TclError:
+ # user must have unset the selection
+ found = self.tag_nextrange('found', Tkinter.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', Tkinter.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=Tkinter.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(Tkinter.SEL, '1.0', Tkinter.END)
+ self.tag_add(Tkinter.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(Tkinter.Frame):
+
+ def __init__(self, parent=None, **kwargs):
+ if parent is None:
+ # create a new window
+ parent = Tkinter.Tk()
+ self.parent = parent
+ Tkinter.Frame.__init__(self, parent)
+ self.editbox = MultiCallCreator(TextEditor)(self, **kwargs)
+ self.editbox.pack(side=Tkinter.TOP)
+ self.editbox.add_bindings()
+ self.bind("<<open-config-dialog>>", self.config_dialog)
+
+ bottom = Tkinter.Frame(parent)
+ # lower left subframe which will contain a textfield and a Search button
+ bottom_left_frame = Tkinter.Frame(bottom)
+ self.textfield = Tkinter.Entry(bottom_left_frame)
+ self.textfield.pack(side=Tkinter.LEFT, fill=Tkinter.X, expand=1)
+
+ buttonSearch = Tkinter.Button(bottom_left_frame, text='Find next',
+ command=self.find)
+ buttonSearch.pack(side=Tkinter.RIGHT)
+ bottom_left_frame.pack(side=Tkinter.LEFT, expand=1)
+
+ # lower right subframe which will contain OK and Cancel buttons
+ bottom_right_frame = Tkinter.Frame(bottom)
+
+ buttonOK = Tkinter.Button(bottom_right_frame, text='OK',
+ command=self.pressedOK)
+ buttonCancel = Tkinter.Button(bottom_right_frame, text='Cancel',
+ command=parent.destroy)
+ buttonOK.pack(side=Tkinter.LEFT, fill=Tkinter.X)
+ buttonCancel.pack(side=Tkinter.RIGHT, fill=Tkinter.X)
+ bottom_right_frame.pack(side=Tkinter.RIGHT, expand=1)
+
+ bottom.pack(side=Tkinter.TOP)
+
+ # create a toplevel menu
+ menubar = Tkinter.Menu(self.parent)
+
+ findmenu = Tkinter.Menu(menubar)
+ findmenu.add_command(label="Find",
+ command=self.editbox.find_event,
+ accelerator="Ctrl+F",
+ underline=0)
+ findmenu.add_command(label="Find again",
+ command=self.editbox.find_again_event,
+ accelerator="Ctrl+G",
+ underline=6)
+ findmenu.add_command(label="Find all",
+ command=self.find_all,
+ underline=5)
+ findmenu.add_command(label="Find selection",
+ command=self.editbox.find_selection_event,
+ accelerator="Ctrl+F3",
+ underline=5)
+ findmenu.add_command(label="Replace",
+ command=self.editbox.replace_event,
+ accelerator="Ctrl+H",
+ underline=0)
+ menubar.add_cascade(label="Find", menu=findmenu, underline=0)
+
+ editmenu = Tkinter.Menu(menubar)
+ editmenu.add_command(label="Cut",
+ command=self.editbox.cut,
+ accelerator="Ctrl+X",
+ underline=2)
+ editmenu.add_command(label="Copy",
+ command=self.editbox.copy,
+ accelerator="Ctrl+C",
+ underline=0)
+ editmenu.add_command(label="Paste",
+ command=self.editbox.paste,
+ accelerator="Ctrl+V",
+ underline=0)
+ editmenu.add_separator()
+ editmenu.add_command(label="Select all",
+ command=self.editbox.select_all,
+ accelerator="Ctrl+A",
+ underline=7)
+ editmenu.add_command(label="Clear selection",
+ command=self.editbox.remove_selection,
+ accelerator="Esc")
+ menubar.add_cascade(label="Edit", menu=editmenu, underline=0)
+
+ optmenu = Tkinter.Menu(menubar)
+ optmenu.add_command(label="Settings...",
+ command=self.config_dialog,
+ underline=0)
+ menubar.add_cascade(label="Options", menu=optmenu, underline=0)
+
+ # display the menu
+ self.parent.config(menu=menubar)
+ self.pack()
+
+ def edit(self, text, jumpIndex=None, highlight=None):
+ """
+ Parameters:
+ * text - a Unicode string
+ * jumpIndex - an integer: position at which to put the caret
+ * highlight - a substring; each occurence will be highlighted
+ """
+ self.text = None
+ # put given text into our textarea
+ self.editbox.insert(Tkinter.END, text)
+ # wait for user to push a button which will destroy (close) the window
+ # enable word wrap
+ self.editbox.tag_add('all', '1.0', Tkinter.END)
+ self.editbox.tag_config('all', wrap=Tkinter.WORD)
+ # start search if required
+ if highlight:
+ self.find_all(highlight)
+ if jumpIndex:
+ print jumpIndex
+ # lines are indexed starting at 1
+ line = text[:jumpIndex].count('\n') + 1
+ column = jumpIndex - (text[:jumpIndex].rfind('\n') + 1)
+ # don't know how to place the caret, but scrolling to the right line
+ # 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.parent.mainloop()
+ return self.text
+
+ def find_all(self, target):
+ self.textfield.insert(Tkinter.END, target)
+ self.editbox.find_all(target)
+
+ def find(self):
+ # get text to search for
+ s = self.textfield.get()
+ if s:
+ self.editbox.find_all(s)
+
+ def config_dialog(self, event=None):
+ configDialog.ConfigDialog(self, 'Settings')
+
+ 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', Tkinter.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.parent.destroy()
+
+ def __init__(self, parent=None):
+ if parent is None:
+ # create a new window
+ parent = Tkinter.Tk()
+ self.parent = parent
+
+ # selectable: only one item
+ self.listbox = Tkinter.Listbox(parent, selectmode=Tkinter.SINGLE)
+ # put list into main frame, using all available space
+ self.listbox.pack(anchor=Tkinter.CENTER, fill=Tkinter.BOTH)
+
+ # lower subframe which will contain one button
+ self.bottom_frame = Tkinter.Frame(parent)
+ self.bottom_frame.pack(side=Tkinter.BOTTOM)
+
+ buttonOK = Tkinter.Button(self.bottom_frame, text='OK',
command=self.pressedOK)
+ buttonOK.pack(side=Tkinter.LEFT, fill=Tkinter.X)
+ # idea: set title to cur_disambiguation
+
+ def list(self, list):
+ # put list of alternatives into listbox
+ self.list = list
+ # find required area
+ laenge = len(list)
+ maxbreite = 0
+ for i in range(laenge):
+ # cycle through all listitems to find maxlength
+ if len(list[i]) + len(str(i)) > maxbreite:
+ maxbreite = len(list[i]) + len(str(i))
+ # show list as formerly in DOS-window
+ self.listbox.insert(Tkinter.END, str(i) + ' - ' + list[i])
+ # set optimized height & width
+ self.listbox.config(height=laenge, width=maxbreite + 2)
+ # wait for user to push a button which will destroy (close) the window
+ return self.list
+
+
+if __name__ == "__main__":
+ import pywikibot
+ try:
+ root = Tkinter.Tk()
+ root.resizable(width=Tkinter.FALSE, height=Tkinter.FALSE)
+ root.title("pywikibot GUI")
+ page = pywikibot.Page(pywikibot.Site(), u'Main Page')
+ content = page.get()
+ myapp = EditBoxWindow(root)
+ myapp.bind("<Control-d>", myapp.debug)
+ v = myapp.edit(content, highlight=page.title())
+ finally:
+ pywikibot.stopme()
--
To view, visit
https://gerrit.wikimedia.org/r/151973
To unsubscribe, visit
https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ib9371bbb21e688f0679664b3723aad3514263a28
Gerrit-PatchSet: 4
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: jenkins-bot <>