Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b61a51a

Browse files
miss-islingtoncsabella
andauthoredSep 30, 2022
bpo-35675: IDLE - separate config_key window and frame (GH-11427)
bpo-35598: IDLE: Refactor window and frame class Co-authored-by: Terry Jan Reedy <[email protected]> (cherry picked from commit 1cc308d) Co-authored-by: Cheryl Sabella <[email protected]>
1 parent 1dc1d5d commit b61a51a

File tree

4 files changed

+174
-82
lines changed

4 files changed

+174
-82
lines changed
 

‎Lib/idlelib/config_key.py‎

Lines changed: 82 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -41,32 +41,22 @@ def translate_key(key, modifiers):
4141
return f'Key-{key}'
4242

4343

44-
class GetKeysDialog(Toplevel):
44+
class GetKeysFrame(Frame):
4545

4646
# Dialog title for invalid key sequence
4747
keyerror_title = 'Key Sequence Error'
4848

49-
def __init__(self, parent, title, action, current_key_sequences,
50-
*, _htest=False, _utest=False):
49+
def __init__(self, parent, action, current_key_sequences):
5150
"""
5251
parent - parent of this dialog
53-
title - string which is the title of the popup dialog
54-
action - string, the name of the virtual event these keys will be
52+
action - the name of the virtual event these keys will be
5553
mapped to
56-
current_key_sequences - list, a list of all key sequence lists
54+
current_key_sequences - a list of all key sequence lists
5755
currently mapped to virtual events, for overlap checking
58-
_htest - bool, change box location when running htest
59-
_utest - bool, do not wait when running unittest
6056
"""
61-
Toplevel.__init__(self, parent)
62-
self.withdraw() # Hide while setting geometry.
63-
self.configure(borderwidth=5)
64-
self.resizable(height=False, width=False)
65-
self.title(title)
66-
self.transient(parent)
67-
_setup_dialog(self)
68-
self.grab_set()
69-
self.protocol("WM_DELETE_WINDOW", self.cancel)
57+
super().__init__(parent)
58+
self['borderwidth'] = 2
59+
self['relief'] = 'sunken'
7060
self.parent = parent
7161
self.action = action
7262
self.current_key_sequences = current_key_sequences
@@ -82,39 +72,14 @@ def __init__(self, parent, title, action, current_key_sequences,
8272
self.modifier_vars.append(variable)
8373
self.advanced = False
8474
self.create_widgets()
85-
self.update_idletasks()
86-
self.geometry(
87-
"+%d+%d" % (
88-
parent.winfo_rootx() +
89-
(parent.winfo_width()/2 - self.winfo_reqwidth()/2),
90-
parent.winfo_rooty() +
91-
((parent.winfo_height()/2 - self.winfo_reqheight()/2)
92-
if not _htest else 150)
93-
) ) # Center dialog over parent (or below htest box).
94-
if not _utest:
95-
self.deiconify() # Geometry set, unhide.
96-
self.wait_window()
9775

9876
def showerror(self, *args, **kwargs):
9977
# Make testing easier. Replace in #30751.
10078
messagebox.showerror(*args, **kwargs)
10179

10280
def create_widgets(self):
103-
self.frame = frame = Frame(self, borderwidth=2, relief='sunken')
104-
frame.pack(side='top', expand=True, fill='both')
105-
106-
frame_buttons = Frame(self)
107-
frame_buttons.pack(side='bottom', fill='x')
108-
109-
self.button_ok = Button(frame_buttons, text='OK',
110-
width=8, command=self.ok)
111-
self.button_ok.grid(row=0, column=0, padx=5, pady=5)
112-
self.button_cancel = Button(frame_buttons, text='Cancel',
113-
width=8, command=self.cancel)
114-
self.button_cancel.grid(row=0, column=1, padx=5, pady=5)
115-
11681
# Basic entry key sequence.
117-
self.frame_keyseq_basic = Frame(frame, name='keyseq_basic')
82+
self.frame_keyseq_basic = Frame(self, name='keyseq_basic')
11883
self.frame_keyseq_basic.grid(row=0, column=0, sticky='nsew',
11984
padx=5, pady=5)
12085
basic_title = Label(self.frame_keyseq_basic,
@@ -127,7 +92,7 @@ def create_widgets(self):
12792
basic_keys.pack(ipadx=5, ipady=5, fill='x')
12893

12994
# Basic entry controls.
130-
self.frame_controls_basic = Frame(frame)
95+
self.frame_controls_basic = Frame(self)
13196
self.frame_controls_basic.grid(row=1, column=0, sticky='nsew', padx=5)
13297

13398
# Basic entry modifiers.
@@ -169,7 +134,7 @@ def create_widgets(self):
169134
self.button_clear.grid(row=2, column=0, columnspan=4)
170135

171136
# Advanced entry key sequence.
172-
self.frame_keyseq_advanced = Frame(frame, name='keyseq_advanced')
137+
self.frame_keyseq_advanced = Frame(self, name='keyseq_advanced')
173138
self.frame_keyseq_advanced.grid(row=0, column=0, sticky='nsew',
174139
padx=5, pady=5)
175140
advanced_title = Label(self.frame_keyseq_advanced, justify='left',
@@ -181,7 +146,7 @@ def create_widgets(self):
181146
self.advanced_keys.pack(fill='x')
182147

183148
# Advanced entry help text.
184-
self.frame_help_advanced = Frame(frame)
149+
self.frame_help_advanced = Frame(self)
185150
self.frame_help_advanced.grid(row=1, column=0, sticky='nsew', padx=5)
186151
help_advanced = Label(self.frame_help_advanced, justify='left',
187152
text="Key bindings are specified using Tkinter keysyms as\n"+
@@ -196,7 +161,7 @@ def create_widgets(self):
196161
help_advanced.grid(row=0, column=0, sticky='nsew')
197162

198163
# Switch between basic and advanced.
199-
self.button_level = Button(frame, command=self.toggle_level,
164+
self.button_level = Button(self, command=self.toggle_level,
200165
text='<< Basic Key Binding Entry')
201166
self.button_level.grid(row=2, column=0, stick='ew', padx=5, pady=5)
202167
self.toggle_level()
@@ -257,21 +222,16 @@ def clear_key_seq(self):
257222
variable.set('')
258223
self.key_string.set('')
259224

260-
def ok(self, event=None):
225+
def ok(self):
226+
self.result = ''
261227
keys = self.key_string.get().strip()
262228
if not keys:
263229
self.showerror(title=self.keyerror_title, parent=self,
264230
message="No key specified.")
265231
return
266232
if (self.advanced or self.keys_ok(keys)) and self.bind_ok(keys):
267233
self.result = keys
268-
self.grab_release()
269-
self.destroy()
270-
271-
def cancel(self, event=None):
272-
self.result = ''
273-
self.grab_release()
274-
self.destroy()
234+
return
275235

276236
def keys_ok(self, keys):
277237
"""Validity check on user's 'basic' keybinding selection.
@@ -319,6 +279,73 @@ def bind_ok(self, keys):
319279
return True
320280

321281

282+
class GetKeysWindow(Toplevel):
283+
284+
def __init__(self, parent, title, action, current_key_sequences,
285+
*, _htest=False, _utest=False):
286+
"""
287+
parent - parent of this dialog
288+
title - string which is the title of the popup dialog
289+
action - string, the name of the virtual event these keys will be
290+
mapped to
291+
current_key_sequences - list, a list of all key sequence lists
292+
currently mapped to virtual events, for overlap checking
293+
_htest - bool, change box location when running htest
294+
_utest - bool, do not wait when running unittest
295+
"""
296+
super().__init__(parent)
297+
self.withdraw() # Hide while setting geometry.
298+
self['borderwidth'] = 5
299+
self.resizable(height=False, width=False)
300+
# Needed for winfo_reqwidth().
301+
self.update_idletasks()
302+
# Center dialog over parent (or below htest box).
303+
x = (parent.winfo_rootx() +
304+
(parent.winfo_width()//2 - self.winfo_reqwidth()//2))
305+
y = (parent.winfo_rooty() +
306+
((parent.winfo_height()//2 - self.winfo_reqheight()//2)
307+
if not _htest else 150))
308+
self.geometry(f"+{x}+{y}")
309+
310+
self.title(title)
311+
self.frame = frame = GetKeysFrame(self, action, current_key_sequences)
312+
self.protocol("WM_DELETE_WINDOW", self.cancel)
313+
frame_buttons = Frame(self)
314+
self.button_ok = Button(frame_buttons, text='OK',
315+
width=8, command=self.ok)
316+
self.button_cancel = Button(frame_buttons, text='Cancel',
317+
width=8, command=self.cancel)
318+
self.button_ok.grid(row=0, column=0, padx=5, pady=5)
319+
self.button_cancel.grid(row=0, column=1, padx=5, pady=5)
320+
frame.pack(side='top', expand=True, fill='both')
321+
frame_buttons.pack(side='bottom', fill='x')
322+
323+
self.transient(parent)
324+
_setup_dialog(self)
325+
self.grab_set()
326+
if not _utest:
327+
self.deiconify() # Geometry set, unhide.
328+
self.wait_window()
329+
330+
@property
331+
def result(self):
332+
return self.frame.result
333+
334+
@result.setter
335+
def result(self, value):
336+
self.frame.result = value
337+
338+
def ok(self, event=None):
339+
self.frame.ok()
340+
self.grab_release()
341+
self.destroy()
342+
343+
def cancel(self, event=None):
344+
self.result = ''
345+
self.grab_release()
346+
self.destroy()
347+
348+
322349
if __name__ == '__main__':
323350
from unittest import main
324351
main('idlelib.idle_test.test_config_key', verbosity=2, exit=False)

‎Lib/idlelib/configdialog.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from tkinter import messagebox
2525

2626
from idlelib.config import idleConf, ConfigChanges
27-
from idlelib.config_key import GetKeysDialog
27+
from idlelib.config_key import GetKeysWindow
2828
from idlelib.dynoption import DynOptionMenu
2929
from idlelib import macosx
3030
from idlelib.query import SectionName, HelpSource
@@ -1397,7 +1397,7 @@ def get_new_keys(self):
13971397
for event in key_set_changes:
13981398
current_bindings[event] = key_set_changes[event].split()
13991399
current_key_sequences = list(current_bindings.values())
1400-
new_keys = GetKeysDialog(self, 'Get New Keys', bind_name,
1400+
new_keys = GetKeysWindow(self, 'Get New Keys', bind_name,
14011401
current_key_sequences).result
14021402
if new_keys:
14031403
if self.keyset_source.get(): # Current key set is a built-in.

‎Lib/idlelib/idle_test/test_config_key.py‎

Lines changed: 87 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,13 @@
1313
from idlelib.idle_test.mock_idle import Func
1414
from idlelib.idle_test.mock_tk import Mbox_func
1515

16-
gkd = config_key.GetKeysDialog
17-
1816

1917
class ValidationTest(unittest.TestCase):
2018
"Test validation methods: ok, keys_ok, bind_ok."
2119

22-
class Validator(gkd):
20+
class Validator(config_key.GetKeysFrame):
2321
def __init__(self, *args, **kwargs):
24-
config_key.GetKeysDialog.__init__(self, *args, **kwargs)
22+
super().__init__(*args, **kwargs)
2523
class list_keys_final:
2624
get = Func()
2725
self.list_keys_final = list_keys_final
@@ -34,15 +32,14 @@ def setUpClass(cls):
3432
cls.root = Tk()
3533
cls.root.withdraw()
3634
keylist = [['<Key-F12>'], ['<Control-Key-x>', '<Control-Key-X>']]
37-
cls.dialog = cls.Validator(
38-
cls.root, 'Title', '<<Test>>', keylist, _utest=True)
35+
cls.dialog = cls.Validator(cls.root, '<<Test>>', keylist)
3936

4037
@classmethod
4138
def tearDownClass(cls):
42-
cls.dialog.cancel()
39+
del cls.dialog
4340
cls.root.update_idletasks()
4441
cls.root.destroy()
45-
del cls.dialog, cls.root
42+
del cls.root
4643

4744
def setUp(self):
4845
self.dialog.showerror.message = ''
@@ -111,14 +108,14 @@ def setUpClass(cls):
111108
requires('gui')
112109
cls.root = Tk()
113110
cls.root.withdraw()
114-
cls.dialog = gkd(cls.root, 'Title', '<<Test>>', [], _utest=True)
111+
cls.dialog = config_key.GetKeysFrame(cls.root, '<<Test>>', [])
115112

116113
@classmethod
117114
def tearDownClass(cls):
118-
cls.dialog.cancel()
115+
del cls.dialog
119116
cls.root.update_idletasks()
120117
cls.root.destroy()
121-
del cls.dialog, cls.root
118+
del cls.root
122119

123120
def test_toggle_level(self):
124121
dialog = self.dialog
@@ -130,7 +127,7 @@ def stackorder():
130127
this can be used to check whether a frame is above or
131128
below another one.
132129
"""
133-
for index, child in enumerate(dialog.frame.winfo_children()):
130+
for index, child in enumerate(dialog.winfo_children()):
134131
if child._name == 'keyseq_basic':
135132
basic = index
136133
if child._name == 'keyseq_advanced':
@@ -161,7 +158,7 @@ def stackorder():
161158
class KeySelectionTest(unittest.TestCase):
162159
"Test selecting key on Basic frames."
163160

164-
class Basic(gkd):
161+
class Basic(config_key.GetKeysFrame):
165162
def __init__(self, *args, **kwargs):
166163
super().__init__(*args, **kwargs)
167164
class list_keys_final:
@@ -179,14 +176,14 @@ def setUpClass(cls):
179176
requires('gui')
180177
cls.root = Tk()
181178
cls.root.withdraw()
182-
cls.dialog = cls.Basic(cls.root, 'Title', '<<Test>>', [], _utest=True)
179+
cls.dialog = cls.Basic(cls.root, '<<Test>>', [])
183180

184181
@classmethod
185182
def tearDownClass(cls):
186-
cls.dialog.cancel()
183+
del cls.dialog
187184
cls.root.update_idletasks()
188185
cls.root.destroy()
189-
del cls.dialog, cls.root
186+
del cls.root
190187

191188
def setUp(self):
192189
self.dialog.clear_key_seq()
@@ -206,7 +203,7 @@ def test_get_modifiers(self):
206203
dialog.modifier_checkbuttons['foo'].invoke()
207204
eq(gm(), ['BAZ'])
208205

209-
@mock.patch.object(gkd, 'get_modifiers')
206+
@mock.patch.object(config_key.GetKeysFrame, 'get_modifiers')
210207
def test_build_key_string(self, mock_modifiers):
211208
dialog = self.dialog
212209
key = dialog.list_keys_final
@@ -227,7 +224,7 @@ def test_build_key_string(self, mock_modifiers):
227224
dialog.build_key_string()
228225
eq(string(), '<mymod-test>')
229226

230-
@mock.patch.object(gkd, 'get_modifiers')
227+
@mock.patch.object(config_key.GetKeysFrame, 'get_modifiers')
231228
def test_final_key_selected(self, mock_modifiers):
232229
dialog = self.dialog
233230
key = dialog.list_keys_final
@@ -240,29 +237,97 @@ def test_final_key_selected(self, mock_modifiers):
240237
eq(string(), '<Shift-Key-braceleft>')
241238

242239

243-
class CancelTest(unittest.TestCase):
240+
class CancelWindowTest(unittest.TestCase):
244241
"Simulate user clicking [Cancel] button."
245242

246243
@classmethod
247244
def setUpClass(cls):
248245
requires('gui')
249246
cls.root = Tk()
250247
cls.root.withdraw()
251-
cls.dialog = gkd(cls.root, 'Title', '<<Test>>', [], _utest=True)
248+
cls.dialog = config_key.GetKeysWindow(
249+
cls.root, 'Title', '<<Test>>', [], _utest=True)
252250

253251
@classmethod
254252
def tearDownClass(cls):
255253
cls.dialog.cancel()
254+
del cls.dialog
256255
cls.root.update_idletasks()
257256
cls.root.destroy()
258-
del cls.dialog, cls.root
257+
del cls.root
259258

260-
def test_cancel(self):
259+
@mock.patch.object(config_key.GetKeysFrame, 'ok')
260+
def test_cancel(self, mock_frame_ok):
261261
self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
262262
self.dialog.button_cancel.invoke()
263263
with self.assertRaises(TclError):
264264
self.dialog.winfo_class()
265265
self.assertEqual(self.dialog.result, '')
266+
mock_frame_ok.assert_not_called()
267+
268+
269+
class OKWindowTest(unittest.TestCase):
270+
"Simulate user clicking [OK] button."
271+
272+
@classmethod
273+
def setUpClass(cls):
274+
requires('gui')
275+
cls.root = Tk()
276+
cls.root.withdraw()
277+
cls.dialog = config_key.GetKeysWindow(
278+
cls.root, 'Title', '<<Test>>', [], _utest=True)
279+
280+
@classmethod
281+
def tearDownClass(cls):
282+
cls.dialog.cancel()
283+
del cls.dialog
284+
cls.root.update_idletasks()
285+
cls.root.destroy()
286+
del cls.root
287+
288+
@mock.patch.object(config_key.GetKeysFrame, 'ok')
289+
def test_ok(self, mock_frame_ok):
290+
self.assertEqual(self.dialog.winfo_class(), 'Toplevel')
291+
self.dialog.button_ok.invoke()
292+
with self.assertRaises(TclError):
293+
self.dialog.winfo_class()
294+
mock_frame_ok.assert_called()
295+
296+
297+
class WindowResultTest(unittest.TestCase):
298+
"Test window result get and set."
299+
300+
@classmethod
301+
def setUpClass(cls):
302+
requires('gui')
303+
cls.root = Tk()
304+
cls.root.withdraw()
305+
cls.dialog = config_key.GetKeysWindow(
306+
cls.root, 'Title', '<<Test>>', [], _utest=True)
307+
308+
@classmethod
309+
def tearDownClass(cls):
310+
cls.dialog.cancel()
311+
del cls.dialog
312+
cls.root.update_idletasks()
313+
cls.root.destroy()
314+
del cls.root
315+
316+
def test_result(self):
317+
dialog = self.dialog
318+
eq = self.assertEqual
319+
320+
dialog.result = ''
321+
eq(dialog.result, '')
322+
eq(dialog.frame.result,'')
323+
324+
dialog.result = 'bar'
325+
eq(dialog.result,'bar')
326+
eq(dialog.frame.result,'bar')
327+
328+
dialog.frame.result = 'foo'
329+
eq(dialog.result, 'foo')
330+
eq(dialog.frame.result,'foo')
266331

267332

268333
class HelperTest(unittest.TestCase):

‎Lib/idlelib/idle_test/test_configdialog.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -954,8 +954,8 @@ def test_set_keys_type(self):
954954
def test_get_new_keys(self):
955955
eq = self.assertEqual
956956
d = self.page
957-
orig_getkeysdialog = configdialog.GetKeysDialog
958-
gkd = configdialog.GetKeysDialog = Func(return_self=True)
957+
orig_getkeysdialog = configdialog.GetKeysWindow
958+
gkd = configdialog.GetKeysWindow = Func(return_self=True)
959959
gnkn = d.get_new_keys_name = Func()
960960

961961
d.button_new_keys.state(('!disabled',))
@@ -997,7 +997,7 @@ def test_get_new_keys(self):
997997
eq(d.keybinding.get(), '<Key-p>')
998998

999999
del d.get_new_keys_name
1000-
configdialog.GetKeysDialog = orig_getkeysdialog
1000+
configdialog.GetKeysWindow = orig_getkeysdialog
10011001

10021002
def test_get_new_keys_name(self):
10031003
orig_sectionname = configdialog.SectionName

0 commit comments

Comments
 (0)
Please sign in to comment.