| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | """
 | 
					
						
							|  |  |  | Dialogs that query users and verify the answer before accepting. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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. | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  | Subclass ModuleName gets a name for File => Open Module. | 
					
						
							|  |  |  | Subclass HelpSource gets menu item and path for additions to Help menu. | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | """
 | 
					
						
							|  |  |  | # Query and Section name result from splitting GetCfgSectionNameDialog | 
					
						
							|  |  |  | # of configSectionNameDialog.py (temporarily config_sec.py) into | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  | # 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. | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-28 08:02:47 +02:00
										 |  |  | import importlib.util, importlib.abc | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  | import os | 
					
						
							| 
									
										
										
										
											2019-06-17 22:24:10 -04:00
										 |  |  | import shlex | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  | from sys import executable, platform  # Platform is set for one test. | 
					
						
							| 
									
										
										
										
											2016-08-31 00:50:55 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-17 22:24:10 -04:00
										 |  |  | from tkinter import Toplevel, StringVar, BooleanVar, W, E, S | 
					
						
							|  |  |  | from tkinter.ttk import Frame, Button, Entry, Label, Checkbutton | 
					
						
							| 
									
										
										
										
											2016-08-31 00:50:55 -04:00
										 |  |  | from tkinter import filedialog | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  | from tkinter.font import Font | 
					
						
							| 
									
										
										
										
											2021-04-25 13:07:58 +03:00
										 |  |  | from tkinter.simpledialog import _setup_dialog | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | class Query(Toplevel): | 
					
						
							|  |  |  |     """Base class for getting verified answer from a user.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     For this base class, accept any non-blank string. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |     def __init__(self, parent, title, message, *, text0='', used_names={}, | 
					
						
							|  |  |  |                  _htest=False, _utest=False): | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |         """Create modal popup, return when destroyed.
 | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |         Additional subclass init must be done before this unless | 
					
						
							|  |  |  |         _utest=True is passed to suppress wait_window(). | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         title - string, title of popup dialog | 
					
						
							|  |  |  |         message - string, informational message to display | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |         text0 - initial value for entry | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |         used_names - names already in use | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         _htest - bool, change box location when running htest | 
					
						
							|  |  |  |         _utest - bool, leave window hidden and not modal | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |         self.parent = parent  # Needed for Font call. | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         self.message = message | 
					
						
							|  |  |  |         self.text0 = text0 | 
					
						
							|  |  |  |         self.used_names = used_names | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         Toplevel.__init__(self, parent) | 
					
						
							|  |  |  |         self.withdraw()  # Hide while configuring, especially geometry. | 
					
						
							|  |  |  |         self.title(title) | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         self.transient(parent) | 
					
						
							| 
									
										
										
										
											2020-06-28 08:02:47 +02:00
										 |  |  |         if not _utest:  # Otherwise fail when directly run unittest. | 
					
						
							|  |  |  |             self.grab_set() | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-25 13:07:58 +03:00
										 |  |  |         _setup_dialog(self) | 
					
						
							|  |  |  |         if self._windowingsystem == 'aqua': | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |             self.bind("<Command-.>", self.cancel) | 
					
						
							| 
									
										
										
										
											2016-07-25 20:58:43 -04:00
										 |  |  |         self.bind('<Key-Escape>', self.cancel) | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         self.protocol("WM_DELETE_WINDOW", self.cancel) | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         self.bind('<Key-Return>', self.ok) | 
					
						
							|  |  |  |         self.bind("<KP_Enter>", self.ok) | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         self.create_widgets() | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |         self.update_idletasks()  # Need here for winfo_reqwidth below. | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |         self.geometry(  # Center dialog over parent (or below htest box). | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |                 "+%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) | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |                 ) ) | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |         self.resizable(height=False, width=False) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         if not _utest: | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             self.deiconify()  # Unhide now that geometry set. | 
					
						
							| 
									
										
										
										
											2022-02-03 17:06:17 -05:00
										 |  |  |             self.entry.focus_set() | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |             self.wait_window() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |     def create_widgets(self, ok_text='OK'):  # Do not replace. | 
					
						
							|  |  |  |         """Create entry (rows, extras, buttons.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         Entry stuff on rows 0-2, spanning cols 0-2. | 
					
						
							|  |  |  |         Buttons on row 99, cols 1, 2. | 
					
						
							|  |  |  |         """
 | 
					
						
							|  |  |  |         # Bind to self the widgets needed for entry_ok or unittest. | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         self.frame = frame = Frame(self, padding=10) | 
					
						
							|  |  |  |         frame.grid(column=0, row=0, sticky='news') | 
					
						
							|  |  |  |         frame.grid_columnconfigure(0, weight=1) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |         entrylabel = Label(frame, anchor='w', justify='left', | 
					
						
							|  |  |  |                            text=self.message) | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |         self.entryvar = StringVar(self, self.text0) | 
					
						
							|  |  |  |         self.entry = Entry(frame, width=30, textvariable=self.entryvar) | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         self.error_font = Font(name='TkCaptionFont', | 
					
						
							|  |  |  |                                exists=True, root=self.parent) | 
					
						
							|  |  |  |         self.entry_error = Label(frame, text=' ', foreground='red', | 
					
						
							|  |  |  |                                  font=self.error_font) | 
					
						
							| 
									
										
										
										
											2020-03-09 01:38:07 -04:00
										 |  |  |         # Display or blank error by setting ['text'] =. | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         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) | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |         self.create_extra() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         self.button_ok = Button( | 
					
						
							|  |  |  |                 frame, text=ok_text, default='active', command=self.ok) | 
					
						
							|  |  |  |         self.button_cancel = Button( | 
					
						
							|  |  |  |                 frame, text='Cancel', command=self.cancel) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         self.button_ok.grid(column=1, row=99, padx=5) | 
					
						
							|  |  |  |         self.button_cancel.grid(column=2, row=99, padx=5) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |     def create_extra(self): pass  # Override to add widgets. | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |     def showerror(self, message, widget=None): | 
					
						
							|  |  |  |         #self.bell(displayof=self) | 
					
						
							|  |  |  |         (widget or self.entry_error)['text'] = 'ERROR: ' + message | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |     def entry_ok(self):  # Example: usually replace. | 
					
						
							|  |  |  |         "Return non-blank entry or None." | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         entry = self.entry.get().strip() | 
					
						
							|  |  |  |         if not entry: | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |             self.showerror('blank line.') | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         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. | 
					
						
							|  |  |  |         '''
 | 
					
						
							| 
									
										
										
										
											2020-03-09 01:38:07 -04:00
										 |  |  |         self.entry_error['text'] = '' | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         entry = self.entry_ok() | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |         if entry is not None: | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |             self.result = entry | 
					
						
							|  |  |  |             self.destroy() | 
					
						
							|  |  |  |         else: | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             # [Ok] moves focus.  (<Return> does not.)  Move it back. | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |             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() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-08-02 09:18:29 +03:00
										 |  |  |     def destroy(self): | 
					
						
							|  |  |  |         self.grab_release() | 
					
						
							|  |  |  |         super().destroy() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  | class SectionName(Query): | 
					
						
							|  |  |  |     "Get a name for a config file section name." | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |     # Used in ConfigDialog.GetNewKeysName, .GetNewThemeName (837) | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, parent, title, message, used_names, | 
					
						
							|  |  |  |                  *, _htest=False, _utest=False): | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |         super().__init__(parent, title, message, used_names=used_names, | 
					
						
							|  |  |  |                          _htest=_htest, _utest=_utest) | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def entry_ok(self): | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |         "Return sensible ConfigParser section name or None." | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         name = self.entry.get().strip() | 
					
						
							|  |  |  |         if not name: | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |             self.showerror('no name specified.') | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         elif len(name)>30: | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |             self.showerror('name is longer than 30 characters.') | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         elif name in self.used_names: | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |             self.showerror('name is already in use.') | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  |         return name | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  | class ModuleName(Query): | 
					
						
							|  |  |  |     "Get a module name for Open Module menu entry." | 
					
						
							|  |  |  |     # Used in open_module (editor.EditorWindow until move to iobinding). | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |     def __init__(self, parent, title, message, text0, | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |                  *, _htest=False, _utest=False): | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |         super().__init__(parent, title, message, text0=text0, | 
					
						
							|  |  |  |                        _htest=_htest, _utest=_utest) | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def entry_ok(self): | 
					
						
							|  |  |  |         "Return entered module name as file path or None." | 
					
						
							|  |  |  |         name = self.entry.get().strip() | 
					
						
							|  |  |  |         if not name: | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |             self.showerror('no name specified.') | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							|  |  |  |         # XXX Ought to insert current file's directory in front of path. | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |         try: | 
					
						
							|  |  |  |             spec = importlib.util.find_spec(name) | 
					
						
							|  |  |  |         except (ValueError, ImportError) as msg: | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |             self.showerror(str(msg)) | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |         if spec is None: | 
					
						
							| 
									
										
										
										
											2020-06-28 08:02:47 +02:00
										 |  |  |             self.showerror("module not found.") | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |         if not isinstance(spec.loader, importlib.abc.SourceLoader): | 
					
						
							| 
									
										
										
										
											2020-06-28 08:02:47 +02:00
										 |  |  |             self.showerror("not a source-based module.") | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |         try: | 
					
						
							|  |  |  |             file_path = spec.loader.get_filename(name) | 
					
						
							|  |  |  |         except AttributeError: | 
					
						
							| 
									
										
										
										
											2020-06-28 08:02:47 +02:00
										 |  |  |             self.showerror("loader does not support get_filename.") | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							| 
									
										
										
										
											2020-06-28 08:02:47 +02:00
										 |  |  |         except ImportError: | 
					
						
							|  |  |  |             # Some special modules require this (e.g. os.path) | 
					
						
							|  |  |  |             try: | 
					
						
							|  |  |  |                 file_path = spec.loader.get_filename() | 
					
						
							|  |  |  |             except TypeError: | 
					
						
							|  |  |  |                 self.showerror("loader failed to get filename.") | 
					
						
							|  |  |  |                 return None | 
					
						
							| 
									
										
										
										
											2016-07-03 19:11:13 -04:00
										 |  |  |         return file_path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-03-09 16:51:20 -04:00
										 |  |  | class Goto(Query): | 
					
						
							|  |  |  |     "Get a positive line number for editor Go To Line." | 
					
						
							|  |  |  |     # Used in editor.EditorWindow.goto_line_event. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def entry_ok(self): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             lineno = int(self.entry.get()) | 
					
						
							|  |  |  |         except ValueError: | 
					
						
							|  |  |  |             self.showerror('not a base 10 integer.') | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         if lineno <= 0: | 
					
						
							|  |  |  |             self.showerror('not a positive integer.') | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         return lineno | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  | 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:' | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         super().__init__( | 
					
						
							|  |  |  |                 parent, title, message, text0=menuitem, | 
					
						
							|  |  |  |                 used_names=used_names, _htest=_htest, _utest=_utest) | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |     def create_extra(self): | 
					
						
							|  |  |  |         "Add path widjets to rows 10-12." | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |         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) | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         self.path_error = Label(frame, text=' ', foreground='red', | 
					
						
							|  |  |  |                                 font=self.error_font) | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         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) | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     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 | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |             self.showerror('no help file path specified.', self.path_error) | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |             return None | 
					
						
							|  |  |  |         elif not path.startswith(('www.', 'http')): | 
					
						
							|  |  |  |             if path[:5] == 'file:': | 
					
						
							|  |  |  |                 path = path[5:] | 
					
						
							|  |  |  |             if not os.path.exists(path): | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |                 self.showerror('help file path does not exist.', | 
					
						
							|  |  |  |                                self.path_error) | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |                 return None | 
					
						
							|  |  |  |             if platform == 'darwin':  # for Mac Safari | 
					
						
							|  |  |  |                 path =  "file://" + path | 
					
						
							|  |  |  |         return path | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def entry_ok(self): | 
					
						
							|  |  |  |         "Return apparently valid (name, path) or None" | 
					
						
							| 
									
										
										
										
											2016-08-10 12:50:16 -04:00
										 |  |  |         self.path_error['text'] = '' | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  |         name = self.item_ok() | 
					
						
							|  |  |  |         path = self.path_ok() | 
					
						
							|  |  |  |         return None if name is None or path is None else (name, path) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-17 22:24:10 -04:00
										 |  |  | class CustomRun(Query): | 
					
						
							|  |  |  |     """Get settings for custom run of module.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     1. Command line arguments to extend sys.argv. | 
					
						
							|  |  |  |     2. Whether to restart Shell or not. | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     # Used in runscript.run_custom_event | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-07-21 22:37:28 +07:00
										 |  |  |     def __init__(self, parent, title, *, cli_args=[], | 
					
						
							| 
									
										
										
										
											2019-06-17 22:24:10 -04:00
										 |  |  |                  _htest=False, _utest=False): | 
					
						
							| 
									
										
										
										
											2019-07-21 22:37:28 +07:00
										 |  |  |         """cli_args is a list of strings.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         The list is assigned to the default Entry StringVar. | 
					
						
							|  |  |  |         The strings are displayed joined by ' ' for display. | 
					
						
							|  |  |  |         """
 | 
					
						
							| 
									
										
										
										
											2019-06-17 22:24:10 -04:00
										 |  |  |         message = 'Command Line Arguments for sys.argv:' | 
					
						
							|  |  |  |         super().__init__( | 
					
						
							|  |  |  |                 parent, title, message, text0=cli_args, | 
					
						
							|  |  |  |                 _htest=_htest, _utest=_utest) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |     def create_extra(self): | 
					
						
							|  |  |  |         "Add run mode on rows 10-12." | 
					
						
							| 
									
										
										
										
											2019-06-17 22:24:10 -04:00
										 |  |  |         frame = self.frame | 
					
						
							|  |  |  |         self.restartvar = BooleanVar(self, value=True) | 
					
						
							|  |  |  |         restart = Checkbutton(frame, variable=self.restartvar, onvalue=True, | 
					
						
							|  |  |  |                               offvalue=False, text='Restart shell') | 
					
						
							|  |  |  |         self.args_error = Label(frame, text=' ', foreground='red', | 
					
						
							|  |  |  |                                 font=self.error_font) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-06-18 17:08:24 -04:00
										 |  |  |         restart.grid(column=0, row=10, columnspan=3, padx=5, sticky='w') | 
					
						
							| 
									
										
										
										
											2019-06-17 22:24:10 -04:00
										 |  |  |         self.args_error.grid(column=0, row=12, columnspan=3, padx=5, | 
					
						
							|  |  |  |                              sticky='we') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def cli_args_ok(self): | 
					
						
							|  |  |  |         "Validity check and parsing for command line arguments." | 
					
						
							|  |  |  |         cli_string = self.entry.get().strip() | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             cli_args = shlex.split(cli_string, posix=True) | 
					
						
							|  |  |  |         except ValueError as err: | 
					
						
							|  |  |  |             self.showerror(str(err)) | 
					
						
							|  |  |  |             return None | 
					
						
							|  |  |  |         return cli_args | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def entry_ok(self): | 
					
						
							| 
									
										
										
										
											2020-06-28 08:02:47 +02:00
										 |  |  |         "Return apparently valid (cli_args, restart) or None." | 
					
						
							| 
									
										
										
										
											2019-06-17 22:24:10 -04:00
										 |  |  |         cli_args = self.cli_args_ok() | 
					
						
							|  |  |  |         restart = self.restartvar.get() | 
					
						
							|  |  |  |         return None if cli_args is None else (cli_args, restart) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-07-08 00:22:50 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | if __name__ == '__main__': | 
					
						
							| 
									
										
										
										
											2018-06-18 04:47:59 -04:00
										 |  |  |     from unittest import main | 
					
						
							|  |  |  |     main('idlelib.idle_test.test_query', verbosity=2, exit=False) | 
					
						
							| 
									
										
										
										
											2016-06-26 22:05:10 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     from idlelib.idle_test.htest import run | 
					
						
							| 
									
										
										
										
											2019-06-17 22:24:10 -04:00
										 |  |  |     run(Query, HelpSource, CustomRun) |