summaryrefslogtreecommitdiffstats
path: root/Lib/idlelib/query.py
diff options
context:
space:
mode:
authorTerry Jan Reedy <tjreedy@udel.edu>2016-06-27 02:05:10 (GMT)
committerTerry Jan Reedy <tjreedy@udel.edu>2016-06-27 02:05:10 (GMT)
commit68a53c5d3964ae2f4658491822f83cf36510f39b (patch)
tree6124e372ec80523d1718af3882c1b811febf67c5 /Lib/idlelib/query.py
parent754a5c1a1d8cf0b3d61fb4552c57350b0d849089 (diff)
downloadcpython-68a53c5d3964ae2f4658491822f83cf36510f39b.zip
cpython-68a53c5d3964ae2f4658491822f83cf36510f39b.tar.gz
cpython-68a53c5d3964ae2f4658491822f83cf36510f39b.tar.bz2
Issue #27380: IDLE: add base Query dialog, with ttk widgets and subclass
SectionName. These split class GetCfgSectionNameDialog from configSectionNameDialog.py, temporarily renamed config_sec.py in 3.7.9a2. More Query subclasses are planned.
Diffstat (limited to 'Lib/idlelib/query.py')
-rw-r--r--Lib/idlelib/query.py148
1 files changed, 148 insertions, 0 deletions
diff --git a/Lib/idlelib/query.py b/Lib/idlelib/query.py
new file mode 100644
index 0000000..e3937a1
--- /dev/null
+++ b/Lib/idlelib/query.py
@@ -0,0 +1,148 @@
+"""
+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.
+
+from tkinter import FALSE, TRUE, Toplevel
+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,
+ *, _htest=False, _utest=False): # Call from override.
+ """Create popup, do not return until tk widget destroyed.
+
+ Additional subclass init must be done before calling this.
+
+ title - string, title of popup dialog
+ message - string, informational message to display
+ _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.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.
+ frame = Frame(self, borderwidth=2, relief='sunken', )
+ label = Label(frame, anchor='w', justify='left',
+ text=self.message)
+ self.entry = Entry(frame, width=30) # Bind name for entry_ok.
+ self.entry.focus_set()
+
+ buttons = Frame(self) # Bind buttons for invoke in unittest.
+ 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): # Usually replace.
+ "Check that entry not blank."
+ entry = self.entry.get().strip()
+ if not entry:
+ showerror(title='Entry Error',
+ message='Blank line.', parent=self)
+ 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:
+ 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)
+ # This call does ot return until tk widget is destroyed.
+
+ def entry_ok(self):
+ '''Stripping entered name, check that it is a sensible
+ ConfigParser file section name. Return it if it is, '' if not.
+ '''
+ name = self.entry.get().strip()
+ if not name:
+ showerror(title='Name Error',
+ message='No name specified.', parent=self)
+ elif len(name)>30:
+ showerror(title='Name Error',
+ message='Name too long. It should be no more than '+
+ '30 characters.', parent=self)
+ name = ''
+ elif name in self.used_names:
+ showerror(title='Name Error',
+ message='This name is already in use.', parent=self)
+ name = ''
+ return name
+
+
+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)