Python-checkins
Threads by month
- ----- 2024 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
July 2017
- 4 participants
- 406 discussions
bpo-31091: Remove dead code in PyErr_GivenExceptionMatches(). (#2963)
by Serhiy Storchaka 31 Jul '17
by Serhiy Storchaka 31 Jul '17
31 Jul '17
https://github.com/python/cpython/commit/e4c06bcca358c6dcb6393a75a1589ff6a2…
commit: e4c06bcca358c6dcb6393a75a1589ff6a2d45cde
branch: master
author: scoder <stefan_ml(a)behnel.de>
committer: Serhiy Storchaka <storchaka(a)gmail.com>
date: 2017-07-31T23:27:46+03:00
summary:
bpo-31091: Remove dead code in PyErr_GivenExceptionMatches(). (#2963)
According to the comment, there was previously a call to PyObject_IsSubclass() involved which could fail, but since it was replaced with a call to PyType_IsSubtype(), it can no longer fail.
files:
M Python/errors.c
diff --git a/Python/errors.c b/Python/errors.c
index 3785e6981c6..261dd7b27cb 100644
--- a/Python/errors.c
+++ b/Python/errors.c
@@ -191,19 +191,7 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc)
err = PyExceptionInstance_Class(err);
if (PyExceptionClass_Check(err) && PyExceptionClass_Check(exc)) {
- int res = 0;
- PyObject *exception, *value, *tb;
- PyErr_Fetch(&exception, &value, &tb);
- /* PyObject_IsSubclass() can recurse and therefore is
- not safe (see test_bad_getattr in test.pickletester). */
- res = PyType_IsSubtype((PyTypeObject *)err, (PyTypeObject *)exc);
- /* This function must not fail, so print the error here */
- if (res == -1) {
- PyErr_WriteUnraisable(err);
- res = 0;
- }
- PyErr_Restore(exception, value, tb);
- return res;
+ return PyType_IsSubtype((PyTypeObject *)err, (PyTypeObject *)exc);
}
return err == exc;
1
0
[3.6] bpo-25684: ttk.OptionMenu radiobuttons weren't unique (GH-2276) (#2959)
by Serhiy Storchaka 31 Jul '17
by Serhiy Storchaka 31 Jul '17
31 Jul '17
https://github.com/python/cpython/commit/2bf1586e60a6639b532cd8e3442ae33064…
commit: 2bf1586e60a6639b532cd8e3442ae33064523eb1
branch: 3.6
author: csabella <cheryl.sabella(a)gmail.com>
committer: Serhiy Storchaka <storchaka(a)gmail.com>
date: 2017-07-31T22:10:13+03:00
summary:
[3.6] bpo-25684: ttk.OptionMenu radiobuttons weren't unique (GH-2276) (#2959)
between instances of OptionMenu.
(cherry picked from commit a568e5273382a5dca0c27274f7d8e34c41a87d4d)
* Update Misc/ACKS
files:
A Misc/NEWS.d/next/Library/2017-07-17-11-35-00.bpo-25684.usELVx.rst
M Lib/tkinter/test/test_ttk/test_extensions.py
M Lib/tkinter/ttk.py
M Misc/ACKS
diff --git a/Lib/tkinter/test/test_ttk/test_extensions.py b/Lib/tkinter/test/test_ttk/test_extensions.py
index 218b27fc30e..a45f882bb00 100644
--- a/Lib/tkinter/test/test_ttk/test_extensions.py
+++ b/Lib/tkinter/test/test_ttk/test_extensions.py
@@ -291,6 +291,31 @@ def cb_test(item):
optmenu.destroy()
+ def test_unique_radiobuttons(self):
+ # check that radiobuttons are unique across instances (bpo25684)
+ items = ('a', 'b', 'c')
+ default = 'a'
+ optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items)
+ textvar2 = tkinter.StringVar(self.root)
+ optmenu2 = ttk.OptionMenu(self.root, textvar2, default, *items)
+ optmenu.pack()
+ optmenu.wait_visibility()
+ optmenu2.pack()
+ optmenu2.wait_visibility()
+ optmenu['menu'].invoke(1)
+ optmenu2['menu'].invoke(2)
+ optmenu_stringvar_name = optmenu['menu'].entrycget(0, 'variable')
+ optmenu2_stringvar_name = optmenu2['menu'].entrycget(0, 'variable')
+ self.assertNotEqual(optmenu_stringvar_name,
+ optmenu2_stringvar_name)
+ self.assertEqual(self.root.tk.globalgetvar(optmenu_stringvar_name),
+ items[1])
+ self.assertEqual(self.root.tk.globalgetvar(optmenu2_stringvar_name),
+ items[2])
+
+ optmenu.destroy()
+ optmenu2.destroy()
+
tests_gui = (LabeledScaleTest, OptionMenuTest)
diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py
index c474e60713f..9cd0c2073a9 100644
--- a/Lib/tkinter/ttk.py
+++ b/Lib/tkinter/ttk.py
@@ -1638,7 +1638,8 @@ def set_menu(self, default=None, *values):
menu.delete(0, 'end')
for val in values:
menu.add_radiobutton(label=val,
- command=tkinter._setit(self._variable, val, self._callback))
+ command=tkinter._setit(self._variable, val, self._callback),
+ variable=self._variable)
if default:
self._variable.set(default)
diff --git a/Misc/ACKS b/Misc/ACKS
index b6b355b614b..7bc13ff0c9e 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1340,6 +1340,7 @@ Chris Ryland
Bernt Røskar Brenna
Constantina S.
Matthieu S
+Cheryl Sabella
Patrick Sabin
Sébastien Sablé
Amit Saha
diff --git a/Misc/NEWS.d/next/Library/2017-07-17-11-35-00.bpo-25684.usELVx.rst b/Misc/NEWS.d/next/Library/2017-07-17-11-35-00.bpo-25684.usELVx.rst
new file mode 100644
index 00000000000..61d6b29cafc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-07-17-11-35-00.bpo-25684.usELVx.rst
@@ -0,0 +1,2 @@
+Change ``ttk.OptionMenu`` radiobuttons to be unique across instances of
+``OptionMenu``.
1
0
https://github.com/python/cpython/commit/8474d87165593bac2bc231287f42c4cff3…
commit: 8474d87165593bac2bc231287f42c4cff3fd6aaf
branch: master
author: Mariatta <Mariatta(a)users.noreply.github.com>
committer: GitHub <noreply(a)github.com>
date: 2017-07-31T11:16:14-07:00
summary:
bpo-25910: Update LICENSE (GH-2873)
Use the copy provided in https://bugs.python.org/issue25910#msg295200
files:
M LICENSE
diff --git a/LICENSE b/LICENSE
index f5d0b39a0cd..529349e4b38 100644
--- a/LICENSE
+++ b/LICENSE
@@ -13,12 +13,11 @@ software.
In May 2000, Guido and the Python core development team moved to
BeOpen.com to form the BeOpen PythonLabs team. In October of the same
-year, the PythonLabs team moved to Digital Creations (now Zope
-Corporation, see http://www.zope.com). In 2001, the Python Software
-Foundation (PSF, see http://www.python.org/psf/) was formed, a
-non-profit organization created specifically to own Python-related
-Intellectual Property. Zope Corporation is a sponsoring member of
-the PSF.
+year, the PythonLabs team moved to Digital Creations, which became
+Zope Corporation. In 2001, the Python Software Foundation (PSF, see
+https://www.python.org/psf/) was formed, a non-profit organization
+created specifically to own Python-related Intellectual Property.
+Zope Corporation was a sponsoring member of the PSF.
All Python releases are Open Source (see http://www.opensource.org for
the Open Source Definition). Historically, most, but not all, Python
1
0
31 Jul '17
https://github.com/python/cpython/commit/3e37f4a11547a226c3c2f8bd612510465d…
commit: 3e37f4a11547a226c3c2f8bd612510465db397b9
branch: 2.7
author: INADA Naoki <methane(a)users.noreply.github.com>
committer: Łukasz Langa <lukasz(a)langa.pl>
date: 2017-07-31T10:52:46-07:00
summary:
bpo-29519: weakref spewing exceptions during interp finalization (#2958)
(cherry pick from 9cd7e17640a49635d1c1f8c2989578a8fc2c1de6)
files:
A Misc/NEWS.d/next/Library/2017-07-31-19-32-57.bpo-29519._j1awg.rst
M Lib/weakref.py
diff --git a/Lib/weakref.py b/Lib/weakref.py
index c66943f02e2..3e1fb815806 100644
--- a/Lib/weakref.py
+++ b/Lib/weakref.py
@@ -53,7 +53,7 @@ def __init__(*args, **kw):
args = args[1:]
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
- def remove(wr, selfref=ref(self)):
+ def remove(wr, selfref=ref(self), _atomic_removal=_remove_dead_weakref):
self = selfref()
if self is not None:
if self._iterating:
@@ -61,7 +61,7 @@ def remove(wr, selfref=ref(self)):
else:
# Atomic removal is necessary since this function
# can be called asynchronously by the GC
- _remove_dead_weakref(self.data, wr.key)
+ _atomic_removal(self.data, wr.key)
self._remove = remove
# A list of keys to be removed
self._pending_removals = []
diff --git a/Misc/NEWS.d/next/Library/2017-07-31-19-32-57.bpo-29519._j1awg.rst b/Misc/NEWS.d/next/Library/2017-07-31-19-32-57.bpo-29519._j1awg.rst
new file mode 100644
index 00000000000..9b2e39d3318
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-07-31-19-32-57.bpo-29519._j1awg.rst
@@ -0,0 +1,2 @@
+Fix weakref spewing exceptions during interpreter shutdown when used with a
+rare combination of multiprocessing and custom codecs.
1
0
Closes issue bpo-5288: Allow tzinfo objects with sub-minute offsets. (#2896)
by Alexander Belopolsky 31 Jul '17
by Alexander Belopolsky 31 Jul '17
31 Jul '17
https://github.com/python/cpython/commit/018d353c1c8c87767d2335cd884017c2ce…
commit: 018d353c1c8c87767d2335cd884017c2ce12e045
branch: master
author: Alexander Belopolsky <abalkin(a)users.noreply.github.com>
committer: GitHub <noreply(a)github.com>
date: 2017-07-31T10:26:50-04:00
summary:
Closes issue bpo-5288: Allow tzinfo objects with sub-minute offsets. (#2896)
* Closes issue bpo-5288: Allow tzinfo objects with sub-minute offsets.
* bpo-5288: Implemented %z formatting of sub-minute offsets.
* bpo-5288: Removed mentions of the whole minute limitation on TZ offsets.
* bpo-5288: Removed one more mention of the whole minute limitation.
Thanks @csabella!
* Fix a formatting error in the docs
* Addressed review comments.
Thanks, @haypo.
files:
A Misc/NEWS.d/next/Library/2017-07-26-13-18-29.bpo-5288.o_xEGj.rst
M Doc/library/datetime.rst
M Lib/datetime.py
M Lib/test/datetimetester.py
M Modules/_datetimemodule.c
diff --git a/Doc/library/datetime.rst b/Doc/library/datetime.rst
index 3880c2eb0b7..55be8694a06 100644
--- a/Doc/library/datetime.rst
+++ b/Doc/library/datetime.rst
@@ -1071,16 +1071,20 @@ Instance methods:
If :attr:`.tzinfo` is ``None``, returns ``None``, else returns
``self.tzinfo.utcoffset(self)``, and raises an exception if the latter doesn't
- return ``None``, or a :class:`timedelta` object representing a whole number of
- minutes with magnitude less than one day.
+ return ``None`` or a :class:`timedelta` object with magnitude less than one day.
+
+ .. versionchanged:: 3.7
+ The UTC offset is not restricted to a whole number of minutes.
.. method:: datetime.dst()
If :attr:`.tzinfo` is ``None``, returns ``None``, else returns
``self.tzinfo.dst(self)``, and raises an exception if the latter doesn't return
- ``None``, or a :class:`timedelta` object representing a whole number of minutes
- with magnitude less than one day.
+ ``None`` or a :class:`timedelta` object with magnitude less than one day.
+
+ .. versionchanged:: 3.7
+ The DST offset is not restricted to a whole number of minutes.
.. method:: datetime.tzname()
@@ -1562,17 +1566,20 @@ Instance methods:
If :attr:`.tzinfo` is ``None``, returns ``None``, else returns
``self.tzinfo.utcoffset(None)``, and raises an exception if the latter doesn't
- return ``None`` or a :class:`timedelta` object representing a whole number of
- minutes with magnitude less than one day.
+ return ``None`` or a :class:`timedelta` object with magnitude less than one day.
+
+ .. versionchanged:: 3.7
+ The UTC offset is not restricted to a whole number of minutes.
.. method:: time.dst()
If :attr:`.tzinfo` is ``None``, returns ``None``, else returns
``self.tzinfo.dst(None)``, and raises an exception if the latter doesn't return
- ``None``, or a :class:`timedelta` object representing a whole number of minutes
- with magnitude less than one day.
+ ``None``, or a :class:`timedelta` object with magnitude less than one day.
+ .. versionchanged:: 3.7
+ The DST offset is not restricted to a whole number of minutes.
.. method:: time.tzname()
@@ -1641,13 +1648,14 @@ Example:
.. method:: tzinfo.utcoffset(dt)
- Return offset of local time from UTC, in minutes east of UTC. If local time is
+ Return offset of local time from UTC, as a :class:`timedelta` object that is
+ positive east of UTC. If local time is
west of UTC, this should be negative. Note that this is intended to be the
total offset from UTC; for example, if a :class:`tzinfo` object represents both
time zone and DST adjustments, :meth:`utcoffset` should return their sum. If
the UTC offset isn't known, return ``None``. Else the value returned must be a
- :class:`timedelta` object specifying a whole number of minutes in the range
- -1439 to 1439 inclusive (1440 = 24\*60; the magnitude of the offset must be less
+ :class:`timedelta` object strictly between ``-timedelta(hours=24)`` and
+ ``timedelta(hours=24)`` (the magnitude of the offset must be less
than one day). Most implementations of :meth:`utcoffset` will probably look
like one of these two::
@@ -1660,10 +1668,14 @@ Example:
The default implementation of :meth:`utcoffset` raises
:exc:`NotImplementedError`.
+ .. versionchanged:: 3.7
+ The UTC offset is not restricted to a whole number of minutes.
+
.. method:: tzinfo.dst(dt)
- Return the daylight saving time (DST) adjustment, in minutes east of UTC, or
+ Return the daylight saving time (DST) adjustment, as a :class:`timedelta`
+ object or
``None`` if DST information isn't known. Return ``timedelta(0)`` if DST is not
in effect. If DST is in effect, return the offset as a :class:`timedelta` object
(see :meth:`utcoffset` for details). Note that DST offset, if applicable, has
@@ -1708,6 +1720,9 @@ Example:
The default implementation of :meth:`dst` raises :exc:`NotImplementedError`.
+ .. versionchanged:: 3.7
+ The DST offset is not restricted to a whole number of minutes.
+
.. method:: tzinfo.tzname(dt)
@@ -1887,14 +1902,17 @@ made to civil time.
The *offset* argument must be specified as a :class:`timedelta`
object representing the difference between the local time and UTC. It must
be strictly between ``-timedelta(hours=24)`` and
- ``timedelta(hours=24)`` and represent a whole number of minutes,
- otherwise :exc:`ValueError` is raised.
+ ``timedelta(hours=24)``, otherwise :exc:`ValueError` is raised.
The *name* argument is optional. If specified it must be a string that
will be used as the value returned by the :meth:`datetime.tzname` method.
.. versionadded:: 3.2
+ .. versionchanged:: 3.7
+ The UTC offset is not restricted to a whole number of minutes.
+
+
.. method:: timezone.utcoffset(dt)
Return the fixed value specified when the :class:`timezone` instance is
@@ -1902,6 +1920,9 @@ made to civil time.
:class:`timedelta` instance equal to the difference between the
local time and UTC.
+ .. versionchanged:: 3.7
+ The UTC offset is not restricted to a whole number of minutes.
+
.. method:: timezone.tzname(dt)
Return the fixed value specified when the :class:`timezone` instance
@@ -2025,8 +2046,8 @@ format codes.
| | number, zero-padded on the | 999999 | |
| | left. | | |
+-----------+--------------------------------+------------------------+-------+
-| ``%z`` | UTC offset in the form +HHMM | (empty), +0000, -0400, | \(6) |
-| | or -HHMM (empty string if the | +1030 | |
+| ``%z`` | UTC offset in the form | (empty), +0000, -0400, | \(6) |
+| | ±HHMM[SS] (empty string if the | +1030 | |
| | object is naive). | | |
+-----------+--------------------------------+------------------------+-------+
| ``%Z`` | Time zone name (empty string | (empty), UTC, EST, CST | |
@@ -2139,12 +2160,19 @@ Notes:
For an aware object:
``%z``
- :meth:`utcoffset` is transformed into a 5-character string of the form
- +HHMM or -HHMM, where HH is a 2-digit string giving the number of UTC
+ :meth:`utcoffset` is transformed into a string of the form
+ ±HHMM[SS[.uuuuuu]], where HH is a 2-digit string giving the number of UTC
offset hours, and MM is a 2-digit string giving the number of UTC offset
- minutes. For example, if :meth:`utcoffset` returns
- ``timedelta(hours=-3, minutes=-30)``, ``%z`` is replaced with the string
- ``'-0330'``.
+ minutes, SS is a 2-digit string string giving the number of UTC offset
+ seconds and uuuuuu is a 2-digit string string giving the number of UTC
+ offset microseconds. The uuuuuu part is omitted when the offset is a
+ whole number of minutes and both the uuuuuu and the SS parts are omitted
+ when the offset is a whole number of minutes. For example, if
+ :meth:`utcoffset` returns ``timedelta(hours=-3, minutes=-30)``, ``%z`` is
+ replaced with the string ``'-0330'``.
+
+ .. versionchanged:: 3.7
+ The UTC offset is not restricted to a whole number of minutes.
``%Z``
If :meth:`tzname` returns ``None``, ``%Z`` is replaced by an empty
diff --git a/Lib/datetime.py b/Lib/datetime.py
index 76a6f957e08..2f03847be0a 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -206,10 +206,16 @@ def _wrap_strftime(object, format, timetuple):
if offset.days < 0:
offset = -offset
sign = '-'
- h, m = divmod(offset, timedelta(hours=1))
- assert not m % timedelta(minutes=1), "whole minute"
- m //= timedelta(minutes=1)
- zreplace = '%c%02d%02d' % (sign, h, m)
+ h, rest = divmod(offset, timedelta(hours=1))
+ m, rest = divmod(rest, timedelta(minutes=1))
+ s = rest.seconds
+ u = offset.microseconds
+ if u:
+ zreplace = '%c%02d%02d%02d.%06d' % (sign, h, m, s, u)
+ elif s:
+ zreplace = '%c%02d%02d%02d' % (sign, h, m, s)
+ else:
+ zreplace = '%c%02d%02d' % (sign, h, m)
assert '%' not in zreplace
newformat.append(zreplace)
elif ch == 'Z':
@@ -241,7 +247,7 @@ def _check_tzname(name):
# offset is what it returned.
# If offset isn't None or timedelta, raises TypeError.
# If offset is None, returns None.
-# Else offset is checked for being in range, and a whole # of minutes.
+# Else offset is checked for being in range.
# If it is, its integer value is returned. Else ValueError is raised.
def _check_utc_offset(name, offset):
assert name in ("utcoffset", "dst")
@@ -250,9 +256,6 @@ def _check_utc_offset(name, offset):
if not isinstance(offset, timedelta):
raise TypeError("tzinfo.%s() must return None "
"or timedelta, not '%s'" % (name, type(offset)))
- if offset.microseconds:
- raise ValueError("tzinfo.%s() must return a whole number "
- "of seconds, got %s" % (name, offset))
if not -timedelta(1) < offset < timedelta(1):
raise ValueError("%s()=%s, must be strictly between "
"-timedelta(hours=24) and timedelta(hours=24)" %
@@ -960,11 +963,11 @@ def tzname(self, dt):
raise NotImplementedError("tzinfo subclass must override tzname()")
def utcoffset(self, dt):
- "datetime -> minutes east of UTC (negative for west of UTC)"
+ "datetime -> timedelta, positive for east of UTC, negative for west of UTC"
raise NotImplementedError("tzinfo subclass must override utcoffset()")
def dst(self, dt):
- """datetime -> DST offset in minutes east of UTC.
+ """datetime -> DST offset as timedelta, positive for east of UTC.
Return 0 if DST not in effect. utcoffset() must include the DST
offset.
@@ -1262,8 +1265,8 @@ def __format__(self, fmt):
# Timezone functions
def utcoffset(self):
- """Return the timezone offset in minutes east of UTC (negative west of
- UTC)."""
+ """Return the timezone offset as timedelta, positive east of UTC
+ (negative west of UTC)."""
if self._tzinfo is None:
return None
offset = self._tzinfo.utcoffset(None)
@@ -1284,8 +1287,8 @@ def tzname(self):
return name
def dst(self):
- """Return 0 if DST is not in effect, or the DST offset (in minutes
- eastward) if DST is in effect.
+ """Return 0 if DST is not in effect, or the DST offset (as timedelta
+ positive eastward) if DST is in effect.
This is purely informational; the DST offset has already been added to
the UTC offset returned by utcoffset() if applicable, so there's no
@@ -1714,7 +1717,7 @@ def strptime(cls, date_string, format):
return _strptime._strptime_datetime(cls, date_string, format)
def utcoffset(self):
- """Return the timezone offset in minutes east of UTC (negative west of
+ """Return the timezone offset as timedelta positive east of UTC (negative west of
UTC)."""
if self._tzinfo is None:
return None
@@ -1736,8 +1739,8 @@ def tzname(self):
return name
def dst(self):
- """Return 0 if DST is not in effect, or the DST offset (in minutes
- eastward) if DST is in effect.
+ """Return 0 if DST is not in effect, or the DST offset (as timedelta
+ positive eastward) if DST is in effect.
This is purely informational; the DST offset has already been added to
the UTC offset returned by utcoffset() if applicable, so there's no
@@ -1962,9 +1965,6 @@ def __new__(cls, offset, name=_Omitted):
raise ValueError("offset must be a timedelta "
"strictly between -timedelta(hours=24) and "
"timedelta(hours=24).")
- if (offset.microseconds != 0 or offset.seconds % 60 != 0):
- raise ValueError("offset must be a timedelta "
- "representing a whole number of minutes")
return cls._create(offset, name)
@classmethod
@@ -2053,8 +2053,15 @@ def _name_from_offset(delta):
else:
sign = '+'
hours, rest = divmod(delta, timedelta(hours=1))
- minutes = rest // timedelta(minutes=1)
- return 'UTC{}{:02d}:{:02d}'.format(sign, hours, minutes)
+ minutes, rest = divmod(rest, timedelta(minutes=1))
+ seconds = rest.seconds
+ microseconds = rest.microseconds
+ if microseconds:
+ return (f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}'
+ f'.{microseconds:06d}')
+ if seconds:
+ return f'UTC{sign}{hours:02d}:{minutes:02d}:{seconds:02d}'
+ return f'UTC{sign}{hours:02d}:{minutes:02d}'
timezone.utc = timezone._create(timedelta(0))
timezone.min = timezone._create(timezone._minoffset)
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 22008884b54..29b70e1a8a0 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -255,14 +255,15 @@ def test_class_members(self):
self.assertEqual(timezone.min.utcoffset(None), -limit)
self.assertEqual(timezone.max.utcoffset(None), limit)
-
def test_constructor(self):
self.assertIs(timezone.utc, timezone(timedelta(0)))
self.assertIsNot(timezone.utc, timezone(timedelta(0), 'UTC'))
self.assertEqual(timezone.utc, timezone(timedelta(0), 'UTC'))
+ for subminute in [timedelta(microseconds=1), timedelta(seconds=1)]:
+ tz = timezone(subminute)
+ self.assertNotEqual(tz.utcoffset(None) % timedelta(minutes=1), 0)
# invalid offsets
- for invalid in [timedelta(microseconds=1), timedelta(1, 1),
- timedelta(seconds=1), timedelta(1), -timedelta(1)]:
+ for invalid in [timedelta(1, 1), timedelta(1)]:
self.assertRaises(ValueError, timezone, invalid)
self.assertRaises(ValueError, timezone, -invalid)
@@ -301,6 +302,15 @@ def test_tzname(self):
self.assertEqual('UTC-00:01', timezone(timedelta(minutes=-1)).tzname(None))
self.assertEqual('XYZ', timezone(-5 * HOUR, 'XYZ').tzname(None))
+ # Sub-minute offsets:
+ self.assertEqual('UTC+01:06:40', timezone(timedelta(0, 4000)).tzname(None))
+ self.assertEqual('UTC-01:06:40',
+ timezone(-timedelta(0, 4000)).tzname(None))
+ self.assertEqual('UTC+01:06:40.000001',
+ timezone(timedelta(0, 4000, 1)).tzname(None))
+ self.assertEqual('UTC-01:06:40.000001',
+ timezone(-timedelta(0, 4000, 1)).tzname(None))
+
with self.assertRaises(TypeError): self.EST.tzname('')
with self.assertRaises(TypeError): self.EST.tzname(5)
@@ -2152,6 +2162,9 @@ def test_more_strftime(self):
t = self.theclass(2004, 12, 31, 6, 22, 33, 47)
self.assertEqual(t.strftime("%m %d %y %f %S %M %H %j"),
"12 31 04 000047 33 22 06 366")
+ tz = timezone(-timedelta(hours=2, seconds=33, microseconds=123))
+ t = t.replace(tzinfo=tz)
+ self.assertEqual(t.strftime("%z"), "-020033.000123")
def test_extract(self):
dt = self.theclass(2002, 3, 4, 18, 45, 3, 1234)
@@ -2717,8 +2730,8 @@ class C7(tzinfo):
def utcoffset(self, dt): return timedelta(microseconds=61)
def dst(self, dt): return timedelta(microseconds=-81)
t = cls(1, 1, 1, tzinfo=C7())
- self.assertRaises(ValueError, t.utcoffset)
- self.assertRaises(ValueError, t.dst)
+ self.assertEqual(t.utcoffset(), timedelta(microseconds=61))
+ self.assertEqual(t.dst(), timedelta(microseconds=-81))
def test_aware_compare(self):
cls = self.theclass
@@ -4297,7 +4310,6 @@ def test_vilnius_1941_toutc(self):
self.assertEqual(gdt.strftime("%c %Z"),
'Mon Jun 23 22:00:00 1941 UTC')
-
def test_constructors(self):
t = time(0, fold=1)
dt = datetime(1, 1, 1, fold=1)
@@ -4372,7 +4384,6 @@ def test_fromtimestamp_lord_howe(self):
self.assertEqual(t0.fold, 0)
self.assertEqual(t1.fold, 1)
-
@support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
def test_timestamp(self):
dt0 = datetime(2014, 11, 2, 1, 30)
@@ -4390,7 +4401,6 @@ def test_timestamp_lord_howe(self):
s1 = t.replace(fold=1).timestamp()
self.assertEqual(s0 + 1800, s1)
-
@support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0')
def test_astimezone(self):
dt0 = datetime(2014, 11, 2, 1, 30)
@@ -4406,7 +4416,6 @@ def test_astimezone(self):
self.assertEqual(adt0.fold, 0)
self.assertEqual(adt1.fold, 0)
-
def test_pickle_fold(self):
t = time(fold=1)
dt = datetime(1, 1, 1, fold=1)
diff --git a/Misc/NEWS.d/next/Library/2017-07-26-13-18-29.bpo-5288.o_xEGj.rst b/Misc/NEWS.d/next/Library/2017-07-26-13-18-29.bpo-5288.o_xEGj.rst
new file mode 100644
index 00000000000..a7eaa06107f
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-07-26-13-18-29.bpo-5288.o_xEGj.rst
@@ -0,0 +1 @@
+Support tzinfo objects with sub-minute offsets.
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index 28805d18da8..1b68ff3b372 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -859,12 +859,6 @@ new_timezone(PyObject *offset, PyObject *name)
Py_INCREF(PyDateTime_TimeZone_UTC);
return PyDateTime_TimeZone_UTC;
}
- if (GET_TD_MICROSECONDS(offset) != 0 || GET_TD_SECONDS(offset) % 60 != 0) {
- PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
- " representing a whole number of minutes,"
- " not %R.", offset);
- return NULL;
- }
if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
@@ -935,12 +929,6 @@ call_tzinfo_method(PyObject *tzinfo, const char *name, PyObject *tzinfoarg)
if (offset == Py_None || offset == NULL)
return offset;
if (PyDelta_Check(offset)) {
- if (GET_TD_MICROSECONDS(offset) != 0) {
- Py_DECREF(offset);
- PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
- " representing a whole number of seconds");
- return NULL;
- }
if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
Py_DECREF(offset);
@@ -966,9 +954,9 @@ call_tzinfo_method(PyObject *tzinfo, const char *name, PyObject *tzinfoarg)
* result. tzinfo must be an instance of the tzinfo class. If utcoffset()
* returns None, call_utcoffset returns 0 and sets *none to 1. If uctoffset()
* doesn't return None or timedelta, TypeError is raised and this returns -1.
- * If utcoffset() returns an invalid timedelta (out of range, or not a whole
- * # of minutes), ValueError is raised and this returns -1. Else *none is
- * set to 0 and the offset is returned (as int # of minutes east of UTC).
+ * If utcoffset() returns an out of range timedelta,
+ * ValueError is raised and this returns -1. Else *none is
+ * set to 0 and the offset is returned (as timedelta, positive east of UTC).
*/
static PyObject *
call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg)
@@ -979,10 +967,10 @@ call_utcoffset(PyObject *tzinfo, PyObject *tzinfoarg)
/* Call tzinfo.dst(tzinfoarg), and extract an integer from the
* result. tzinfo must be an instance of the tzinfo class. If dst()
* returns None, call_dst returns 0 and sets *none to 1. If dst()
- & doesn't return None or timedelta, TypeError is raised and this
+ * doesn't return None or timedelta, TypeError is raised and this
* returns -1. If dst() returns an invalid timedelta for a UTC offset,
* ValueError is raised and this returns -1. Else *none is set to 0 and
- * the offset is returned (as an int # of minutes east of UTC).
+ * the offset is returned (as timedelta, positive east of UTC).
*/
static PyObject *
call_dst(PyObject *tzinfo, PyObject *tzinfoarg)
@@ -1100,13 +1088,13 @@ format_ctime(PyDateTime_Date *date, int hours, int minutes, int seconds)
static PyObject *delta_negative(PyDateTime_Delta *self);
-/* Add an hours & minutes UTC offset string to buf. buf has no more than
+/* Add formatted UTC offset string to buf. buf has no more than
* buflen bytes remaining. The UTC offset is gotten by calling
* tzinfo.uctoffset(tzinfoarg). If that returns None, \0 is stored into
* *buf, and that's all. Else the returned value is checked for sanity (an
* integer in range), and if that's OK it's converted to an hours & minutes
* string of the form
- * sign HH sep MM
+ * sign HH sep MM [sep SS [. UUUUUU]]
* Returns 0 if everything is OK. If the return value from utcoffset() is
* bogus, an appropriate exception is set and -1 is returned.
*/
@@ -1115,7 +1103,7 @@ format_utcoffset(char *buf, size_t buflen, const char *sep,
PyObject *tzinfo, PyObject *tzinfoarg)
{
PyObject *offset;
- int hours, minutes, seconds;
+ int hours, minutes, seconds, microseconds;
char sign;
assert(buflen >= 1);
@@ -1139,15 +1127,22 @@ format_utcoffset(char *buf, size_t buflen, const char *sep,
sign = '+';
}
/* Offset is not negative here. */
+ microseconds = GET_TD_MICROSECONDS(offset);
seconds = GET_TD_SECONDS(offset);
Py_DECREF(offset);
minutes = divmod(seconds, 60, &seconds);
hours = divmod(minutes, 60, &minutes);
- if (seconds == 0)
- PyOS_snprintf(buf, buflen, "%c%02d%s%02d", sign, hours, sep, minutes);
- else
+ if (microseconds) {
+ PyOS_snprintf(buf, buflen, "%c%02d%s%02d%s%02d.%06d", sign,
+ hours, sep, minutes, sep, seconds, microseconds);
+ return 0;
+ }
+ if (seconds) {
PyOS_snprintf(buf, buflen, "%c%02d%s%02d%s%02d", sign, hours,
sep, minutes, sep, seconds);
+ return 0;
+ }
+ PyOS_snprintf(buf, buflen, "%c%02d%s%02d", sign, hours, sep, minutes);
return 0;
}
@@ -3241,7 +3236,7 @@ static PyMethodDef tzinfo_methods[] = {
"values indicating West of UTC")},
{"dst", (PyCFunction)tzinfo_dst, METH_O,
- PyDoc_STR("datetime -> DST offset in minutes east of UTC.")},
+ PyDoc_STR("datetime -> DST offset as timedelta positive east of UTC.")},
{"fromutc", (PyCFunction)tzinfo_fromutc, METH_O,
PyDoc_STR("datetime in UTC -> datetime in local time.")},
@@ -3375,7 +3370,7 @@ timezone_repr(PyDateTime_TimeZone *self)
static PyObject *
timezone_str(PyDateTime_TimeZone *self)
{
- int hours, minutes, seconds;
+ int hours, minutes, seconds, microseconds;
PyObject *offset;
char sign;
@@ -3401,12 +3396,20 @@ timezone_str(PyDateTime_TimeZone *self)
Py_INCREF(offset);
}
/* Offset is not negative here. */
+ microseconds = GET_TD_MICROSECONDS(offset);
seconds = GET_TD_SECONDS(offset);
Py_DECREF(offset);
minutes = divmod(seconds, 60, &seconds);
hours = divmod(minutes, 60, &minutes);
- /* XXX ignore sub-minute data, currently not allowed. */
- assert(seconds == 0);
+ if (microseconds != 0) {
+ return PyUnicode_FromFormat("UTC%c%02d:%02d:%02d.%06d",
+ sign, hours, minutes,
+ seconds, microseconds);
+ }
+ if (seconds != 0) {
+ return PyUnicode_FromFormat("UTC%c%02d:%02d:%02d",
+ sign, hours, minutes, seconds);
+ }
return PyUnicode_FromFormat("UTC%c%02d:%02d", sign, hours, minutes);
}
1
0
bpo-30640: Fix undefined behavior in _PyFunction_FastCallDict() and PyEval_EvalCodeEx() (#2919)
by Serhiy Storchaka 31 Jul '17
by Serhiy Storchaka 31 Jul '17
31 Jul '17
https://github.com/python/cpython/commit/c6ea8974e2d939223bfd6d64ee13ec89c0…
commit: c6ea8974e2d939223bfd6d64ee13ec89c090d2e0
branch: master
author: Zackery Spytz <Osmunda46(a)gmail.com>
committer: Serhiy Storchaka <storchaka(a)gmail.com>
date: 2017-07-31T17:24:37+03:00
summary:
bpo-30640: Fix undefined behavior in _PyFunction_FastCallDict() and PyEval_EvalCodeEx() (#2919)
k + 1 was calculated with k = NULL.
files:
M Objects/call.c
M Python/ceval.c
diff --git a/Objects/call.c b/Objects/call.c
index c3cc31dba9b..3b08cb25926 100644
--- a/Objects/call.c
+++ b/Objects/call.c
@@ -374,7 +374,7 @@ _PyFunction_FastCallDict(PyObject *func, PyObject **args, Py_ssize_t nargs,
result = _PyEval_EvalCodeWithName((PyObject*)co, globals, (PyObject *)NULL,
args, nargs,
- k, k + 1, nk, 2,
+ k, k != NULL ? k + 1 : NULL, nk, 2,
d, nd, kwdefs,
closure, name, qualname);
Py_XDECREF(kwtuple);
diff --git a/Python/ceval.c b/Python/ceval.c
index 59fc070f9e7..dd90e18a855 100644
--- a/Python/ceval.c
+++ b/Python/ceval.c
@@ -4220,7 +4220,8 @@ PyEval_EvalCodeEx(PyObject *_co, PyObject *globals, PyObject *locals,
{
return _PyEval_EvalCodeWithName(_co, globals, locals,
args, argcount,
- kws, kws + 1, kwcount, 2,
+ kws, kws != NULL ? kws + 1 : NULL,
+ kwcount, 2,
defs, defcount,
kwdefs, closure,
NULL, NULL);
1
0
31 Jul '17
https://github.com/python/cpython/commit/a568e5273382a5dca0c27274f7d8e34c41…
commit: a568e5273382a5dca0c27274f7d8e34c41a87d4d
branch: master
author: csabella <cheryl.sabella(a)gmail.com>
committer: Serhiy Storchaka <storchaka(a)gmail.com>
date: 2017-07-31T12:30:09+03:00
summary:
bpo-25684: ttk.OptionMenu radiobuttons weren't unique (#2276)
between instances of OptionMenu.
files:
A Misc/NEWS.d/next/Library/2017-07-17-11-35-00.bpo-25684.usELVx.rst
M Lib/tkinter/test/test_ttk/test_extensions.py
M Lib/tkinter/ttk.py
diff --git a/Lib/tkinter/test/test_ttk/test_extensions.py b/Lib/tkinter/test/test_ttk/test_extensions.py
index 218b27fc30e..a45f882bb00 100644
--- a/Lib/tkinter/test/test_ttk/test_extensions.py
+++ b/Lib/tkinter/test/test_ttk/test_extensions.py
@@ -291,6 +291,31 @@ def cb_test(item):
optmenu.destroy()
+ def test_unique_radiobuttons(self):
+ # check that radiobuttons are unique across instances (bpo25684)
+ items = ('a', 'b', 'c')
+ default = 'a'
+ optmenu = ttk.OptionMenu(self.root, self.textvar, default, *items)
+ textvar2 = tkinter.StringVar(self.root)
+ optmenu2 = ttk.OptionMenu(self.root, textvar2, default, *items)
+ optmenu.pack()
+ optmenu.wait_visibility()
+ optmenu2.pack()
+ optmenu2.wait_visibility()
+ optmenu['menu'].invoke(1)
+ optmenu2['menu'].invoke(2)
+ optmenu_stringvar_name = optmenu['menu'].entrycget(0, 'variable')
+ optmenu2_stringvar_name = optmenu2['menu'].entrycget(0, 'variable')
+ self.assertNotEqual(optmenu_stringvar_name,
+ optmenu2_stringvar_name)
+ self.assertEqual(self.root.tk.globalgetvar(optmenu_stringvar_name),
+ items[1])
+ self.assertEqual(self.root.tk.globalgetvar(optmenu2_stringvar_name),
+ items[2])
+
+ optmenu.destroy()
+ optmenu2.destroy()
+
tests_gui = (LabeledScaleTest, OptionMenuTest)
diff --git a/Lib/tkinter/ttk.py b/Lib/tkinter/ttk.py
index cbaad76e008..3ecc004f0e0 100644
--- a/Lib/tkinter/ttk.py
+++ b/Lib/tkinter/ttk.py
@@ -1635,7 +1635,8 @@ def set_menu(self, default=None, *values):
menu.delete(0, 'end')
for val in values:
menu.add_radiobutton(label=val,
- command=tkinter._setit(self._variable, val, self._callback))
+ command=tkinter._setit(self._variable, val, self._callback),
+ variable=self._variable)
if default:
self._variable.set(default)
diff --git a/Misc/NEWS.d/next/Library/2017-07-17-11-35-00.bpo-25684.usELVx.rst b/Misc/NEWS.d/next/Library/2017-07-17-11-35-00.bpo-25684.usELVx.rst
new file mode 100644
index 00000000000..61d6b29cafc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2017-07-17-11-35-00.bpo-25684.usELVx.rst
@@ -0,0 +1,2 @@
+Change ``ttk.OptionMenu`` radiobuttons to be unique across instances of
+``OptionMenu``.
1
0
results for 4243df51fe43 on branch "default"
--------------------------------------------
test_collections leaked [7, 0, -7] memory blocks, sum=0
test_functools leaked [0, 3, 1] memory blocks, sum=4
test_multiprocessing_forkserver leaked [-2, 2, -1] memory blocks, sum=-1
Command line was: ['./python', '-m', 'test.regrtest', '-uall', '-R', '3:3:/home/psf-users/antoine/refleaks/reflogZMppdc', '--timeout', '7200']
1
0
[3.6] bpo-31050: IDLE: Factor GenPage class from ConfigDialog (GH-2952) (#2955)
by Terry Jan Reedy 30 Jul '17
by Terry Jan Reedy 30 Jul '17
30 Jul '17
https://github.com/python/cpython/commit/8c4e5be1dfb4d1c74e8cc7ac925e319681…
commit: 8c4e5be1dfb4d1c74e8cc7ac925e3196818e07ac
branch: 3.6
author: Terry Jan Reedy <tjreedy(a)udel.edu>
committer: GitHub <noreply(a)github.com>
date: 2017-07-30T19:02:51-04:00
summary:
[3.6] bpo-31050: IDLE: Factor GenPage class from ConfigDialog (GH-2952) (#2955)
The slightly modified tests for the General tab continue to pass.
Patch by Cheryl Sabella.
(cherry picked from commit e8eb17b)
files:
A Misc/NEWS.d/next/IDLE/2017-07-30-17-39-59.bpo-31050.AXR3kP.rst
M Lib/idlelib/configdialog.py
M Lib/idlelib/idle_test/test_configdialog.py
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 92bbc106344..28070a51709 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -89,14 +89,14 @@ def create_widgets(self):
"""Create and place widgets for tabbed dialog.
Widgets Bound to self:
- tab_pages: TabbedPageSet
+ note: Notebook
+ highpage: self.create_page_highlight
+ fontpage: FontPage
+ keyspage: self.create_page_keys
+ genpage: GenPage
+ extpageL self.create_page_extensions
Methods:
- create_page_font_tab
- create_page_highlight
- create_page_keys
- create_page_general
- create_page_extensions
create_action_buttons
load_configs: Load pages except for extensions.
activate_config_changes: Tell editors to reload.
@@ -105,7 +105,7 @@ def create_widgets(self):
self.highpage = self.create_page_highlight()
self.fontpage = FontPage(note, self.highpage)
self.keyspage = self.create_page_keys()
- self.genpage = self.create_page_general()
+ self.genpage = GenPage(note)
self.extpage = self.create_page_extensions()
note.add(self.fontpage, text='Fonts/Tabs')
note.add(self.highpage, text='Highlights')
@@ -133,7 +133,7 @@ def load_configs(self):
#self.load_tab_cfg()
self.load_theme_cfg()
self.load_key_cfg()
- self.load_general_cfg()
+ # self.load_general_cfg()
# note: extension page handled separately
def create_action_buttons(self):
@@ -1198,256 +1198,6 @@ def activate_config_changes(self):
instance.ApplyKeybindings()
instance.reset_help_menu_entries()
-
- def create_page_general(self):
- """Return frame of widgets for General tab.
-
- Enable users to provisionally change general options. Function
- load_general_cfg intializes tk variables and helplist using
- idleConf. Radiobuttons startup_shell_on and startup_editor_on
- set var startup_edit. Radiobuttons save_ask_on and save_auto_on
- set var autosave. Entry boxes win_width_int and win_height_int
- set var win_width and win_height. Setting var_name invokes the
- default callback that adds option to changes.
-
- Helplist: load_general_cfg loads list user_helplist with
- name, position pairs and copies names to listbox helplist.
- Clicking a name invokes help_source selected. Clicking
- button_helplist_name invokes helplist_item_name, which also
- changes user_helplist. These functions all call
- set_add_delete_state. All but load call update_help_changes to
- rewrite changes['main']['HelpFiles'].
-
- Widget Structure: (*) widgets bound to self
- frame
- frame_run: LabelFrame
- startup_title: Label
- (*)startup_editor_on: Radiobutton - startup_edit
- (*)startup_shell_on: Radiobutton - startup_edit
- frame_save: LabelFrame
- run_save_title: Label
- (*)save_ask_on: Radiobutton - autosave
- (*)save_auto_on: Radiobutton - autosave
- frame_win_size: LabelFrame
- win_size_title: Label
- win_width_title: Label
- (*)win_width_int: Entry - win_width
- win_height_title: Label
- (*)win_height_int: Entry - win_height
- frame_help: LabelFrame
- frame_helplist: Frame
- frame_helplist_buttons: Frame
- (*)button_helplist_edit
- (*)button_helplist_add
- (*)button_helplist_remove
- (*)helplist: ListBox
- scroll_helplist: Scrollbar
- """
- parent = self.parent
- self.startup_edit = tracers.add(
- IntVar(parent), ('main', 'General', 'editor-on-startup'))
- self.autosave = tracers.add(
- IntVar(parent), ('main', 'General', 'autosave'))
- self.win_width = tracers.add(
- StringVar(parent), ('main', 'EditorWindow', 'width'))
- self.win_height = tracers.add(
- StringVar(parent), ('main', 'EditorWindow', 'height'))
-
- # Create widgets:
- # body and section frames.
- frame = Frame(self.note)
- frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
- text=' Startup Preferences ')
- frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
- text=' autosave Preferences ')
- frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
- frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
- text=' Additional Help Sources ')
- # frame_run.
- startup_title = Label(frame_run, text='At Startup')
- self.startup_editor_on = Radiobutton(
- frame_run, variable=self.startup_edit, value=1,
- text="Open Edit Window")
- self.startup_shell_on = Radiobutton(
- frame_run, variable=self.startup_edit, value=0,
- text='Open Shell Window')
- # frame_save.
- run_save_title = Label(frame_save, text='At Start of Run (F5) ')
- self.save_ask_on = Radiobutton(
- frame_save, variable=self.autosave, value=0,
- text="Prompt to Save")
- self.save_auto_on = Radiobutton(
- frame_save, variable=self.autosave, value=1,
- text='No Prompt')
- # frame_win_size.
- win_size_title = Label(
- frame_win_size, text='Initial Window Size (in characters)')
- win_width_title = Label(frame_win_size, text='Width')
- self.win_width_int = Entry(
- frame_win_size, textvariable=self.win_width, width=3)
- win_height_title = Label(frame_win_size, text='Height')
- self.win_height_int = Entry(
- frame_win_size, textvariable=self.win_height, width=3)
- # frame_help.
- frame_helplist = Frame(frame_help)
- frame_helplist_buttons = Frame(frame_helplist)
- self.helplist = Listbox(
- frame_helplist, height=5, takefocus=True,
- exportselection=FALSE)
- scroll_helplist = Scrollbar(frame_helplist)
- scroll_helplist['command'] = self.helplist.yview
- self.helplist['yscrollcommand'] = scroll_helplist.set
- self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
- self.button_helplist_edit = Button(
- frame_helplist_buttons, text='Edit', state=DISABLED,
- width=8, command=self.helplist_item_edit)
- self.button_helplist_add = Button(
- frame_helplist_buttons, text='Add',
- width=8, command=self.helplist_item_add)
- self.button_helplist_remove = Button(
- frame_helplist_buttons, text='Remove', state=DISABLED,
- width=8, command=self.helplist_item_remove)
-
- # Pack widgets:
- # body.
- frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
- frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
- frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
- frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
- # frame_run.
- startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- # frame_save.
- run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- # frame_win_size.
- win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
- win_height_title.pack(side=RIGHT, anchor=E, pady=5)
- self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
- win_width_title.pack(side=RIGHT, anchor=E, pady=5)
- # frame_help.
- frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
- frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
- scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
- self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
- self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
- self.button_helplist_add.pack(side=TOP, anchor=W)
- self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
-
- return frame
-
- def load_general_cfg(self):
- "Load current configuration settings for the general options."
- # Set startup state.
- self.startup_edit.set(idleConf.GetOption(
- 'main', 'General', 'editor-on-startup', default=0, type='bool'))
- # Set autosave state.
- self.autosave.set(idleConf.GetOption(
- 'main', 'General', 'autosave', default=0, type='bool'))
- # Set initial window size.
- self.win_width.set(idleConf.GetOption(
- 'main', 'EditorWindow', 'width', type='int'))
- self.win_height.set(idleConf.GetOption(
- 'main', 'EditorWindow', 'height', type='int'))
- # Set additional help sources.
- self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
- self.helplist.delete(0, 'end')
- for help_item in self.user_helplist:
- self.helplist.insert(END, help_item[0])
- self.set_add_delete_state()
-
- def var_changed_startup_edit(self, *params):
- "Store change to toggle for starting IDLE in the editor or shell."
- value = self.startup_edit.get()
- changes.add_option('main', 'General', 'editor-on-startup', value)
-
- def var_changed_autosave(self, *params):
- "Store change to autosave."
- value = self.autosave.get()
- changes.add_option('main', 'General', 'autosave', value)
-
- def var_changed_win_width(self, *params):
- "Store change to window width."
- value = self.win_width.get()
- changes.add_option('main', 'EditorWindow', 'width', value)
-
- def var_changed_win_height(self, *params):
- "Store change to window height."
- value = self.win_height.get()
- changes.add_option('main', 'EditorWindow', 'height', value)
-
- def help_source_selected(self, event):
- "Handle event for selecting additional help."
- self.set_add_delete_state()
-
- def set_add_delete_state(self):
- "Toggle the state for the help list buttons based on list entries."
- if self.helplist.size() < 1: # No entries in list.
- self.button_helplist_edit['state'] = DISABLED
- self.button_helplist_remove['state'] = DISABLED
- else: # Some entries.
- if self.helplist.curselection(): # There currently is a selection.
- self.button_helplist_edit['state'] = NORMAL
- self.button_helplist_remove['state'] = NORMAL
- else: # There currently is not a selection.
- self.button_helplist_edit['state'] = DISABLED
- self.button_helplist_remove['state'] = DISABLED
-
- def helplist_item_add(self):
- """Handle add button for the help list.
-
- Query for name and location of new help sources and add
- them to the list.
- """
- help_source = HelpSource(self, 'New Help Source').result
- if help_source:
- self.user_helplist.append(help_source)
- self.helplist.insert(END, help_source[0])
- self.update_help_changes()
-
- def helplist_item_edit(self):
- """Handle edit button for the help list.
-
- Query with existing help source information and update
- config if the values are changed.
- """
- item_index = self.helplist.index(ANCHOR)
- help_source = self.user_helplist[item_index]
- new_help_source = HelpSource(
- self, 'Edit Help Source',
- menuitem=help_source[0],
- filepath=help_source[1],
- ).result
- if new_help_source and new_help_source != help_source:
- self.user_helplist[item_index] = new_help_source
- self.helplist.delete(item_index)
- self.helplist.insert(item_index, new_help_source[0])
- self.update_help_changes()
- self.set_add_delete_state() # Selected will be un-selected
-
- def helplist_item_remove(self):
- """Handle remove button for the help list.
-
- Delete the help list item from config.
- """
- item_index = self.helplist.index(ANCHOR)
- del(self.user_helplist[item_index])
- self.helplist.delete(item_index)
- self.update_help_changes()
- self.set_add_delete_state()
-
- def update_help_changes(self):
- "Clear and rebuild the HelpFiles section in changes"
- changes['main']['HelpFiles'] = {}
- for num in range(1, len(self.user_helplist) + 1):
- changes.add_option(
- 'main', 'HelpFiles', str(num),
- ';'.join(self.user_helplist[num-1][:2]))
-
-
def create_page_extensions(self):
"""Part of the config dialog used for configuring IDLE extensions.
@@ -1845,6 +1595,238 @@ def var_changed_space_num(self, *params):
changes.add_option('main', 'Indent', 'num-spaces', value)
+class GenPage(Frame):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+ self.create_page_general()
+ self.load_general_cfg()
+
+ def create_page_general(self):
+ """Return frame of widgets for General tab.
+
+ Enable users to provisionally change general options. Function
+ load_general_cfg intializes tk variables and helplist using
+ idleConf. Radiobuttons startup_shell_on and startup_editor_on
+ set var startup_edit. Radiobuttons save_ask_on and save_auto_on
+ set var autosave. Entry boxes win_width_int and win_height_int
+ set var win_width and win_height. Setting var_name invokes the
+ default callback that adds option to changes.
+
+ Helplist: load_general_cfg loads list user_helplist with
+ name, position pairs and copies names to listbox helplist.
+ Clicking a name invokes help_source selected. Clicking
+ button_helplist_name invokes helplist_item_name, which also
+ changes user_helplist. These functions all call
+ set_add_delete_state. All but load call update_help_changes to
+ rewrite changes['main']['HelpFiles'].
+
+ Widget Structure: (*) widgets bound to self
+ frame
+ frame_run: LabelFrame
+ startup_title: Label
+ (*)startup_editor_on: Radiobutton - startup_edit
+ (*)startup_shell_on: Radiobutton - startup_edit
+ frame_save: LabelFrame
+ run_save_title: Label
+ (*)save_ask_on: Radiobutton - autosave
+ (*)save_auto_on: Radiobutton - autosave
+ frame_win_size: LabelFrame
+ win_size_title: Label
+ win_width_title: Label
+ (*)win_width_int: Entry - win_width
+ win_height_title: Label
+ (*)win_height_int: Entry - win_height
+ frame_help: LabelFrame
+ frame_helplist: Frame
+ frame_helplist_buttons: Frame
+ (*)button_helplist_edit
+ (*)button_helplist_add
+ (*)button_helplist_remove
+ (*)helplist: ListBox
+ scroll_helplist: Scrollbar
+ """
+ self.startup_edit = tracers.add(
+ IntVar(self), ('main', 'General', 'editor-on-startup'))
+ self.autosave = tracers.add(
+ IntVar(self), ('main', 'General', 'autosave'))
+ self.win_width = tracers.add(
+ StringVar(self), ('main', 'EditorWindow', 'width'))
+ self.win_height = tracers.add(
+ StringVar(self), ('main', 'EditorWindow', 'height'))
+
+ # Create widgets:
+ # Section frames.
+ frame_run = LabelFrame(self, borderwidth=2, relief=GROOVE,
+ text=' Startup Preferences ')
+ frame_save = LabelFrame(self, borderwidth=2, relief=GROOVE,
+ text=' autosave Preferences ')
+ frame_win_size = Frame(self, borderwidth=2, relief=GROOVE)
+ frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE,
+ text=' Additional Help Sources ')
+ # frame_run.
+ startup_title = Label(frame_run, text='At Startup')
+ self.startup_editor_on = Radiobutton(
+ frame_run, variable=self.startup_edit, value=1,
+ text="Open Edit Window")
+ self.startup_shell_on = Radiobutton(
+ frame_run, variable=self.startup_edit, value=0,
+ text='Open Shell Window')
+ # frame_save.
+ run_save_title = Label(frame_save, text='At Start of Run (F5) ')
+ self.save_ask_on = Radiobutton(
+ frame_save, variable=self.autosave, value=0,
+ text="Prompt to Save")
+ self.save_auto_on = Radiobutton(
+ frame_save, variable=self.autosave, value=1,
+ text='No Prompt')
+ # frame_win_size.
+ win_size_title = Label(
+ frame_win_size, text='Initial Window Size (in characters)')
+ win_width_title = Label(frame_win_size, text='Width')
+ self.win_width_int = Entry(
+ frame_win_size, textvariable=self.win_width, width=3)
+ win_height_title = Label(frame_win_size, text='Height')
+ self.win_height_int = Entry(
+ frame_win_size, textvariable=self.win_height, width=3)
+ # frame_help.
+ frame_helplist = Frame(frame_help)
+ frame_helplist_buttons = Frame(frame_helplist)
+ self.helplist = Listbox(
+ frame_helplist, height=5, takefocus=True,
+ exportselection=FALSE)
+ scroll_helplist = Scrollbar(frame_helplist)
+ scroll_helplist['command'] = self.helplist.yview
+ self.helplist['yscrollcommand'] = scroll_helplist.set
+ self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
+ self.button_helplist_edit = Button(
+ frame_helplist_buttons, text='Edit', state=DISABLED,
+ width=8, command=self.helplist_item_edit)
+ self.button_helplist_add = Button(
+ frame_helplist_buttons, text='Add',
+ width=8, command=self.helplist_item_add)
+ self.button_helplist_remove = Button(
+ frame_helplist_buttons, text='Remove', state=DISABLED,
+ width=8, command=self.helplist_item_remove)
+
+ # Pack widgets:
+ # body.
+ frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
+ frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
+ frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
+ frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+ # frame_run.
+ startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+ self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ # frame_save.
+ run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+ self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ # frame_win_size.
+ win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+ self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
+ win_height_title.pack(side=RIGHT, anchor=E, pady=5)
+ self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
+ win_width_title.pack(side=RIGHT, anchor=E, pady=5)
+ # frame_help.
+ frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
+ frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+ scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
+ self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
+ self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
+ self.button_helplist_add.pack(side=TOP, anchor=W)
+ self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
+
+ def load_general_cfg(self):
+ "Load current configuration settings for the general options."
+ # Set startup state.
+ self.startup_edit.set(idleConf.GetOption(
+ 'main', 'General', 'editor-on-startup', default=0, type='bool'))
+ # Set autosave state.
+ self.autosave.set(idleConf.GetOption(
+ 'main', 'General', 'autosave', default=0, type='bool'))
+ # Set initial window size.
+ self.win_width.set(idleConf.GetOption(
+ 'main', 'EditorWindow', 'width', type='int'))
+ self.win_height.set(idleConf.GetOption(
+ 'main', 'EditorWindow', 'height', type='int'))
+ # Set additional help sources.
+ self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
+ self.helplist.delete(0, 'end')
+ for help_item in self.user_helplist:
+ self.helplist.insert(END, help_item[0])
+ self.set_add_delete_state()
+
+ def help_source_selected(self, event):
+ "Handle event for selecting additional help."
+ self.set_add_delete_state()
+
+ def set_add_delete_state(self):
+ "Toggle the state for the help list buttons based on list entries."
+ if self.helplist.size() < 1: # No entries in list.
+ self.button_helplist_edit['state'] = DISABLED
+ self.button_helplist_remove['state'] = DISABLED
+ else: # Some entries.
+ if self.helplist.curselection(): # There currently is a selection.
+ self.button_helplist_edit['state'] = NORMAL
+ self.button_helplist_remove['state'] = NORMAL
+ else: # There currently is not a selection.
+ self.button_helplist_edit['state'] = DISABLED
+ self.button_helplist_remove['state'] = DISABLED
+
+ def helplist_item_add(self):
+ """Handle add button for the help list.
+
+ Query for name and location of new help sources and add
+ them to the list.
+ """
+ help_source = HelpSource(self, 'New Help Source').result
+ if help_source:
+ self.user_helplist.append(help_source)
+ self.helplist.insert(END, help_source[0])
+ self.update_help_changes()
+
+ def helplist_item_edit(self):
+ """Handle edit button for the help list.
+
+ Query with existing help source information and update
+ config if the values are changed.
+ """
+ item_index = self.helplist.index(ANCHOR)
+ help_source = self.user_helplist[item_index]
+ new_help_source = HelpSource(
+ self, 'Edit Help Source',
+ menuitem=help_source[0],
+ filepath=help_source[1],
+ ).result
+ if new_help_source and new_help_source != help_source:
+ self.user_helplist[item_index] = new_help_source
+ self.helplist.delete(item_index)
+ self.helplist.insert(item_index, new_help_source[0])
+ self.update_help_changes()
+ self.set_add_delete_state() # Selected will be un-selected
+
+ def helplist_item_remove(self):
+ """Handle remove button for the help list.
+
+ Delete the help list item from config.
+ """
+ item_index = self.helplist.index(ANCHOR)
+ del(self.user_helplist[item_index])
+ self.helplist.delete(item_index)
+ self.update_help_changes()
+ self.set_add_delete_state()
+
+ def update_help_changes(self):
+ "Clear and rebuild the HelpFiles section in changes"
+ changes['main']['HelpFiles'] = {}
+ for num in range(1, len(self.user_helplist) + 1):
+ changes.add_option(
+ 'main', 'HelpFiles', str(num),
+ ';'.join(self.user_helplist[num-1][:2]))
+
+
class VarTrace:
"""Maintain Tk variables trace state."""
diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py
index 8da726b80c0..caeb2b56787 100644
--- a/Lib/idlelib/idle_test/test_configdialog.py
+++ b/Lib/idlelib/idle_test/test_configdialog.py
@@ -237,7 +237,7 @@ def setUp(self):
changes.clear()
-class GeneralTest(unittest.TestCase):
+class GenPageTest(unittest.TestCase):
"""Test that general tab widgets enable users to make changes.
Test that widget actions set vars, that var changes add
@@ -245,20 +245,18 @@ class GeneralTest(unittest.TestCase):
"""
@classmethod
def setUpClass(cls):
- d = dialog
- # Select General tab so can force focus on helplist.
- d.note.select(d.genpage)
- # Mask instance methods used by help functions.
- d.set = d.set_add_delete_state = Func()
- d.upc = d.update_help_changes = Func()
+ page = cls.page = dialog.genpage
+ dialog.note.select(page)
+ page.set = page.set_add_delete_state = Func()
+ page.upc = page.update_help_changes = Func()
@classmethod
def tearDownClass(cls):
- d = dialog
- del d.set, d.set_add_delete_state
- del d.upc, d.update_help_changes
- d.helplist.delete(0, 'end')
- d.user_helplist.clear()
+ page = cls.page
+ del page.set, page.set_add_delete_state
+ del page.upc, page.update_help_changes
+ page.helplist.delete(0, 'end')
+ page.user_helplist.clear()
def setUp(self):
changes.clear()
@@ -266,7 +264,7 @@ def setUp(self):
def test_load_general_cfg(self):
# Set to wrong values, load, check right values.
eq = self.assertEqual
- d = dialog
+ d = self.page
d.startup_edit.set(1)
d.autosave.set(1)
d.win_width.set(1)
@@ -283,29 +281,32 @@ def test_load_general_cfg(self):
eq(d.user_helplist, [('name', 'file', '1')])
def test_startup(self):
- dialog.startup_editor_on.invoke()
+ d = self.page
+ d.startup_editor_on.invoke()
self.assertEqual(mainpage,
{'General': {'editor-on-startup': '1'}})
changes.clear()
- dialog.startup_shell_on.invoke()
+ d.startup_shell_on.invoke()
self.assertEqual(mainpage,
{'General': {'editor-on-startup': '0'}})
def test_autosave(self):
- dialog.save_auto_on.invoke()
+ d = self.page
+ d.save_auto_on.invoke()
self.assertEqual(mainpage, {'General': {'autosave': '1'}})
- dialog.save_ask_on.invoke()
+ d.save_ask_on.invoke()
self.assertEqual(mainpage, {'General': {'autosave': '0'}})
def test_editor_size(self):
- dialog.win_height_int.insert(0, '1')
+ d = self.page
+ d.win_height_int.insert(0, '1')
self.assertEqual(mainpage, {'EditorWindow': {'height': '140'}})
changes.clear()
- dialog.win_width_int.insert(0, '1')
+ d.win_width_int.insert(0, '1')
self.assertEqual(mainpage, {'EditorWindow': {'width': '180'}})
def test_source_selected(self):
- d = dialog
+ d = self.page
d.set = d.set_add_delete_state
d.upc = d.update_help_changes
helplist = d.helplist
@@ -331,7 +332,7 @@ def test_source_selected(self):
def test_set_add_delete_state(self):
# Call with 0 items, 1 unselected item, 1 selected item.
eq = self.assertEqual
- d = dialog
+ d = self.page
del d.set_add_delete_state # Unmask method.
sad = d.set_add_delete_state
h = d.helplist
@@ -358,7 +359,7 @@ def test_helplist_item_add(self):
eq = self.assertEqual
orig_helpsource = configdialog.HelpSource
hs = configdialog.HelpSource = Func(return_self=True)
- d = dialog
+ d = self.page
d.helplist.delete(0, 'end')
d.user_helplist.clear()
d.set.called = d.upc.called = 0
@@ -385,7 +386,7 @@ def test_helplist_item_edit(self):
eq = self.assertEqual
orig_helpsource = configdialog.HelpSource
hs = configdialog.HelpSource = Func(return_self=True)
- d = dialog
+ d = self.page
d.helplist.delete(0, 'end')
d.helplist.insert(0, 'name1')
d.helplist.selection_set(0)
@@ -412,7 +413,7 @@ def test_helplist_item_edit(self):
def test_helplist_item_remove(self):
eq = self.assertEqual
- d = dialog
+ d = self.page
d.helplist.delete(0, 'end')
d.helplist.insert(0, 'name1')
d.helplist.selection_set(0)
@@ -427,7 +428,7 @@ def test_helplist_item_remove(self):
self.assertTrue(d.upc.called == d.set.called == 1)
def test_update_help_changes(self):
- d = dialog
+ d = self.page
del d.update_help_changes
d.user_helplist.clear()
d.user_helplist.append(('name1', 'file1'))
@@ -435,7 +436,7 @@ def test_update_help_changes(self):
d.update_help_changes()
self.assertEqual(mainpage['HelpFiles'],
- {'1': 'name1;file1', '2': 'name2;file2'})
+ {'1': 'name1;file1', '2': 'name2;file2'})
d.update_help_changes = Func()
diff --git a/Misc/NEWS.d/next/IDLE/2017-07-30-17-39-59.bpo-31050.AXR3kP.rst b/Misc/NEWS.d/next/IDLE/2017-07-30-17-39-59.bpo-31050.AXR3kP.rst
new file mode 100644
index 00000000000..e33b2e231d6
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2017-07-30-17-39-59.bpo-31050.AXR3kP.rst
@@ -0,0 +1,2 @@
+Factor GenPage(Frame) class from ConfigDialog. The slightly modified tests
+continue to pass. Patch by Cheryl Sabella.
1
0
30 Jul '17
https://github.com/python/cpython/commit/e8eb17b2c11dcd1550a94b175e557c234a…
commit: e8eb17b2c11dcd1550a94b175e557c234a804482
branch: master
author: csabella <cheryl.sabella(a)gmail.com>
committer: Terry Jan Reedy <tjreedy(a)udel.edu>
date: 2017-07-30T18:39:17-04:00
summary:
bpo-31050: IDLE: Factor GenPage class from ConfigDialog (#2952)
The slightly modified tests for the General tab continue to pass.
Patch by Cheryl Sabella.
files:
A Misc/NEWS.d/next/IDLE/2017-07-30-17-39-59.bpo-31050.AXR3kP.rst
M Lib/idlelib/configdialog.py
M Lib/idlelib/idle_test/test_configdialog.py
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 92bbc106344..28070a51709 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -89,14 +89,14 @@ def create_widgets(self):
"""Create and place widgets for tabbed dialog.
Widgets Bound to self:
- tab_pages: TabbedPageSet
+ note: Notebook
+ highpage: self.create_page_highlight
+ fontpage: FontPage
+ keyspage: self.create_page_keys
+ genpage: GenPage
+ extpageL self.create_page_extensions
Methods:
- create_page_font_tab
- create_page_highlight
- create_page_keys
- create_page_general
- create_page_extensions
create_action_buttons
load_configs: Load pages except for extensions.
activate_config_changes: Tell editors to reload.
@@ -105,7 +105,7 @@ def create_widgets(self):
self.highpage = self.create_page_highlight()
self.fontpage = FontPage(note, self.highpage)
self.keyspage = self.create_page_keys()
- self.genpage = self.create_page_general()
+ self.genpage = GenPage(note)
self.extpage = self.create_page_extensions()
note.add(self.fontpage, text='Fonts/Tabs')
note.add(self.highpage, text='Highlights')
@@ -133,7 +133,7 @@ def load_configs(self):
#self.load_tab_cfg()
self.load_theme_cfg()
self.load_key_cfg()
- self.load_general_cfg()
+ # self.load_general_cfg()
# note: extension page handled separately
def create_action_buttons(self):
@@ -1198,256 +1198,6 @@ def activate_config_changes(self):
instance.ApplyKeybindings()
instance.reset_help_menu_entries()
-
- def create_page_general(self):
- """Return frame of widgets for General tab.
-
- Enable users to provisionally change general options. Function
- load_general_cfg intializes tk variables and helplist using
- idleConf. Radiobuttons startup_shell_on and startup_editor_on
- set var startup_edit. Radiobuttons save_ask_on and save_auto_on
- set var autosave. Entry boxes win_width_int and win_height_int
- set var win_width and win_height. Setting var_name invokes the
- default callback that adds option to changes.
-
- Helplist: load_general_cfg loads list user_helplist with
- name, position pairs and copies names to listbox helplist.
- Clicking a name invokes help_source selected. Clicking
- button_helplist_name invokes helplist_item_name, which also
- changes user_helplist. These functions all call
- set_add_delete_state. All but load call update_help_changes to
- rewrite changes['main']['HelpFiles'].
-
- Widget Structure: (*) widgets bound to self
- frame
- frame_run: LabelFrame
- startup_title: Label
- (*)startup_editor_on: Radiobutton - startup_edit
- (*)startup_shell_on: Radiobutton - startup_edit
- frame_save: LabelFrame
- run_save_title: Label
- (*)save_ask_on: Radiobutton - autosave
- (*)save_auto_on: Radiobutton - autosave
- frame_win_size: LabelFrame
- win_size_title: Label
- win_width_title: Label
- (*)win_width_int: Entry - win_width
- win_height_title: Label
- (*)win_height_int: Entry - win_height
- frame_help: LabelFrame
- frame_helplist: Frame
- frame_helplist_buttons: Frame
- (*)button_helplist_edit
- (*)button_helplist_add
- (*)button_helplist_remove
- (*)helplist: ListBox
- scroll_helplist: Scrollbar
- """
- parent = self.parent
- self.startup_edit = tracers.add(
- IntVar(parent), ('main', 'General', 'editor-on-startup'))
- self.autosave = tracers.add(
- IntVar(parent), ('main', 'General', 'autosave'))
- self.win_width = tracers.add(
- StringVar(parent), ('main', 'EditorWindow', 'width'))
- self.win_height = tracers.add(
- StringVar(parent), ('main', 'EditorWindow', 'height'))
-
- # Create widgets:
- # body and section frames.
- frame = Frame(self.note)
- frame_run = LabelFrame(frame, borderwidth=2, relief=GROOVE,
- text=' Startup Preferences ')
- frame_save = LabelFrame(frame, borderwidth=2, relief=GROOVE,
- text=' autosave Preferences ')
- frame_win_size = Frame(frame, borderwidth=2, relief=GROOVE)
- frame_help = LabelFrame(frame, borderwidth=2, relief=GROOVE,
- text=' Additional Help Sources ')
- # frame_run.
- startup_title = Label(frame_run, text='At Startup')
- self.startup_editor_on = Radiobutton(
- frame_run, variable=self.startup_edit, value=1,
- text="Open Edit Window")
- self.startup_shell_on = Radiobutton(
- frame_run, variable=self.startup_edit, value=0,
- text='Open Shell Window')
- # frame_save.
- run_save_title = Label(frame_save, text='At Start of Run (F5) ')
- self.save_ask_on = Radiobutton(
- frame_save, variable=self.autosave, value=0,
- text="Prompt to Save")
- self.save_auto_on = Radiobutton(
- frame_save, variable=self.autosave, value=1,
- text='No Prompt')
- # frame_win_size.
- win_size_title = Label(
- frame_win_size, text='Initial Window Size (in characters)')
- win_width_title = Label(frame_win_size, text='Width')
- self.win_width_int = Entry(
- frame_win_size, textvariable=self.win_width, width=3)
- win_height_title = Label(frame_win_size, text='Height')
- self.win_height_int = Entry(
- frame_win_size, textvariable=self.win_height, width=3)
- # frame_help.
- frame_helplist = Frame(frame_help)
- frame_helplist_buttons = Frame(frame_helplist)
- self.helplist = Listbox(
- frame_helplist, height=5, takefocus=True,
- exportselection=FALSE)
- scroll_helplist = Scrollbar(frame_helplist)
- scroll_helplist['command'] = self.helplist.yview
- self.helplist['yscrollcommand'] = scroll_helplist.set
- self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
- self.button_helplist_edit = Button(
- frame_helplist_buttons, text='Edit', state=DISABLED,
- width=8, command=self.helplist_item_edit)
- self.button_helplist_add = Button(
- frame_helplist_buttons, text='Add',
- width=8, command=self.helplist_item_add)
- self.button_helplist_remove = Button(
- frame_helplist_buttons, text='Remove', state=DISABLED,
- width=8, command=self.helplist_item_remove)
-
- # Pack widgets:
- # body.
- frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
- frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
- frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
- frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
- # frame_run.
- startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- # frame_save.
- run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
- # frame_win_size.
- win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
- self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
- win_height_title.pack(side=RIGHT, anchor=E, pady=5)
- self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
- win_width_title.pack(side=RIGHT, anchor=E, pady=5)
- # frame_help.
- frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
- frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
- scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
- self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
- self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
- self.button_helplist_add.pack(side=TOP, anchor=W)
- self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
-
- return frame
-
- def load_general_cfg(self):
- "Load current configuration settings for the general options."
- # Set startup state.
- self.startup_edit.set(idleConf.GetOption(
- 'main', 'General', 'editor-on-startup', default=0, type='bool'))
- # Set autosave state.
- self.autosave.set(idleConf.GetOption(
- 'main', 'General', 'autosave', default=0, type='bool'))
- # Set initial window size.
- self.win_width.set(idleConf.GetOption(
- 'main', 'EditorWindow', 'width', type='int'))
- self.win_height.set(idleConf.GetOption(
- 'main', 'EditorWindow', 'height', type='int'))
- # Set additional help sources.
- self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
- self.helplist.delete(0, 'end')
- for help_item in self.user_helplist:
- self.helplist.insert(END, help_item[0])
- self.set_add_delete_state()
-
- def var_changed_startup_edit(self, *params):
- "Store change to toggle for starting IDLE in the editor or shell."
- value = self.startup_edit.get()
- changes.add_option('main', 'General', 'editor-on-startup', value)
-
- def var_changed_autosave(self, *params):
- "Store change to autosave."
- value = self.autosave.get()
- changes.add_option('main', 'General', 'autosave', value)
-
- def var_changed_win_width(self, *params):
- "Store change to window width."
- value = self.win_width.get()
- changes.add_option('main', 'EditorWindow', 'width', value)
-
- def var_changed_win_height(self, *params):
- "Store change to window height."
- value = self.win_height.get()
- changes.add_option('main', 'EditorWindow', 'height', value)
-
- def help_source_selected(self, event):
- "Handle event for selecting additional help."
- self.set_add_delete_state()
-
- def set_add_delete_state(self):
- "Toggle the state for the help list buttons based on list entries."
- if self.helplist.size() < 1: # No entries in list.
- self.button_helplist_edit['state'] = DISABLED
- self.button_helplist_remove['state'] = DISABLED
- else: # Some entries.
- if self.helplist.curselection(): # There currently is a selection.
- self.button_helplist_edit['state'] = NORMAL
- self.button_helplist_remove['state'] = NORMAL
- else: # There currently is not a selection.
- self.button_helplist_edit['state'] = DISABLED
- self.button_helplist_remove['state'] = DISABLED
-
- def helplist_item_add(self):
- """Handle add button for the help list.
-
- Query for name and location of new help sources and add
- them to the list.
- """
- help_source = HelpSource(self, 'New Help Source').result
- if help_source:
- self.user_helplist.append(help_source)
- self.helplist.insert(END, help_source[0])
- self.update_help_changes()
-
- def helplist_item_edit(self):
- """Handle edit button for the help list.
-
- Query with existing help source information and update
- config if the values are changed.
- """
- item_index = self.helplist.index(ANCHOR)
- help_source = self.user_helplist[item_index]
- new_help_source = HelpSource(
- self, 'Edit Help Source',
- menuitem=help_source[0],
- filepath=help_source[1],
- ).result
- if new_help_source and new_help_source != help_source:
- self.user_helplist[item_index] = new_help_source
- self.helplist.delete(item_index)
- self.helplist.insert(item_index, new_help_source[0])
- self.update_help_changes()
- self.set_add_delete_state() # Selected will be un-selected
-
- def helplist_item_remove(self):
- """Handle remove button for the help list.
-
- Delete the help list item from config.
- """
- item_index = self.helplist.index(ANCHOR)
- del(self.user_helplist[item_index])
- self.helplist.delete(item_index)
- self.update_help_changes()
- self.set_add_delete_state()
-
- def update_help_changes(self):
- "Clear and rebuild the HelpFiles section in changes"
- changes['main']['HelpFiles'] = {}
- for num in range(1, len(self.user_helplist) + 1):
- changes.add_option(
- 'main', 'HelpFiles', str(num),
- ';'.join(self.user_helplist[num-1][:2]))
-
-
def create_page_extensions(self):
"""Part of the config dialog used for configuring IDLE extensions.
@@ -1845,6 +1595,238 @@ def var_changed_space_num(self, *params):
changes.add_option('main', 'Indent', 'num-spaces', value)
+class GenPage(Frame):
+
+ def __init__(self, parent):
+ super().__init__(parent)
+ self.create_page_general()
+ self.load_general_cfg()
+
+ def create_page_general(self):
+ """Return frame of widgets for General tab.
+
+ Enable users to provisionally change general options. Function
+ load_general_cfg intializes tk variables and helplist using
+ idleConf. Radiobuttons startup_shell_on and startup_editor_on
+ set var startup_edit. Radiobuttons save_ask_on and save_auto_on
+ set var autosave. Entry boxes win_width_int and win_height_int
+ set var win_width and win_height. Setting var_name invokes the
+ default callback that adds option to changes.
+
+ Helplist: load_general_cfg loads list user_helplist with
+ name, position pairs and copies names to listbox helplist.
+ Clicking a name invokes help_source selected. Clicking
+ button_helplist_name invokes helplist_item_name, which also
+ changes user_helplist. These functions all call
+ set_add_delete_state. All but load call update_help_changes to
+ rewrite changes['main']['HelpFiles'].
+
+ Widget Structure: (*) widgets bound to self
+ frame
+ frame_run: LabelFrame
+ startup_title: Label
+ (*)startup_editor_on: Radiobutton - startup_edit
+ (*)startup_shell_on: Radiobutton - startup_edit
+ frame_save: LabelFrame
+ run_save_title: Label
+ (*)save_ask_on: Radiobutton - autosave
+ (*)save_auto_on: Radiobutton - autosave
+ frame_win_size: LabelFrame
+ win_size_title: Label
+ win_width_title: Label
+ (*)win_width_int: Entry - win_width
+ win_height_title: Label
+ (*)win_height_int: Entry - win_height
+ frame_help: LabelFrame
+ frame_helplist: Frame
+ frame_helplist_buttons: Frame
+ (*)button_helplist_edit
+ (*)button_helplist_add
+ (*)button_helplist_remove
+ (*)helplist: ListBox
+ scroll_helplist: Scrollbar
+ """
+ self.startup_edit = tracers.add(
+ IntVar(self), ('main', 'General', 'editor-on-startup'))
+ self.autosave = tracers.add(
+ IntVar(self), ('main', 'General', 'autosave'))
+ self.win_width = tracers.add(
+ StringVar(self), ('main', 'EditorWindow', 'width'))
+ self.win_height = tracers.add(
+ StringVar(self), ('main', 'EditorWindow', 'height'))
+
+ # Create widgets:
+ # Section frames.
+ frame_run = LabelFrame(self, borderwidth=2, relief=GROOVE,
+ text=' Startup Preferences ')
+ frame_save = LabelFrame(self, borderwidth=2, relief=GROOVE,
+ text=' autosave Preferences ')
+ frame_win_size = Frame(self, borderwidth=2, relief=GROOVE)
+ frame_help = LabelFrame(self, borderwidth=2, relief=GROOVE,
+ text=' Additional Help Sources ')
+ # frame_run.
+ startup_title = Label(frame_run, text='At Startup')
+ self.startup_editor_on = Radiobutton(
+ frame_run, variable=self.startup_edit, value=1,
+ text="Open Edit Window")
+ self.startup_shell_on = Radiobutton(
+ frame_run, variable=self.startup_edit, value=0,
+ text='Open Shell Window')
+ # frame_save.
+ run_save_title = Label(frame_save, text='At Start of Run (F5) ')
+ self.save_ask_on = Radiobutton(
+ frame_save, variable=self.autosave, value=0,
+ text="Prompt to Save")
+ self.save_auto_on = Radiobutton(
+ frame_save, variable=self.autosave, value=1,
+ text='No Prompt')
+ # frame_win_size.
+ win_size_title = Label(
+ frame_win_size, text='Initial Window Size (in characters)')
+ win_width_title = Label(frame_win_size, text='Width')
+ self.win_width_int = Entry(
+ frame_win_size, textvariable=self.win_width, width=3)
+ win_height_title = Label(frame_win_size, text='Height')
+ self.win_height_int = Entry(
+ frame_win_size, textvariable=self.win_height, width=3)
+ # frame_help.
+ frame_helplist = Frame(frame_help)
+ frame_helplist_buttons = Frame(frame_helplist)
+ self.helplist = Listbox(
+ frame_helplist, height=5, takefocus=True,
+ exportselection=FALSE)
+ scroll_helplist = Scrollbar(frame_helplist)
+ scroll_helplist['command'] = self.helplist.yview
+ self.helplist['yscrollcommand'] = scroll_helplist.set
+ self.helplist.bind('<ButtonRelease-1>', self.help_source_selected)
+ self.button_helplist_edit = Button(
+ frame_helplist_buttons, text='Edit', state=DISABLED,
+ width=8, command=self.helplist_item_edit)
+ self.button_helplist_add = Button(
+ frame_helplist_buttons, text='Add',
+ width=8, command=self.helplist_item_add)
+ self.button_helplist_remove = Button(
+ frame_helplist_buttons, text='Remove', state=DISABLED,
+ width=8, command=self.helplist_item_remove)
+
+ # Pack widgets:
+ # body.
+ frame_run.pack(side=TOP, padx=5, pady=5, fill=X)
+ frame_save.pack(side=TOP, padx=5, pady=5, fill=X)
+ frame_win_size.pack(side=TOP, padx=5, pady=5, fill=X)
+ frame_help.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+ # frame_run.
+ startup_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+ self.startup_shell_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ self.startup_editor_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ # frame_save.
+ run_save_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+ self.save_auto_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ self.save_ask_on.pack(side=RIGHT, anchor=W, padx=5, pady=5)
+ # frame_win_size.
+ win_size_title.pack(side=LEFT, anchor=W, padx=5, pady=5)
+ self.win_height_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
+ win_height_title.pack(side=RIGHT, anchor=E, pady=5)
+ self.win_width_int.pack(side=RIGHT, anchor=E, padx=10, pady=5)
+ win_width_title.pack(side=RIGHT, anchor=E, pady=5)
+ # frame_help.
+ frame_helplist_buttons.pack(side=RIGHT, padx=5, pady=5, fill=Y)
+ frame_helplist.pack(side=TOP, padx=5, pady=5, expand=TRUE, fill=BOTH)
+ scroll_helplist.pack(side=RIGHT, anchor=W, fill=Y)
+ self.helplist.pack(side=LEFT, anchor=E, expand=TRUE, fill=BOTH)
+ self.button_helplist_edit.pack(side=TOP, anchor=W, pady=5)
+ self.button_helplist_add.pack(side=TOP, anchor=W)
+ self.button_helplist_remove.pack(side=TOP, anchor=W, pady=5)
+
+ def load_general_cfg(self):
+ "Load current configuration settings for the general options."
+ # Set startup state.
+ self.startup_edit.set(idleConf.GetOption(
+ 'main', 'General', 'editor-on-startup', default=0, type='bool'))
+ # Set autosave state.
+ self.autosave.set(idleConf.GetOption(
+ 'main', 'General', 'autosave', default=0, type='bool'))
+ # Set initial window size.
+ self.win_width.set(idleConf.GetOption(
+ 'main', 'EditorWindow', 'width', type='int'))
+ self.win_height.set(idleConf.GetOption(
+ 'main', 'EditorWindow', 'height', type='int'))
+ # Set additional help sources.
+ self.user_helplist = idleConf.GetAllExtraHelpSourcesList()
+ self.helplist.delete(0, 'end')
+ for help_item in self.user_helplist:
+ self.helplist.insert(END, help_item[0])
+ self.set_add_delete_state()
+
+ def help_source_selected(self, event):
+ "Handle event for selecting additional help."
+ self.set_add_delete_state()
+
+ def set_add_delete_state(self):
+ "Toggle the state for the help list buttons based on list entries."
+ if self.helplist.size() < 1: # No entries in list.
+ self.button_helplist_edit['state'] = DISABLED
+ self.button_helplist_remove['state'] = DISABLED
+ else: # Some entries.
+ if self.helplist.curselection(): # There currently is a selection.
+ self.button_helplist_edit['state'] = NORMAL
+ self.button_helplist_remove['state'] = NORMAL
+ else: # There currently is not a selection.
+ self.button_helplist_edit['state'] = DISABLED
+ self.button_helplist_remove['state'] = DISABLED
+
+ def helplist_item_add(self):
+ """Handle add button for the help list.
+
+ Query for name and location of new help sources and add
+ them to the list.
+ """
+ help_source = HelpSource(self, 'New Help Source').result
+ if help_source:
+ self.user_helplist.append(help_source)
+ self.helplist.insert(END, help_source[0])
+ self.update_help_changes()
+
+ def helplist_item_edit(self):
+ """Handle edit button for the help list.
+
+ Query with existing help source information and update
+ config if the values are changed.
+ """
+ item_index = self.helplist.index(ANCHOR)
+ help_source = self.user_helplist[item_index]
+ new_help_source = HelpSource(
+ self, 'Edit Help Source',
+ menuitem=help_source[0],
+ filepath=help_source[1],
+ ).result
+ if new_help_source and new_help_source != help_source:
+ self.user_helplist[item_index] = new_help_source
+ self.helplist.delete(item_index)
+ self.helplist.insert(item_index, new_help_source[0])
+ self.update_help_changes()
+ self.set_add_delete_state() # Selected will be un-selected
+
+ def helplist_item_remove(self):
+ """Handle remove button for the help list.
+
+ Delete the help list item from config.
+ """
+ item_index = self.helplist.index(ANCHOR)
+ del(self.user_helplist[item_index])
+ self.helplist.delete(item_index)
+ self.update_help_changes()
+ self.set_add_delete_state()
+
+ def update_help_changes(self):
+ "Clear and rebuild the HelpFiles section in changes"
+ changes['main']['HelpFiles'] = {}
+ for num in range(1, len(self.user_helplist) + 1):
+ changes.add_option(
+ 'main', 'HelpFiles', str(num),
+ ';'.join(self.user_helplist[num-1][:2]))
+
+
class VarTrace:
"""Maintain Tk variables trace state."""
diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py
index 8da726b80c0..caeb2b56787 100644
--- a/Lib/idlelib/idle_test/test_configdialog.py
+++ b/Lib/idlelib/idle_test/test_configdialog.py
@@ -237,7 +237,7 @@ def setUp(self):
changes.clear()
-class GeneralTest(unittest.TestCase):
+class GenPageTest(unittest.TestCase):
"""Test that general tab widgets enable users to make changes.
Test that widget actions set vars, that var changes add
@@ -245,20 +245,18 @@ class GeneralTest(unittest.TestCase):
"""
@classmethod
def setUpClass(cls):
- d = dialog
- # Select General tab so can force focus on helplist.
- d.note.select(d.genpage)
- # Mask instance methods used by help functions.
- d.set = d.set_add_delete_state = Func()
- d.upc = d.update_help_changes = Func()
+ page = cls.page = dialog.genpage
+ dialog.note.select(page)
+ page.set = page.set_add_delete_state = Func()
+ page.upc = page.update_help_changes = Func()
@classmethod
def tearDownClass(cls):
- d = dialog
- del d.set, d.set_add_delete_state
- del d.upc, d.update_help_changes
- d.helplist.delete(0, 'end')
- d.user_helplist.clear()
+ page = cls.page
+ del page.set, page.set_add_delete_state
+ del page.upc, page.update_help_changes
+ page.helplist.delete(0, 'end')
+ page.user_helplist.clear()
def setUp(self):
changes.clear()
@@ -266,7 +264,7 @@ def setUp(self):
def test_load_general_cfg(self):
# Set to wrong values, load, check right values.
eq = self.assertEqual
- d = dialog
+ d = self.page
d.startup_edit.set(1)
d.autosave.set(1)
d.win_width.set(1)
@@ -283,29 +281,32 @@ def test_load_general_cfg(self):
eq(d.user_helplist, [('name', 'file', '1')])
def test_startup(self):
- dialog.startup_editor_on.invoke()
+ d = self.page
+ d.startup_editor_on.invoke()
self.assertEqual(mainpage,
{'General': {'editor-on-startup': '1'}})
changes.clear()
- dialog.startup_shell_on.invoke()
+ d.startup_shell_on.invoke()
self.assertEqual(mainpage,
{'General': {'editor-on-startup': '0'}})
def test_autosave(self):
- dialog.save_auto_on.invoke()
+ d = self.page
+ d.save_auto_on.invoke()
self.assertEqual(mainpage, {'General': {'autosave': '1'}})
- dialog.save_ask_on.invoke()
+ d.save_ask_on.invoke()
self.assertEqual(mainpage, {'General': {'autosave': '0'}})
def test_editor_size(self):
- dialog.win_height_int.insert(0, '1')
+ d = self.page
+ d.win_height_int.insert(0, '1')
self.assertEqual(mainpage, {'EditorWindow': {'height': '140'}})
changes.clear()
- dialog.win_width_int.insert(0, '1')
+ d.win_width_int.insert(0, '1')
self.assertEqual(mainpage, {'EditorWindow': {'width': '180'}})
def test_source_selected(self):
- d = dialog
+ d = self.page
d.set = d.set_add_delete_state
d.upc = d.update_help_changes
helplist = d.helplist
@@ -331,7 +332,7 @@ def test_source_selected(self):
def test_set_add_delete_state(self):
# Call with 0 items, 1 unselected item, 1 selected item.
eq = self.assertEqual
- d = dialog
+ d = self.page
del d.set_add_delete_state # Unmask method.
sad = d.set_add_delete_state
h = d.helplist
@@ -358,7 +359,7 @@ def test_helplist_item_add(self):
eq = self.assertEqual
orig_helpsource = configdialog.HelpSource
hs = configdialog.HelpSource = Func(return_self=True)
- d = dialog
+ d = self.page
d.helplist.delete(0, 'end')
d.user_helplist.clear()
d.set.called = d.upc.called = 0
@@ -385,7 +386,7 @@ def test_helplist_item_edit(self):
eq = self.assertEqual
orig_helpsource = configdialog.HelpSource
hs = configdialog.HelpSource = Func(return_self=True)
- d = dialog
+ d = self.page
d.helplist.delete(0, 'end')
d.helplist.insert(0, 'name1')
d.helplist.selection_set(0)
@@ -412,7 +413,7 @@ def test_helplist_item_edit(self):
def test_helplist_item_remove(self):
eq = self.assertEqual
- d = dialog
+ d = self.page
d.helplist.delete(0, 'end')
d.helplist.insert(0, 'name1')
d.helplist.selection_set(0)
@@ -427,7 +428,7 @@ def test_helplist_item_remove(self):
self.assertTrue(d.upc.called == d.set.called == 1)
def test_update_help_changes(self):
- d = dialog
+ d = self.page
del d.update_help_changes
d.user_helplist.clear()
d.user_helplist.append(('name1', 'file1'))
@@ -435,7 +436,7 @@ def test_update_help_changes(self):
d.update_help_changes()
self.assertEqual(mainpage['HelpFiles'],
- {'1': 'name1;file1', '2': 'name2;file2'})
+ {'1': 'name1;file1', '2': 'name2;file2'})
d.update_help_changes = Func()
diff --git a/Misc/NEWS.d/next/IDLE/2017-07-30-17-39-59.bpo-31050.AXR3kP.rst b/Misc/NEWS.d/next/IDLE/2017-07-30-17-39-59.bpo-31050.AXR3kP.rst
new file mode 100644
index 00000000000..e33b2e231d6
--- /dev/null
+++ b/Misc/NEWS.d/next/IDLE/2017-07-30-17-39-59.bpo-31050.AXR3kP.rst
@@ -0,0 +1,2 @@
+Factor GenPage(Frame) class from ConfigDialog. The slightly modified tests
+continue to pass. Patch by Cheryl Sabella.
1
0