summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib/query.py
diff options
context:
space:
mode:
authorTerry Jan Reedy <tjreedy@udel.edu>2016-07-08 04:22:50 (GMT)
committerTerry Jan Reedy <tjreedy@udel.edu>2016-07-08 04:22:50 (GMT)
commit8b22c0aada7ef9fdf6226ee07bd95b4916e5bbb1 (patch)
tree34c1ec8489a871881a08cbabf690de37e7d6e945 /Lib/idlelib/query.py
parentd6402a40d3b52aa945dfc0a875b05510e88a2dab (diff)
downloadcpython-8b22c0aada7ef9fdf6226ee07bd95b4916e5bbb1.zip
cpython-8b22c0aada7ef9fdf6226ee07bd95b4916e5bbb1.tar.gz
cpython-8b22c0aada7ef9fdf6226ee07bd95b4916e5bbb1.tar.bz2
Issue #27380: IDLE: add query.HelpSource class and tests.
Remove modules that are combined in new module.
Diffstat (limited to 'Lib/idlelib/query.py')
-rw-r--r--Lib/idlelib/query.py163
1 files changed, 127 insertions, 36 deletions
diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py
index fd9716f..d2d1472 100644
--- a/Lib/idlelib/query.py
+++ b/Lib/idlelib/query.py
@@ -13,10 +13,16 @@ 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.
+# generic and specific parts. 3.6 only, July 2016.
+# ModuleName.entry_ok came from editor.EditorWindow.load_module.
+# HelpSource was extracted from configHelpSourceEdit.py (temporarily
+# config_help.py), with darwin code moved from ok to path_ok.
import importlib
+import os
+from sys import executable, platform # Platform is set for one test.
from tkinter import Toplevel, StringVar
+from tkinter import filedialog
from tkinter.messagebox import showerror
from tkinter.ttk import Frame, Button, Entry, Label
@@ -25,8 +31,8 @@ class Query(Toplevel):
For this base class, accept any non-blank string.
"""
- def __init__(self, parent, title, message, text0='',
- *, _htest=False, _utest=False):
+ def __init__(self, parent, title, message, *, text0='', used_names={},
+ _htest=False, _utest=False):
"""Create popup, do not return until tk widget destroyed.
Additional subclass init must be done before calling this
@@ -35,10 +41,12 @@ class Query(Toplevel):
title - string, title of popup dialog
message - string, informational message to display
text0 - initial value for entry
+ used_names - names already in use
_htest - bool, change box location when running htest
_utest - bool, leave window hidden and not modal
"""
Toplevel.__init__(self, parent)
+ self.withdraw() # Hide while configuring, especially geometry.
self.configure(borderwidth=5)
self.resizable(height=False, width=False)
self.title(title)
@@ -49,27 +57,26 @@ class Query(Toplevel):
self.parent = parent
self.message = message
self.text0 = text0
+ self.used_names = used_names
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(
+ self.update_idletasks() # Needed here for winfo_reqwidth below.
+ self.geometry( # Center dialog over parent (or below htest box).
"+%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.deiconify() # Unhide now that geometry set.
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)
+ # Bind to self widgets needed for entry_ok or unittest.
+ self.frame = frame = Frame(self, borderwidth=2, relief='sunken', )
+ entrylabel = 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()
@@ -81,7 +88,7 @@ class Query(Toplevel):
width=8, command=self.cancel)
frame.pack(side='top', expand=True, fill='both')
- label.pack(padx=5, pady=5)
+ entrylabel.pack(padx=5, pady=5)
self.entry.pack(padx=5, pady=5)
buttons.pack(side='bottom')
self.button_ok.pack(side='left', padx=5)
@@ -93,7 +100,7 @@ class Query(Toplevel):
if not entry:
showerror(title='Entry Error',
message='Blank line.', parent=self)
- return
+ return None
return entry
def ok(self, event=None): # Do not replace.
@@ -106,7 +113,7 @@ class Query(Toplevel):
self.result = entry
self.destroy()
else:
- # [Ok] (but not <Return>) moves focus. Move it back.
+ # [Ok] moves focus. (<Return> does not.) Move it back.
self.entry.focus_set()
def cancel(self, event=None): # Do not replace.
@@ -117,13 +124,12 @@ class Query(Toplevel):
class SectionName(Query):
"Get a name for a config file section name."
+ # Used in ConfigDialog.GetNewKeysName, .GetNewThemeName (837)
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)
+ super().__init__(parent, title, message, used_names=used_names,
+ _htest=_htest, _utest=_utest)
def entry_ok(self):
"Return sensible ConfigParser section name or None."
@@ -131,16 +137,16 @@ class SectionName(Query):
if not name:
showerror(title='Name Error',
message='No name specified.', parent=self)
- return
+ return None
elif len(name)>30:
showerror(title='Name Error',
message='Name too long. It should be no more than '+
'30 characters.', parent=self)
- return
+ return None
elif name in self.used_names:
showerror(title='Name Error',
message='This name is already in use.', parent=self)
- return
+ return None
return name
@@ -148,48 +154,133 @@ 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='',
+ 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)
+ super().__init__(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
+ return None
+ # 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
+ return None
if spec is None:
showerror("Import Error", "module not found",
parent=self)
- return
+ return None
if not isinstance(spec.loader, importlib.abc.SourceLoader):
showerror("Import Error", "not a source-based module",
parent=self)
- return
+ return None
try:
file_path = spec.loader.get_filename(name)
except AttributeError:
showerror("Import Error",
"loader does not support get_filename",
parent=self)
- return
+ return None
return file_path
+class HelpSource(Query):
+ "Get menu name and help source for Help menu."
+ # Used in ConfigDialog.HelpListItemAdd/Edit, (941/9)
+
+ def __init__(self, parent, title, *, menuitem='', filepath='',
+ used_names={}, _htest=False, _utest=False):
+ """Get menu entry and url/local file for Additional Help.
+
+ User enters a name for the Help resource and a web url or file
+ name. The user can browse for the file.
+ """
+ self.filepath = filepath
+ message = 'Name for item on Help menu:'
+ super().__init__(parent, title, message, text0=menuitem,
+ used_names=used_names, _htest=_htest, _utest=_utest)
+
+ def create_widgets(self):
+ super().create_widgets()
+ frame = self.frame
+ pathlabel = Label(frame, anchor='w', justify='left',
+ text='Help File Path: Enter URL or browse for file')
+ self.pathvar = StringVar(self, self.filepath)
+ self.path = Entry(frame, textvariable=self.pathvar, width=40)
+ browse = Button(frame, text='Browse', width=8,
+ command=self.browse_file)
+
+ pathlabel.pack(anchor='w', padx=5, pady=3)
+ self.path.pack(anchor='w', padx=5, pady=3)
+ browse.pack(pady=3)
+
+ def askfilename(self, filetypes, initdir, initfile): # htest #
+ # Extracted from browse_file so can mock for unittests.
+ # Cannot unittest as cannot simulate button clicks.
+ # Test by running htest, such as by running this file.
+ return filedialog.Open(parent=self, filetypes=filetypes)\
+ .show(initialdir=initdir, initialfile=initfile)
+
+ def browse_file(self):
+ filetypes = [
+ ("HTML Files", "*.htm *.html", "TEXT"),
+ ("PDF Files", "*.pdf", "TEXT"),
+ ("Windows Help Files", "*.chm"),
+ ("Text Files", "*.txt", "TEXT"),
+ ("All Files", "*")]
+ path = self.pathvar.get()
+ if path:
+ dir, base = os.path.split(path)
+ else:
+ base = None
+ if platform[:3] == 'win':
+ dir = os.path.join(os.path.dirname(executable), 'Doc')
+ if not os.path.isdir(dir):
+ dir = os.getcwd()
+ else:
+ dir = os.getcwd()
+ file = self.askfilename(filetypes, dir, base)
+ if file:
+ self.pathvar.set(file)
+
+ item_ok = SectionName.entry_ok # localize for test override
+
+ def path_ok(self):
+ "Simple validity check for menu file path"
+ path = self.path.get().strip()
+ if not path: #no path specified
+ showerror(title='File Path Error',
+ message='No help file path specified.',
+ parent=self)
+ return None
+ elif not path.startswith(('www.', 'http')):
+ if path[:5] == 'file:':
+ path = path[5:]
+ if not os.path.exists(path):
+ showerror(title='File Path Error',
+ message='Help file path does not exist.',
+ parent=self)
+ return None
+ if platform == 'darwin': # for Mac Safari
+ path = "file://" + path
+ return path
+
+ def entry_ok(self):
+ "Return apparently valid (name, path) or None"
+ name = self.item_ok()
+ path = self.path_ok()
+ return None if name is None or path is None else (name, 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)
+ run(Query, HelpSource)