bpo-30781: IDLE -  use ttk widgets in configdialog (#2654)

Patch by Cheryl Sabella.
diff --git a/Lib/idlelib/configdialog.py b/Lib/idlelib/configdialog.py
index 81ea8af..ff7b638 100644
--- a/Lib/idlelib/configdialog.py
+++ b/Lib/idlelib/configdialog.py
@@ -9,13 +9,13 @@
 Refer to comments in EditorWindow autoindent code for details.
 
 """
-from tkinter import (Toplevel, Frame, LabelFrame, Listbox, Label, Button,
-                     Entry, Text, Scale, Radiobutton, Checkbutton, Canvas,
+from tkinter import (Toplevel, Listbox, Text, Scale, Canvas,
                      StringVar, BooleanVar, IntVar, TRUE, FALSE,
                      TOP, BOTTOM, RIGHT, LEFT, SOLID, GROOVE, NORMAL, DISABLED,
                      NONE, BOTH, X, Y, W, E, EW, NS, NSEW, NW,
                      HORIZONTAL, VERTICAL, ANCHOR, ACTIVE, END)
-from tkinter.ttk import Notebook, Scrollbar
+from tkinter.ttk import (Button, Checkbutton, Entry, Frame, Label, LabelFrame,
+                         Notebook, Radiobutton, Scrollbar, Style)
 import tkinter.colorchooser as tkColorChooser
 import tkinter.font as tkFont
 import tkinter.messagebox as tkMessageBox
@@ -137,9 +137,9 @@
             # text in the buttons.
             padding_args = {}
         else:
-            padding_args = {'padx':6, 'pady':3}
-        outer = Frame(self, pady=2)
-        buttons = Frame(outer, pady=2)
+            padding_args = {'padding': (6, 3)}
+        outer = Frame(self, padding=2)
+        buttons = Frame(outer, padding=2)
         for txt, cmd in (
             ('Ok', self.ok),
             ('Apply', self.apply),
@@ -266,7 +266,7 @@
         self.extension_list.grid(column=0, row=0, sticky='nws')
         scroll.grid(column=1, row=0, sticky='ns')
         self.details_frame.grid(column=2, row=0, sticky='nsew', padx=[10, 0])
-        frame.configure(padx=10, pady=10)
+        frame.configure(padding=10)
         self.config_frame = {}
         self.current_extension = None
 
@@ -356,9 +356,8 @@
             label.grid(row=row, column=0, sticky=NW)
             var = opt['var']
             if opt['type'] == 'bool':
-                Checkbutton(entry_area, textvariable=var, variable=var,
-                            onvalue='True', offvalue='False',
-                            indicatoron=FALSE, selectcolor='', width=8
+                Checkbutton(entry_area, variable=var,
+                            onvalue='True', offvalue='False', width=8
                             ).grid(row=row, column=1, sticky=W, padx=7)
             elif opt['type'] == 'int':
                 Entry(entry_area, textvariable=var, validate='key',
@@ -635,6 +634,7 @@
     def __init__(self, master):
         super().__init__(master)
         self.cd = master.master
+        self.style = Style(master)
         self.create_page_highlight()
         self.load_theme_cfg()
 
@@ -821,12 +821,14 @@
                 self.highlight_target.set(elem)
             text.tag_bind(
                     self.theme_elements[element][0], '<ButtonPress-1>', tem)
-        text['state'] = DISABLED
-        self.frame_color_set = Frame(frame_custom, relief=SOLID, borderwidth=1)
+        text['state'] = 'disabled'
+        self.style.configure('frame_color_set.TFrame', borderwidth=1,
+                             relief='solid')
+        self.frame_color_set = Frame(frame_custom, style='frame_color_set.TFrame')
         frame_fg_bg_toggle = Frame(frame_custom)
         self.button_set_color = Button(
                 self.frame_color_set, text='Choose Color for :',
-                command=self.get_color, highlightthickness=0)
+                command=self.get_color)
         self.targetlist = DynOptionMenu(
                 self.frame_color_set, self.highlight_target, None,
                 highlightthickness=0) #, command=self.set_highlight_targetBinding
@@ -855,7 +857,7 @@
         self.button_delete_custom = Button(
                 frame_theme, text='Delete Custom Theme',
                 command=self.delete_custom)
-        self.theme_message = Label(frame_theme, bd=2)
+        self.theme_message = Label(frame_theme, borderwidth=2)
 
         # Pack widgets:
         # body.
@@ -913,7 +915,7 @@
             item_list = idleConf.GetSectionList('user', 'highlight')
             item_list.sort()
             if not item_list:
-                self.custom_theme_on['state'] = DISABLED
+                self.custom_theme_on.state(('disabled',))
                 self.custom_name.set('- no custom themes -')
             else:
                 self.customlist.SetMenu(item_list, item_list[0])
@@ -945,12 +947,10 @@
                 changes.add_option('main', 'Theme', 'name', old_themes[0])
             changes.add_option('main', 'Theme', 'name2', value)
             self.theme_message['text'] = 'New theme, see Help'
-            self.theme_message['fg'] = '#500000'
         else:
             changes.add_option('main', 'Theme', 'name', value)
             changes.add_option('main', 'Theme', 'name2', '')
             self.theme_message['text'] = ''
-            self.theme_message['fg'] = 'black'
         self.paint_theme_sample()
 
     def var_changed_custom_name(self, *params):
@@ -1004,14 +1004,14 @@
             load_theme_cfg
         """
         if self.theme_source.get():
-            self.builtinlist['state'] = NORMAL
-            self.customlist['state'] = DISABLED
-            self.button_delete_custom['state'] = DISABLED
+            self.builtinlist['state'] = 'normal'
+            self.customlist['state'] = 'disabled'
+            self.button_delete_custom.state(('disabled',))
         else:
-            self.builtinlist['state'] = DISABLED
-            self.custom_theme_on['state'] = NORMAL
-            self.customlist['state'] = NORMAL
-            self.button_delete_custom['state'] = NORMAL
+            self.builtinlist['state'] = 'disabled'
+            self.custom_theme_on.state(('!disabled',))
+            self.customlist['state'] = 'normal'
+            self.button_delete_custom.state(('!disabled',))
 
     def get_color(self):
         """Handle button to select a new color for the target tag.
@@ -1032,7 +1032,8 @@
             create_new
         """
         target = self.highlight_target.get()
-        prev_color = self.frame_color_set.cget('bg')
+        prev_color = self.style.lookup(self.frame_color_set['style'],
+                                       'background')
         rgbTuplet, color_string = tkColorChooser.askcolor(
                 parent=self, title='Pick new color for : '+target,
                 initialcolor=prev_color)
@@ -1053,7 +1054,7 @@
     def on_new_color_set(self):
         "Display sample of new color selection on the dialog."
         new_color = self.color.get()
-        self.frame_color_set['bg'] = new_color  # Set sample.
+        self.style.configure('frame_color_set.TFrame', background=new_color)
         plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
         sample_element = self.theme_elements[self.highlight_target.get()][0]
         self.highlight_sample.tag_config(sample_element, **{plane: new_color})
@@ -1139,12 +1140,12 @@
             load_theme_cfg
         """
         if self.highlight_target.get() == 'Cursor':  # bg not possible
-            self.fg_on['state'] = DISABLED
-            self.bg_on['state'] = DISABLED
+            self.fg_on.state(('disabled',))
+            self.bg_on.state(('disabled',))
             self.fg_bg_toggle.set(1)
         else:  # Both fg and bg can be set.
-            self.fg_on['state'] = NORMAL
-            self.bg_on['state'] = NORMAL
+            self.fg_on.state(('!disabled',))
+            self.bg_on.state(('!disabled',))
             self.fg_bg_toggle.set(1)
         self.set_color_sample()
 
@@ -1172,7 +1173,7 @@
         tag = self.theme_elements[self.highlight_target.get()][0]
         plane = 'foreground' if self.fg_bg_toggle.get() else 'background'
         color = self.highlight_sample.tag_cget(tag, plane)
-        self.frame_color_set['bg'] = color
+        self.style.configure('frame_color_set.TFrame', background=color)
 
     def paint_theme_sample(self):
         """Apply the theme colors to each element tag in the sample text.
@@ -1260,7 +1261,7 @@
         item_list = idleConf.GetSectionList('user', 'highlight')
         item_list.sort()
         if not item_list:
-            self.custom_theme_on['state'] = DISABLED
+            self.custom_theme_on.state(('disabled',))
             self.customlist.SetMenu(item_list, '- no custom themes -')
         else:
             self.customlist.SetMenu(item_list, item_list[0])
@@ -1397,7 +1398,7 @@
                 frame_custom, text='Get New Keys for Selection',
                 command=self.get_new_keys, state=DISABLED)
         # frame_key_sets.
-        frames = [Frame(frame_key_sets, padx=2, pady=2, borderwidth=0)
+        frames = [Frame(frame_key_sets, padding=2, borderwidth=0)
                   for i in range(2)]
         self.builtin_keyset_on = Radiobutton(
                 frames[0], variable=self.keyset_source, value=1,
@@ -1415,7 +1416,7 @@
         self.button_save_custom_keys = Button(
                 frames[1], text='Save as New Custom Key Set',
                 command=self.save_as_new_key_set)
-        self.keys_message = Label(frames[0], bd=2)
+        self.keys_message = Label(frames[0], borderwidth=2)
 
         # Pack widgets:
         # body.
@@ -1457,7 +1458,7 @@
             item_list = idleConf.GetSectionList('user', 'keys')
             item_list.sort()
             if not item_list:
-                self.custom_keyset_on['state'] = DISABLED
+                self.custom_keyset_on.state(('disabled',))
                 self.custom_name.set('- no custom keys -')
             else:
                 self.customlist.SetMenu(item_list, item_list[0])
@@ -1487,12 +1488,10 @@
                 changes.add_option('main', 'Keys', 'name', old_keys[0])
             changes.add_option('main', 'Keys', 'name2', value)
             self.keys_message['text'] = 'New key set, see Help'
-            self.keys_message['fg'] = '#500000'
         else:
             changes.add_option('main', 'Keys', 'name', value)
             changes.add_option('main', 'Keys', 'name2', '')
             self.keys_message['text'] = ''
-            self.keys_message['fg'] = 'black'
         self.load_keys_list(value)
 
     def var_changed_custom_name(self, *params):
@@ -1526,14 +1525,14 @@
     def set_keys_type(self):
         "Set available screen options based on builtin or custom key set."
         if self.keyset_source.get():
-            self.builtinlist['state'] = NORMAL
-            self.customlist['state'] = DISABLED
-            self.button_delete_custom_keys['state'] = DISABLED
+            self.builtinlist['state'] = 'normal'
+            self.customlist['state'] = 'disabled'
+            self.button_delete_custom_keys.state(('disabled',))
         else:
-            self.builtinlist['state'] = DISABLED
-            self.custom_keyset_on['state'] = NORMAL
-            self.customlist['state'] = NORMAL
-            self.button_delete_custom_keys['state'] = NORMAL
+            self.builtinlist['state'] = 'disabled'
+            self.custom_keyset_on.state(('!disabled',))
+            self.customlist['state'] = 'normal'
+            self.button_delete_custom_keys.state(('!disabled',))
 
     def get_new_keys(self):
         """Handle event to change key binding for selected line.
@@ -1595,7 +1594,7 @@
 
     def on_bindingslist_select(self, event):
         "Activate button to assign new keys to selected action."
-        self.button_new_keys['state'] = NORMAL
+        self.button_new_keys.state(('!disabled',))
 
     def create_new_key_set(self, new_key_set_name):
         """Create a new custom key set with the given name.
@@ -1689,7 +1688,7 @@
         item_list = idleConf.GetSectionList('user', 'keys')
         item_list.sort()
         if not item_list:
-            self.custom_keyset_on['state'] = DISABLED
+            self.custom_keyset_on.state(('disabled',))
             self.customlist.SetMenu(item_list, '- no custom keys -')
         else:
             self.customlist.SetMenu(item_list, item_list[0])
@@ -1809,13 +1808,13 @@
         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,
+                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,
+                frame_helplist_buttons, text='Remove', state='disabled',
                 width=8, command=self.helplist_item_remove)
 
         # Pack widgets:
@@ -1874,15 +1873,15 @@
     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
+            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
+                self.button_helplist_edit.state(('!disabled',))
+                self.button_helplist_remove.state(('!disabled',))
             else:  # There currently is not a selection.
-                self.button_helplist_edit['state'] = DISABLED
-                self.button_helplist_remove['state'] = DISABLED
+                self.button_helplist_edit.state(('disabled',))
+                self.button_helplist_remove.state(('disabled',))
 
     def helplist_item_add(self):
         """Handle add button for the help list.
@@ -2062,7 +2061,7 @@
         # Create a canvas object and a vertical scrollbar for scrolling it.
         vscrollbar = Scrollbar(self, orient=VERTICAL)
         vscrollbar.pack(fill=Y, side=RIGHT, expand=FALSE)
-        canvas = Canvas(self, bd=0, highlightthickness=0,
+        canvas = Canvas(self, borderwidth=0, highlightthickness=0,
                         yscrollcommand=vscrollbar.set, width=240)
         canvas.pack(side=LEFT, fill=BOTH, expand=TRUE)
         vscrollbar.config(command=canvas.yview)
diff --git a/Lib/idlelib/idle_test/test_configdialog.py b/Lib/idlelib/idle_test/test_configdialog.py
index df801c3..c947da1 100644
--- a/Lib/idlelib/idle_test/test_configdialog.py
+++ b/Lib/idlelib/idle_test/test_configdialog.py
@@ -272,7 +272,7 @@
         # builtinlist sets variable builtin_name to the CurrentTheme default.
         eq(d.builtin_name.get(), 'IDLE Classic')
         eq(d.custom_name.get(), '- no custom themes -')
-        eq(d.custom_theme_on['state'], DISABLED)
+        eq(d.custom_theme_on.state(), ('disabled',))
         eq(d.set_theme_type.called, 1)
         eq(d.paint_theme_sample.called, 1)
         eq(d.set_highlight_target.called, 1)
@@ -315,7 +315,7 @@
         changes.clear()
 
         # Custom selected.
-        d.custom_theme_on['state'] = NORMAL
+        d.custom_theme_on.state(('!disabled',))
         d.custom_theme_on.invoke()
         self.assertEqual(mainpage, {'Theme': {'default': 'False'}})
         eq(d.var_changed_builtin_name.called, 1)
@@ -428,15 +428,15 @@
         d.set_theme_type()
         eq(d.builtinlist['state'], NORMAL)
         eq(d.customlist['state'], DISABLED)
-        eq(d.button_delete_custom['state'], DISABLED)
+        eq(d.button_delete_custom.state(), ('disabled',))
 
         # Custom theme selected.
         d.theme_source.set(False)
         d.set_theme_type()
         eq(d.builtinlist['state'], DISABLED)
-        eq(d.custom_theme_on['state'], NORMAL)
+        eq(d.custom_theme_on.state(), ('selected',))
         eq(d.customlist['state'], NORMAL)
-        eq(d.button_delete_custom['state'], NORMAL)
+        eq(d.button_delete_custom.state(), ())
         d.set_theme_type = Func()
 
     def test_get_color(self):
@@ -455,7 +455,7 @@
         eq(d.color.get(), '#ffffff')
 
         # Selection same as previous color.
-        chooser.result = ('', d.frame_color_set.cget('bg'))
+        chooser.result = ('', d.style.lookup(d.frame_color_set['style'], 'background'))
         d.button_set_color.invoke()
         eq(d.color.get(), '#ffffff')
 
@@ -494,7 +494,7 @@
         d.fg_bg_toggle.set(True)
 
         d.color.set(color)
-        self.assertEqual(d.frame_color_set.cget('bg'), color)
+        self.assertEqual(d.style.lookup(d.frame_color_set['style'], 'background'), color)
         self.assertEqual(d.highlight_sample.tag_cget('hilite', 'foreground'), color)
         self.assertEqual(highpage,
                          {'Python': {'hilite-foreground': color}})
@@ -567,15 +567,15 @@
 
         # Target is cursor.
         d.highlight_target.set('Cursor')
-        eq(d.fg_on['state'], DISABLED)
-        eq(d.bg_on['state'], DISABLED)
+        eq(d.fg_on.state(), ('disabled', 'selected'))
+        eq(d.bg_on.state(), ('disabled',))
         self.assertTrue(d.fg_bg_toggle)
         eq(d.set_color_sample.called, 1)
 
         # Target is not cursor.
         d.highlight_target.set('Comment')
-        eq(d.fg_on['state'], NORMAL)
-        eq(d.bg_on['state'], NORMAL)
+        eq(d.fg_on.state(), ('selected',))
+        eq(d.bg_on.state(), ())
         self.assertTrue(d.fg_bg_toggle)
         eq(d.set_color_sample.called, 2)
 
@@ -597,8 +597,9 @@
         d.highlight_target.set('Selected Text')
         d.fg_bg_toggle.set(True)
         d.set_color_sample()
-        self.assertEqual(d.frame_color_set.cget('bg'),
-                         d.highlight_sample.tag_cget('hilite', 'foreground'))
+        self.assertEqual(
+                d.style.lookup(d.frame_color_set['style'], 'background'),
+                d.highlight_sample.tag_cget('hilite', 'foreground'))
         d.set_color_sample = Func()
 
     def test_paint_theme_sample(self):
@@ -641,7 +642,7 @@
     def test_delete_custom(self):
         eq = self.assertEqual
         d = self.page
-        d.button_delete_custom['state'] = NORMAL
+        d.button_delete_custom.state(('!disabled',))
         yesno = configdialog.tkMessageBox.askyesno = Func()
         dialog.deactivate_current_config = Func()
         dialog.activate_config_changes = Func()
@@ -670,7 +671,7 @@
         eq(yesno.called, 2)
         self.assertNotIn(theme_name, highpage)
         eq(idleConf.GetSectionList('user', 'highlight'), [])
-        eq(d.custom_theme_on['state'], DISABLED)
+        eq(d.custom_theme_on.state(), ('disabled',))
         eq(d.custom_name.get(), '- no custom themes -')
         eq(dialog.deactivate_current_config.called, 1)
         eq(dialog.activate_config_changes.called, 1)
@@ -721,7 +722,7 @@
         # builtinlist sets variable builtin_name to the CurrentKeys default.
         eq(d.builtin_name.get(), 'IDLE Classic OSX')
         eq(d.custom_name.get(), '- no custom keys -')
-        eq(d.custom_keyset_on['state'], DISABLED)
+        eq(d.custom_keyset_on.state(), ('disabled',))
         eq(d.set_keys_type.called, 1)
         eq(d.load_keys_list.called, 1)
         eq(d.load_keys_list.args, ('IDLE Classic OSX', ))
@@ -765,7 +766,7 @@
         changes.clear()
 
         # Custom selected.
-        d.custom_keyset_on['state'] = NORMAL
+        d.custom_keyset_on.state(('!disabled',))
         d.custom_keyset_on.invoke()
         self.assertEqual(mainpage, {'Keys': {'default': 'False'}})
         eq(d.var_changed_builtin_name.called, 1)
@@ -847,15 +848,15 @@
         d.set_keys_type()
         eq(d.builtinlist['state'], NORMAL)
         eq(d.customlist['state'], DISABLED)
-        eq(d.button_delete_custom_keys['state'], DISABLED)
+        eq(d.button_delete_custom_keys.state(), ('disabled',))
 
         # Custom keyset selected.
         d.keyset_source.set(False)
         d.set_keys_type()
         eq(d.builtinlist['state'], DISABLED)
-        eq(d.custom_keyset_on['state'], NORMAL)
+        eq(d.custom_keyset_on.state(), ('selected',))
         eq(d.customlist['state'], NORMAL)
-        eq(d.button_delete_custom_keys['state'], NORMAL)
+        eq(d.button_delete_custom_keys.state(), ())
         d.set_keys_type = Func()
 
     def test_get_new_keys(self):
@@ -865,7 +866,7 @@
         gkd = configdialog.GetKeysDialog = Func(return_self=True)
         gnkn = d.get_new_keys_name = Func()
 
-        d.button_new_keys['state'] = NORMAL
+        d.button_new_keys.state(('!disabled',))
         d.bindingslist.delete(0, 'end')
         d.bindingslist.insert(0, 'copy - <Control-Shift-Key-C>')
         d.bindingslist.selection_set(0)
@@ -953,7 +954,7 @@
         b.event_generate('<Button-1>', x=x, y=y)
         b.event_generate('<ButtonRelease-1>', x=x, y=y)
         self.assertEqual(b.get('anchor'), 'find')
-        self.assertEqual(d.button_new_keys['state'], NORMAL)
+        self.assertEqual(d.button_new_keys.state(), ())
 
     def test_create_new_key_set_and_save_new_key_set(self):
         eq = self.assertEqual
@@ -1032,7 +1033,7 @@
     def test_delete_custom_keys(self):
         eq = self.assertEqual
         d = self.page
-        d.button_delete_custom_keys['state'] = NORMAL
+        d.button_delete_custom_keys.state(('!disabled',))
         yesno = configdialog.tkMessageBox.askyesno = Func()
         dialog.deactivate_current_config = Func()
         dialog.activate_config_changes = Func()
@@ -1061,7 +1062,7 @@
         eq(yesno.called, 2)
         self.assertNotIn(keyset_name, keyspage)
         eq(idleConf.GetSectionList('user', 'keys'), [])
-        eq(d.custom_keyset_on['state'], DISABLED)
+        eq(d.custom_keyset_on.state(), ('disabled',))
         eq(d.custom_name.get(), '- no custom keys -')
         eq(dialog.deactivate_current_config.called, 1)
         eq(dialog.activate_config_changes.called, 1)
@@ -1173,18 +1174,18 @@
 
         h.delete(0, 'end')
         sad()
-        eq(d.button_helplist_edit['state'], DISABLED)
-        eq(d.button_helplist_remove['state'], DISABLED)
+        eq(d.button_helplist_edit.state(), ('disabled',))
+        eq(d.button_helplist_remove.state(), ('disabled',))
 
         h.insert(0, 'source')
         sad()
-        eq(d.button_helplist_edit['state'], DISABLED)
-        eq(d.button_helplist_remove['state'], DISABLED)
+        eq(d.button_helplist_edit.state(), ('disabled',))
+        eq(d.button_helplist_remove.state(), ('disabled',))
 
         h.selection_set(0)
         sad()
-        eq(d.button_helplist_edit['state'], NORMAL)
-        eq(d.button_helplist_remove['state'], NORMAL)
+        eq(d.button_helplist_edit.state(), ())
+        eq(d.button_helplist_remove.state(), ())
         d.set_add_delete_state = Func()  # Mask method.
 
     def test_helplist_item_add(self):
diff --git a/Misc/NEWS.d/next/IDLE/2017-07-28-18-59-06.bpo-30781.ud5m18.rst b/Misc/NEWS.d/next/IDLE/2017-07-28-18-59-06.bpo-30781.ud5m18.rst
index 18f40a4..3031adf 100644
--- a/Misc/NEWS.d/next/IDLE/2017-07-28-18-59-06.bpo-30781.ud5m18.rst
+++ b/Misc/NEWS.d/next/IDLE/2017-07-28-18-59-06.bpo-30781.ud5m18.rst
@@ -1 +1,2 @@
-IDLE - Use ttk Notebook in ConfigDialog
+IDLE - Use ttk widgets in ConfigDialog.
+Patches by Terry Jan Reedy and Cheryl Sabella.