Issue #27380: IDLE: add query.HelpSource class and tests.

Remove modules that are combined in new module.
This commit is contained in:
Terry Jan Reedy 2016-07-08 00:22:50 -04:00
parent d6402a40d3
commit 8b22c0aada
6 changed files with 341 additions and 367 deletions

View File

@ -1,170 +0,0 @@
"Dialog to specify or edit the parameters for a user configured help source."
import os
import sys
from tkinter import *
import tkinter.messagebox as tkMessageBox
import tkinter.filedialog as tkFileDialog
class GetHelpSourceDialog(Toplevel):
def __init__(self, parent, title, menuItem='', filePath='', _htest=False):
"""Get menu entry and url/ local file location for Additional Help
User selects a name for the Help resource and provides a web url
or a local file as its source. The user can enter a url or browse
for the file.
_htest - bool, change box location when running htest
"""
Toplevel.__init__(self, parent)
self.configure(borderwidth=5)
self.resizable(height=FALSE, width=FALSE)
self.title(title)
self.transient(parent)
self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.cancel)
self.parent = parent
self.result = None
self.create_widgets()
self.menu.set(menuItem)
self.path.set(filePath)
self.withdraw() #hide while setting geometry
#needs to be done here so that the winfo_reqwidth is valid
self.update_idletasks()
#centre dialog over parent. below parent if running htest.
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)))
self.deiconify() #geometry set, unhide
self.bind('<Return>', self.ok)
self.wait_window()
def create_widgets(self):
self.menu = StringVar(self)
self.path = StringVar(self)
self.fontSize = StringVar(self)
self.frameMain = Frame(self, borderwidth=2, relief=GROOVE)
self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
labelMenu = Label(self.frameMain, anchor=W, justify=LEFT,
text='Menu Item:')
self.entryMenu = Entry(self.frameMain, textvariable=self.menu,
width=30)
self.entryMenu.focus_set()
labelPath = Label(self.frameMain, anchor=W, justify=LEFT,
text='Help File Path: Enter URL or browse for file')
self.entryPath = Entry(self.frameMain, textvariable=self.path,
width=40)
self.entryMenu.focus_set()
labelMenu.pack(anchor=W, padx=5, pady=3)
self.entryMenu.pack(anchor=W, padx=5, pady=3)
labelPath.pack(anchor=W, padx=5, pady=3)
self.entryPath.pack(anchor=W, padx=5, pady=3)
browseButton = Button(self.frameMain, text='Browse', width=8,
command=self.browse_file)
browseButton.pack(pady=3)
frameButtons = Frame(self)
frameButtons.pack(side=BOTTOM, fill=X)
self.buttonOk = Button(frameButtons, text='OK',
width=8, default=ACTIVE, command=self.ok)
self.buttonOk.grid(row=0, column=0, padx=5,pady=5)
self.buttonCancel = Button(frameButtons, text='Cancel',
width=8, command=self.cancel)
self.buttonCancel.grid(row=0, column=1, padx=5, pady=5)
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.path.get()
if path:
dir, base = os.path.split(path)
else:
base = None
if sys.platform[:3] == 'win':
dir = os.path.join(os.path.dirname(sys.executable), 'Doc')
if not os.path.isdir(dir):
dir = os.getcwd()
else:
dir = os.getcwd()
opendialog = tkFileDialog.Open(parent=self, filetypes=filetypes)
file = opendialog.show(initialdir=dir, initialfile=base)
if file:
self.path.set(file)
def menu_ok(self):
"Simple validity check for a sensible menu item name"
menu_ok = True
menu = self.menu.get()
menu.strip()
if not menu:
tkMessageBox.showerror(title='Menu Item Error',
message='No menu item specified',
parent=self)
self.entryMenu.focus_set()
menu_ok = False
elif len(menu) > 30:
tkMessageBox.showerror(title='Menu Item Error',
message='Menu item too long:'
'\nLimit 30 characters.',
parent=self)
self.entryMenu.focus_set()
menu_ok = False
return menu_ok
def path_ok(self):
"Simple validity check for menu file path"
path_ok = True
path = self.path.get()
path.strip()
if not path: #no path specified
tkMessageBox.showerror(title='File Path Error',
message='No help file path specified.',
parent=self)
self.entryPath.focus_set()
path_ok = False
elif path.startswith(('www.', 'http')):
pass
else:
if path[:5] == 'file:':
path = path[5:]
if not os.path.exists(path):
tkMessageBox.showerror(title='File Path Error',
message='Help file path does not exist.',
parent=self)
self.entryPath.focus_set()
path_ok = False
return path_ok
def ok(self, event=None):
if self.menu_ok() and self.path_ok():
self.result = (self.menu.get().strip(),
self.path.get().strip())
if sys.platform == 'darwin':
path = self.result[1]
if path.startswith(('www', 'file:', 'http:', 'https:')):
pass
else:
# Mac Safari insists on using the URI form for local files
self.result = list(self.result)
self.result[1] = "file://" + path
self.destroy()
def cancel(self, event=None):
self.result = None
self.destroy()
if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_config_help',
verbosity=2, exit=False)
from idlelib.idle_test.htest import run
run(GetHelpSourceDialog)

View File

@ -18,8 +18,7 @@ import tkinter.font as tkFont
from idlelib.config import idleConf
from idlelib.dynoption import DynOptionMenu
from idlelib.config_key import GetKeysDialog
from idlelib.query import SectionName
from idlelib.config_help import GetHelpSourceDialog
from idlelib.query import SectionName, HelpSource
from idlelib.tabbedpages import TabbedPageSet
from idlelib.textview import view_text
from idlelib import macosx
@ -940,7 +939,8 @@ class ConfigDialog(Toplevel):
self.buttonHelpListRemove.config(state=DISABLED)
def HelpListItemAdd(self):
helpSource = GetHelpSourceDialog(self, 'New Help Source').result
helpSource = HelpSource(self, 'New Help Source',
).result
if helpSource:
self.userHelpList.append((helpSource[0], helpSource[1]))
self.listHelp.insert(END, helpSource[0])
@ -950,16 +950,17 @@ class ConfigDialog(Toplevel):
def HelpListItemEdit(self):
itemIndex = self.listHelp.index(ANCHOR)
helpSource = self.userHelpList[itemIndex]
newHelpSource = GetHelpSourceDialog(
self, 'Edit Help Source', menuItem=helpSource[0],
filePath=helpSource[1]).result
if (not newHelpSource) or (newHelpSource == helpSource):
return #no changes
self.userHelpList[itemIndex] = newHelpSource
self.listHelp.delete(itemIndex)
self.listHelp.insert(itemIndex, newHelpSource[0])
self.UpdateUserHelpChangedItems()
self.SetHelpListButtonStates()
newHelpSource = HelpSource(
self, 'Edit Help Source',
menuitem=helpSource[0],
filepath=helpSource[1],
).result
if newHelpSource and newHelpSource != helpSource:
self.userHelpList[itemIndex] = newHelpSource
self.listHelp.delete(itemIndex)
self.listHelp.insert(itemIndex, newHelpSource[0])
self.UpdateUserHelpChangedItems()
self.SetHelpListButtonStates()
def HelpListItemRemove(self):
itemIndex = self.listHelp.index(ANCHOR)

View File

@ -137,18 +137,6 @@ _editor_window_spec = {
"Best to close editor first."
}
GetHelpSourceDialog_spec = {
'file': 'config_help',
'kwds': {'title': 'Get helpsource',
'_htest': True},
'msg': "Enter menu item name and help file path\n "
"<nothing> and more than 30 chars are invalid menu item names.\n"
"<nothing>, file does not exist are invalid path items.\n"
"Test for incomplete web address for help file path.\n"
"A valid entry will be printed to shell with [0k].\n"
"[Cancel] will print None to shell",
}
# Update once issue21519 is resolved.
GetKeysDialog_spec = {
'file': 'config_key',
@ -175,6 +163,22 @@ _grep_dialog_spec = {
"should open that file \nin a new EditorWindow."
}
HelpSource_spec = {
'file': 'query',
'kwds': {'title': 'Help name and source',
'menuitem': 'test',
'filepath': __file__,
'used_names': {'abc'},
'_htest': True},
'msg': "Enter menu item name and help file path\n"
"'', > than 30 chars, and 'abc' are invalid menu item names.\n"
"'' and file does not exist are invalid path items.\n"
"Any url ('www...', 'http...') is accepted.\n"
"Test Browse with and without path, as cannot unittest.\n"
"A valid entry will be printed to shell with [0k]\n"
"or <return>. [Cancel] will print None to shell"
}
_io_binding_spec = {
'file': 'iomenu',
'kwds': {},
@ -241,7 +245,7 @@ Query_spec = {
'_htest': True},
'msg': "Enter with <Return> or [Ok]. Print valid entry to Shell\n"
"Blank line, after stripping, is ignored\n"
"Close dialog with valid entry, [Cancel] or [X]",
"Close dialog with valid entry, [Cancel] or [X]"
}

View File

@ -1,106 +0,0 @@
"""Unittests for idlelib.config_help.py"""
import unittest
from idlelib.idle_test.mock_tk import Var, Mbox, Entry
from idlelib import config_help as help_dialog_module
help_dialog = help_dialog_module.GetHelpSourceDialog
class Dummy_help_dialog:
# Mock for testing the following methods of help_dialog
menu_ok = help_dialog.menu_ok
path_ok = help_dialog.path_ok
ok = help_dialog.ok
cancel = help_dialog.cancel
# Attributes, constant or variable, needed for tests
menu = Var()
entryMenu = Entry()
path = Var()
entryPath = Entry()
result = None
destroyed = False
def destroy(self):
self.destroyed = True
# menu_ok and path_ok call Mbox.showerror if menu and path are not ok.
orig_mbox = help_dialog_module.tkMessageBox
showerror = Mbox.showerror
class ConfigHelpTest(unittest.TestCase):
dialog = Dummy_help_dialog()
@classmethod
def setUpClass(cls):
help_dialog_module.tkMessageBox = Mbox
@classmethod
def tearDownClass(cls):
help_dialog_module.tkMessageBox = orig_mbox
def test_blank_menu(self):
self.dialog.menu.set('')
self.assertFalse(self.dialog.menu_ok())
self.assertEqual(showerror.title, 'Menu Item Error')
self.assertIn('No', showerror.message)
def test_long_menu(self):
self.dialog.menu.set('hello' * 10)
self.assertFalse(self.dialog.menu_ok())
self.assertEqual(showerror.title, 'Menu Item Error')
self.assertIn('long', showerror.message)
def test_good_menu(self):
self.dialog.menu.set('help')
showerror.title = 'No Error' # should not be called
self.assertTrue(self.dialog.menu_ok())
self.assertEqual(showerror.title, 'No Error')
def test_blank_path(self):
self.dialog.path.set('')
self.assertFalse(self.dialog.path_ok())
self.assertEqual(showerror.title, 'File Path Error')
self.assertIn('No', showerror.message)
def test_invalid_file_path(self):
self.dialog.path.set('foobar' * 100)
self.assertFalse(self.dialog.path_ok())
self.assertEqual(showerror.title, 'File Path Error')
self.assertIn('not exist', showerror.message)
def test_invalid_url_path(self):
self.dialog.path.set('ww.foobar.com')
self.assertFalse(self.dialog.path_ok())
self.assertEqual(showerror.title, 'File Path Error')
self.assertIn('not exist', showerror.message)
self.dialog.path.set('htt.foobar.com')
self.assertFalse(self.dialog.path_ok())
self.assertEqual(showerror.title, 'File Path Error')
self.assertIn('not exist', showerror.message)
def test_good_path(self):
self.dialog.path.set('https://docs.python.org')
showerror.title = 'No Error' # should not be called
self.assertTrue(self.dialog.path_ok())
self.assertEqual(showerror.title, 'No Error')
def test_ok(self):
self.dialog.destroyed = False
self.dialog.menu.set('help')
self.dialog.path.set('https://docs.python.org')
self.dialog.ok()
self.assertEqual(self.dialog.result, ('help',
'https://docs.python.org'))
self.assertTrue(self.dialog.destroyed)
def test_cancel(self):
self.dialog.destroyed = False
self.dialog.cancel()
self.assertEqual(self.dialog.result, None)
self.assertTrue(self.dialog.destroyed)
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View File

@ -1,6 +1,16 @@
"""Test idlelib.query.
Coverage: 100%.
Non-gui tests for Query, SectionName, ModuleName, and HelpSource use
dummy versions that extract the non-gui methods and add other needed
attributes. GUI tests create an instance of each class and simulate
entries and button clicks. Subclass tests only target the new code in
the subclass definition.
The appearance of the widgets is checked by the Query and
HelpSource htests. These are run by running query.py.
Coverage: 94% (100% for Query and SectionName).
6 of 8 missing are ModuleName exceptions I don't know how to trigger.
"""
from test.support import requires
from tkinter import Tk
@ -9,21 +19,9 @@ from unittest import mock
from idlelib.idle_test.mock_tk import Var, Mbox_func
from idlelib import query
Query = query.Query
class Dummy_Query:
# Mock for testing the following methods Query
entry_ok = Query.entry_ok
ok = Query.ok
cancel = Query.cancel
# Attributes, constant or variable, needed for tests
entry = Var()
result = None
destroyed = False
def destroy(self):
self.destroyed = True
# Mock entry.showerror messagebox so don't need click to continue
# when entry_ok and path_ok methods call it to display errors.
# entry_ok calls modal messagebox.showerror if entry is not ok.
# Mock showerrer so don't need to click to continue.
orig_showerror = query.showerror
showerror = Mbox_func() # Instance has __call__ method.
@ -34,7 +32,23 @@ def tearDownModule():
query.showerror = orig_showerror
# NON-GUI TESTS
class QueryTest(unittest.TestCase):
"Test Query base class."
class Dummy_Query:
# Test the following Query methods.
entry_ok = query.Query.entry_ok
ok = query.Query.ok
cancel = query.Query.cancel
# Add attributes needed for the tests.
entry = Var()
result = None
destroyed = False
def destroy(self):
self.destroyed = True
dialog = Dummy_Query()
def setUp(self):
@ -42,7 +56,7 @@ class QueryTest(unittest.TestCase):
self.dialog.result = None
self.dialog.destroyed = False
def test_blank_entry(self):
def test_entry_ok_blank(self):
dialog = self.dialog
Equal = self.assertEqual
dialog.entry.set(' ')
@ -51,7 +65,7 @@ class QueryTest(unittest.TestCase):
Equal(showerror.title, 'Entry Error')
self.assertIn('Blank', showerror.message)
def test_good_entry(self):
def test_entry_ok_good(self):
dialog = self.dialog
Equal = self.assertEqual
dialog.entry.set(' good ')
@ -59,7 +73,17 @@ class QueryTest(unittest.TestCase):
Equal((dialog.result, dialog.destroyed), (None, False))
Equal(showerror.title, None)
def test_ok(self):
def test_ok_blank(self):
dialog = self.dialog
Equal = self.assertEqual
dialog.entry.set('')
dialog.entry.focus_set = mock.Mock()
Equal(dialog.ok(), None)
self.assertTrue(dialog.entry.focus_set.called)
del dialog.entry.focus_set
Equal((dialog.result, dialog.destroyed), (None, False))
def test_ok_good(self):
dialog = self.dialog
Equal = self.assertEqual
dialog.entry.set('good')
@ -73,12 +97,14 @@ class QueryTest(unittest.TestCase):
Equal((dialog.result, dialog.destroyed), (None, True))
class Dummy_SectionName:
entry_ok = query.SectionName.entry_ok # Test override.
used_names = ['used']
entry = Var()
class SectionNameTest(unittest.TestCase):
"Test SectionName subclass of Query."
class Dummy_SectionName:
entry_ok = query.SectionName.entry_ok # Function being tested.
used_names = ['used']
entry = Var()
dialog = Dummy_SectionName()
def setUp(self):
@ -116,12 +142,14 @@ class SectionNameTest(unittest.TestCase):
Equal(showerror.title, None)
class Dummy_ModuleName:
entry_ok = query.ModuleName.entry_ok # Test override
text0 = ''
entry = Var()
class ModuleNameTest(unittest.TestCase):
"Test ModuleName subclass of Query."
class Dummy_ModuleName:
entry_ok = query.ModuleName.entry_ok # Funtion being tested.
text0 = ''
entry = Var()
dialog = Dummy_ModuleName()
def setUp(self):
@ -159,13 +187,119 @@ class ModuleNameTest(unittest.TestCase):
Equal(showerror.title, None)
# 3 HelpSource test classes each test one function.
orig_platform = query.platform
class HelpsourceBrowsefileTest(unittest.TestCase):
"Test browse_file method of ModuleName subclass of Query."
class Dummy_HelpSource:
browse_file = query.HelpSource.browse_file
pathvar = Var()
dialog = Dummy_HelpSource()
def test_file_replaces_path(self):
# Path is widget entry, file is file dialog return.
dialog = self.dialog
for path, func, result in (
# We need all combination to test all (most) code paths.
('', lambda a,b,c:'', ''),
('', lambda a,b,c: __file__, __file__),
('htest', lambda a,b,c:'', 'htest'),
('htest', lambda a,b,c: __file__, __file__)):
with self.subTest():
dialog.pathvar.set(path)
dialog.askfilename = func
dialog.browse_file()
self.assertEqual(dialog.pathvar.get(), result)
class HelpsourcePathokTest(unittest.TestCase):
"Test path_ok method of ModuleName subclass of Query."
class Dummy_HelpSource:
path_ok = query.HelpSource.path_ok
path = Var()
dialog = Dummy_HelpSource()
@classmethod
def tearDownClass(cls):
query.platform = orig_platform
def setUp(self):
showerror.title = None
def test_path_ok_blank(self):
dialog = self.dialog
Equal = self.assertEqual
dialog.path.set(' ')
Equal(dialog.path_ok(), None)
Equal(showerror.title, 'File Path Error')
self.assertIn('No help', showerror.message)
def test_path_ok_bad(self):
dialog = self.dialog
Equal = self.assertEqual
dialog.path.set(__file__ + 'bad-bad-bad')
Equal(dialog.path_ok(), None)
Equal(showerror.title, 'File Path Error')
self.assertIn('not exist', showerror.message)
def test_path_ok_web(self):
dialog = self.dialog
Equal = self.assertEqual
for url in 'www.py.org', 'http://py.org':
with self.subTest():
dialog.path.set(url)
Equal(dialog.path_ok(), url)
Equal(showerror.title, None)
def test_path_ok_file(self):
dialog = self.dialog
Equal = self.assertEqual
for platform, prefix in ('darwin', 'file://'), ('other', ''):
with self.subTest():
query.platform = platform
dialog.path.set(__file__)
Equal(dialog.path_ok(), prefix + __file__)
Equal(showerror.title, None)
class HelpsourceEntryokTest(unittest.TestCase):
"Test entry_ok method of ModuleName subclass of Query."
class Dummy_HelpSource:
entry_ok = query.HelpSource.entry_ok
def item_ok(self):
return self.name
def path_ok(self):
return self.path
dialog = Dummy_HelpSource()
def test_entry_ok_helpsource(self):
dialog = self.dialog
for name, path, result in ((None, None, None),
(None, 'doc.txt', None),
('doc', None, None),
('doc', 'doc.txt', ('doc', 'doc.txt'))):
with self.subTest():
dialog.name, dialog.path = name, path
self.assertEqual(self.dialog.entry_ok(), result)
# GUI TESTS
class QueryGuiTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
cls.root = root = Tk()
cls.dialog = Query(root, 'TEST', 'test', _utest=True)
cls.dialog = query.Query(root, 'TEST', 'test', _utest=True)
cls.dialog.destroy = mock.Mock()
@classmethod
@ -238,5 +372,25 @@ class ModulenameGuiTest(unittest.TestCase):
del root
class HelpsourceGuiTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
requires('gui')
def test_click_help_source(self):
root = Tk()
dialog = query.HelpSource(root, 'T', menuitem='__test__',
filepath=__file__, _utest=True)
Equal = self.assertEqual
Equal(dialog.entry.get(), '__test__')
Equal(dialog.path.get(), __file__)
dialog.button_ok.invoke()
Equal(dialog.result, ('__test__', __file__))
del dialog
root.destroy()
del root
if __name__ == '__main__':
unittest.main(verbosity=2, exit=False)

View File

@ -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)