Merge with 3.4

This commit is contained in:
Terry Jan Reedy 2014-07-11 00:16:16 -04:00
commit 55740d73b3
3 changed files with 145 additions and 13 deletions

View File

@ -3,19 +3,21 @@ from tkinter import TclError
class WidgetRedirector:
"""Support for redirecting arbitrary widget subcommands.
Some Tk operations don't normally pass through Tkinter. For example, if a
Some Tk operations don't normally pass through tkinter. For example, if a
character is inserted into a Text widget by pressing a key, a default Tk
binding to the widget's 'insert' operation is activated, and the Tk library
processes the insert without calling back into Tkinter.
processes the insert without calling back into tkinter.
Although a binding to <Key> could be made via Tkinter, what we really want
to do is to hook the Tk 'insert' operation itself.
Although a binding to <Key> could be made via tkinter, what we really want
to do is to hook the Tk 'insert' operation itself. For one thing, we want
a text.insert call in idle code to have the same effect as a key press.
When a widget is instantiated, a Tcl command is created whose name is the
same as the pathname widget._w. This command is used to invoke the various
widget operations, e.g. insert (for a Text widget). We are going to hook
this command and provide a facility ('register') to intercept the widget
operation.
operation. We will also intercept method calls on the tkinter class
instance that represents the tk widget.
In IDLE, WidgetRedirector is used in Percolator to intercept Text
commands. The function being registered provides access to the top
@ -64,9 +66,13 @@ class WidgetRedirector:
def register(self, operation, function):
'''Return OriginalCommand(operation) after registering function.
Registration adds an instance function attribute that masks the
class instance method attribute. If a second function is
registered for the same operation, the first function is replaced.
Registration adds an operation: function pair to ._operations.
It also adds an widget function attribute that masks the tkinter
class instance method. Method masking operates independently
from command dispatch.
If a second function is registered for the same operation, the
first function is replaced in both places.
'''
self._operations[operation] = function
setattr(self.widget, operation, function)
@ -80,8 +86,10 @@ class WidgetRedirector:
if operation in self._operations:
function = self._operations[operation]
del self._operations[operation]
if hasattr(self.widget, operation):
try:
delattr(self.widget, operation)
except AttributeError:
pass
return function
else:
return None
@ -160,8 +168,7 @@ def _widget_redirector(parent): # htest #
if __name__ == "__main__":
import unittest
## unittest.main('idlelib.idle_test.test_widgetredir',
## verbosity=2, exit=False)
unittest.main('idlelib.idle_test.test_widgetredir',
verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(_widget_redirector)

View File

@ -26,7 +26,10 @@ class Func:
self.called = True
self.args = args
self.kwds = kwds
return self.result
if isinstance(self.result, BaseException):
raise self.result
else:
return self.result
class Editor:

View File

@ -0,0 +1,122 @@
"""Unittest for idlelib.WidgetRedirector
100% coverage
"""
from test.support import requires
import unittest
from idlelib.idle_test.mock_idle import Func
from tkinter import Tk, Text, TclError
from idlelib.WidgetRedirector import WidgetRedirector
class InitCloseTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.tk = Tk()
cls.text = Text(cls.tk)
@classmethod
def tearDownClass(cls):
cls.text.destroy()
cls.tk.destroy()
del cls.text, cls.tk
def test_init(self):
redir = WidgetRedirector(self.text)
self.assertEqual(redir.widget, self.text)
self.assertEqual(redir.tk, self.text.tk)
self.assertRaises(TclError, WidgetRedirector, self.text)
redir.close() # restore self.tk, self.text
def test_close(self):
redir = WidgetRedirector(self.text)
redir.register('insert', Func)
redir.close()
self.assertEqual(redir._operations, {})
self.assertFalse(hasattr(self.text, 'widget'))
class WidgetRedirectorTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.tk = Tk()
cls.text = Text(cls.tk)
@classmethod
def tearDownClass(cls):
cls.text.destroy()
cls.tk.destroy()
del cls.text, cls.tk
def setUp(self):
self.redir = WidgetRedirector(self.text)
self.func = Func()
self.orig_insert = self.redir.register('insert', self.func)
self.text.insert('insert', 'asdf') # leaves self.text empty
def tearDown(self):
self.text.delete('1.0', 'end')
self.redir.close()
def test_repr(self): # partly for 100% coverage
self.assertIn('Redirector', repr(self.redir))
self.assertIn('Original', repr(self.orig_insert))
def test_register(self):
self.assertEqual(self.text.get('1.0', 'end'), '\n')
self.assertEqual(self.func.args, ('insert', 'asdf'))
self.assertIn('insert', self.redir._operations)
self.assertIn('insert', self.text.__dict__)
self.assertEqual(self.text.insert, self.func)
def test_original_command(self):
self.assertEqual(self.orig_insert.operation, 'insert')
self.assertEqual(self.orig_insert.tk_call, self.text.tk.call)
self.orig_insert('insert', 'asdf')
self.assertEqual(self.text.get('1.0', 'end'), 'asdf\n')
def test_unregister(self):
self.assertIsNone(self.redir.unregister('invalid operation name'))
self.assertEqual(self.redir.unregister('insert'), self.func)
self.assertNotIn('insert', self.redir._operations)
self.assertNotIn('insert', self.text.__dict__)
def test_unregister_no_attribute(self):
del self.text.insert
self.assertEqual(self.redir.unregister('insert'), self.func)
def test_dispatch_intercept(self):
self.func.__init__(True)
self.assertTrue(self.redir.dispatch('insert', False))
self.assertFalse(self.func.args[0])
def test_dispatch_bypass(self):
self.orig_insert('insert', 'asdf')
# tk.call returns '' where Python would return None
self.assertEqual(self.redir.dispatch('delete', '1.0', 'end'), '')
self.assertEqual(self.text.get('1.0', 'end'), '\n')
def test_dispatch_error(self):
self.func.__init__(TclError())
self.assertEqual(self.redir.dispatch('insert', False), '')
self.assertEqual(self.redir.dispatch('invalid'), '')
def test_command_dispatch(self):
# Test that .__init__ causes redirection of tk calls
# through redir.dispatch
self.tk.call(self.text._w, 'insert', 'hello')
self.assertEqual(self.func.args, ('hello',))
self.assertEqual(self.text.get('1.0', 'end'), '\n')
# Ensure that called through redir .dispatch and not through
# self.text.insert by having mock raise TclError.
self.func.__init__(TclError())
self.assertEqual(self.tk.call(self.text._w, 'insert', 'boo'), '')
if __name__ == '__main__':
unittest.main(verbosity=2)