diff --git a/Lib/idlelib/help_about.py b/Lib/idlelib/help_about.py
index f0a40e9..6e283ff 100644
--- a/Lib/idlelib/help_about.py
+++ b/Lib/idlelib/help_about.py
@@ -4,124 +4,28 @@
import os
from sys import version
-from tkinter import Toplevel, Frame, Label, Button
-from tkinter import SUNKEN, TOP, BOTTOM, LEFT, X, BOTH, W, EW, NSEW
+from tkinter import Toplevel
+from tkinter.ttk import Frame, Button, Label, Separator
from idlelib import textview
-class AboutDialog(Toplevel):
- """Modal about dialog for idle
-
- """
- def __init__(self, parent, title, _htest=False, _utest=False):
- """Create popup, do not return until tk widget destroyed.
+class PyButtons(Frame):
+ "Display frame for buttons for Python about."
- parent - parent of this dialog
- title - string which is title of popup dialog
- _htest - bool, change box location when running htest
- _utest - bool, don't wait_window when running unittest
- """
- Toplevel.__init__(self, parent)
- self.configure(borderwidth=5)
- # place dialog below parent if running htest
- self.geometry("+%d+%d" % (
- parent.winfo_rootx()+30,
- parent.winfo_rooty()+(30 if not _htest else 100)))
- self.bg = "#707070"
- self.fg = "#ffffff"
- self.create_widgets()
- self.resizable(height=False, width=False)
- self.title(title)
- self.transient(parent)
- self.grab_set()
- self.protocol("WM_DELETE_WINDOW", self.ok)
- self.parent = parent
- self.button_ok.focus_set()
- self.bind('', self.ok) # dismiss dialog
- self.bind('', self.ok) # dismiss dialog
- self._current_textview = None
+ def __init__(self, parent, _utest=False):
+ super().__init__(parent)
self._utest = _utest
-
- if not _utest:
- self.deiconify()
- self.wait_window()
-
- def create_widgets(self):
- release = version[:version.index(' ')]
- frame = Frame(self, borderwidth=2, relief=SUNKEN)
- frame_buttons = Frame(self)
- frame_buttons.pack(side=BOTTOM, fill=X)
- frame.pack(side=TOP, expand=True, fill=BOTH)
- self.button_ok = Button(frame_buttons, text='Close',
- command=self.ok)
- self.button_ok.pack(padx=5, pady=5)
-
- frame_background = Frame(frame, bg=self.bg)
- frame_background.pack(expand=True, fill=BOTH)
-
- header = Label(frame_background, text='IDLE', fg=self.fg,
- bg=self.bg, font=('courier', 24, 'bold'))
- header.grid(row=0, column=0, sticky=W, padx=10, pady=10)
- byline_text = "Python's Integrated DeveLopment Environment" + 5*'\n'
- byline = Label(frame_background, text=byline_text, justify=LEFT,
- fg=self.fg, bg=self.bg)
- byline.grid(row=2, column=0, sticky=W, columnspan=3, padx=10, pady=5)
- email = Label(frame_background, text='email: [email protected]',
- justify=LEFT, fg=self.fg, bg=self.bg)
- email.grid(row=6, column=0, columnspan=2, sticky=W, padx=10, pady=0)
- docs = Label(frame_background, text='https://docs.python.org/' +
- version[:3] + '/library/idle.html',
- justify=LEFT, fg=self.fg, bg=self.bg)
- docs.grid(row=7, column=0, columnspan=2, sticky=W, padx=10, pady=0)
-
- Frame(frame_background, borderwidth=1, relief=SUNKEN,
- height=2, bg=self.bg).grid(row=8, column=0, sticky=EW,
- columnspan=3, padx=5, pady=5)
-
- pyver = Label(frame_background, text='Python version: ' + release,
- fg=self.fg, bg=self.bg)
- pyver.grid(row=9, column=0, sticky=W, padx=10, pady=0)
- tk_patchlevel = self.tk.call('info', 'patchlevel')
- tkver = Label(frame_background, text='Tk version: ' + tk_patchlevel,
- fg=self.fg, bg=self.bg)
- tkver.grid(row=9, column=1, sticky=W, padx=2, pady=0)
- py_buttons = Frame(frame_background, bg=self.bg)
- py_buttons.grid(row=10, column=0, columnspan=2, sticky=NSEW)
- self.py_license = Button(py_buttons, text='License', width=8,
- highlightbackground=self.bg,
+ self.py_license = Button(self, text='License', width=8,
command=self.show_py_license)
- self.py_license.pack(side=LEFT, padx=10, pady=10)
- self.py_copyright = Button(py_buttons, text='Copyright', width=8,
- highlightbackground=self.bg,
+ self.py_copyright = Button(self, text='Copyright', width=8,
command=self.show_py_copyright)
- self.py_copyright.pack(side=LEFT, padx=10, pady=10)
- self.py_credits = Button(py_buttons, text='Credits', width=8,
- highlightbackground=self.bg,
+ self.py_credits = Button(self, text='Credits', width=8,
command=self.show_py_credits)
- self.py_credits.pack(side=LEFT, padx=10, pady=10)
-
- Frame(frame_background, borderwidth=1, relief=SUNKEN,
- height=2, bg=self.bg).grid(row=11, column=0, sticky=EW,
- columnspan=3, padx=5, pady=5)
-
- idlever = Label(frame_background, text='IDLE version: ' + release,
- fg=self.fg, bg=self.bg)
- idlever.grid(row=12, column=0, sticky=W, padx=10, pady=0)
- idle_buttons = Frame(frame_background, bg=self.bg)
- idle_buttons.grid(row=13, column=0, columnspan=3, sticky=NSEW)
- self.readme = Button(idle_buttons, text='README', width=8,
- highlightbackground=self.bg,
- command=self.show_readme)
- self.readme.pack(side=LEFT, padx=10, pady=10)
- self.idle_news = Button(idle_buttons, text='NEWS', width=8,
- highlightbackground=self.bg,
- command=self.show_idle_news)
- self.idle_news.pack(side=LEFT, padx=10, pady=10)
- self.idle_credits = Button(idle_buttons, text='Credits', width=8,
- highlightbackground=self.bg,
- command=self.show_idle_credits)
- self.idle_credits.pack(side=LEFT, padx=10, pady=10)
+
+ self.py_license.pack(side='left', padx=10, pady=10)
+ self.py_copyright.pack(side='left', padx=10, pady=10)
+ self.py_credits.pack(side='left', padx=10, pady=10)
# License, copyright, and credits are of type _sitebuiltins._Printer
def show_py_license(self):
@@ -136,6 +40,37 @@ class AboutDialog(Toplevel):
"Handle Python Credits button event."
self.display_printer_text('About - Python Credits', credits)
+ def display_printer_text(self, title, printer):
+ """Create textview for built-in constants.
+
+ Built-in constants have type _sitebuiltins._Printer. The
+ text is extracted from the built-in and then sent to a text
+ viewer with self as the parent and title as the title of
+ the popup.
+ """
+ printer._Printer__setup()
+ text = '\n'.join(printer._Printer__lines)
+ self._current_textview = textview.view_text(
+ self, title, text, _utest=self._utest)
+
+
+class IdleButtons(Frame):
+ "Display frame for buttons for IDLE about."
+
+ def __init__(self, parent, _utest=False):
+ super().__init__(parent)
+ self._utest = _utest
+ self.readme = Button(self, text='README', width=8,
+ command=self.show_readme)
+ self.idle_news = Button(self, text='NEWS', width=8,
+ command=self.show_idle_news)
+ self.idle_credits = Button(self, text='Credits', width=8,
+ command=self.show_idle_credits)
+
+ self.readme.pack(side='left', padx=10, pady=10)
+ self.idle_news.pack(side='left', padx=10, pady=10)
+ self.idle_credits.pack(side='left', padx=10, pady=10)
+
# Encode CREDITS.txt to utf-8 for proper version of Loewis.
# Specify others as ascii until need utf-8, so catch errors.
def show_idle_credits(self):
@@ -150,19 +85,6 @@ class AboutDialog(Toplevel):
"Handle News button event."
self.display_file_text('About - NEWS', 'NEWS.txt', 'utf-8')
- def display_printer_text(self, title, printer):
- """Create textview for built-in constants.
-
- Built-in constants have type _sitebuiltins._Printer. The
- text is extracted from the built-in and then sent to a text
- viewer with self as the parent and title as the title of
- the popup.
- """
- printer._Printer__setup()
- text = '\n'.join(printer._Printer__lines)
- self._current_textview = textview.view_text(
- self, title, text, _utest=self._utest)
-
def display_file_text(self, title, filename, encoding=None):
"""Create textview for filename.
@@ -174,6 +96,106 @@ class AboutDialog(Toplevel):
self._current_textview = textview.view_file(
self, title, fn, encoding, _utest=self._utest)
+
+class AboutFrame(Frame):
+ "Display frame for about information."
+
+ def __init__(self, parent, _utest=False):
+ super().__init__(parent)
+ self.release = version[:version.index(' ')]
+ self.tk_patchlevel = self.tk.call('info', 'patchlevel')
+ self._utest = _utest
+ self['borderwidth'] = 2
+ self['relief'] = 'sunken'
+
+ self.create_top_widgets()
+ self.create_py_widgets()
+ self.create_idle_widgets()
+ self.place_widgets()
+
+ def create_top_widgets(self):
+ self.header = Label(self, text='IDLE', font=('courier', 24, 'bold'))
+ byline_text = "Python's Integrated DeveLopment Environment" + 5*'\n'
+ self.byline = Label(self, text=byline_text, justify='left')
+ self.email = Label(self, text='email: [email protected]',
+ justify='left')
+ self.docs = Label(self, text='https://docs.python.org/' +
+ version[:3] + '/library/idle.html', justify='left')
+
+ def create_py_widgets(self):
+ self.pyver = Label(self, text=f'Python version: {self.release}')
+ self.tkver = Label(self, text=f'Tk version: {self.tk_patchlevel}')
+ self.py_buttons = PyButtons(self, self._utest)
+
+ def create_idle_widgets(self):
+ self.idlever = Label(self, text=f'IDLE version: {self.release}')
+ self.idle_buttons = IdleButtons(self, self._utest)
+
+ def place_widgets(self):
+ sep1 = sep2 = Separator(self, orient='horizontal')
+ self.header.grid(row=0, column=0, sticky='w', padx=10, pady=10)
+ self.byline.grid(row=2, column=0, sticky='w', columnspan=3,
+ padx=10, pady=5)
+ self.email.grid(row=6, column=0, columnspan=2, sticky='w',
+ padx=10, pady=0)
+ self.docs.grid(row=7, column=0, columnspan=2, sticky='w',
+ padx=10, pady=0)
+ sep1.grid(row=8, column=0, columnspan=3, sticky='ew', padx=5, pady=5)
+ self.pyver.grid(row=9, column=0, sticky='w', padx=10, pady=0)
+ self.tkver.grid(row=9, column=1, sticky='w', padx=2, pady=0)
+ self.py_buttons.grid(row=10, column=0, columnspan=2, sticky='nsew')
+ sep2.grid(row=11, column=0, columnspan=3, sticky='ew', padx=5, pady=5)
+ self.idlever.grid(row=12, column=0, sticky='w', padx=10, pady=0)
+ self.idle_buttons.grid(row=13, column=0, columnspan=3, sticky='nsew')
+
+
+class AboutDialog(Toplevel):
+ "Modal about dialog for idle."
+
+ def __init__(self, parent, title, _htest=False, _utest=False):
+ """Create popup, do not return until tk widget destroyed.
+
+ parent - parent of this dialog
+ title - string which is title of popup dialog
+ _htest - bool, change box location when running htest
+ _utest - bool, don't wait_window when running unittest
+ """
+ super().__init__(parent)
+ self['borderwidth'] = 5
+ # place dialog below parent if running htest
+ x = parent.winfo_rootx()
+ y = parent.winfo_rooty() + (30 if not _htest else 100)
+ self.geometry(f'+{x}+{y}')
+
+ self.bg = "#707070"
+ self.fg = "#ffffff"
+
+ self.resizable(height=False, width=False)
+ self.title(title)
+ self.transient(parent)
+ self.grab_set()
+ self.protocol("WM_DELETE_WINDOW", self.ok)
+ self.bind('', self.ok)
+ self.bind('', self.ok)
+
+ self.aboutframe = aboutframe = AboutFrame(self, _utest)
+ self.buttonframe = buttonframe = Frame(self)
+
+ self.button_ok = button_ok = Button(buttonframe, text='Close',
+ command=self.ok)
+ button_ok.focus_set()
+ button_ok.pack(padx=5, pady=5)
+
+ aboutframe.pack(side='top', expand=True, fill='both')
+ buttonframe.pack(side='bottom', fill='x')
+
+ self._current_textview = None
+ self._utest = _utest
+
+ if not _utest:
+ self.deiconify()
+ self.wait_window()
+
def ok(self, event=None):
"Dismiss help_about dialog."
self.destroy()
diff --git a/Lib/idlelib/idle_test/test_textview.py b/Lib/idlelib/idle_test/test_textview.py
index 7a2f7e4..09af75f 100644
--- a/Lib/idlelib/idle_test/test_textview.py
+++ b/Lib/idlelib/idle_test/test_textview.py
@@ -1,6 +1,6 @@
'''Test idlelib.textview.
-Since all methods and functions create (or destroy) a TextViewer, which
+Since all methods and functions create (or destroy) a TextviewWindow, which
is a widget containing multiple widgets, all tests must be gui tests.
Using mock Text would not change this. Other mocks are used to retrieve
information about calls.
@@ -13,7 +13,8 @@ requires('gui')
import unittest
import os
-from tkinter import Tk, Button
+from tkinter import Tk
+from tkinter.ttk import Button
from idlelib.idle_test.mock_idle import Func
from idlelib.idle_test.mock_tk import Mbox_func
@@ -28,12 +29,12 @@ def tearDownModule():
root.destroy() # Pyflakes falsely sees root as undefined.
del root
-# If we call TextViewer or wrapper functions with defaults
+# If we call TextviewWindow or wrapper functions with defaults
# modal=True, _utest=False, test hangs on call to wait_window.
# Have also gotten tk error 'can't invoke "event" command'.
-class TV(tv.TextViewer): # Used in TextViewTest.
+class TV(tv.TextviewWindow): # Used in TextViewTest.
transient = Func()
grab_set = Func()
wait_window = Func()
@@ -70,7 +71,28 @@ class TextViewTest(unittest.TestCase):
view.destroy()
-# Call TextViewer with modal=False.
+class TextviewFrameTest(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ "By itself, this tests that file parsed without exception."
+ cls.root = root = Tk()
+ root.withdraw()
+ cls.frame = tv.TextviewFrame(root, 'test text')
+
+ @classmethod
+ def tearDownClass(cls):
+ del cls.frame
+ cls.root.update_idletasks()
+ cls.root.destroy()
+ del cls.root
+
+ def test_line1(self):
+ text = self.frame.text
+ self.assertEqual(text.get('1.0', '1.end'), 'test text')
+
+
+# Call TextviewWindow with modal=False.
class ViewFunctionTest(unittest.TestCase):
@classmethod
@@ -85,13 +107,15 @@ class ViewFunctionTest(unittest.TestCase):
def test_view_text(self):
view = tv.view_text(root, 'Title', 'test text', modal=False)
- self.assertIsInstance(view, tv.TextViewer)
+ self.assertIsInstance(view, tv.TextviewWindow)
+ self.assertIsInstance(view.textframe, tv.TextviewFrame)
view.ok()
def test_view_file(self):
view = tv.view_file(root, 'Title', __file__, modal=False)
- self.assertIsInstance(view, tv.TextViewer)
- self.assertIn('Test', view.text.get('1.0', '1.end'))
+ self.assertIsInstance(view, tv.TextviewWindow)
+ self.assertIsInstance(view.textframe, tv.TextviewFrame)
+ self.assertIn('Test', view.textframe.text.get('1.0', '1.end'))
view.ok()
def test_bad_file(self):
@@ -109,8 +133,7 @@ class ViewFunctionTest(unittest.TestCase):
self.assertEqual(tv.showerror.title, 'Unicode Decode Error')
-
-# Call TextViewer with _utest=True.
+# Call TextviewWindow with _utest=True.
class ButtonClickTest(unittest.TestCase):
def setUp(self):
@@ -131,7 +154,7 @@ class ButtonClickTest(unittest.TestCase):
self.assertEqual(self.called, True)
self.assertEqual(self.view.title(), 'TITLE_TEXT')
- self.assertEqual(self.view.text.get('1.0', '1.end'), 'COMMAND')
+ self.assertEqual(self.view.textframe.text.get('1.0', '1.end'), 'COMMAND')
def test_view_file_bind_with_button(self):
def _command():
@@ -144,10 +167,10 @@ class ButtonClickTest(unittest.TestCase):
self.assertEqual(self.called, True)
self.assertEqual(self.view.title(), 'TITLE_FILE')
with open(__file__) as f:
- self.assertEqual(self.view.text.get('1.0', '1.end'),
+ self.assertEqual(self.view.textframe.text.get('1.0', '1.end'),
f.readline().strip())
f.readline()
- self.assertEqual(self.view.text.get('3.0', '3.end'),
+ self.assertEqual(self.view.textframe.text.get('3.0', '3.end'),
f.readline().strip())
diff --git a/Lib/idlelib/textview.py b/Lib/idlelib/textview.py
index ab653a9..fce2580 100644
--- a/Lib/idlelib/textview.py
+++ b/Lib/idlelib/textview.py
@@ -1,14 +1,42 @@
"""Simple text browser for IDLE
"""
-from tkinter import Toplevel, Frame, Button, Text
-from tkinter import DISABLED, SUNKEN, VERTICAL, WORD
-from tkinter import RIGHT, LEFT, TOP, BOTTOM, BOTH, X, Y
-from tkinter.ttk import Scrollbar
+from tkinter import Toplevel, Text
+from tkinter.ttk import Frame, Scrollbar, Button
from tkinter.messagebox import showerror
-class TextViewer(Toplevel):
+class TextviewFrame(Frame):
+ "Display frame for text and scroll."
+
+ def __init__(self, parent, content):
+ """Create a frame for Textview.
+
+ parent - parent widget for this frame
+ content - text to display
+ """
+ super().__init__(parent)
+ self['relief'] = 'sunken'
+ self['height'] = 700
+ # TODO: get fg/bg from theme.
+ self.bg = '#ffffff'
+ self.fg = '#000000'
+
+ self.text = text = Text(self, wrap='word', highlightthickness=0,
+ fg=self.fg, bg=self.bg)
+ self.scroll = scroll = Scrollbar(self, orient='vertical',
+ takefocus=False, command=text.yview)
+ text['yscrollcommand'] = scroll.set
+ text.insert(0.0, content)
+ text['state'] = 'disabled'
+ text.focus_set()
+
+
+ scroll.pack(side='right', fill='y')
+ text.pack(side='left', expand=True, fill='both')
+
+
+class TextviewWindow(Toplevel):
"A simple text viewer dialog for IDLE."
def __init__(self, parent, title, text, modal=True,
@@ -24,26 +52,26 @@ class TextViewer(Toplevel):
_htest - bool; change box location when running htest.
_utest - bool; don't wait_window when running unittest.
"""
- Toplevel.__init__(self, parent)
- self.configure(borderwidth=5)
+ super().__init__(parent)
+ self['borderwidth'] = 5
# Place dialog below parent if running htest.
- self.geometry("=%dx%d+%d+%d" % (750, 500,
- parent.winfo_rootx() + 10,
- parent.winfo_rooty() + (10 if not _htest else 100)))
- # TODO: get fg/bg from theme.
- self.bg = '#ffffff'
- self.fg = '#000000'
+ x = parent.winfo_rootx() + 10
+ y = parent.winfo_rooty() + (10 if not _htest else 100)
+ self.geometry(f'=750x500+{x}+{y}')
- self.create_widgets()
self.title(title)
self.protocol("WM_DELETE_WINDOW", self.ok)
- self.parent = parent
- self.text.focus_set()
- # Bind keys for closing this dialog.
self.bind('', self.ok)
self.bind('', self.ok)
- self.text.insert(0.0, text)
- self.text.config(state=DISABLED)
+ self.textframe = textframe = TextviewFrame(self, text)
+ self.buttonframe = buttonframe = Frame(self)
+
+ self.button_ok = button_ok = Button(buttonframe, text='Close',
+ command=self.ok, takefocus=False)
+ button_ok.pack()
+
+ textframe.pack(side='top', expand=True, fill='both')
+ buttonframe.pack(side='bottom', fill='x')
if modal:
self.transient(parent)
@@ -51,31 +79,13 @@ class TextViewer(Toplevel):
if not _utest:
self.wait_window()
- def create_widgets(self):
- "Create Frame with Text (with vertical Scrollbar) and Button."
- frame = Frame(self, relief=SUNKEN, height=700)
- frame_buttons = Frame(self)
- self.button_ok = Button(frame_buttons, text='Close',
- command=self.ok, takefocus=False)
- self.scrollbar = Scrollbar(frame, orient=VERTICAL, takefocus=False)
- self.text = Text(frame, wrap=WORD, highlightthickness=0,
- fg=self.fg, bg=self.bg)
- self.scrollbar.config(command=self.text.yview)
- self.text.config(yscrollcommand=self.scrollbar.set)
-
- self.button_ok.pack()
- self.scrollbar.pack(side=RIGHT, fill=Y)
- self.text.pack(side=LEFT, expand=True, fill=BOTH)
- frame_buttons.pack(side=BOTTOM, fill=X)
- frame.pack(side=TOP, expand=True, fill=BOTH)
-
def ok(self, event=None):
"""Dismiss text viewer dialog."""
self.destroy()
def view_text(parent, title, text, modal=True, _utest=False):
- """Create TextViewer for given text.
+ """Create text viewer for given text.
parent - parent of this dialog
title - string which is the title of popup dialog
@@ -84,11 +94,11 @@ def view_text(parent, title, text, modal=True, _utest=False):
dialog is displayed
_utest - bool; controls wait_window on unittest
"""
- return TextViewer(parent, title, text, modal, _utest=_utest)
+ return TextviewWindow(parent, title, text, modal, _utest=_utest)
def view_file(parent, title, filename, encoding=None, modal=True, _utest=False):
- """Create TextViewer for text in filename.
+ """Create text viewer for text in filename.
Return error message if file cannot be read. Otherwise calls view_text
with contents of the file.
@@ -113,4 +123,4 @@ if __name__ == '__main__':
import unittest
unittest.main('idlelib.idle_test.test_textview', verbosity=2, exit=False)
from idlelib.idle_test.htest import run
- run(TextViewer)
+ run(TextviewWindow)