1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
"""
Dialogs that query users and verify the answer before accepting.
Use ttk widgets, limiting use to tcl/tk 8.5+, as in IDLE 3.6+.
Query is the generic base class for a popup dialog.
The user must either enter a valid answer or close the dialog.
Entries are validated when <Return> is entered or [Ok] is clicked.
Entries are ignored when [Cancel] or [X] are clicked.
The 'return value' is .result set to either a valid answer or None.
Subclass SectionName gets a name for a new config file section.
Configdialog uses it for new highlight theme and keybinding set names.
"""
# Query and Section name result from splitting GetCfgSectionNameDialog
# of configSectionNameDialog.py (temporarily config_sec.py) into
# generic and specific parts.
import importlib
from tkinter import Toplevel, StringVar
from tkinter.messagebox import showerror
from tkinter.ttk import Frame, Button, Entry, Label
class Query(Toplevel):
"""Base class for getting verified answer from a user.
For this base class, accept any non-blank string.
"""
def __init__(self, parent, title, message, text0='',
*, _htest=False, _utest=False):
"""Create popup, do not return until tk widget destroyed.
Additional subclass init must be done before calling this
unless _utest=True is passed to suppress wait_window().
title - string, title of popup dialog
message - string, informational message to display
text0 - initial value for entry
_htest - bool, change box location when running htest
_utest - bool, leave window hidden and not modal
"""
Toplevel.__init__(self, parent)
self.configure(borderwidth=5)
self.resizable(height=False, width=False)
self.title(title)
self.transient(parent)
self.grab_set()
self.bind('<Key-Return>', self.ok)
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.parent = parent
self.message = message
self.text0 = text0
self.create_widgets()
self.update_idletasks()
#needs to be done here so that the winfo_reqwidth is valid
self.withdraw() # Hide while configuring, especially geometry.
self.geometry(
"+%d+%d" % (
parent.winfo_rootx() +
(parent.winfo_width()/2 - self.winfo_reqwidth()/2),
parent.winfo_rooty() +
((parent.winfo_height()/2 - self.winfo_reqheight()/2)
if not _htest else 150)
) ) #centre dialog over parent (or below htest box)
if not _utest:
self.deiconify() #geometry set, unhide
self.wait_window()
def create_widgets(self): # Call from override, if any.
# Bind widgets needed for entry_ok or unittest to self.
frame = Frame(self, borderwidth=2, relief='sunken', )
label = Label(frame, anchor='w', justify='left',
text=self.message)
self.entryvar = StringVar(self, self.text0)
self.entry = Entry(frame, width=30, textvariable=self.entryvar)
self.entry.focus_set()
buttons = Frame(self)
self.button_ok = Button(buttons, text='Ok',
width=8, command=self.ok)
self.button_cancel = Button(buttons, text='Cancel',
width=8, command=self.cancel)
frame.pack(side='top', expand=True, fill='both')
label.pack(padx=5, pady=5)
self.entry.pack(padx=5, pady=5)
buttons.pack(side='bottom')
self.button_ok.pack(side='left', padx=5)
self.button_cancel.pack(side='right', padx=5)
def entry_ok(self): # Example: usually replace.
"Return non-blank entry or None."
entry = self.entry.get().strip()
if not entry:
showerror(title='Entry Error',
message='Blank line.', parent=self)
return
return entry
def ok(self, event=None): # Do not replace.
'''If entry is valid, bind it to 'result' and destroy tk widget.
Otherwise leave dialog open for user to correct entry or cancel.
'''
entry = self.entry_ok()
if entry is not None:
self.result = entry
self.destroy()
else:
# [Ok] (but not <Return>) moves focus. Move it back.
self.entry.focus_set()
def cancel(self, event=None): # Do not replace.
"Set dialog result to None and destroy tk widget."
self.result = None
self.destroy()
class SectionName(Query):
"Get a name for a config file section name."
def __init__(self, parent, title, message, used_names,
*, _htest=False, _utest=False):
"used_names - collection of strings already in use"
self.used_names = used_names
Query.__init__(self, parent, title, message,
_htest=_htest, _utest=_utest)
def entry_ok(self):
"Return sensible ConfigParser section name or None."
name = self.entry.get().strip()
if not name:
showerror(title='Name Error',
message='No name specified.', parent=self)
return
elif len(name)>30:
showerror(title='Name Error',
message='Name too long. It should be no more than '+
'30 characters.', parent=self)
return
elif name in self.used_names:
showerror(title='Name Error',
message='This name is already in use.', parent=self)
return
return name
class ModuleName(Query):
"Get a module name for Open Module menu entry."
# Used in open_module (editor.EditorWindow until move to iobinding).
def __init__(self, parent, title, message, text0='',
*, _htest=False, _utest=False):
"""text0 - name selected in text before Open Module invoked"
"""
Query.__init__(self, parent, title, message, text0=text0,
_htest=_htest, _utest=_utest)
def entry_ok(self):
"Return entered module name as file path or None."
# Moved here from Editor_Window.load_module 2016 July.
name = self.entry.get().strip()
if not name:
showerror(title='Name Error',
message='No name specified.', parent=self)
return
# XXX Ought to insert current file's directory in front of path
try:
spec = importlib.util.find_spec(name)
except (ValueError, ImportError) as msg:
showerror("Import Error", str(msg), parent=self)
return
if spec is None:
showerror("Import Error", "module not found",
parent=self)
return
if not isinstance(spec.loader, importlib.abc.SourceLoader):
showerror("Import Error", "not a source-based module",
parent=self)
return
try:
file_path = spec.loader.get_filename(name)
except AttributeError:
showerror("Import Error",
"loader does not support get_filename",
parent=self)
return
return file_path
if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_query', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(Query)
|