diff options
author | Terry Jan Reedy <tjreedy@udel.edu> | 2016-07-08 04:22:50 (GMT) |
---|---|---|
committer | Terry Jan Reedy <tjreedy@udel.edu> | 2016-07-08 04:22:50 (GMT) |
commit | 8b22c0aada7ef9fdf6226ee07bd95b4916e5bbb1 (patch) | |
tree | 34c1ec8489a871881a08cbabf690de37e7d6e945 /Lib/idlelib/query.py | |
parent | d6402a40d3b52aa945dfc0a875b05510e88a2dab (diff) | |
download | cpython-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.py | 163 |
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) |