2424import shlex
2525from sys import executable , platform # Platform is set for one test.
2626
27- from tkinter import Toplevel , StringVar , BooleanVar , W , E , S
28- from tkinter .ttk import Frame , Button , Entry , Label , Checkbutton
27+ from tkinter import StringVar , BooleanVar , W , S , EW , LEFT
28+ from tkinter .ttk import Button , Checkbutton , Entry , Label
2929from tkinter import filedialog
3030from tkinter .font import Font
31- from tkinter .simpledialog import _setup_dialog
31+ from tkinter .simpledialog import Dialog
3232
33- class Query (Toplevel ):
33+ class Query (Dialog ):
3434 """Base class for getting verified answer from a user.
3535
3636 For this base class, accept any non-blank string.
37+ Built on tkinter.simpledialog.Dialog, which provides the modal
38+ behavior and the OK/Cancel buttons (with <Return>, <Escape>, and
39+ Alt-underline keyboard equivalents).
3740 """
3841 def __init__ (self , parent , title , message , * , text0 = '' , used_names = {},
3942 _htest = False , _utest = False ):
4043 """Create modal popup, return when destroyed.
4144
42- Additional subclass init must be done before this unless
43- _utest=True is passed to suppress wait_window().
45+ Additional subclass init must be done before calling this
46+ unless _utest=True is passed to suppress wait_window().
4447
4548 title - string, title of popup dialog
4649 message - string, informational message to display
@@ -49,78 +52,56 @@ def __init__(self, parent, title, message, *, text0='', used_names={},
4952 _htest - bool, change box location when running htest
5053 _utest - bool, leave window hidden and not modal
5154 """
52- self .parent = parent # Needed for Font call.
5355 self .message = message
5456 self .text0 = text0
5557 self .used_names = used_names
58+ self ._htest = _htest
59+ self ._utest = _utest
60+ super ().__init__ (parent , title , use_ttk = True )
5661
57- Toplevel .__init__ (self , parent )
58- self .withdraw () # Hide while configuring, especially geometry.
59- self .title (title )
60- self .transient (parent )
61- if not _utest : # Otherwise fail when directly run unittest.
62- self .grab_set ()
62+ def _show_modal (self ):
63+ "Suppress the modal wait when unit testing."
64+ if not self ._utest :
65+ super ()._show_modal ()
6366
64- _setup_dialog (self )
65- if self ._windowingsystem == 'aqua' :
66- self .bind ("<Command-.>" , self .cancel )
67- self .bind ('<Key-Escape>' , self .cancel )
68- self .protocol ("WM_DELETE_WINDOW" , self .cancel )
69- self .bind ('<Key-Return>' , self .ok )
70- self .bind ("<KP_Enter>" , self .ok )
71-
72- self .create_widgets ()
73- self .update_idletasks () # Need here for winfo_reqwidth below.
74- self .geometry ( # Center dialog over parent (or below htest box).
75- "+%d+%d" % (
76- parent .winfo_rootx () +
77- (parent .winfo_width ()/ 2 - self .winfo_reqwidth ()/ 2 ),
78- parent .winfo_rooty () +
79- ((parent .winfo_height ()/ 2 - self .winfo_reqheight ()/ 2 )
80- if not _htest else 150 )
81- ) )
82- self .resizable (height = False , width = False )
83-
84- if not _utest :
85- self .deiconify () # Unhide now that geometry set.
86- self .entry .focus_set ()
87- self .wait_window ()
88-
89- def create_widgets (self , ok_text = 'OK' ): # Do not replace.
90- """Create entry (rows, extras, buttons.
67+ def body (self , master ): # Do not replace.
68+ """Create entry widgets; return the entry for initial focus.
9169
9270 Entry stuff on rows 0-2, spanning cols 0-2.
93- Buttons on row 99, cols 1, 2 .
71+ Subclass extras (create_extra) go on rows 10-12 .
9472 """
9573 # Bind to self the widgets needed for entry_ok or unittest.
96- self .frame = frame = Frame (self , padding = 10 )
97- frame .grid (column = 0 , row = 0 , sticky = 'news' )
98- frame .grid_columnconfigure (0 , weight = 1 )
74+ self .frame = master
75+ master .columnconfigure (0 , weight = 1 )
9976
100- entrylabel = Label (frame , anchor = 'w' , justify = 'left' ,
101- text = self .message )
77+ entrylabel = Label (master , anchor = W , justify = LEFT , text = self .message )
10278 self .entryvar = StringVar (self , self .text0 )
103- self .entry = Entry (frame , width = 30 , textvariable = self .entryvar )
79+ self .entry = Entry (master , width = 30 , textvariable = self .entryvar )
10480 self .error_font = Font (name = 'TkCaptionFont' ,
10581 exists = True , root = self .parent )
106- self .entry_error = Label (frame , text = ' ' , foreground = 'red' ,
82+ self .entry_error = Label (master , text = ' ' , foreground = 'red' ,
10783 font = self .error_font )
10884 # Display or blank error by setting ['text'] =.
109- entrylabel .grid (column = 0 , row = 0 , columnspan = 3 , padx = 5 , sticky = W )
110- self .entry .grid (column = 0 , row = 1 , columnspan = 3 , padx = 5 , sticky = W + E ,
111- pady = [10 ,0 ])
112- self .entry_error .grid (column = 0 , row = 2 , columnspan = 3 , padx = 5 ,
113- sticky = W + E )
85+ entrylabel .grid (column = 0 , row = 0 , columnspan = 3 , padx = '2m' , pady = '2m' ,
86+ sticky = EW )
87+ self .entry .grid (column = 0 , row = 1 , columnspan = 3 , padx = '2m' ,
88+ pady = (0 , '2m' ), sticky = EW )
89+ self .entry_error .grid (column = 0 , row = 2 , columnspan = 3 , padx = '2m' ,
90+ pady = (0 , '2m' ), sticky = EW )
11491
11592 self .create_extra ()
11693
117- self .button_ok = Button (
118- frame , text = ok_text , default = 'active' , command = self .ok )
119- self .button_cancel = Button (
120- frame , text = 'Cancel' , command = self .cancel )
94+ self .resizable (height = False , width = False )
95+ if self ._windowingsystem == 'aqua' :
96+ self .bind ("<Command-.>" , self .cancel )
97+ self .bind ("<KP_Enter>" , self .ok )
98+ return self .entry
12199
122- self .button_ok .grid (column = 1 , row = 99 , padx = 5 )
123- self .button_cancel .grid (column = 2 , row = 99 , padx = 5 )
100+ def buttonbox (self ): # Do not replace.
101+ "Add the standard buttons and expose them for unittest."
102+ super ().buttonbox ()
103+ self .button_ok = self .nametowidget ('ok' )
104+ self .button_cancel = self .nametowidget ('cancel' )
124105
125106 def create_extra (self ): pass # Override to add widgets.
126107
@@ -136,28 +117,18 @@ def entry_ok(self): # Example: usually replace.
136117 return None
137118 return entry
138119
139- def ok (self , event = None ): # Do not replace.
140- ''' If entry is valid, bind it to 'result' and destroy tk widget .
120+ def validate (self ): # Do not replace.
121+ """ If entry is valid, store it in 'result' and return True .
141122
142- Otherwise leave dialog open for user to correct entry or cancel.
143- '''
123+ Otherwise show the error and leave the dialog open (Dialog.ok
124+ puts the focus back on the entry).
125+ """
144126 self .entry_error ['text' ] = ''
145127 entry = self .entry_ok ()
146- if entry is not None :
147- self .result = entry
148- self .destroy ()
149- else :
150- # [Ok] moves focus. (<Return> does not.) Move it back.
151- self .entry .focus_set ()
152-
153- def cancel (self , event = None ): # Do not replace.
154- "Set dialog result to None and destroy tk widget."
155- self .result = None
156- self .destroy ()
157-
158- def destroy (self ):
159- self .grab_release ()
160- super ().destroy ()
128+ if entry is None :
129+ return False
130+ self .result = entry
131+ return True
161132
162133
163134class SectionName (Query ):
@@ -260,9 +231,9 @@ def __init__(self, parent, title, *, menuitem='', filepath='',
260231 used_names = used_names , _htest = _htest , _utest = _utest )
261232
262233 def create_extra (self ):
263- "Add path widjets to rows 10-12."
234+ "Add path widgets to rows 10-12."
264235 frame = self .frame
265- pathlabel = Label (frame , anchor = 'w' , justify = 'left' ,
236+ pathlabel = Label (frame , anchor = W , justify = LEFT ,
266237 text = 'Help File Path: Enter URL or browse for file' )
267238 self .pathvar = StringVar (self , self .filepath )
268239 self .path = Entry (frame , textvariable = self .pathvar , width = 40 )
@@ -271,13 +242,14 @@ def create_extra(self):
271242 self .path_error = Label (frame , text = ' ' , foreground = 'red' ,
272243 font = self .error_font )
273244
274- pathlabel .grid (column = 0 , row = 10 , columnspan = 3 , padx = 5 , pady = [10 ,0 ],
275- sticky = W )
276- self .path .grid (column = 0 , row = 11 , columnspan = 2 , padx = 5 , sticky = W + E ,
277- pady = [10 ,0 ])
278- browse .grid (column = 2 , row = 11 , padx = 5 , sticky = W + S )
279- self .path_error .grid (column = 0 , row = 12 , columnspan = 3 , padx = 5 ,
280- sticky = W + E )
245+ pathlabel .grid (column = 0 , row = 10 , columnspan = 3 , padx = '2m' ,
246+ pady = (0 , '2m' ), sticky = EW )
247+ self .path .grid (column = 0 , row = 11 , columnspan = 2 , padx = '2m' ,
248+ pady = (0 , '2m' ), sticky = EW )
249+ browse .grid (column = 2 , row = 11 , padx = (0 , '2m' ), pady = (0 , '2m' ),
250+ sticky = W + S )
251+ self .path_error .grid (column = 0 , row = 12 , columnspan = 3 , padx = '2m' ,
252+ pady = (0 , '2m' ), sticky = EW )
281253
282254 def askfilename (self , filetypes , initdir , initfile ): # htest #
283255 # Extracted from browse_file so can mock for unittests.
@@ -361,9 +333,10 @@ def create_extra(self):
361333 self .args_error = Label (frame , text = ' ' , foreground = 'red' ,
362334 font = self .error_font )
363335
364- restart .grid (column = 0 , row = 10 , columnspan = 3 , padx = 5 , sticky = 'w' )
365- self .args_error .grid (column = 0 , row = 12 , columnspan = 3 , padx = 5 ,
366- sticky = 'we' )
336+ restart .grid (column = 0 , row = 10 , columnspan = 3 , padx = '2m' ,
337+ pady = (0 , '2m' ), sticky = W )
338+ self .args_error .grid (column = 0 , row = 12 , columnspan = 3 , padx = '2m' ,
339+ pady = (0 , '2m' ), sticky = EW )
367340
368341 def cli_args_ok (self ):
369342 "Return command line arg list or None if error."
0 commit comments