|
|
"""
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.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# 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.utilimport osfrom sys import executable, platform # Platform is set for one test.
from tkinter import Toplevel, StringVar, W, E, S, Tkfrom tkinter.ttk import Frame, Button, Entry, Labelfrom tkinter import filedialogfrom tkinter.font import Font
class Query(Toplevel): """Base class for getting verified answer from a user.
For this base class, accept any non-blank string. """
def __init__(self, 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 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 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) self.withdraw() # Hide while configuring, especially geometry. # self.parent = parent self.title(title) self.message = message self.text0 = text0 self.used_names = used_names # self.transient(parent) self.grab_set() 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.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). "+150+150" ) if not _utest: self.deiconify() # Unhide now that geometry set. self.wait_window()
def create_widgets(self): # Call from override, if any. # Bind to self widgets needed for entry_ok or unittest. 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() self.error_font = Font(name='TkCaptionFont', exists=True, ) 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: self.showerror('blank line.') return None 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] moves focus. (<Return> does not.) 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 ModuleName(Query): "Get a module name for Open Module menu entry." # Used in open_module (editor.EditorWindow until move to iobinding).
def __init__(self, title, message, text0, *, _htest=False, _utest=False): super().__init__( title, message, text0=text0, _htest=_htest, _utest=_utest)
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: 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: self.showerror(str(msg)) return None if spec is None: self.showerror("module not found") return None if not isinstance(spec.loader, importlib.abc.SourceLoader): self.showerror("not a source-based module") return None try: file_path = spec.loader.get_filename(name) except AttributeError: self.showerror("loader does not support get_filename", parent=self) return None return file_path
if __name__ == "__main__": root = Tk() root.withdraw() result = ModuleName('Module','Enter module name','').result print(result) root.destroy()
|