Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 36 additions & 41 deletions Lib/idlelib/config_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,38 @@
import sys


FUNCTION_KEYS = ('F1', 'F2' ,'F3' ,'F4' ,'F5' ,'F6',
'F7', 'F8' ,'F9' ,'F10' ,'F11' ,'F12')
ALPHANUM_KEYS = tuple(string.ascii_lowercase + string.digits)
PUNCTUATION_KEYS = tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
WHITESPACE_KEYS = ('Tab', 'Space', 'Return')
EDIT_KEYS = ('BackSpace', 'Delete', 'Insert')
MOVE_KEYS = ('Home', 'End', 'Page Up', 'Page Down', 'Left Arrow',
'Right Arrow', 'Up Arrow', 'Down Arrow')
AVAILABLE_KEYS = (ALPHANUM_KEYS + PUNCTUATION_KEYS + FUNCTION_KEYS +
WHITESPACE_KEYS + EDIT_KEYS + MOVE_KEYS)


def translate_key(key, modifiers):
"Translate from keycap symbol to the Tkinter keysym."
mapping = {'Space':'space',
'~':'asciitilde', '!':'exclam', '@':'at', '#':'numbersign',
'%':'percent', '^':'asciicircum', '&':'ampersand',
'*':'asterisk', '(':'parenleft', ')':'parenright',
'_':'underscore', '-':'minus', '+':'plus', '=':'equal',
'{':'braceleft', '}':'braceright',
'[':'bracketleft', ']':'bracketright', '|':'bar',
';':'semicolon', ':':'colon', ',':'comma', '.':'period',
'<':'less', '>':'greater', '/':'slash', '?':'question',
'Page Up':'Prior', 'Page Down':'Next',
'Left Arrow':'Left', 'Right Arrow':'Right',
'Up Arrow':'Up', 'Down Arrow': 'Down', 'Tab':'Tab'}
key = mapping.get(key, key)
if 'Shift' in modifiers and key in string.ascii_lowercase:
key = key.upper()
return f'Key-{key}'


class GetKeysDialog(Toplevel):

# Dialog title for invalid key sequence
Expand Down Expand Up @@ -48,7 +80,6 @@ def __init__(self, parent, title, action, current_key_sequences,
self.modifier_vars.append(variable)
self.advanced = False
self.create_widgets()
self.load_final_key_list()
self.update_idletasks()
self.geometry(
"+%d+%d" % (
Expand Down Expand Up @@ -122,6 +153,7 @@ def create_widgets(self):
# Basic entry key list.
self.list_keys_final = Listbox(self.frame_controls_basic, width=15,
height=10, selectmode='single')
self.list_keys_final.insert('end', *AVAILABLE_KEYS)
self.list_keys_final.bind('<ButtonRelease-1>', self.final_key_selected)
self.list_keys_final.grid(row=0, column=4, rowspan=4, sticky='ns')
scroll_keys_final = Scrollbar(self.frame_controls_basic,
Expand Down Expand Up @@ -206,7 +238,7 @@ def build_key_string(self):
keylist = modifiers = self.get_modifiers()
final_key = self.list_keys_final.get('anchor')
if final_key:
final_key = self.translate_key(final_key, modifiers)
final_key = translate_key(final_key, modifiers)
keylist.append(final_key)
self.key_string.set(f"<{'-'.join(keylist)}>")

Expand All @@ -223,43 +255,6 @@ def clear_key_seq(self):
variable.set('')
self.key_string.set('')

def load_final_key_list(self):
"Populate listbox of available keys."
# These tuples are also available for use in validity checks.
self.function_keys = ('F1', 'F2' ,'F3' ,'F4' ,'F5' ,'F6',
'F7', 'F8' ,'F9' ,'F10' ,'F11' ,'F12')
self.alphanum_keys = tuple(string.ascii_lowercase + string.digits)
self.punctuation_keys = tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
self.whitespace_keys = ('Tab', 'Space', 'Return')
self.edit_keys = ('BackSpace', 'Delete', 'Insert')
self.move_keys = ('Home', 'End', 'Page Up', 'Page Down', 'Left Arrow',
'Right Arrow', 'Up Arrow', 'Down Arrow')
# Make a tuple of most of the useful common 'final' keys.
keys = (self.alphanum_keys + self.punctuation_keys + self.function_keys +
self.whitespace_keys + self.edit_keys + self.move_keys)
self.list_keys_final.insert('end', *keys)

@staticmethod
def translate_key(key, modifiers):
"Translate from keycap symbol to the Tkinter keysym."
translate_dict = {'Space':'space',
'~':'asciitilde', '!':'exclam', '@':'at', '#':'numbersign',
'%':'percent', '^':'asciicircum', '&':'ampersand',
'*':'asterisk', '(':'parenleft', ')':'parenright',
'_':'underscore', '-':'minus', '+':'plus', '=':'equal',
'{':'braceleft', '}':'braceright',
'[':'bracketleft', ']':'bracketright', '|':'bar',
';':'semicolon', ':':'colon', ',':'comma', '.':'period',
'<':'less', '>':'greater', '/':'slash', '?':'question',
'Page Up':'Prior', 'Page Down':'Next',
'Left Arrow':'Left', 'Right Arrow':'Right',
'Up Arrow':'Up', 'Down Arrow': 'Down', 'Tab':'Tab'}
if key in translate_dict:
key = translate_dict[key]
if 'Shift' in modifiers and key in string.ascii_lowercase:
key = key.upper()
return f'Key-{key}'

def ok(self, event=None):
keys = self.key_string.get().strip()
if not keys:
Expand Down Expand Up @@ -291,12 +286,12 @@ def keys_ok(self, keys):
self.showerror(title, parent=self,
message='Missing the final Key')
elif (not modifiers
and final_key not in self.function_keys + self.move_keys):
and final_key not in FUNCTION_KEYS + MOVE_KEYS):
self.showerror(title=title, parent=self,
message='No modifier key(s) specified.')
elif (modifiers == ['Shift']) \
and (final_key not in
self.function_keys + self.move_keys + ('Tab', 'Space')):
FUNCTION_KEYS + MOVE_KEYS + ('Tab', 'Space')):
msg = 'The shift modifier by itself may not be used with'\
' this key symbol.'
self.showerror(title=title, parent=self, message=msg)
Expand Down
41 changes: 22 additions & 19 deletions Lib/idlelib/idle_test/test_config_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,25 +206,6 @@ def test_get_modifiers(self):
dialog.modifier_checkbuttons['foo'].invoke()
eq(gm(), ['BAZ'])

def test_translate_key(self):
dialog = self.dialog
tr = dialog.translate_key
eq = self.assertEqual

# Letters return unchanged with no 'Shift'.
eq(tr('q', []), 'Key-q')
eq(tr('q', ['Control', 'Alt']), 'Key-q')

# 'Shift' uppercases single lowercase letters.
eq(tr('q', ['Shift']), 'Key-Q')
eq(tr('q', ['Control', 'Shift']), 'Key-Q')
eq(tr('q', ['Control', 'Alt', 'Shift']), 'Key-Q')

# Convert key name to keysym.
eq(tr('Page Up', []), 'Key-Prior')
# 'Shift' doesn't change case.
eq(tr('Page Down', ['Shift']), 'Key-Next')

@mock.patch.object(gkd, 'get_modifiers')
def test_build_key_string(self, mock_modifiers):
dialog = self.dialog
Expand Down Expand Up @@ -284,5 +265,27 @@ def test_cancel(self):
self.assertEqual(self.dialog.result, '')


class HelperTest(unittest.TestCase):
"Test module level helper functions."

def test_translate_key(self):
tr = config_key.translate_key
eq = self.assertEqual

# Letters return unchanged with no 'Shift'.
eq(tr('q', []), 'Key-q')
eq(tr('q', ['Control', 'Alt']), 'Key-q')

# 'Shift' uppercases single lowercase letters.
eq(tr('q', ['Shift']), 'Key-Q')
eq(tr('q', ['Control', 'Shift']), 'Key-Q')
eq(tr('q', ['Control', 'Alt', 'Shift']), 'Key-Q')

# Convert key name to keysym.
eq(tr('Page Up', []), 'Key-Prior')
# 'Shift' doesn't change case when it's not a single char.
eq(tr('*', ['Shift']), 'Key-asterisk')


if __name__ == '__main__':
unittest.main(verbosity=2)
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
Update config_key: use PEP 8 names, ttk widgets, and add tests.
Update config_key: use PEP 8 names and ttk widgets,
make some objects global, and add tests.