summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib/query.py
diff options
context:
space:
mode:
authorTerry Jan Reedy <tjreedy@udel.edu>2016-08-10 16:50:16 (GMT)
committerTerry Jan Reedy <tjreedy@udel.edu>2016-08-10 16:50:16 (GMT)
commit65db8544bf2ae83e181d407d23600e08f2b99249 (patch)
tree1c31cabeb73c943bd3dca1884b7026bcd625f072 /Lib/idlelib/query.py
parent83545f1c76076f52a6d6797c3ee059748edc5a25 (diff)
downloadcpython-65db8544bf2ae83e181d407d23600e08f2b99249.zip
cpython-65db8544bf2ae83e181d407d23600e08f2b99249.tar.gz
cpython-65db8544bf2ae83e181d407d23600e08f2b99249.tar.bz2
Issue #27621: Put query response validation error messages in query box
instead of in separate massagebox. Redo tests to match. Add Mac OSX refinements. Original patch by Mark Roseman.
Diffstat (limited to 'Lib/idlelib/query.py')
-rw-r--r--Lib/idlelib/query.py124
1 files changed, 72 insertions, 52 deletions
diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py
index c806c6b..a4584df 100644
--- a/Lib/idlelib/query.py
+++ b/Lib/idlelib/query.py
@@ -10,6 +10,8 @@ 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.
+Subclass ModuleName gets a name for File => Open Module.
+Subclass HelpSource gets menu item and path for additions to Help menu.
"""
# Query and Section name result from splitting GetCfgSectionNameDialog
# of configSectionNameDialog.py (temporarily config_sec.py) into
@@ -21,10 +23,10 @@ Configdialog uses it for new highlight theme and keybinding set names.
import importlib
import os
from sys import executable, platform # Platform is set for one test.
-from tkinter import Toplevel, StringVar
+from tkinter import Toplevel, StringVar, W, E, N, S
from tkinter import filedialog
-from tkinter.messagebox import showerror
from tkinter.ttk import Frame, Button, Entry, Label
+from tkinter.font import Font
class Query(Toplevel):
"""Base class for getting verified answer from a user.
@@ -47,18 +49,26 @@ class Query(Toplevel):
"""
Toplevel.__init__(self, parent)
self.withdraw() # Hide while configuring, especially geometry.
- self.configure(borderwidth=5)
- self.resizable(height=False, width=False)
+ self.parent = parent
self.title(title)
+ self.message = message
+ self.text0 = text0
+ self.used_names = used_names
self.transient(parent)
self.grab_set()
- self.bind('<Key-Return>', self.ok)
+ windowingsystem = self.tk.call('tk', 'windowingsystem')
+ if windowingsystem == 'aqua':
+ try:
+ self.tk.call('::tk::unsupported::MacWindowStyle', 'style',
+ self._w, 'moveableModal', '')
+ except:
+ pass
+ self.bind("<Command-.>", self.cancel)
self.bind('<Key-Escape>', self.cancel)
self.protocol("WM_DELETE_WINDOW", self.cancel)
- self.parent = parent
- self.message = message
- self.text0 = text0
- self.used_names = used_names
+ self.bind('<Key-Return>', self.ok)
+ self.bind("<KP_Enter>", self.ok)
+ self.resizable(height=False, width=False)
self.create_widgets()
self.update_idletasks() # Needed here for winfo_reqwidth below.
self.geometry( # Center dialog over parent (or below htest box).
@@ -75,32 +85,42 @@ class Query(Toplevel):
def create_widgets(self): # Call from override, if any.
# Bind to self widgets needed for entry_ok or unittest.
- self.frame = frame = Frame(self, borderwidth=2, relief='sunken', )
+ self.frame = frame = Frame(self, padding=10)
+ frame.grid(column=0, row=0, sticky='news')
+ frame.grid_columnconfigure(0, weight=1)
+
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()
-
- buttons = Frame(self)
- self.button_ok = Button(buttons, text='Ok', default='active',
- 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')
- 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)
- self.button_cancel.pack(side='right', padx=5)
+ self.error_font = Font(name='TkCaptionFont',
+ exists=True, root=self.parent)
+ self.entry_error = Label(frame, text=' ', foreground='red',
+ font=self.error_font)
+ self.button_ok = Button(
+ frame, text='OK', default='active', command=self.ok)
+ self.button_cancel = Button(
+ frame, text='Cancel', command=self.cancel)
+
+ entrylabel.grid(column=0, row=0, columnspan=3, padx=5, sticky=W)
+ self.entry.grid(column=0, row=1, columnspan=3, padx=5, sticky=W+E,
+ pady=[10,0])
+ self.entry_error.grid(column=0, row=2, columnspan=3, padx=5,
+ sticky=W+E)
+ self.button_ok.grid(column=1, row=99, padx=5)
+ self.button_cancel.grid(column=2, row=99, padx=5)
+
+ def showerror(self, message, widget=None):
+ #self.bell(displayof=self)
+ (widget or self.entry_error)['text'] = 'ERROR: ' + message
def entry_ok(self): # Example: usually replace.
"Return non-blank entry or None."
+ self.entry_error['text'] = ''
entry = self.entry.get().strip()
if not entry:
- showerror(title='Entry Error',
- message='Blank line.', parent=self)
+ self.showerror('blank line.')
return None
return entry
@@ -134,19 +154,16 @@ class SectionName(Query):
def entry_ok(self):
"Return sensible ConfigParser section name or None."
+ self.entry_error['text'] = ''
name = self.entry.get().strip()
if not name:
- showerror(title='Name Error',
- message='No name specified.', parent=self)
+ self.showerror('no name specified.')
return None
elif len(name)>30:
- showerror(title='Name Error',
- message='Name too long. It should be no more than '+
- '30 characters.', parent=self)
+ self.showerror('name is longer than 30 characters.')
return None
elif name in self.used_names:
- showerror(title='Name Error',
- message='This name is already in use.', parent=self)
+ self.showerror('name is already in use.')
return None
return name
@@ -162,30 +179,27 @@ class ModuleName(Query):
def entry_ok(self):
"Return entered module name as file path or None."
+ self.entry_error['text'] = ''
name = self.entry.get().strip()
if not name:
- showerror(title='Name Error',
- message='No name specified.', parent=self)
+ self.showerror('no name specified.')
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)
+ self.showerror(str(msg))
return None
if spec is None:
- showerror("Import Error", "module not found",
- parent=self)
+ self.showerror("module not found")
return None
if not isinstance(spec.loader, importlib.abc.SourceLoader):
- showerror("Import Error", "not a source-based module",
- parent=self)
+ self.showerror("not a source-based module")
return None
try:
file_path = spec.loader.get_filename(name)
except AttributeError:
- showerror("Import Error",
- "loader does not support get_filename",
+ self.showerror("loader does not support get_filename",
parent=self)
return None
return file_path
@@ -204,8 +218,9 @@ class HelpSource(Query):
"""
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)
+ super().__init__(
+ parent, title, message, text0=menuitem,
+ used_names=used_names, _htest=_htest, _utest=_utest)
def create_widgets(self):
super().create_widgets()
@@ -216,10 +231,16 @@ class HelpSource(Query):
self.path = Entry(frame, textvariable=self.pathvar, width=40)
browse = Button(frame, text='Browse', width=8,
command=self.browse_file)
+ self.path_error = Label(frame, text=' ', foreground='red',
+ font=self.error_font)
- pathlabel.pack(anchor='w', padx=5, pady=3)
- self.path.pack(anchor='w', padx=5, pady=3)
- browse.pack(pady=3)
+ pathlabel.grid(column=0, row=10, columnspan=3, padx=5, pady=[10,0],
+ sticky=W)
+ self.path.grid(column=0, row=11, columnspan=2, padx=5, sticky=W+E,
+ pady=[10,0])
+ browse.grid(column=2, row=11, padx=5, sticky=W+S)
+ self.path_error.grid(column=0, row=12, columnspan=3, padx=5,
+ sticky=W+E)
def askfilename(self, filetypes, initdir, initfile): # htest #
# Extracted from browse_file so can mock for unittests.
@@ -256,17 +277,14 @@ class HelpSource(Query):
"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)
+ self.showerror('no help file path specified.', self.path_error)
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)
+ self.showerror('help file path does not exist.',
+ self.path_error)
return None
if platform == 'darwin': # for Mac Safari
path = "file://" + path
@@ -274,6 +292,8 @@ class HelpSource(Query):
def entry_ok(self):
"Return apparently valid (name, path) or None"
+ self.entry_error['text'] = ''
+ self.path_error['text'] = ''
name = self.item_ok()
path = self.path_ok()
return None if name is None or path is None else (name, path)