ยปCore Development>Code coverage>Lib/idlelib/query.py

Python code coverage for Lib/idlelib/query.py

#countcontent
1n/a"""
2n/aDialogs that query users and verify the answer before accepting.
3n/aUse ttk widgets, limiting use to tcl/tk 8.5+, as in IDLE 3.6+.
4n/a
5n/aQuery is the generic base class for a popup dialog.
6n/aThe user must either enter a valid answer or close the dialog.
7n/aEntries are validated when <Return> is entered or [Ok] is clicked.
8n/aEntries are ignored when [Cancel] or [X] are clicked.
9n/aThe 'return value' is .result set to either a valid answer or None.
10n/a
11n/aSubclass SectionName gets a name for a new config file section.
12n/aConfigdialog uses it for new highlight theme and keybinding set names.
13n/aSubclass ModuleName gets a name for File => Open Module.
14n/aSubclass HelpSource gets menu item and path for additions to Help menu.
15n/a"""
16n/a# Query and Section name result from splitting GetCfgSectionNameDialog
17n/a# of configSectionNameDialog.py (temporarily config_sec.py) into
18n/a# generic and specific parts. 3.6 only, July 2016.
19n/a# ModuleName.entry_ok came from editor.EditorWindow.load_module.
20n/a# HelpSource was extracted from configHelpSourceEdit.py (temporarily
21n/a# config_help.py), with darwin code moved from ok to path_ok.
22n/a
23n/aimport importlib
24n/aimport os
25n/afrom sys import executable, platform # Platform is set for one test.
26n/a
27n/afrom tkinter import Toplevel, StringVar, W, E, N, S
28n/afrom tkinter.ttk import Frame, Button, Entry, Label
29n/afrom tkinter import filedialog
30n/afrom tkinter.font import Font
31n/a
32n/aclass Query(Toplevel):
33n/a """Base class for getting verified answer from a user.
34n/a
35n/a For this base class, accept any non-blank string.
36n/a """
37n/a def __init__(self, parent, title, message, *, text0='', used_names={},
38n/a _htest=False, _utest=False):
39n/a """Create popup, do not return until tk widget destroyed.
40n/a
41n/a Additional subclass init must be done before calling this
42n/a unless _utest=True is passed to suppress wait_window().
43n/a
44n/a title - string, title of popup dialog
45n/a message - string, informational message to display
46n/a text0 - initial value for entry
47n/a used_names - names already in use
48n/a _htest - bool, change box location when running htest
49n/a _utest - bool, leave window hidden and not modal
50n/a """
51n/a Toplevel.__init__(self, parent)
52n/a self.withdraw() # Hide while configuring, especially geometry.
53n/a self.parent = parent
54n/a self.title(title)
55n/a self.message = message
56n/a self.text0 = text0
57n/a self.used_names = used_names
58n/a self.transient(parent)
59n/a self.grab_set()
60n/a windowingsystem = self.tk.call('tk', 'windowingsystem')
61n/a if windowingsystem == 'aqua':
62n/a try:
63n/a self.tk.call('::tk::unsupported::MacWindowStyle', 'style',
64n/a self._w, 'moveableModal', '')
65n/a except:
66n/a pass
67n/a self.bind("<Command-.>", self.cancel)
68n/a self.bind('<Key-Escape>', self.cancel)
69n/a self.protocol("WM_DELETE_WINDOW", self.cancel)
70n/a self.bind('<Key-Return>', self.ok)
71n/a self.bind("<KP_Enter>", self.ok)
72n/a self.resizable(height=False, width=False)
73n/a self.create_widgets()
74n/a self.update_idletasks() # Needed here for winfo_reqwidth below.
75n/a self.geometry( # Center dialog over parent (or below htest box).
76n/a "+%d+%d" % (
77n/a parent.winfo_rootx() +
78n/a (parent.winfo_width()/2 - self.winfo_reqwidth()/2),
79n/a parent.winfo_rooty() +
80n/a ((parent.winfo_height()/2 - self.winfo_reqheight()/2)
81n/a if not _htest else 150)
82n/a ) )
83n/a if not _utest:
84n/a self.deiconify() # Unhide now that geometry set.
85n/a self.wait_window()
86n/a
87n/a def create_widgets(self): # Call from override, if any.
88n/a # Bind to self widgets needed for entry_ok or unittest.
89n/a self.frame = frame = Frame(self, padding=10)
90n/a frame.grid(column=0, row=0, sticky='news')
91n/a frame.grid_columnconfigure(0, weight=1)
92n/a
93n/a entrylabel = Label(frame, anchor='w', justify='left',
94n/a text=self.message)
95n/a self.entryvar = StringVar(self, self.text0)
96n/a self.entry = Entry(frame, width=30, textvariable=self.entryvar)
97n/a self.entry.focus_set()
98n/a self.error_font = Font(name='TkCaptionFont',
99n/a exists=True, root=self.parent)
100n/a self.entry_error = Label(frame, text=' ', foreground='red',
101n/a font=self.error_font)
102n/a self.button_ok = Button(
103n/a frame, text='OK', default='active', command=self.ok)
104n/a self.button_cancel = Button(
105n/a frame, text='Cancel', command=self.cancel)
106n/a
107n/a entrylabel.grid(column=0, row=0, columnspan=3, padx=5, sticky=W)
108n/a self.entry.grid(column=0, row=1, columnspan=3, padx=5, sticky=W+E,
109n/a pady=[10,0])
110n/a self.entry_error.grid(column=0, row=2, columnspan=3, padx=5,
111n/a sticky=W+E)
112n/a self.button_ok.grid(column=1, row=99, padx=5)
113n/a self.button_cancel.grid(column=2, row=99, padx=5)
114n/a
115n/a def showerror(self, message, widget=None):
116n/a #self.bell(displayof=self)
117n/a (widget or self.entry_error)['text'] = 'ERROR: ' + message
118n/a
119n/a def entry_ok(self): # Example: usually replace.
120n/a "Return non-blank entry or None."
121n/a self.entry_error['text'] = ''
122n/a entry = self.entry.get().strip()
123n/a if not entry:
124n/a self.showerror('blank line.')
125n/a return None
126n/a return entry
127n/a
128n/a def ok(self, event=None): # Do not replace.
129n/a '''If entry is valid, bind it to 'result' and destroy tk widget.
130n/a
131n/a Otherwise leave dialog open for user to correct entry or cancel.
132n/a '''
133n/a entry = self.entry_ok()
134n/a if entry is not None:
135n/a self.result = entry
136n/a self.destroy()
137n/a else:
138n/a # [Ok] moves focus. (<Return> does not.) Move it back.
139n/a self.entry.focus_set()
140n/a
141n/a def cancel(self, event=None): # Do not replace.
142n/a "Set dialog result to None and destroy tk widget."
143n/a self.result = None
144n/a self.destroy()
145n/a
146n/a
147n/aclass SectionName(Query):
148n/a "Get a name for a config file section name."
149n/a # Used in ConfigDialog.GetNewKeysName, .GetNewThemeName (837)
150n/a
151n/a def __init__(self, parent, title, message, used_names,
152n/a *, _htest=False, _utest=False):
153n/a super().__init__(parent, title, message, used_names=used_names,
154n/a _htest=_htest, _utest=_utest)
155n/a
156n/a def entry_ok(self):
157n/a "Return sensible ConfigParser section name or None."
158n/a self.entry_error['text'] = ''
159n/a name = self.entry.get().strip()
160n/a if not name:
161n/a self.showerror('no name specified.')
162n/a return None
163n/a elif len(name)>30:
164n/a self.showerror('name is longer than 30 characters.')
165n/a return None
166n/a elif name in self.used_names:
167n/a self.showerror('name is already in use.')
168n/a return None
169n/a return name
170n/a
171n/a
172n/aclass ModuleName(Query):
173n/a "Get a module name for Open Module menu entry."
174n/a # Used in open_module (editor.EditorWindow until move to iobinding).
175n/a
176n/a def __init__(self, parent, title, message, text0,
177n/a *, _htest=False, _utest=False):
178n/a super().__init__(parent, title, message, text0=text0,
179n/a _htest=_htest, _utest=_utest)
180n/a
181n/a def entry_ok(self):
182n/a "Return entered module name as file path or None."
183n/a self.entry_error['text'] = ''
184n/a name = self.entry.get().strip()
185n/a if not name:
186n/a self.showerror('no name specified.')
187n/a return None
188n/a # XXX Ought to insert current file's directory in front of path.
189n/a try:
190n/a spec = importlib.util.find_spec(name)
191n/a except (ValueError, ImportError) as msg:
192n/a self.showerror(str(msg))
193n/a return None
194n/a if spec is None:
195n/a self.showerror("module not found")
196n/a return None
197n/a if not isinstance(spec.loader, importlib.abc.SourceLoader):
198n/a self.showerror("not a source-based module")
199n/a return None
200n/a try:
201n/a file_path = spec.loader.get_filename(name)
202n/a except AttributeError:
203n/a self.showerror("loader does not support get_filename",
204n/a parent=self)
205n/a return None
206n/a return file_path
207n/a
208n/a
209n/aclass HelpSource(Query):
210n/a "Get menu name and help source for Help menu."
211n/a # Used in ConfigDialog.HelpListItemAdd/Edit, (941/9)
212n/a
213n/a def __init__(self, parent, title, *, menuitem='', filepath='',
214n/a used_names={}, _htest=False, _utest=False):
215n/a """Get menu entry and url/local file for Additional Help.
216n/a
217n/a User enters a name for the Help resource and a web url or file
218n/a name. The user can browse for the file.
219n/a """
220n/a self.filepath = filepath
221n/a message = 'Name for item on Help menu:'
222n/a super().__init__(
223n/a parent, title, message, text0=menuitem,
224n/a used_names=used_names, _htest=_htest, _utest=_utest)
225n/a
226n/a def create_widgets(self):
227n/a super().create_widgets()
228n/a frame = self.frame
229n/a pathlabel = Label(frame, anchor='w', justify='left',
230n/a text='Help File Path: Enter URL or browse for file')
231n/a self.pathvar = StringVar(self, self.filepath)
232n/a self.path = Entry(frame, textvariable=self.pathvar, width=40)
233n/a browse = Button(frame, text='Browse', width=8,
234n/a command=self.browse_file)
235n/a self.path_error = Label(frame, text=' ', foreground='red',
236n/a font=self.error_font)
237n/a
238n/a pathlabel.grid(column=0, row=10, columnspan=3, padx=5, pady=[10,0],
239n/a sticky=W)
240n/a self.path.grid(column=0, row=11, columnspan=2, padx=5, sticky=W+E,
241n/a pady=[10,0])
242n/a browse.grid(column=2, row=11, padx=5, sticky=W+S)
243n/a self.path_error.grid(column=0, row=12, columnspan=3, padx=5,
244n/a sticky=W+E)
245n/a
246n/a def askfilename(self, filetypes, initdir, initfile): # htest #
247n/a # Extracted from browse_file so can mock for unittests.
248n/a # Cannot unittest as cannot simulate button clicks.
249n/a # Test by running htest, such as by running this file.
250n/a return filedialog.Open(parent=self, filetypes=filetypes)\
251n/a .show(initialdir=initdir, initialfile=initfile)
252n/a
253n/a def browse_file(self):
254n/a filetypes = [
255n/a ("HTML Files", "*.htm *.html", "TEXT"),
256n/a ("PDF Files", "*.pdf", "TEXT"),
257n/a ("Windows Help Files", "*.chm"),
258n/a ("Text Files", "*.txt", "TEXT"),
259n/a ("All Files", "*")]
260n/a path = self.pathvar.get()
261n/a if path:
262n/a dir, base = os.path.split(path)
263n/a else:
264n/a base = None
265n/a if platform[:3] == 'win':
266n/a dir = os.path.join(os.path.dirname(executable), 'Doc')
267n/a if not os.path.isdir(dir):
268n/a dir = os.getcwd()
269n/a else:
270n/a dir = os.getcwd()
271n/a file = self.askfilename(filetypes, dir, base)
272n/a if file:
273n/a self.pathvar.set(file)
274n/a
275n/a item_ok = SectionName.entry_ok # localize for test override
276n/a
277n/a def path_ok(self):
278n/a "Simple validity check for menu file path"
279n/a path = self.path.get().strip()
280n/a if not path: #no path specified
281n/a self.showerror('no help file path specified.', self.path_error)
282n/a return None
283n/a elif not path.startswith(('www.', 'http')):
284n/a if path[:5] == 'file:':
285n/a path = path[5:]
286n/a if not os.path.exists(path):
287n/a self.showerror('help file path does not exist.',
288n/a self.path_error)
289n/a return None
290n/a if platform == 'darwin': # for Mac Safari
291n/a path = "file://" + path
292n/a return path
293n/a
294n/a def entry_ok(self):
295n/a "Return apparently valid (name, path) or None"
296n/a self.entry_error['text'] = ''
297n/a self.path_error['text'] = ''
298n/a name = self.item_ok()
299n/a path = self.path_ok()
300n/a return None if name is None or path is None else (name, path)
301n/a
302n/a
303n/aif __name__ == '__main__':
304n/a import unittest
305n/a unittest.main('idlelib.idle_test.test_query', verbosity=2, exit=False)
306n/a
307n/a from idlelib.idle_test.htest import run
308n/a run(Query, HelpSource)