mirror of
https://github.com/python/cpython.git
synced 2025-10-02 05:12:23 +00:00
bpo-6739: IDLE: Check for valid keybinding in config_keys (#2377)
Verify user-entered key sequences by trying to bind them with tk. Add tests for all 3 validation functions. Original patch by G Polo. Tests added by Cheryl Sabella.
This commit is contained in:
parent
af5392f5c6
commit
8c78aa70c8
3 changed files with 118 additions and 26 deletions
|
@ -3,11 +3,16 @@ Dialog for building Tkinter accelerator key bindings
|
||||||
"""
|
"""
|
||||||
from tkinter import *
|
from tkinter import *
|
||||||
from tkinter.ttk import Scrollbar
|
from tkinter.ttk import Scrollbar
|
||||||
import tkinter.messagebox as tkMessageBox
|
from tkinter import messagebox
|
||||||
import string
|
import string
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
class GetKeysDialog(Toplevel):
|
class GetKeysDialog(Toplevel):
|
||||||
|
|
||||||
|
# Dialog title for invalid key sequence
|
||||||
|
keyerror_title = 'Key Sequence Error'
|
||||||
|
|
||||||
def __init__(self, parent, title, action, currentKeySequences,
|
def __init__(self, parent, title, action, currentKeySequences,
|
||||||
_htest=False, _utest=False):
|
_htest=False, _utest=False):
|
||||||
"""
|
"""
|
||||||
|
@ -54,6 +59,10 @@ class GetKeysDialog(Toplevel):
|
||||||
self.deiconify() #geometry set, unhide
|
self.deiconify() #geometry set, unhide
|
||||||
self.wait_window()
|
self.wait_window()
|
||||||
|
|
||||||
|
def showerror(self, *args, **kwargs):
|
||||||
|
# Make testing easier. Replace in #30751.
|
||||||
|
messagebox.showerror(*args, **kwargs)
|
||||||
|
|
||||||
def CreateWidgets(self):
|
def CreateWidgets(self):
|
||||||
frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
|
frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
|
||||||
frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
|
frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
|
||||||
|
@ -219,53 +228,70 @@ class GetKeysDialog(Toplevel):
|
||||||
return key
|
return key
|
||||||
|
|
||||||
def OK(self, event=None):
|
def OK(self, event=None):
|
||||||
if self.advanced or self.KeysOK(): # doesn't check advanced string yet
|
keys = self.keyString.get().strip()
|
||||||
self.result=self.keyString.get()
|
if not keys:
|
||||||
self.destroy()
|
self.showerror(title=self.keyerror_title, parent=self,
|
||||||
|
message="No key specified.")
|
||||||
|
return
|
||||||
|
if (self.advanced or self.KeysOK(keys)) and self.bind_ok(keys):
|
||||||
|
self.result = keys
|
||||||
|
self.destroy()
|
||||||
|
|
||||||
def Cancel(self, event=None):
|
def Cancel(self, event=None):
|
||||||
self.result=''
|
self.result=''
|
||||||
self.destroy()
|
self.destroy()
|
||||||
|
|
||||||
def KeysOK(self):
|
def KeysOK(self, keys):
|
||||||
'''Validity check on user's 'basic' keybinding selection.
|
'''Validity check on user's 'basic' keybinding selection.
|
||||||
|
|
||||||
Doesn't check the string produced by the advanced dialog because
|
Doesn't check the string produced by the advanced dialog because
|
||||||
'modifiers' isn't set.
|
'modifiers' isn't set.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
keys = self.keyString.get()
|
|
||||||
keys.strip()
|
|
||||||
finalKey = self.listKeysFinal.get(ANCHOR)
|
finalKey = self.listKeysFinal.get(ANCHOR)
|
||||||
modifiers = self.GetModifiers()
|
modifiers = self.GetModifiers()
|
||||||
# create a key sequence list for overlap check:
|
# create a key sequence list for overlap check:
|
||||||
keySequence = keys.split()
|
keySequence = keys.split()
|
||||||
keysOK = False
|
keysOK = False
|
||||||
title = 'Key Sequence Error'
|
title = self.keyerror_title
|
||||||
if not keys:
|
if not keys.endswith('>'):
|
||||||
tkMessageBox.showerror(title=title, parent=self,
|
self.showerror(title, parent=self,
|
||||||
message='No keys specified.')
|
message='Missing the final Key')
|
||||||
elif not keys.endswith('>'):
|
|
||||||
tkMessageBox.showerror(title=title, parent=self,
|
|
||||||
message='Missing the final Key')
|
|
||||||
elif (not modifiers
|
elif (not modifiers
|
||||||
and finalKey not in self.functionKeys + self.moveKeys):
|
and finalKey not in self.functionKeys + self.moveKeys):
|
||||||
tkMessageBox.showerror(title=title, parent=self,
|
self.showerror(title=title, parent=self,
|
||||||
message='No modifier key(s) specified.')
|
message='No modifier key(s) specified.')
|
||||||
elif (modifiers == ['Shift']) \
|
elif (modifiers == ['Shift']) \
|
||||||
and (finalKey not in
|
and (finalKey not in
|
||||||
self.functionKeys + self.moveKeys + ('Tab', 'Space')):
|
self.functionKeys + self.moveKeys + ('Tab', 'Space')):
|
||||||
msg = 'The shift modifier by itself may not be used with'\
|
msg = 'The shift modifier by itself may not be used with'\
|
||||||
' this key symbol.'
|
' this key symbol.'
|
||||||
tkMessageBox.showerror(title=title, parent=self, message=msg)
|
self.showerror(title=title, parent=self, message=msg)
|
||||||
elif keySequence in self.currentKeySequences:
|
elif keySequence in self.currentKeySequences:
|
||||||
msg = 'This key combination is already in use.'
|
msg = 'This key combination is already in use.'
|
||||||
tkMessageBox.showerror(title=title, parent=self, message=msg)
|
self.showerror(title=title, parent=self, message=msg)
|
||||||
else:
|
else:
|
||||||
keysOK = True
|
keysOK = True
|
||||||
return keysOK
|
return keysOK
|
||||||
|
|
||||||
|
def bind_ok(self, keys):
|
||||||
|
"Return True if Tcl accepts the new keys else show message."
|
||||||
|
|
||||||
|
try:
|
||||||
|
binding = self.bind(keys, lambda: None)
|
||||||
|
except TclError as err:
|
||||||
|
self.showerror(
|
||||||
|
title=self.keyerror_title, parent=self,
|
||||||
|
message=(f'The entered key sequence is not accepted.\n\n'
|
||||||
|
f'Error: {err}'))
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
self.unbind(keys, binding)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
import unittest
|
||||||
|
unittest.main('idlelib.idle_test.test_config_key', verbosity=2, exit=False)
|
||||||
from idlelib.idle_test.htest import run
|
from idlelib.idle_test.htest import run
|
||||||
run(GetKeysDialog)
|
run(GetKeysDialog)
|
||||||
|
|
|
@ -4,29 +4,92 @@ Coverage: 56% from creating and closing dialog.
|
||||||
'''
|
'''
|
||||||
from idlelib import config_key
|
from idlelib import config_key
|
||||||
from test.support import requires
|
from test.support import requires
|
||||||
requires('gui')
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from tkinter import Tk
|
from tkinter import Tk
|
||||||
|
from idlelib.idle_test.mock_idle import Func
|
||||||
|
from idlelib.idle_test.mock_tk import Var, Mbox_func
|
||||||
|
|
||||||
|
|
||||||
class GetKeysTest(unittest.TestCase):
|
class ValidationTest(unittest.TestCase):
|
||||||
|
"Test validation methods: OK, KeysOK, bind_ok."
|
||||||
|
|
||||||
|
class Validator(config_key.GetKeysDialog):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
config_key.GetKeysDialog.__init__(self, *args, **kwargs)
|
||||||
|
class listKeysFinal:
|
||||||
|
get = Func()
|
||||||
|
self.listKeysFinal = listKeysFinal
|
||||||
|
GetModifiers = Func()
|
||||||
|
showerror = Mbox_func()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
|
requires('gui')
|
||||||
cls.root = Tk()
|
cls.root = Tk()
|
||||||
cls.root.withdraw()
|
cls.root.withdraw()
|
||||||
|
cls.dialog = cls.Validator(
|
||||||
|
cls.root, 'Title', '<<Test>>', [['<Key-F12>']], _utest=True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls):
|
def tearDownClass(cls):
|
||||||
cls.root.update() # Stop "can't run event command" warning.
|
cls.dialog.Cancel()
|
||||||
|
cls.root.update_idletasks()
|
||||||
cls.root.destroy()
|
cls.root.destroy()
|
||||||
del cls.root
|
del cls.dialog, cls.root
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.dialog.showerror.message = ''
|
||||||
|
# A test that needs a particular final key value should set it.
|
||||||
|
# A test that sets a non-blank modifier list should reset it to [].
|
||||||
|
|
||||||
def test_init(self):
|
def test_ok_empty(self):
|
||||||
dia = config_key.GetKeysDialog(
|
self.dialog.keyString.set(' ')
|
||||||
self.root, 'test', '<<Test>>', ['<Key-F12>'], _utest=True)
|
self.dialog.OK()
|
||||||
dia.Cancel()
|
self.assertEqual(self.dialog.result, '')
|
||||||
|
self.assertEqual(self.dialog.showerror.message, 'No key specified.')
|
||||||
|
|
||||||
|
def test_ok_good(self):
|
||||||
|
self.dialog.keyString.set('<Key-F11>')
|
||||||
|
self.dialog.listKeysFinal.get.result = 'F11'
|
||||||
|
self.dialog.OK()
|
||||||
|
self.assertEqual(self.dialog.result, '<Key-F11>')
|
||||||
|
self.assertEqual(self.dialog.showerror.message, '')
|
||||||
|
|
||||||
|
def test_keys_no_ending(self):
|
||||||
|
self.assertFalse(self.dialog.KeysOK('<Control-Shift'))
|
||||||
|
self.assertIn('Missing the final', self.dialog.showerror.message)
|
||||||
|
|
||||||
|
def test_keys_no_modifier_bad(self):
|
||||||
|
self.dialog.listKeysFinal.get.result = 'A'
|
||||||
|
self.assertFalse(self.dialog.KeysOK('<Key-A>'))
|
||||||
|
self.assertIn('No modifier', self.dialog.showerror.message)
|
||||||
|
|
||||||
|
def test_keys_no_modifier_ok(self):
|
||||||
|
self.dialog.listKeysFinal.get.result = 'F11'
|
||||||
|
self.assertTrue(self.dialog.KeysOK('<Key-F11>'))
|
||||||
|
self.assertEqual(self.dialog.showerror.message, '')
|
||||||
|
|
||||||
|
def test_keys_shift_bad(self):
|
||||||
|
self.dialog.listKeysFinal.get.result = 'a'
|
||||||
|
self.dialog.GetModifiers.result = ['Shift']
|
||||||
|
self.assertFalse(self.dialog.KeysOK('<a>'))
|
||||||
|
self.assertIn('shift modifier', self.dialog.showerror.message)
|
||||||
|
self.dialog.GetModifiers.result = []
|
||||||
|
|
||||||
|
def test_keys_dup(self):
|
||||||
|
self.dialog.listKeysFinal.get.result = 'F12'
|
||||||
|
self.dialog.GetModifiers.result = []
|
||||||
|
self.assertFalse(self.dialog.KeysOK('<Key-F12>'))
|
||||||
|
self.assertIn('already in use', self.dialog.showerror.message)
|
||||||
|
|
||||||
|
def test_bind_ok(self):
|
||||||
|
self.assertTrue(self.dialog.bind_ok('<Control-Shift-Key-a>'))
|
||||||
|
self.assertEqual(self.dialog.showerror.message, '')
|
||||||
|
|
||||||
|
def test_bind_not_ok(self):
|
||||||
|
self.assertFalse(self.dialog.bind_ok('<Control-Shift>'))
|
||||||
|
self.assertIn('not accepted', self.dialog.showerror.message)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
IDLE: Verify user-entered key sequences by trying to bind them with tk. Add
|
||||||
|
tests for all 3 validation functions. Original patch by G Polo. Tests added
|
||||||
|
by Cheryl Sabella.
|
Loading…
Add table
Add a link
Reference in a new issue