[Python-checkins] r65542 - sandbox/trunk/ttk-gsoc/src/tabs_ttk_and_co.diff
guilherme.polo
python-checkins at python.org
Tue Aug 5 04:02:53 CEST 2008
Author: guilherme.polo
Date: Tue Aug 5 04:02:52 2008
New Revision: 65542
Log:
Current patch for adding tabs support, ttk support and other things to the idlelib
Added:
sandbox/trunk/ttk-gsoc/src/tabs_ttk_and_co.diff
Added: sandbox/trunk/ttk-gsoc/src/tabs_ttk_and_co.diff
==============================================================================
--- (empty file)
+++ sandbox/trunk/ttk-gsoc/src/tabs_ttk_and_co.diff Tue Aug 5 04:02:52 2008
@@ -0,0 +1,8367 @@
+Index: ToolTip.py
+===================================================================
+--- ToolTip.py (revision 63995)
++++ ToolTip.py (revision 65541)
+@@ -1,89 +0,0 @@
+-# general purpose 'tooltip' routines - currently unused in idlefork
+-# (although the 'calltips' extension is partly based on this code)
+-# may be useful for some purposes in (or almost in ;) the current project scope
+-# Ideas gleaned from PySol
+-
+-from Tkinter import *
+-
+-class ToolTipBase:
+-
+- def __init__(self, button):
+- self.button = button
+- self.tipwindow = None
+- self.id = None
+- self.x = self.y = 0
+- self._id1 = self.button.bind("<Enter>", self.enter)
+- self._id2 = self.button.bind("<Leave>", self.leave)
+- self._id3 = self.button.bind("<ButtonPress>", self.leave)
+-
+- def enter(self, event=None):
+- self.schedule()
+-
+- def leave(self, event=None):
+- self.unschedule()
+- self.hidetip()
+-
+- def schedule(self):
+- self.unschedule()
+- self.id = self.button.after(1500, self.showtip)
+-
+- def unschedule(self):
+- id = self.id
+- self.id = None
+- if id:
+- self.button.after_cancel(id)
+-
+- def showtip(self):
+- if self.tipwindow:
+- return
+- # The tip window must be completely outside the button;
+- # otherwise when the mouse enters the tip window we get
+- # a leave event and it disappears, and then we get an enter
+- # event and it reappears, and so on forever :-(
+- x = self.button.winfo_rootx() + 20
+- y = self.button.winfo_rooty() + self.button.winfo_height() + 1
+- self.tipwindow = tw = Toplevel(self.button)
+- tw.wm_overrideredirect(1)
+- tw.wm_geometry("+%d+%d" % (x, y))
+- self.showcontents()
+-
+- def showcontents(self, text="Your text here"):
+- # Override this in derived class
+- label = Label(self.tipwindow, text=text, justify=LEFT,
+- background="#ffffe0", relief=SOLID, borderwidth=1)
+- label.pack()
+-
+- def hidetip(self):
+- tw = self.tipwindow
+- self.tipwindow = None
+- if tw:
+- tw.destroy()
+-
+-class ToolTip(ToolTipBase):
+- def __init__(self, button, text):
+- ToolTipBase.__init__(self, button)
+- self.text = text
+- def showcontents(self):
+- ToolTipBase.showcontents(self, self.text)
+-
+-class ListboxToolTip(ToolTipBase):
+- def __init__(self, button, items):
+- ToolTipBase.__init__(self, button)
+- self.items = items
+- def showcontents(self):
+- listbox = Listbox(self.tipwindow, background="#ffffe0")
+- listbox.pack()
+- for item in self.items:
+- listbox.insert(END, item)
+-
+-def main():
+- # Test code
+- root = Tk()
+- b = Button(root, text="Hello", command=root.destroy)
+- b.pack()
+- root.update()
+- tip = ListboxToolTip(b, ["Hello", "world"])
+- root.mainloop()
+-
+-if __name__ == '__main__':
+- main()
+Index: AutoCompleteWindow.py
+===================================================================
+--- AutoCompleteWindow.py (revision 63995)
++++ AutoCompleteWindow.py (revision 65541)
+@@ -1,10 +1,16 @@
+ """
+ An auto-completion window for IDLE, used by the AutoComplete extension
+ """
+-from Tkinter import *
++from Tkinter import Toplevel, Listbox, Scrollbar, TclError
++from Tkconstants import END, LEFT, RIGHT, BOTH, VERTICAL, Y
++
++import AutoComplete
+ from MultiCall import MC_SHIFT
+-import AutoComplete
++from configHandler import idleConf
+
++if idleConf.GetOption('main', 'General', 'use-ttk', type='int'):
++ from ttk import Scrollbar
++
+ HIDE_VIRTUAL_EVENT_NAME = "<<autocompletewindow-hide>>"
+ HIDE_SEQUENCES = ("<FocusOut>", "<ButtonPress>")
+ KEYPRESS_VIRTUAL_EVENT_NAME = "<<autocompletewindow-keypress>>"
+Index: UndoDelegator.py
+===================================================================
+--- UndoDelegator.py (revision 63995)
++++ UndoDelegator.py (revision 65541)
+@@ -1,5 +1,5 @@
+ import string
+-from Tkinter import *
++
+ from Delegator import Delegator
+
+ #$ event <<redo>>
+@@ -74,7 +74,7 @@
+ if is_saved != self.was_saved:
+ self.was_saved = is_saved
+ if self.saved_change_hook:
+- self.saved_change_hook()
++ self.saved_change_hook(None)
+
+ def insert(self, index, chars, tags=None):
+ self.addcmd(InsertCommand(index, chars, tags))
+@@ -336,6 +336,7 @@
+ return self.depth
+
+ def main():
++ from Tkinter import Tk, Text
+ from Percolator import Percolator
+ root = Tk()
+ root.wm_protocol("WM_DELETE_WINDOW", root.quit)
+Index: Bindings.py
+===================================================================
+--- Bindings.py (revision 63995)
++++ Bindings.py (revision 65541)
+@@ -6,15 +6,16 @@
+ makes it possible, for example, to define a Debug menu which is only present in
+ the PythonShell window, and a Format menu which is only present in the Editor
+ windows.
+-
+ """
+ import sys
++
+ from configHandler import idleConf
+
+ menudefs = [
+ # underscore prefixes character to underscore
+ ('file', [
+ ('_New Window', '<<open-new-window>>'),
++ ('New _Tab', '<<new-tab>>'),
+ ('_Open...', '<<open-window-from-file>>'),
+ ('Open _Module...', '<<open-module>>'),
+ ('Class _Browser', '<<open-class-browser>>'),
+@@ -26,6 +27,7 @@
+ None,
+ ('Prin_t Window', '<<print-window>>'),
+ None,
++ ('Close Tab', '<<close-tab>>'),
+ ('_Close', '<<close-window>>'),
+ ('E_xit', '<<close-all-windows>>'),
+ ]),
+@@ -80,7 +82,6 @@
+ ]),
+ ]
+
+-import sys
+ if sys.platform == 'darwin' and '.app' in sys.executable:
+ # Running as a proper MacOS application bundle. This block restructures
+ # the menus a little to make them conform better to the HIG.
+Index: AutoComplete.py
+===================================================================
+--- AutoComplete.py (revision 63995)
++++ AutoComplete.py (revision 65541)
+@@ -38,11 +38,11 @@
+ popupwait = idleConf.GetOption("extensions", "AutoComplete",
+ "popupwait", type="int", default=0)
+
+- def __init__(self, editwin=None):
+- self.editwin = editwin
+- if editwin is None: # subprocess and test
++ def __init__(self, editpage=None):
++ self.editpage = editpage
++ if editpage is None: # subprocess and test
+ return
+- self.text = editwin.text
++ self.text = editpage.text
+ self.autocompletewindow = None
+
+ # id of delayed call, and the index of the text insert when the delayed
+@@ -120,7 +120,7 @@
+ self.text.after_cancel(self._delayed_completion_id)
+ self._delayed_completion_id = None
+
+- hp = HyperParser(self.editwin, "insert")
++ hp = HyperParser(self.editpage, "insert")
+ curline = self.text.get("insert linestart", "insert")
+ i = j = len(curline)
+ if hp.is_in_string() and (not mode or mode==COMPLETE_FILES):
+@@ -176,7 +176,7 @@
+ module may be inoperative if the module was not the last to run.
+ """
+ try:
+- rpcclt = self.editwin.flist.pyshell.interp.rpcclt
++ rpcclt = self.editpage.editwin.flist.pyshell.interp.rpcclt
+ except:
+ rpcclt = None
+ if rpcclt:
+Index: configHandler.py
+===================================================================
+--- configHandler.py (revision 63995)
++++ configHandler.py (revision 65541)
+@@ -19,10 +19,10 @@
+ """
+ import os
+ import sys
+-import string
+-import macosxSupport
+ from ConfigParser import ConfigParser, NoOptionError, NoSectionError
+
++import macosxSupport
++
+ class InvalidConfigType(Exception): pass
+ class InvalidConfigSet(Exception): pass
+ class InvalidFgBg(Exception): pass
+@@ -295,7 +295,10 @@
+ back=themeDict['normal-background']
+ else:
+ back=themeDict[element+'-background']
+- highlight={"foreground": fore,"background": back}
++ #bold = 0
++ #if element + '-bold' in themeDict:
++ # bold = themeDict[element + '-bold']
++ highlight={"foreground": fore, "background": back}#, "font": bold}
+ if not fgBg: #return dict of both colours
+ return highlight
+ else: #return specified colour only
+@@ -558,6 +561,10 @@
+ defined here.
+ """
+ keyBindings={
++ '<<close-tab>>': ['<Control-w>'],
++ '<<new-tab>>': ['<Control-t>'],
++ '<<next-tab>>': ['<Control-Next'],
++ '<<prev-tab>>': ['<Control-Prior'],
+ '<<copy>>': ['<Control-c>', '<Control-C>'],
+ '<<cut>>': ['<Control-x>', '<Control-X>'],
+ '<<paste>>': ['<Control-v>', '<Control-V>'],
+@@ -649,7 +656,7 @@
+ menuItem='' #make these empty
+ helpPath='' #so value won't be added to list
+ else: #config entry contains ';' as expected
+- value=string.split(value,';')
++ value = value.split(';')
+ menuItem=value[0].strip()
+ helpPath=value[1].strip()
+ if menuItem and helpPath: #neither are empty strings
+Index: HyperParser.py
+===================================================================
+--- HyperParser.py (revision 63995)
++++ HyperParser.py (revision 65541)
+@@ -10,22 +10,22 @@
+
+ import string
+ import keyword
++
+ import PyParse
++from editorpage import index2line
+
+ class HyperParser:
+
+- def __init__(self, editwin, index):
++ def __init__(self, editpage, index):
+ """Initialize the HyperParser to analyze the surroundings of the given
+ index.
+ """
+
+- self.editwin = editwin
+- self.text = text = editwin.text
++ self.editwin = editwin = editpage.editwin
++ self.text = text = editpage.text
+
+ parser = PyParse.Parser(editwin.indentwidth, editwin.tabwidth)
+
+- def index2line(index):
+- return int(float(index))
+ lno = index2line(text.index(index))
+
+ if not editwin.context_use_ps1:
+@@ -38,7 +38,7 @@
+ # its status will be the same as the char before it, if should.
+ parser.set_str(text.get(startatindex, stopatindex)+' \n')
+ bod = parser.find_good_parse_start(
+- editwin._build_char_in_string_func(startatindex))
++ editwin.build_char_in_string_func(startatindex))
+ if bod is not None or startat == 1:
+ break
+ parser.set_lo(bod or 0)
+Index: ColorDelegator.py
+===================================================================
+--- ColorDelegator.py (revision 63995)
++++ ColorDelegator.py (revision 65541)
+@@ -1,8 +1,8 @@
++import re
+ import time
+-import re
+ import keyword
+ import __builtin__
+-from Tkinter import *
++
+ from Delegator import Delegator
+ from configHandler import idleConf
+
+@@ -182,8 +182,11 @@
+ ok = False
+ while not ok:
+ mark = next
+- next = self.index(mark + "+%d lines linestart" %
+- lines_to_get)
++ # XXX self could be None here in some cases.
++ # I can reproduce it by clicking "Apply" then "Ok"
++ # quickly in the config dialog (while editing a not so
++ # small file).
++ next = self.index(mark + "+%d lines linestart" % lines_to_get)
+ lines_to_get = min(lines_to_get * 2, 100)
+ ok = "SYNC" in self.tag_names(next + "-1c")
+ line = self.get(mark, next)
+@@ -248,6 +251,7 @@
+ self.tag_remove(tag, "1.0", "end")
+
+ def main():
++ from Tkinter import Tk, Text
+ from Percolator import Percolator
+ root = Tk()
+ root.wm_protocol("WM_DELETE_WINDOW", root.quit)
+Index: configSectionNameDialog.py
+===================================================================
+--- configSectionNameDialog.py (revision 63995)
++++ configSectionNameDialog.py (revision 65541)
+@@ -2,96 +2,111 @@
+ Dialog that allows user to specify a new config file section name.
+ Used to get new highlight theme and keybinding set names.
+ """
+-from Tkinter import *
++from Tkinter import Toplevel, Entry, Frame, Label, Button, StringVar
++from Tkconstants import TOP, BOTTOM, RIGHT, BOTH, SUNKEN, X
+ import tkMessageBox
+
++from configHandler import idleConf
++
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++if TTK:
++ from ttk import Entry, Frame, Label, Button
++
+ class GetCfgSectionNameDialog(Toplevel):
+- def __init__(self,parent,title,message,usedNames):
++ def __init__(self, parent, title, message, usedNames):
+ """
+ message - string, informational message to display
+ usedNames - list, list of names already in use for validity check
+ """
+ Toplevel.__init__(self, parent)
+ self.configure(borderwidth=5)
+- self.resizable(height=FALSE,width=FALSE)
++ self.resizable(height=False, width=False)
+ self.title(title)
+ self.transient(parent)
+ self.grab_set()
+ self.protocol("WM_DELETE_WINDOW", self.Cancel)
+ self.parent = parent
+- self.message=message
+- self.usedNames=usedNames
+- self.result=''
++ self.message = message
++ self.usedNames = usedNames
++ self.result = ''
+ self.CreateWidgets()
+ self.withdraw() #hide while setting geometry
+ self.update_idletasks()
+- #needs to be done here so that the winfo_reqwidth is valid
+- self.messageInfo.config(width=self.frameMain.winfo_reqwidth())
+ self.geometry("+%d+%d" %
+- ((parent.winfo_rootx()+((parent.winfo_width()/2)
+- -(self.winfo_reqwidth()/2)),
+- parent.winfo_rooty()+((parent.winfo_height()/2)
+- -(self.winfo_reqheight()/2)) )) ) #centre dialog over parent
++ ((parent.winfo_rootx() + ((parent.winfo_width() / 2)
++ - (self.winfo_reqwidth() / 2)),
++ parent.winfo_rooty() + ((parent.winfo_height() / 2)
++ - (self.winfo_reqheight() / 2)) )) ) #centre dialog over parent
+ self.deiconify() #geometry set, unhide
+ self.wait_window()
+
+ def CreateWidgets(self):
+- self.name=StringVar(self)
+- self.fontSize=StringVar(self)
+- self.frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
+- self.frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
+- self.messageInfo=Message(self.frameMain,anchor=W,justify=LEFT,padx=5,pady=5,
+- text=self.message)#,aspect=200)
+- entryName=Entry(self.frameMain,textvariable=self.name,width=30)
++ self.name = StringVar(self)
++ self.fontSize = StringVar(self)
++ self.frameMain = Frame(self, borderwidth=2, relief=SUNKEN)
++ self.messageInfo = Label(self.frameMain, text=self.message)
++ entryName = Entry(self.frameMain, textvariable=self.name, width=30)
++ frameButtons = Frame(self)
++ self.buttonOk = Button(frameButtons, text='Ok', command=self.Ok)
++ self.buttonCancel = Button(frameButtons, text='Cancel',
++ command=self.Cancel)
++
+ entryName.focus_set()
+- self.messageInfo.pack(padx=5,pady=5)#,expand=TRUE,fill=BOTH)
+- entryName.pack(padx=5,pady=5)
+- frameButtons=Frame(self)
+- frameButtons.pack(side=BOTTOM,fill=X)
+- self.buttonOk = Button(frameButtons,text='Ok',
+- width=8,command=self.Ok)
+- self.buttonOk.grid(row=0,column=0,padx=5,pady=5)
+- self.buttonCancel = Button(frameButtons,text='Cancel',
+- width=8,command=self.Cancel)
+- self.buttonCancel.grid(row=0,column=1,padx=5,pady=5)
+
++ self.frameMain.pack(side=TOP, expand=True, fill=BOTH)
++ self.messageInfo.pack(padx=5, pady=5)
++ entryName.pack(padx=5, pady=5)
++ frameButtons.pack(side=BOTTOM, fill=X)
++ self.buttonOk.pack(padx=1, pady=5, side=RIGHT)
++ self.buttonCancel.pack(pady=5, padx=5, side=RIGHT)
++
++ if TTK:
++ self.messageInfo['padding'] = 5
++ frameButtons['style'] = 'RootColor.TFrame'
++ else:
++ self.messageInfo.configure(padx=5, pady=5)
++
+ def NameOk(self):
+ #simple validity check for a sensible
+ #ConfigParser file section name
+- nameOk=1
+- name=self.name.get()
++ nameOk = 1
++ name = self.name.get()
+ name.strip()
++
+ if not name: #no name specified
+ tkMessageBox.showerror(title='Name Error',
+- message='No name specified.', parent=self)
+- nameOk=0
+- elif len(name)>30: #name too long
++ message='No name specified.', parent=self)
++ nameOk = 0
++ elif len(name) > 30: #name too long
+ tkMessageBox.showerror(title='Name Error',
+- message='Name too long. It should be no more than '+
+- '30 characters.', parent=self)
++ message=('Name too long. It should be no more than '
++ '30 characters.'), parent=self)
+ nameOk=0
+ elif name in self.usedNames:
+ tkMessageBox.showerror(title='Name Error',
+ message='This name is already in use.', parent=self)
+ nameOk=0
++
+ return nameOk
+
+ def Ok(self, event=None):
+ if self.NameOk():
+- self.result=self.name.get().strip()
++ self.result = self.name.get().strip()
+ self.destroy()
+
+ def Cancel(self, event=None):
+- self.result=''
++ self.result = ''
+ self.destroy()
+
+ if __name__ == '__main__':
++ from Tkinter import Tk
+ #test the dialog
+- root=Tk()
+ def run():
+- keySeq=''
+- dlg=GetCfgSectionNameDialog(root,'Get Name',
+- 'The information here should need to be word wrapped. Test.')
++ keySeq = ''
++ dlg = GetCfgSectionNameDialog(root, 'Get Name',
++ 'The information here should need to be word wrapped. Test.', [])
+ print dlg.result
+- Button(root,text='Dialog',command=run).pack()
++
++ root=Tk()
++ Button(root, text='Dialog', command=run).pack()
+ root.mainloop()
+Index: stylist.py
+===================================================================
+--- stylist.py (revision 0)
++++ stylist.py (revision 65541)
+@@ -0,0 +1,29 @@
++from configHandler import idleConf
++
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++
++class PoorManStyle(object):
++ def __init__(self, parent, styles=None, cfgstyles=None):
++ self.parent = parent
++ self.cfgstyles = cfgstyles
++ self.styles = styles
++
++ def configure(self, style, lookup=None, background=None):
++ if style not in self.cfgstyles: # passed wrong style probably
++ return
++
++ widget = getattr(self.parent, self.cfgstyles[style])
++ if lookup:
++ return widget.cget('bg')
++
++ widget.configure(bg=background)
++
++ def style_it(self, w, style):
++ if TTK:
++ w['style'] = style
++ return
++
++ if not style in self.styles: # may not need to be styled
++ return
++
++ w.configure(**self.styles[style])
+Index: ZoomHeight.py
+===================================================================
+--- ZoomHeight.py (revision 63995)
++++ ZoomHeight.py (revision 65541)
+@@ -12,11 +12,11 @@
+ ])
+ ]
+
+- def __init__(self, editwin):
+- self.editwin = editwin
++ def __init__(self, editpage):
++ self.editpage = editpage
+
+ def zoom_height_event(self, event):
+- top = self.editwin.top
++ top = self.editpage.editwin.top
+ zoom_height(top)
+
+ def zoom_height(top):
+Index: PyShell.py
+===================================================================
+--- PyShell.py (revision 63995)
++++ PyShell.py (revision 65541)
+@@ -1,40 +1,50 @@
+ #! /usr/bin/env python
+-
+ import os
+-import os.path
++import re
+ import sys
++import time
++import types
+ import string
+ import getopt
+-import re
+ import socket
+-import time
++import linecache
+ import threading
+-import traceback
+-import types
+-import macosxSupport
+-
+-import linecache
+ from code import InteractiveInterpreter
+
+ try:
+- from Tkinter import *
++ from Tkinter import Tk, Toplevel, TclError
++ from Tkconstants import END
+ except ImportError:
+ print>>sys.__stderr__, "** IDLE can't import Tkinter. " \
+ "Your Python may not be configured for Tk. **"
+ sys.exit(1)
++try:
++ from ttk import Style
++ TTK = 1
++except ImportError:
++ print >> sys.stderr, "** IDLE can't import ttk."
++ TTK = 0
++
+ import tkMessageBox
+
+-from EditorWindow import EditorWindow, fixwordbreaks
+-from FileList import FileList
+-from ColorDelegator import ColorDelegator
+-from UndoDelegator import UndoDelegator
+-from OutputWindow import OutputWindow
++import macosxSupport
+ from configHandler import idleConf
+-import idlever
+
++# store ttk availability
++idleConf.SetOption('main', 'General', 'use-ttk', str(TTK))
++idleConf.SaveUserCfgFiles()
++
+ import rpc
++import utils
++import idlever
+ import Debugger
++import IOBinding
+ import RemoteDebugger
++from FileList import FileList
++from OutputWindow import OutputWindow
++from EditorWindow import EditorWindow, fixwordbreaks
++from UndoDelegator import UndoDelegator
++from ColorDelegator import ColorDelegator
+
+ IDENTCHARS = string.ascii_letters + string.digits + "_"
+ LOCALHOST = '127.0.0.1'
+@@ -55,18 +65,20 @@
+ except ImportError:
+ pass
+ else:
+- def idle_showwarning(message, category, filename, lineno):
++ def idle_showwarning(message, category, filename, lineno, line=None):
+ file = warning_stream
+ try:
+- file.write(warnings.formatwarning(message, category, filename, lineno))
++ file.write(warnings.formatwarning(message, category, filename,
++ lineno, line))
+ except IOError:
+ pass ## file (probably __stderr__) is invalid, warning dropped.
+ warnings.showwarning = idle_showwarning
+- def idle_formatwarning(message, category, filename, lineno):
++ def idle_formatwarning(message, category, filename, lineno, line=None):
+ """Format warnings the IDLE way"""
+ s = "\nWarning (from warnings module):\n"
+ s += ' File \"%s\", line %s\n' % (filename, lineno)
+- line = linecache.getline(filename, lineno).strip()
++ if line is None:
++ line = linecache.getline(filename, lineno).strip()
+ if line:
+ s += " %s\n" % line
+ s += "%s: %s\n>>> " % (category.__name__, message)
+@@ -100,58 +112,67 @@
+ class PyShellEditorWindow(EditorWindow):
+ "Regular text edit window in IDLE, supports breakpoints"
+
++ rmenu_specs = [("Set Breakpoint", "<<set-breakpoint-here>>"),
++ ("Clear Breakpoint", "<<clear-breakpoint-here>>")]
++
+ def __init__(self, *args):
+- self.breakpoints = []
+ EditorWindow.__init__(self, *args)
+- self.text.bind("<<set-breakpoint-here>>", self.set_breakpoint_here)
+- self.text.bind("<<clear-breakpoint-here>>", self.clear_breakpoint_here)
+- self.text.bind("<<open-python-shell>>", self.flist.open_shell)
+
+ self.breakpointPath = os.path.join(idleConf.GetUserCfgDir(),
+- 'breakpoints.lst')
++ 'breakpoints.lst')
++ self.top.bind('<<tab-created>>', self.__configure_new_tab)
++ self.__configure_new_tab()
++
++ def __configure_new_tab(self, event=None):
++ page = self.text_notebook.last_page().editpage
++ page.breakpoints = []
++ text = page.text
++
++ text.bind("<<set-breakpoint-here>>",
++ utils.callback(self._set_breakpoint_here, text, page))
++ text.bind("<<clear-breakpoint-here>>",
++ utils.callback(self._clear_breakpoint_here, text, page))
++ text.bind("<<open-python-shell>>", self.flist.open_shell)
++
+ # whenever a file is changed, restore breakpoints
+- if self.io.filename: self.restore_file_breaks()
+- def filename_changed_hook(old_hook=self.io.filename_change_hook,
++ if page.io.filename:
++ self.restore_file_breaks(text, page)
++
++ def filename_changed_hook(old_hook=page.io.filename_change_hook,
+ self=self):
+- self.restore_file_breaks()
++ self.restore_file_breaks(text, page)
+ old_hook()
+- self.io.set_filename_change_hook(filename_changed_hook)
++ page.io.set_filename_change_hook(filename_changed_hook)
+
+- rmenu_specs = [("Set Breakpoint", "<<set-breakpoint-here>>"),
+- ("Clear Breakpoint", "<<clear-breakpoint-here>>")]
+-
+- def set_breakpoint(self, lineno):
+- text = self.text
+- filename = self.io.filename
++ def set_breakpoint(self, lineno, text, page):
++ filename = page.io.filename
+ text.tag_add("BREAK", "%d.0" % lineno, "%d.0" % (lineno+1))
+ try:
+- i = self.breakpoints.index(lineno)
++ i = page.breakpoints.index(lineno)
+ except ValueError: # only add if missing, i.e. do once
+- self.breakpoints.append(lineno)
++ page.breakpoints.append(lineno)
+ try: # update the subprocess debugger
+ debug = self.flist.pyshell.interp.debugger
+ debug.set_breakpoint_here(filename, lineno)
+ except: # but debugger may not be active right now....
+ pass
+
+- def set_breakpoint_here(self, event=None):
+- text = self.text
+- filename = self.io.filename
++ def _set_breakpoint_here(self, event=None, text=None, page=None):
++ filename = page.io.filename
+ if not filename:
+ text.bell()
+ return
+ lineno = int(float(text.index("insert")))
+- self.set_breakpoint(lineno)
++ self.set_breakpoint(lineno, text, page)
+
+- def clear_breakpoint_here(self, event=None):
+- text = self.text
+- filename = self.io.filename
++ def _clear_breakpoint_here(self, event=None, text=None, page=None):
++ filename = page.io.filename
+ if not filename:
+ text.bell()
+ return
+ lineno = int(float(text.index("insert")))
+ try:
+- self.breakpoints.remove(lineno)
++ page.breakpoints.remove(lineno)
+ except:
+ pass
+ text.tag_remove("BREAK", "insert linestart",\
+@@ -163,13 +184,17 @@
+ pass
+
+ def clear_file_breaks(self):
+- if self.breakpoints:
+- text = self.text
+- filename = self.io.filename
++ for page in self.text_notebook.pages.itervalues():
++ page = page.editpage
++ text = page.text
++ filename = page.io.filename
++
++ if not page.breakpoints:
++ continue
+ if not filename:
+ text.bell()
+- return
+- self.breakpoints = []
++ continue
++ page.breakpoints = []
+ text.tag_remove("BREAK", "1.0", END)
+ try:
+ debug = self.flist.pyshell.interp.debugger
+@@ -177,7 +202,7 @@
+ except:
+ pass
+
+- def store_file_breaks(self):
++ def store_file_breaks(self, page):
+ "Save breakpoints when file is saved"
+ # XXX 13 Dec 2002 KBK Currently the file must be saved before it can
+ # be run. The breaks are saved at that time. If we introduce
+@@ -200,8 +225,8 @@
+ # debugger is loaded) is updated during the save, the visible
+ # breaks stay synched with the subprocess even if one of these
+ # unexpected breakpoint deletions occurs.
+- breaks = self.breakpoints
+- filename = self.io.filename
++ breaks = page.breakpoints
++ filename = page.io.filename
+ try:
+ lines = open(self.breakpointPath,"r").readlines()
+ except IOError:
+@@ -210,49 +235,42 @@
+ for line in lines:
+ if not line.startswith(filename + '='):
+ new_file.write(line)
+- self.update_breakpoints()
+- breaks = self.breakpoints
++ self.update_breakpoints(page)
++ breaks = page.breakpoints
+ if breaks:
+ new_file.write(filename + '=' + str(breaks) + '\n')
+ new_file.close()
+
+- def restore_file_breaks(self):
+- self.text.update() # this enables setting "BREAK" tags to be visible
+- filename = self.io.filename
++ def restore_file_breaks(self, text, page):
++ text.update() # this enables setting "BREAK" tags to be visible
++ filename = page.io.filename
+ if filename is None:
+ return
+ if os.path.isfile(self.breakpointPath):
+- lines = open(self.breakpointPath,"r").readlines()
++ lines = open(self.breakpointPath, "r").readlines()
+ for line in lines:
+ if line.startswith(filename + '='):
+ breakpoint_linenumbers = eval(line[len(filename)+1:])
+ for breakpoint_linenumber in breakpoint_linenumbers:
+- self.set_breakpoint(breakpoint_linenumber)
++ self.set_breakpoint(breakpoint_linenumber, text, page)
+
+- def update_breakpoints(self):
+- "Retrieves all the breakpoints in the current window"
+- text = self.text
++ def update_breakpoints(self, page):
++ "Retrieves all the breakpoints in the current page"
++ text = page.text
+ ranges = text.tag_ranges("BREAK")
+ linenumber_list = self.ranges_to_linenumbers(ranges)
+- self.breakpoints = linenumber_list
++ page.breakpoints = linenumber_list
+
+ def ranges_to_linenumbers(self, ranges):
+ lines = []
+ for index in range(0, len(ranges), 2):
+- lineno = int(float(ranges[index]))
+- end = int(float(ranges[index+1]))
++ lineno = int(float(str(ranges[index])))
++ end = int(float(str(ranges[index+1])))
+ while lineno < end:
+ lines.append(lineno)
+ lineno += 1
+ return lines
+
+-# XXX 13 Dec 2002 KBK Not used currently
+-# def saved_change_hook(self):
+-# "Extend base method - clear breaks if module is modified"
+-# if not self.get_saved():
+-# self.clear_file_breaks()
+-# EditorWindow.saved_change_hook(self)
+-
+ def _close(self):
+ "Extend base method - clear breaks when module is closed"
+ self.clear_file_breaks()
+@@ -589,7 +607,6 @@
+ self.save_warnings_filters = warnings.filters[:]
+ warnings.filterwarnings(action="error", category=SyntaxWarning)
+ if isinstance(source, types.UnicodeType):
+- import IOBinding
+ try:
+ source = source.encode(IOBinding.encoding)
+ except UnicodeError:
+@@ -814,30 +831,47 @@
+ flist = PyShellFileList(root)
+ #
+ OutputWindow.__init__(self, flist, None, None)
++ # remove things related to tabs. The window running the shell is
++ # considered special enough to have a single window for it.
++
++ # remove tabs area
++ if TTK:
++ self.text_notebook['style'] = 'PyShell.TNotebook'
++ style = Style(self.top)
++ style.layout('PyShell.TNotebook.Tab', [('null', '')])
++ else:
++ self.text_notebook._tab_set.grid_forget()
++
++ # remove commands related to tab
++ if 'file' in self.menudict:
++ menu = self.menudict['file']
++ curr_entry = None
++ i = 0
++ while True:
++ last_entry, curr_entry = curr_entry, menu.entryconfigure(i)
++ if last_entry == curr_entry:
++ break
++
++ if 'label' in curr_entry and 'Tab' in curr_entry['label'][-1]:
++ if 'New' in ' '.join(curr_entry['label'][-1]):
++ menu.delete(i)
++ i += 1
++ self.text.unbind('<<new-tab>>')
++ #self.text.unbind('<<close-tab>>')
++
+ #
+-## self.config(usetabs=1, indentwidth=8, context_use_ps1=1)
+ self.usetabs = True
+ # indentwidth must be 8 when using tabs. See note in EditorWindow:
+ self.indentwidth = 8
+ self.context_use_ps1 = True
+ #
+- text = self.text
+- text.configure(wrap="char")
+- text.bind("<<newline-and-indent>>", self.enter_callback)
+- text.bind("<<plain-newline-and-indent>>", self.linefeed_callback)
+- text.bind("<<interrupt-execution>>", self.cancel_callback)
+- text.bind("<<end-of-file>>", self.eof_callback)
+- text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
+- text.bind("<<toggle-debugger>>", self.toggle_debugger)
+- text.bind("<<toggle-jit-stack-viewer>>", self.toggle_jit_stack_viewer)
+- if use_subprocess:
+- text.bind("<<view-restart>>", self.view_restart_mark)
+- text.bind("<<restart-shell>>", self.restart_shell)
+- #
++ self.wtext = None
++ self.textpage = None
++ self.__configure_new_tab()
++
+ self.save_stdout = sys.stdout
+ self.save_stderr = sys.stderr
+ self.save_stdin = sys.stdin
+- import IOBinding
+ self.stdout = PseudoFile(self, "stdout", IOBinding.encoding)
+ self.stderr = PseudoFile(self, "stderr", IOBinding.encoding)
+ self.console = PseudoFile(self, "console", IOBinding.encoding)
+@@ -845,11 +879,37 @@
+ sys.stdout = self.stdout
+ sys.stderr = self.stderr
+ sys.stdin = self
+- #
+- self.history = self.History(self.text)
+- #
++
+ self.pollinterval = 50 # millisec
+
++ if TTK:
++ self.set_theme(Style(self.root))
++
++ def __configure_new_tab(self):
++ # select the last page (the one added)
++ page = self.text_notebook.last_page().editpage
++ self.wtext = text = page.text
++ self.textpage = page
++
++ text.configure(wrap="char")
++ text.bind("<<newline-and-indent>>",
++ utils.callback(self._enter_callback, text))
++ text.bind("<<plain-newline-and-indent>>",
++ utils.callback(self._linefeed_callback, text))
++ text.bind("<<interrupt-execution>>",
++ utils.callback(self._cancel_callback, text))
++ text.bind("<<end-of-file>>",
++ utils.callback(self._eof_callback, text))
++ text.bind("<<toggle-debugger>>", self._toggle_debugger)
++ text.bind("<<toggle-jit-stack-viewer>>", self._toggle_jit_stack_viewer)
++ text.bind("<<open-stack-viewer>>", self.open_stack_viewer)
++ if use_subprocess:
++ text.bind("<<view-restart>>",
++ utils.callback(self._view_restart_mark, text))
++ text.bind("<<restart-shell>>", self.restart_shell)
++
++ page.history = self.History(text)
++
+ def get_standard_extension_names(self):
+ return idleConf.GetExtensions(shell_only=True)
+
+@@ -866,11 +926,11 @@
+ def get_warning_stream(self):
+ return warning_stream
+
+- def toggle_debugger(self, event=None):
++ def _toggle_debugger(self, event=None):
+ if self.executing:
+ tkMessageBox.showerror("Don't debug now",
+ "You can only toggle the debugger when idle",
+- master=self.text)
++ master=self.text_notebook)
+ self.set_debugger_indicator()
+ return "break"
+ else:
+@@ -884,7 +944,7 @@
+ db = self.interp.getdebugger()
+ self.setvar("<<toggle-debugger>>", not not db)
+
+- def toggle_jit_stack_viewer(self, event=None):
++ def _toggle_jit_stack_viewer(self, event=None):
+ pass # All we need is the variable
+
+ def close_debugger(self):
+@@ -930,7 +990,7 @@
+ "Kill?",
+ "The program is still running!\n Do you want to kill it?",
+ default="ok",
+- parent=self.text)
++ parent=self.text_notebook)
+ if response is False:
+ return "cancel"
+ if self.reading:
+@@ -938,7 +998,7 @@
+ self.canceled = True
+ self.closing = True
+ # Wait for poll_subprocess() rescheduling to stop
+- self.text.after(2 * self.pollinterval, self.close2)
++ self.text_notebook.after(2 * self.pollinterval, self.close2)
+
+ def close2(self):
+ return EditorWindow.close(self)
+@@ -956,7 +1016,9 @@
+ self.interp = None
+ self.console = None
+ self.flist.pyshell = None
+- self.history = None
++ for page in self.text_notebook.pages.itervalues():
++ page.editpage.history = None
++
+ EditorWindow._close(self)
+
+ def ispythonsource(self, filename):
+@@ -996,38 +1058,12 @@
+ Tkinter._default_root = None # 03Jan04 KBK What's this?
+ return True
+
+- def readline(self):
+- save = self.reading
+- try:
+- self.reading = 1
+- self.top.mainloop() # nested mainloop()
+- finally:
+- self.reading = save
+- line = self.text.get("iomark", "end-1c")
+- if len(line) == 0: # may be EOF if we quit our mainloop with Ctrl-C
+- line = "\n"
+- if isinstance(line, unicode):
+- import IOBinding
+- try:
+- line = line.encode(IOBinding.encoding)
+- except UnicodeError:
+- pass
+- self.resetoutput()
+- if self.canceled:
+- self.canceled = 0
+- if not use_subprocess:
+- raise KeyboardInterrupt
+- if self.endoffile:
+- self.endoffile = 0
+- line = ""
+- return line
+-
+ def isatty(self):
+ return True
+
+- def cancel_callback(self, event=None):
++ def _cancel_callback(self, event=None, text=None):
+ try:
+- if self.text.compare("sel.first", "!=", "sel.last"):
++ if text.compare("sel.first", "!=", "sel.last"):
+ return # Active selection -- always use default binding
+ except:
+ pass
+@@ -1047,11 +1083,11 @@
+ self.top.quit() # exit the nested mainloop() in readline()
+ return "break"
+
+- def eof_callback(self, event):
++ def _eof_callback(self, event, text):
+ if self.executing and not self.reading:
+ return # Let the default binding (delete next char) take over
+- if not (self.text.compare("iomark", "==", "insert") and
+- self.text.compare("insert", "==", "end-1c")):
++ if not (text.compare("iomark", "==", "insert") and
++ text.compare("insert", "==", "end-1c")):
+ return # Let the default binding (delete next char) take over
+ if not self.executing:
+ self.resetoutput()
+@@ -1062,24 +1098,24 @@
+ self.top.quit()
+ return "break"
+
+- def linefeed_callback(self, event):
++ def _linefeed_callback(self, event, text):
+ # Insert a linefeed without entering anything (still autoindented)
+ if self.reading:
+- self.text.insert("insert", "\n")
+- self.text.see("insert")
++ text.insert("insert", "\n")
++ text.see("insert")
+ else:
+ self.newline_and_indent_event(event)
+ return "break"
+
+- def enter_callback(self, event):
++ def _enter_callback(self, event, text):
+ if self.executing and not self.reading:
+ return # Let the default binding (insert '\n') take over
+ # If some text is selected, recall the selection
+ # (but only if this before the I/O mark)
+ try:
+- sel = self.text.get("sel.first", "sel.last")
++ sel = text.get("sel.first", "sel.last")
+ if sel:
+- if self.text.compare("sel.last", "<=", "iomark"):
++ if text.compare("sel.last", "<=", "iomark"):
+ self.recall(sel, event)
+ return "break"
+ except:
+@@ -1087,51 +1123,52 @@
+ # If we're strictly before the line containing iomark, recall
+ # the current line, less a leading prompt, less leading or
+ # trailing whitespace
+- if self.text.compare("insert", "<", "iomark linestart"):
++ if text.compare("insert", "<", "iomark linestart"):
+ # Check if there's a relevant stdin range -- if so, use it
+- prev = self.text.tag_prevrange("stdin", "insert")
+- if prev and self.text.compare("insert", "<", prev[1]):
+- self.recall(self.text.get(prev[0], prev[1]), event)
++ prev = text.tag_prevrange("stdin", "insert")
++ if prev and text.compare("insert", "<", prev[1]):
++ self.recall(text.get(prev[0], prev[1]), event)
+ return "break"
+- next = self.text.tag_nextrange("stdin", "insert")
+- if next and self.text.compare("insert lineend", ">=", next[0]):
+- self.recall(self.text.get(next[0], next[1]), event)
++ next = text.tag_nextrange("stdin", "insert")
++ if next and text.compare("insert lineend", ">=", next[0]):
++ self.recall(text.get(next[0], next[1]), event)
+ return "break"
+ # No stdin mark -- just get the current line, less any prompt
+- indices = self.text.tag_nextrange("console", "insert linestart")
++ indices = text.tag_nextrange("console", "insert linestart")
+ if indices and \
+- self.text.compare(indices[0], "<=", "insert linestart"):
+- self.recall(self.text.get(indices[1], "insert lineend"), event)
++ text.compare(indices[0], "<=", "insert linestart"):
++ self.recall(text.get(indices[1], "insert lineend"), event)
+ else:
+- self.recall(self.text.get("insert linestart", "insert lineend"), event)
++ self.recall(text.get("insert linestart", "insert lineend"),
++ event)
+ return "break"
+ # If we're between the beginning of the line and the iomark, i.e.
+ # in the prompt area, move to the end of the prompt
+- if self.text.compare("insert", "<", "iomark"):
+- self.text.mark_set("insert", "iomark")
++ if text.compare("insert", "<", "iomark"):
++ text.mark_set("insert", "iomark")
+ # If we're in the current input and there's only whitespace
+ # beyond the cursor, erase that whitespace first
+- s = self.text.get("insert", "end-1c")
++ s = text.get("insert", "end-1c")
+ if s and not s.strip():
+- self.text.delete("insert", "end-1c")
++ text.delete("insert", "end-1c")
+ # If we're in the current input before its last line,
+ # insert a newline right at the insert point
+- if self.text.compare("insert", "<", "end-1c linestart"):
++ if text.compare("insert", "<", "end-1c linestart"):
+ self.newline_and_indent_event(event)
+ return "break"
+ # We're in the last line; append a newline and submit it
+- self.text.mark_set("insert", "end-1c")
++ text.mark_set("insert", "end-1c")
+ if self.reading:
+- self.text.insert("insert", "\n")
+- self.text.see("insert")
++ text.insert("insert", "\n")
++ text.see("insert")
+ else:
+ self.newline_and_indent_event(event)
+- self.text.tag_add("stdin", "iomark", "end-1c")
+- self.text.update_idletasks()
++ text.tag_add("stdin", "iomark", "end-1c")
++ text.update_idletasks()
+ if self.reading:
+ self.top.quit() # Break out of recursive mainloop() in raw_input()
+ else:
+- self.runit()
++ self._runit(text)
+ return "break"
+
+ def recall(self, s, event):
+@@ -1139,15 +1176,15 @@
+ s = re.sub(r'^\s*\n', '' , s)
+ s = re.sub(r'\n\s*$', '', s)
+ lines = s.split('\n')
+- self.text.undo_block_start()
++ self.wtext.undo_block_start()
+ try:
+- self.text.tag_remove("sel", "1.0", "end")
+- self.text.mark_set("insert", "end-1c")
+- prefix = self.text.get("insert linestart", "insert")
++ self.wtext.tag_remove("sel", "1.0", "end")
++ self.wtext.mark_set("insert", "end-1c")
++ prefix = self.wtext.get("insert linestart", "insert")
+ if prefix.rstrip().endswith(':'):
+ self.newline_and_indent_event(event)
+- prefix = self.text.get("insert linestart", "insert")
+- self.text.insert("insert", lines[0].strip())
++ prefix = self.wtext.get("insert linestart", "insert")
++ self.wtext.insert("insert", lines[0].strip())
+ if len(lines) > 1:
+ orig_base_indent = re.search(r'^([ \t]*)', lines[0]).group(0)
+ new_base_indent = re.search(r'^([ \t]*)', prefix).group(0)
+@@ -1155,13 +1192,13 @@
+ if line.startswith(orig_base_indent):
+ # replace orig base indentation with new indentation
+ line = new_base_indent + line[len(orig_base_indent):]
+- self.text.insert('insert', '\n'+line.rstrip())
++ self.wtext.insert('insert', '\n'+line.rstrip())
+ finally:
+- self.text.see("insert")
+- self.text.undo_block_stop()
++ self.wtext.see("insert")
++ self.wtext.undo_block_stop()
+
+- def runit(self):
+- line = self.text.get("iomark", "end-1c")
++ def _runit(self, text):
++ line = text.get("iomark", "end-1c")
+ # Strip off last newline and surrounding whitespace.
+ # (To allow you to hit return twice to end a statement.)
+ i = len(line)
+@@ -1179,18 +1216,18 @@
+ return self.interp.remote_stack_viewer()
+ try:
+ sys.last_traceback
+- except:
++ except AttributeError:
+ tkMessageBox.showerror("No stack trace",
+ "There is no stack trace yet.\n"
+ "(sys.last_traceback is not defined)",
+- master=self.text)
++ master=self.text_notebook)
+ return
+ from StackViewer import StackBrowser
+ sv = StackBrowser(self.root, self.flist)
+
+- def view_restart_mark(self, event=None):
+- self.text.see("iomark")
+- self.text.see("restart")
++ def _view_restart_mark(self, event=None, text=None):
++ text.see("iomark")
++ text.see("restart")
+
+ def restart_shell(self, event=None):
+ self.interp.restart_subprocess()
+@@ -1202,25 +1239,30 @@
+ except:
+ s = ""
+ self.console.write(s)
+- self.text.mark_set("insert", "end-1c")
++
++ curr_page = self.current_page
++ if not curr_page:
++ return
++
++ curr_page.text.mark_set("insert", "end-1c")
+ self.set_line_and_column()
+- self.io.reset_undo()
++ curr_page.io.reset_undo()
+
+ def resetoutput(self):
+- source = self.text.get("iomark", "end-1c")
+- if self.history:
+- self.history.history_store(source)
+- if self.text.get("end-2c") != "\n":
+- self.text.insert("end-1c", "\n")
+- self.text.mark_set("iomark", "end-1c")
++ source = self.wtext.get("iomark", "end-1c")
++ if self.textpage.history:
++ self.textpage.history.history_store(source)
++ if self.wtext.get("end-2c") != "\n":
++ self.wtext.insert("end-1c", "\n")
++ self.wtext.mark_set("iomark", "end-1c")
+ self.set_line_and_column()
+ sys.stdout.softspace = 0
+
+ def write(self, s, tags=()):
+ try:
+- self.text.mark_gravity("iomark", "right")
+- OutputWindow.write(self, s, tags, "iomark")
+- self.text.mark_gravity("iomark", "left")
++ self.wtext.mark_gravity("iomark", "right")
++ OutputWindow.write(self, s, tags, "iomark", self.wtext)
++ self.wtext.mark_gravity("iomark", "left")
+ except:
+ pass
+ if self.canceled:
+@@ -1381,6 +1423,19 @@
+ # start editor and/or shell windows:
+ root = Tk(className="Idle")
+
++ if TTK:
++ # create base styles used along idle files
++ style = Style()
++
++ rootbg = style.map('.', 'background')
++ fstyle = {'background': []}
++ for sspec in rootbg:
++ if 'active' in sspec[:-1]:
++ fstyle['background'].append(('!disabled', sspec[-1]))
++ break
++ style.map('RootColor.TFrame', **fstyle)
++ # end styles
++
+ fixwordbreaks(root)
+ root.withdraw()
+ flist = PyShellFileList(root)
+Index: ParenMatch.py
+===================================================================
+--- ParenMatch.py (revision 63995)
++++ ParenMatch.py (revision 65541)
+@@ -56,14 +56,13 @@
+ RESTORE_SEQUENCES = ("<KeyPress>", "<ButtonPress>",
+ "<Key-Return>", "<Key-BackSpace>")
+
+- def __init__(self, editwin):
+- self.editwin = editwin
+- self.text = editwin.text
++ def __init__(self, editpage):
++ self.editpage = editpage
++ self.text = editpage.text
+ # Bind the check-restore event to the function restore_event,
+ # so that we can then use activate_restore (which calls event_add)
+ # and deactivate_restore (which calls event_delete).
+- editwin.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME,
+- self.restore_event)
++ editpage.text.bind(self.RESTORE_VIRTUAL_EVENT_NAME, self.restore_event)
+ self.counter = 0
+ self.is_restore_active = 0
+ self.set_style(self.STYLE)
+@@ -90,7 +89,7 @@
+ self.set_timeout = self.set_timeout_none
+
+ def flash_paren_event(self, event):
+- indices = HyperParser(self.editwin, "insert").get_surrounding_brackets()
++ indices = HyperParser(self.editpage, "insert").get_surrounding_brackets()
+ if indices is None:
+ self.warn_mismatched()
+ return
+@@ -103,7 +102,7 @@
+ closer = self.text.get("insert-1c")
+ if closer not in _openers:
+ return
+- hp = HyperParser(self.editwin, "insert-1c")
++ hp = HyperParser(self.editpage, "insert-1c")
+ if not hp.is_in_code():
+ return
+ indices = hp.get_surrounding_brackets(_openers[closer], True)
+@@ -159,14 +158,13 @@
+ if index != self.text.index("insert"):
+ self.handle_restore_timer(c)
+ else:
+- self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
+- self.editwin.text_frame.after(CHECK_DELAY, callme, callme)
++ self.text.master.after(CHECK_DELAY, callme, callme)
++ self.text.master.after(CHECK_DELAY, callme, callme)
+
+ def set_timeout_last(self):
+ """The last highlight created will be removed after .5 sec"""
+ # associate a counter with an event; only disable the "paren"
+ # tag if the event is for the most recent timer.
+ self.counter += 1
+- self.editwin.text_frame.after(self.FLASH_DELAY,
+- lambda self=self, c=self.counter: \
+- self.handle_restore_timer(c))
++ self.text.master.after(self.FLASH_DELAY,
++ lambda self=self, c=self.counter: self.handle_restore_timer(c))
+Index: config-keys.def
+===================================================================
+--- config-keys.def (revision 63995)
++++ config-keys.def (revision 65541)
+@@ -8,6 +8,10 @@
+ # configuration gui.
+
+ [IDLE Classic Windows]
++close-tab=<Control-Key-w> <Control-Key-W>
++new-tab=<Control-Key-t> <Control-Key-T>
++next-tab=<Control-Key-Next>
++prev-tab=<Control-Key-Prior>
+ copy=<Control-Key-c> <Control-Key-C>
+ cut=<Control-Key-x> <Control-Key-X>
+ paste=<Control-Key-v> <Control-Key-V>
+@@ -59,6 +63,10 @@
+ del-word-right=<Control-Key-Delete>
+
+ [IDLE Classic Unix]
++close-tab=<Alt-Key-w><Control-Key-w>
++new-tab=<Alt-Key-t><Control-Key-t>
++next-tab=<Control-Key-Next>
++prev-tab=<Control-Key-Prior>
+ copy=<Alt-Key-w> <Meta-Key-w>
+ cut=<Control-Key-w>
+ paste=<Control-Key-y>
+@@ -110,6 +118,10 @@
+ del-word-right=<Alt-Key-d>
+
+ [IDLE Classic Mac]
++close-tab=<Control-key-w>
++new-tab=<Control-Key-t>
++next-tab=<Control-Key-Next>
++prev-tab=<Control-Key-Prior>
+ copy=<Command-Key-c>
+ cut=<Command-Key-x>
+ paste=<Command-Key-v>
+@@ -161,6 +173,10 @@
+ del-word-right=<Control-Key-Delete>
+
+ [IDLE Classic OSX]
++close-tab=<Control-Key-w>
++new-tab = <Control-Key-t>
++next-tab=<Control-Key-Next>
++prev-tab=<Control-Key-Prior>
+ toggle-tabs = <Control-Key-t>
+ interrupt-execution = <Control-Key-c>
+ untabify-region = <Control-Key-6>
+Index: Debugger.py
+===================================================================
+--- Debugger.py (revision 63995)
++++ Debugger.py (revision 65541)
+@@ -1,11 +1,17 @@
+ import os
+ import bdb
+ import types
+-from Tkinter import *
++from Tkinter import Frame, Button, Entry, Checkbutton, Label, Scrollbar, \
++ Canvas, BooleanVar
++from Tkconstants import W, LEFT, DISABLED, X, Y, BOTH, NW, GROOVE, RIGHT
++
++import macosxSupport
+ from WindowList import ListedToplevel
+ from ScrolledList import ScrolledList
+-import macosxSupport
++from configHandler import idleConf
+
++if idleConf.GetOption('main', 'General', 'use-ttk', type='int'):
++ from ttk import Frame, Button, Checkbutton, Scrollbar
+
+ class Idb(bdb.Bdb):
+
+@@ -92,7 +98,7 @@
+ self.top.bind("<Escape>", self.close)
+ #
+ self.bframe = bframe = Frame(top)
+- self.bframe.pack(anchor="w")
++ self.bframe.pack(anchor=W)
+ self.buttons = bl = []
+ #
+ self.bcont = b = Button(bframe, text="Go", command=self.cont)
+@@ -107,11 +113,11 @@
+ bl.append(b)
+ #
+ for b in bl:
+- b.configure(state="disabled")
+- b.pack(side="left")
++ b.configure(state=DISABLED)
++ b.pack(side=LEFT)
+ #
+ self.cframe = cframe = Frame(bframe)
+- self.cframe.pack(side="left")
++ self.cframe.pack(side=LEFT)
+ #
+ if not self.vstack:
+ self.__class__.vstack = BooleanVar(top)
+@@ -136,18 +142,18 @@
+ text="Globals", command=self.show_globals, variable=self.vglobals)
+ self.bglobals.grid(row=1, column=1)
+ #
+- self.status = Label(top, anchor="w")
+- self.status.pack(anchor="w")
+- self.error = Label(top, anchor="w")
+- self.error.pack(anchor="w", fill="x")
++ self.status = Label(top, anchor=W)
++ self.status.pack(anchor=W)
++ self.error = Label(top, anchor=W)
++ self.error.pack(anchor=W, fill=X)
+ self.errorbg = self.error.cget("background")
+ #
+ self.fstack = Frame(top, height=1)
+- self.fstack.pack(expand=1, fill="both")
++ self.fstack.pack(expand=1, fill=BOTH)
+ self.flocals = Frame(top)
+- self.flocals.pack(expand=1, fill="both")
++ self.flocals.pack(expand=1, fill=BOTH)
+ self.fglobals = Frame(top, height=1)
+- self.fglobals.pack(expand=1, fill="both")
++ self.fglobals.pack(expand=1, fill=BOTH)
+ #
+ if self.vstack.get():
+ self.show_stack()
+@@ -155,6 +161,7 @@
+ self.show_locals()
+ if self.vglobals.get():
+ self.show_globals()
++ #
+
+ def interaction(self, message, frame, info=None):
+ self.frame = frame
+@@ -313,12 +320,14 @@
+ "Load PyShellEditorWindow breakpoints into subprocess debugger"
+ pyshell_edit_windows = self.pyshell.flist.inversedict.keys()
+ for editwin in pyshell_edit_windows:
+- filename = editwin.io.filename
+- try:
+- for lineno in editwin.breakpoints:
+- self.set_breakpoint_here(filename, lineno)
+- except AttributeError:
+- continue
++ for page in editwin.text_notebook.pages.itervalues():
++ editpage = page.editpage
++ filename = editpage.io.filename
++ try:
++ for lineno in editpage.breakpoints:
++ self.set_breakpoint_here(filename, lineno)
++ except AttributeError:
++ continue
+
+ class StackViewer(ScrolledList):
+
+@@ -348,8 +357,7 @@
+ funcname = code.co_name
+ import linecache
+ sourceline = linecache.getline(filename, lineno)
+- import string
+- sourceline = string.strip(sourceline)
++ sourceline = sourceline.strip()
+ if funcname in ("?", "", None):
+ item = "%s, line %d: %s" % (modname, lineno, sourceline)
+ else:
+@@ -413,24 +421,25 @@
+ height = 20*len(dict) # XXX 20 == observed height of Entry widget
+ self.master = master
+ self.title = title
++
+ import repr
+ self.repr = repr.Repr()
+ self.repr.maxstring = 60
+ self.repr.maxother = 60
+ self.frame = frame = Frame(master)
+- self.frame.pack(expand=1, fill="both")
+- self.label = Label(frame, text=title, borderwidth=2, relief="groove")
+- self.label.pack(fill="x")
+- self.vbar = vbar = Scrollbar(frame, name="vbar")
+- vbar.pack(side="right", fill="y")
++ self.frame.pack(expand=1, fill=BOTH)
++ self.label = Label(frame, text=title, borderwidth=2, relief=GROOVE)
++ self.label.pack(fill=X)
++ vbar = Scrollbar(frame, name="vbar")
++ vbar.pack(side=RIGHT, fill=Y)
+ self.canvas = canvas = Canvas(frame,
+ height=min(300, max(40, height)),
+ scrollregion=(0, 0, width, height))
+- canvas.pack(side="left", fill="both", expand=1)
++ canvas.pack(side=LEFT, fill=BOTH, expand=1)
+ vbar["command"] = canvas.yview
+ canvas["yscrollcommand"] = vbar.set
+ self.subframe = subframe = Frame(canvas)
+- self.sfid = canvas.create_window(0, 0, window=subframe, anchor="nw")
++ self.sfid = canvas.create_window(0, 0, window=subframe, anchor=NW)
+ self.load_dict(dict)
+
+ dict = -1
+@@ -458,10 +467,10 @@
+ if rpc_client:
+ svalue = svalue[1:-1]
+ l = Label(subframe, text=name)
+- l.grid(row=row, column=0, sticky="nw")
++ l.grid(row=row, column=0, sticky=NW)
+ l = Entry(subframe, width=0, borderwidth=0)
+ l.insert(0, svalue)
+- l.grid(row=row, column=1, sticky="nw")
++ l.grid(row=row, column=1, sticky=NW)
+ row = row+1
+ self.dict = dict
+ # XXX Could we use a <Configure> callback for the following?
+Index: configDialog.py
+===================================================================
+--- configDialog.py (revision 63995)
++++ configDialog.py (revision 65541)
+@@ -7,19 +7,30 @@
+
+ Note that tab width in IDLE is currently fixed at eight due to Tk issues.
+ Refer to comments in EditorWindow autoindent code for details.
+-
+ """
+-from Tkinter import *
++from Tkinter import Toplevel, Frame, Button, Scale, Label, LabelFrame, Text, \
++ Listbox, Scrollbar, Checkbutton, Radiobutton, Entry, \
++ Checkbutton, StringVar, BooleanVar, IntVar
++from Tkconstants import LEFT, RIGHT, BOTTOM, TOP, BOTH, GROOVE, SOLID, NONE, \
++ END, DISABLED, NSEW, Y, X, W, E, HORIZONTAL, NS, EW, \
++ N, ANCHOR, NORMAL
+ import tkMessageBox, tkColorChooser, tkFont
+-import string
+
++from stylist import PoorManStyle
++from tabbedpages import get_tabbedpage
+ from configHandler import idleConf
++from keybindingDialog import GetKeysDialog
+ from dynOptionMenuWidget import DynOptionMenu
+-from tabbedpages import TabbedPageSet
+-from keybindingDialog import GetKeysDialog
++from configHelpSourceEdit import GetHelpSourceDialog
+ from configSectionNameDialog import GetCfgSectionNameDialog
+-from configHelpSourceEdit import GetHelpSourceDialog
+
++TabbedPageSet = get_tabbedpage()
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++if TTK:
++ from ttk import Frame, Button, Checkbutton, LabelFrame, LabeledScale, \
++ Combobox, Checkbutton, Entry, Radiobutton, Scrollbar, \
++ Label, Style
++
+ class ConfigDialog(Toplevel):
+
+ def __init__(self,parent,title):
+@@ -27,8 +38,8 @@
+ self.wm_withdraw()
+
+ self.configure(borderwidth=5)
+- self.geometry("+%d+%d" % (parent.winfo_rootx()+20,
+- parent.winfo_rooty()+30))
++ self.geometry("+%d+%d" % (parent.winfo_rootx() + 20,
++ parent.winfo_rooty() + 30))
+ #Theme Elements. Each theme element key is its display name.
+ #The first value of the tuple is the sample area tag name.
+ #The second value is the display name list sort index.
+@@ -47,8 +58,9 @@
+ 'Shell Stderr Text':('stderr','12'),
+ }
+ self.ResetChangedItems() #load initial values in changed items dict
++ self.SetupStyles()
+ self.CreateWidgets()
+- self.resizable(height=FALSE,width=FALSE)
++ self.resizable(height=False, width=False)
+ self.transient(parent)
+ self.grab_set()
+ self.protocol("WM_DELETE_WINDOW", self.Cancel)
+@@ -64,34 +76,54 @@
+ self.wm_deiconify()
+ self.wait_window()
+
++ def SetupStyles(self):
++ if TTK:
++ style = Style(self.master)
++ style.configure('S.TButton', padding=[6, 3])
++ style.configure('S2.TFrame', padding=2)
++ style.configure('Color.TFrame', background='blue')
++ self.ttkstyle = style
++ self.style = lambda w, style: w.configure(style=style)
++ else:
++ self.ttkstyle = PoorManStyle(self, styles={
++ 'S.TButton': {'pady': 6, 'padx': 3},
++ 'S2.TFrame': {'padx': 2, 'pady': 2}
++ }, cfgstyles={'Color.TFrame': 'frameColourSet'})
++ self.style = self.ttkstyle.style_it
++
+ def CreateWidgets(self):
+ self.tabPages = TabbedPageSet(self,
+- page_names=['Fonts/Tabs','Highlighting','Keys','General'])
+- frameActionButtons = Frame(self,pady=2)
++ page_names=['Fonts/Tabs','Highlighting','Keys','General'])
++ frameActionButtons = Frame(self)
+ #action buttons
+ self.buttonHelp = Button(frameActionButtons,text='Help',
+- command=self.Help,takefocus=FALSE,
+- padx=6,pady=3)
+- self.buttonOk = Button(frameActionButtons,text='Ok',
+- command=self.Ok,takefocus=FALSE,
+- padx=6,pady=3)
+- self.buttonApply = Button(frameActionButtons,text='Apply',
+- command=self.Apply,takefocus=FALSE,
+- padx=6,pady=3)
+- self.buttonCancel = Button(frameActionButtons,text='Cancel',
+- command=self.Cancel,takefocus=FALSE,
+- padx=6,pady=3)
++ command=self.Help, takefocus=False)
++ self.buttonOk = Button(frameActionButtons, text='Ok',
++ command=self.Ok, takefocus=False)
++ self.buttonApply = Button(frameActionButtons, text='Apply',
++ command=self.Apply, takefocus=False)
++ self.buttonCancel = Button(frameActionButtons, text='Cancel',
++ command=self.Cancel, takefocus=False)
++
++ # Apply styles
++ s = self.style
++ s(frameActionButtons, 'RootColor.TFrame')
++ s(self.buttonHelp, 'S.TButton')
++ s(self.buttonOk, 'S.TButton')
++ s(self.buttonApply, 'S.TButton')
++ s(self.buttonCancel, 'S.TButton')
++
+ self.CreatePageFontTab()
+ self.CreatePageHighlight()
+ self.CreatePageKeys()
+ self.CreatePageGeneral()
+- self.buttonHelp.pack(side=RIGHT,padx=5)
+- self.buttonOk.pack(side=LEFT,padx=5)
+- self.buttonApply.pack(side=LEFT,padx=5)
+- self.buttonCancel.pack(side=LEFT,padx=5)
+- frameActionButtons.pack(side=BOTTOM)
++ self.buttonHelp.pack(side=LEFT, pady=6)
++ self.buttonApply.pack(side=RIGHT, pady=6)
++ self.buttonOk.pack(side=RIGHT, padx=6, pady=6)
++ self.buttonCancel.pack(side=RIGHT, pady=6)
++ frameActionButtons.pack(side=BOTTOM, fill=X, expand=True)
+ Frame(self, height=2, borderwidth=0).pack(side=BOTTOM)
+- self.tabPages.pack(side=TOP,expand=TRUE,fill=BOTH)
++ self.tabPages.pack(side=TOP,expand=True,fill=BOTH)
+
+ def CreatePageFontTab(self):
+ #tkVars
+@@ -113,8 +145,8 @@
+ frameFontParam=Frame(frameFont)
+ labelFontNameTitle=Label(frameFontName,justify=LEFT,
+ text='Font Face :')
+- self.listFontName=Listbox(frameFontName,height=5,takefocus=FALSE,
+- exportselection=FALSE)
++ self.listFontName=Listbox(frameFontName,height=5,takefocus=False,
++ exportselection=False)
+ self.listFontName.bind('<ButtonRelease-1>',self.OnListFontButtonRelease)
+ scrollFont=Scrollbar(frameFontName)
+ scrollFont.config(command=self.listFontName.yview)
+@@ -127,122 +159,143 @@
+ frameFontSample=Frame(frameFont,relief=SOLID,borderwidth=1)
+ self.labelFontSample=Label(frameFontSample,
+ text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]',
+- justify=LEFT,font=self.editFont)
++ justify=LEFT, font=self.editFont)
+ #frameIndent
+ frameIndentSize=Frame(frameIndent)
+ labelSpaceNumTitle=Label(frameIndentSize, justify=LEFT,
+ text='Python Standard: 4 Spaces!')
+- self.scaleSpaceNum=Scale(frameIndentSize, variable=self.spaceNum,
+- orient='horizontal',
+- tickinterval=2, from_=2, to=16)
+ #widget packing
+ #body
+- frameFont.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
++ frameFont.pack(side=LEFT,padx=5,pady=5,expand=True,fill=BOTH)
+ frameIndent.pack(side=LEFT,padx=5,pady=5,fill=Y)
+ #frameFont
+ frameFontName.pack(side=TOP,padx=5,pady=5,fill=X)
+ frameFontParam.pack(side=TOP,padx=5,pady=5,fill=X)
+ labelFontNameTitle.pack(side=TOP,anchor=W)
+- self.listFontName.pack(side=LEFT,expand=TRUE,fill=X)
++ self.listFontName.pack(side=LEFT,expand=True,fill=X)
+ scrollFont.pack(side=LEFT,fill=Y)
+ labelFontSizeTitle.pack(side=LEFT,anchor=W)
+ self.optMenuFontSize.pack(side=LEFT,anchor=W)
+ checkFontBold.pack(side=LEFT,anchor=W,padx=20)
+- frameFontSample.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
+- self.labelFontSample.pack(expand=TRUE,fill=BOTH)
++ frameFontSample.pack(side=TOP,padx=5,pady=5,expand=True,fill=BOTH)
++ self.labelFontSample.pack(expand=1, fill=Y)
+ #frameIndent
+ frameIndentSize.pack(side=TOP,fill=X)
+- labelSpaceNumTitle.pack(side=TOP,anchor=W,padx=5)
+- self.scaleSpaceNum.pack(side=TOP,padx=5,fill=X)
++ labelSpaceNumTitle.pack(side=TOP, anchor=W, padx=5, pady=6)
++
++ if TTK:
++ self.scaleSpaceNum = LabeledScale(frameIndentSize, self.spaceNum,
++ from_=2, to=16, padding=2)
++ else:
++ self.scaleSpaceNum=Scale(frameIndentSize, variable=self.spaceNum,
++ orient='horizontal', from_=2, to=16, tickinterval=2)
++
++ self.scaleSpaceNum.pack(side=TOP, padx=5, fill=X)
++
+ return frame
+
+ def CreatePageHighlight(self):
+- self.builtinTheme=StringVar(self)
+- self.customTheme=StringVar(self)
+- self.fgHilite=BooleanVar(self)
+- self.colour=StringVar(self)
+- self.fontName=StringVar(self)
+- self.themeIsBuiltin=BooleanVar(self)
+- self.highlightTarget=StringVar(self)
++ self.builtinTheme = StringVar(self)
++ self.customTheme = StringVar(self)
++ self.fgHilite = BooleanVar(self)
++ self.colour = StringVar(self)
++ self.fontName = StringVar(self)
++ self.themeIsBuiltin = BooleanVar(self)
++ self.highlightTarget = StringVar(self)
++ #self.themeFontBold = BooleanVar(self)
+ ##widget creation
+ #body frame
+- frame=self.tabPages.pages['Highlighting'].frame
++ frame = self.tabPages.pages['Highlighting'].frame
+ #body section frames
+- frameCustom=LabelFrame(frame,borderwidth=2,relief=GROOVE,
+- text=' Custom Highlighting ')
+- frameTheme=LabelFrame(frame,borderwidth=2,relief=GROOVE,
+- text=' Highlighting Theme ')
++ frameCustom = LabelFrame(frame, borderwidth=2, relief=GROOVE,
++ text=' Custom Highlighting ')
++ frameTheme = LabelFrame(frame, borderwidth=2, relief=GROOVE,
++ text=' Highlighting Theme ')
+ #frameCustom
+- self.textHighlightSample=Text(frameCustom,relief=SOLID,borderwidth=1,
+- font=('courier',12,''),cursor='hand2',width=21,height=10,
+- takefocus=FALSE,highlightthickness=0,wrap=NONE)
+- text=self.textHighlightSample
+- text.bind('<Double-Button-1>',lambda e: 'break')
+- text.bind('<B1-Motion>',lambda e: 'break')
+- textAndTags=(('#you can click here','comment'),('\n','normal'),
+- ('#to choose items','comment'),('\n','normal'),('def','keyword'),
+- (' ','normal'),('func','definition'),('(param):','normal'),
+- ('\n ','normal'),('"""string"""','string'),('\n var0 = ','normal'),
+- ("'string'",'string'),('\n var1 = ','normal'),("'selected'",'hilite'),
+- ('\n var2 = ','normal'),("'found'",'hit'),
+- ('\n var3 = ','normal'),('list', 'builtin'), ('(','normal'),
+- ('None', 'builtin'),(')\n\n','normal'),
+- (' error ','error'),(' ','normal'),('cursor |','cursor'),
+- ('\n ','normal'),('shell','console'),(' ','normal'),('stdout','stdout'),
+- (' ','normal'),('stderr','stderr'),('\n','normal'))
++ self.textHighlightSample = Text(frameCustom, relief=SOLID,
++ borderwidth=1, font=('courier', 12, ''), cursor='hand2', width=21,
++ height=11, takefocus=False, highlightthickness=0, wrap=NONE)
++ text = self.textHighlightSample
++ text.bind('<Double-Button-1>', lambda e: 'break')
++ text.bind('<B1-Motion>', lambda e: 'break')
++ textAndTags = (
++ ('#you can click here','comment'), ('\n','normal'),
++ ('#to choose items', 'comment'), ('\n', 'normal'),
++ ('def', 'keyword'), (' ', 'normal'), ('func', 'definition'),
++ ('(param):', 'normal'), ('\n ', 'normal'),
++ ('"""string"""', 'string'),
++ ('\n var0 = ','normal'), ("'string'", 'string'),
++ ('\n var1 = ', 'normal'), ("'selected'", 'hilite'),
++ ('\n var2 = ', 'normal'), ("'found'", 'hit'),
++ ('\n var3 = ', 'normal'), ('list', 'builtin'), ('(', 'normal'),
++ ('None', 'builtin'), (')\n\n', 'normal'),
++ (' error ', 'error'), (' ', 'normal'), ('cursor |', 'cursor'),
++ ('\n ', 'normal'), ('shell', 'console'), (' ', 'normal'),
++ ('stdout', 'stdout'), (' ', 'normal'), ('stderr', 'stderr'),
++ ('\n', 'normal')
++ )
+ for txTa in textAndTags:
+- text.insert(END,txTa[0],txTa[1])
++ text.insert(END, txTa[0], txTa[1])
+ for element in self.themeElements.keys():
+- text.tag_bind(self.themeElements[element][0],'<ButtonPress-1>',
+- lambda event,elem=element: event.widget.winfo_toplevel()
+- .highlightTarget.set(elem))
++ text.tag_bind(self.themeElements[element][0], '<ButtonPress-1>',
++ lambda event, elem=element:
++ event.widget.winfo_toplevel().highlightTarget.set(elem))
+ text.config(state=DISABLED)
+- self.frameColourSet=Frame(frameCustom,relief=SOLID,borderwidth=1)
+- frameFgBg=Frame(frameCustom)
+- buttonSetColour=Button(self.frameColourSet,text='Choose Colour for :',
+- command=self.GetColour,highlightthickness=0)
+- self.optMenuHighlightTarget=DynOptionMenu(self.frameColourSet,
+- self.highlightTarget,None,highlightthickness=0)#,command=self.SetHighlightTargetBinding
+- self.radioFg=Radiobutton(frameFgBg,variable=self.fgHilite,
+- value=1,text='Foreground',command=self.SetColourSampleBinding)
+- self.radioBg=Radiobutton(frameFgBg,variable=self.fgHilite,
+- value=0,text='Background',command=self.SetColourSampleBinding)
++
++ self.frameColourSet = Frame(frameCustom, relief=SOLID, borderwidth=1)
++ self.style(self.frameColourSet, 'Color.TFrame')
++
++ frameFgBg = Frame(frameCustom)
++ buttonSetColour = Button(self.frameColourSet,
++ text='Choose Colour for :', command=self.GetColour)
++ self.optMenuHighlightTarget = DynOptionMenu(self.frameColourSet,
++ self.highlightTarget, None)
++ #self.optBoldText = Checkbutton(self.frameColourSet, text="Bold",
++ # variable=self.themeFontBold)
++ self.radioFg = Radiobutton(frameFgBg, variable=self.fgHilite,
++ value=1, text='Foreground', command=self.SetColourSampleBinding)
++ self.radioBg = Radiobutton(frameFgBg, variable=self.fgHilite,
++ value=0, text='Background', command=self.SetColourSampleBinding)
+ self.fgHilite.set(1)
+- buttonSaveCustomTheme=Button(frameCustom,
+- text='Save as New Custom Theme',command=self.SaveAsNewTheme)
++ buttonSaveCustomTheme = Button(frameCustom,
++ text='Save as New Custom Theme', command=self.SaveAsNewTheme)
+ #frameTheme
+- labelTypeTitle=Label(frameTheme,text='Select : ')
+- self.radioThemeBuiltin=Radiobutton(frameTheme,variable=self.themeIsBuiltin,
+- value=1,command=self.SetThemeType,text='a Built-in Theme')
+- self.radioThemeCustom=Radiobutton(frameTheme,variable=self.themeIsBuiltin,
+- value=0,command=self.SetThemeType,text='a Custom Theme')
+- self.optMenuThemeBuiltin=DynOptionMenu(frameTheme,
+- self.builtinTheme,None,command=None)
+- self.optMenuThemeCustom=DynOptionMenu(frameTheme,
+- self.customTheme,None,command=None)
+- self.buttonDeleteCustomTheme=Button(frameTheme,text='Delete Custom Theme',
+- command=self.DeleteCustomTheme)
++ labelTypeTitle = Label(frameTheme, text='Select : ')
++ self.radioThemeBuiltin = Radiobutton(frameTheme,
++ variable=self.themeIsBuiltin, value=1,
++ command=self.SetThemeType, text='a Built-in Theme')
++ self.radioThemeCustom = Radiobutton(frameTheme,
++ variable=self.themeIsBuiltin, value=0,
++ command=self.SetThemeType, text='a Custom Theme')
++ self.optMenuThemeBuiltin = DynOptionMenu(frameTheme,
++ self.builtinTheme, None, command=None)
++ self.optMenuThemeCustom = DynOptionMenu(frameTheme,
++ self.customTheme, None, command=None)
++ self.buttonDeleteCustomTheme = Button(frameTheme,
++ text='Delete Custom Theme', command=self.DeleteCustomTheme)
+ ##widget packing
+ #body
+- frameCustom.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
+- frameTheme.pack(side=LEFT,padx=5,pady=5,fill=Y)
++ frameCustom.pack(side=LEFT, padx=5, pady=5, expand=True, fill=BOTH)
++ frameTheme.pack(side=LEFT, padx=5, pady=5, fill=Y)
+ #frameCustom
+- self.frameColourSet.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=X)
+- frameFgBg.pack(side=TOP,padx=5,pady=0)
+- self.textHighlightSample.pack(side=TOP,padx=5,pady=5,expand=TRUE,
++ self.frameColourSet.pack(side=TOP, padx=5, pady=5, expand=True, fill=X)
++ frameFgBg.pack(side=TOP, padx=5, pady=0)
++ self.textHighlightSample.pack(side=TOP, padx=5, pady=5, expand=True,
+ fill=BOTH)
+- buttonSetColour.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=4)
+- self.optMenuHighlightTarget.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=3)
+- self.radioFg.pack(side=LEFT,anchor=E)
+- self.radioBg.pack(side=RIGHT,anchor=W)
+- buttonSaveCustomTheme.pack(side=BOTTOM,fill=X,padx=5,pady=5)
++ buttonSetColour.pack(side=TOP, expand=True, fill=X, padx=8, pady=4)
++ self.optMenuHighlightTarget.pack(side=LEFT, anchor=N, expand=True,
++ fill=X, padx=8, pady=3)
++ #self.optBoldText.pack(side=RIGHT, anchor=N, padx=8, pady=3)
++ self.radioFg.pack(side=LEFT, anchor=E)
++ self.radioBg.pack(side=RIGHT, anchor=W)
++ buttonSaveCustomTheme.pack(side=BOTTOM, fill=X, padx=5, pady=5)
+ #frameTheme
+- labelTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
+- self.radioThemeBuiltin.pack(side=TOP,anchor=W,padx=5)
+- self.radioThemeCustom.pack(side=TOP,anchor=W,padx=5,pady=2)
+- self.optMenuThemeBuiltin.pack(side=TOP,fill=X,padx=5,pady=5)
+- self.optMenuThemeCustom.pack(side=TOP,fill=X,anchor=W,padx=5,pady=5)
+- self.buttonDeleteCustomTheme.pack(side=TOP,fill=X,padx=5,pady=5)
++ labelTypeTitle.pack(side=TOP, anchor=W, padx=5, pady=5)
++ self.radioThemeBuiltin.pack(side=TOP, anchor=W, padx=5)
++ self.radioThemeCustom.pack(side=TOP, anchor=W, padx=5, pady=2)
++ self.optMenuThemeBuiltin.pack(side=TOP, fill=X, padx=5, pady=5)
++ self.optMenuThemeCustom.pack(side=TOP, fill=X, anchor=W, padx=5, pady=5)
++ self.buttonDeleteCustomTheme.pack(side=TOP, fill=X, padx=5, pady=5)
+ return frame
+
+ def CreatePageKeys(self):
+@@ -265,8 +318,8 @@
+ labelTargetTitle=Label(frameTarget,text='Action - Key(s)')
+ scrollTargetY=Scrollbar(frameTarget)
+ scrollTargetX=Scrollbar(frameTarget,orient=HORIZONTAL)
+- self.listBindings=Listbox(frameTarget,takefocus=FALSE,
+- exportselection=FALSE)
++ self.listBindings=Listbox(frameTarget,takefocus=False,
++ exportselection=False)
+ self.listBindings.bind('<ButtonRelease-1>',self.KeyBindingSelected)
+ scrollTargetY.config(command=self.listBindings.yview)
+ scrollTargetX.config(command=self.listBindings.xview)
+@@ -275,8 +328,11 @@
+ self.buttonNewKeys=Button(frameCustom,text='Get New Keys for Selection',
+ command=self.GetNewKeys,state=DISABLED)
+ #frameKeySets
+- frames = [Frame(frameKeySets, padx=2, pady=2, borderwidth=0)
+- for i in range(2)]
++ frames = []
++ for i in range(2):
++ f = Frame(frameKeySets, borderwidth=0)
++ self.style(f, 'S2.TFrame')
++ frames.append(f)
+ self.radioKeysBuiltin=Radiobutton(frames[0],variable=self.keysAreBuiltin,
+ value=1,command=self.SetKeysType,text='Use a Built-in Key Set')
+ self.radioKeysCustom=Radiobutton(frames[0],variable=self.keysAreBuiltin,
+@@ -291,11 +347,11 @@
+ text='Save as New Custom Key Set',command=self.SaveAsNewKeySet)
+ ##widget packing
+ #body
+- frameCustom.pack(side=BOTTOM,padx=5,pady=5,expand=TRUE,fill=BOTH)
++ frameCustom.pack(side=BOTTOM,padx=5,pady=5,expand=True,fill=BOTH)
+ frameKeySets.pack(side=BOTTOM,padx=5,pady=5,fill=BOTH)
+ #frameCustom
+ self.buttonNewKeys.pack(side=BOTTOM,fill=X,padx=5,pady=5)
+- frameTarget.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
++ frameTarget.pack(side=LEFT,padx=5,pady=5,expand=True,fill=BOTH)
+ #frame target
+ frameTarget.columnconfigure(0,weight=1)
+ frameTarget.rowconfigure(1,weight=1)
+@@ -316,14 +372,16 @@
+
+ def CreatePageGeneral(self):
+ #tkVars
+- self.winWidth=StringVar(self)
+- self.winHeight=StringVar(self)
+- self.paraWidth=StringVar(self)
+- self.startupEdit=IntVar(self)
+- self.autoSave=IntVar(self)
+- self.encoding=StringVar(self)
+- self.userHelpBrowser=BooleanVar(self)
+- self.helpBrowser=StringVar(self)
++ self.winWidth = StringVar(self)
++ self.winHeight = StringVar(self)
++ self.paraWidth = StringVar(self)
++ self.startupEdit = IntVar(self)
++ self.autoSave = IntVar(self)
++ self.encoding = StringVar(self)
++ self.themename = StringVar(self)
++ self.fileintab = BooleanVar(self)
++ self.userHelpBrowser = BooleanVar(self)
++ self.helpBrowser = StringVar(self)
+ #widget creation
+ #body
+ frame=self.tabPages.pages['General'].frame
+@@ -335,6 +393,9 @@
+ frameWinSize=Frame(frame,borderwidth=2,relief=GROOVE)
+ frameParaSize=Frame(frame,borderwidth=2,relief=GROOVE)
+ frameEncoding=Frame(frame,borderwidth=2,relief=GROOVE)
++ frameModTab = Frame(frame, borderwidth=2, relief=GROOVE)
++ if TTK:
++ frameTheme = Frame(frame, borderwidth=2, relief=GROOVE)
+ frameHelp=LabelFrame(frame,borderwidth=2,relief=GROOVE,
+ text=' Additional Help Sources ')
+ #frameRun
+@@ -371,12 +432,21 @@
+ value="utf-8",text="UTF-8")
+ radioEncNone=Radiobutton(frameEncoding,variable=self.encoding,
+ value="none",text="None")
++ # frameModTab
++ labelMTab = Label(frameModTab, text="Open files and modules in tabs")
++ checkMTab = Checkbutton(frameModTab, variable=self.fileintab,
++ text="Yes")
++ #frameTheme
++ if TTK:
++ labelTheme = Label(frameTheme, text="Display Theme")
++ comboTheme = Combobox(frameTheme, textvariable=self.themename,
++ values=self.ttkstyle.theme_names(), state='readonly')
+ #frameHelp
+ frameHelpList=Frame(frameHelp)
+ frameHelpListButtons=Frame(frameHelpList)
+ scrollHelpList=Scrollbar(frameHelpList)
+- self.listHelp=Listbox(frameHelpList,height=5,takefocus=FALSE,
+- exportselection=FALSE)
++ self.listHelp=Listbox(frameHelpList, height=4, takefocus=False,
++ exportselection=False)
+ scrollHelpList.config(command=self.listHelp.yview)
+ self.listHelp.config(yscrollcommand=scrollHelpList.set)
+ self.listHelp.bind('<ButtonRelease-1>',self.HelpSourceSelected)
+@@ -393,7 +463,10 @@
+ frameWinSize.pack(side=TOP,padx=5,pady=5,fill=X)
+ frameParaSize.pack(side=TOP,padx=5,pady=5,fill=X)
+ frameEncoding.pack(side=TOP,padx=5,pady=5,fill=X)
+- frameHelp.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
++ frameModTab.pack(side=TOP, padx=5, pady=5, fill=X)
++ if TTK:
++ frameTheme.pack(side=TOP, padx=5, pady=5, fill=X)
++ frameHelp.pack(side=TOP,padx=5,pady=5,expand=True,fill=BOTH)
+ #frameRun
+ labelRunChoiceTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
+ radioStartupShell.pack(side=RIGHT,anchor=W,padx=5,pady=5)
+@@ -416,14 +489,22 @@
+ radioEncNone.pack(side=RIGHT,anchor=E,pady=5)
+ radioEncUTF8.pack(side=RIGHT,anchor=E,pady=5)
+ radioEncLocale.pack(side=RIGHT,anchor=E,pady=5)
++ #frameModTab
++ labelMTab.pack(side=LEFT, anchor=W, padx=5, pady=5)
++ checkMTab.pack(side=RIGHT, anchor=E, padx=5, pady=5)
++ #frameTheme
++ if TTK:
++ labelTheme.pack(side=LEFT, anchor=W, padx=5, pady=5)
++ comboTheme.pack(side=RIGHT, anchor=E, padx=5, pady=5)
+ #frameHelp
+ frameHelpListButtons.pack(side=RIGHT,padx=5,pady=5,fill=Y)
+- frameHelpList.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
++ frameHelpList.pack(side=TOP,padx=5,pady=5,expand=True,fill=BOTH)
+ scrollHelpList.pack(side=RIGHT,anchor=W,fill=Y)
+- self.listHelp.pack(side=LEFT,anchor=E,expand=TRUE,fill=BOTH)
++ self.listHelp.pack(side=LEFT,anchor=E,expand=True,fill=BOTH)
+ self.buttonHelpListEdit.pack(side=TOP,anchor=W,pady=5)
+ self.buttonHelpListAdd.pack(side=TOP,anchor=W)
+ self.buttonHelpListRemove.pack(side=TOP,anchor=W,pady=5)
++
+ return frame
+
+ def AttachVarCallbacks(self):
+@@ -432,6 +513,7 @@
+ self.fontBold.trace_variable('w',self.VarChanged_fontBold)
+ self.spaceNum.trace_variable('w',self.VarChanged_spaceNum)
+ self.colour.trace_variable('w',self.VarChanged_colour)
++ #self.themeFontBold.trace_variable('w', self.VarChanged_themeFontBold)
+ self.builtinTheme.trace_variable('w',self.VarChanged_builtinTheme)
+ self.customTheme.trace_variable('w',self.VarChanged_customTheme)
+ self.themeIsBuiltin.trace_variable('w',self.VarChanged_themeIsBuiltin)
+@@ -446,18 +528,20 @@
+ self.startupEdit.trace_variable('w',self.VarChanged_startupEdit)
+ self.autoSave.trace_variable('w',self.VarChanged_autoSave)
+ self.encoding.trace_variable('w',self.VarChanged_encoding)
++ self.themename.trace_variable('w', self.VarChanged_themename)
++ self.fileintab.trace_variable('w', self.VarChanged_fileintab)
+
+ def VarChanged_fontSize(self,*params):
+ value=self.fontSize.get()
+- self.AddChangedItem('main','EditorWindow','font-size',value)
++ self.AddChangedItem('main','EditorPage','font-size',value)
+
+ def VarChanged_fontName(self,*params):
+ value=self.fontName.get()
+- self.AddChangedItem('main','EditorWindow','font',value)
++ self.AddChangedItem('main','EditorPage','font',value)
+
+ def VarChanged_fontBold(self,*params):
+ value=self.fontBold.get()
+- self.AddChangedItem('main','EditorWindow','font-bold',value)
++ self.AddChangedItem('main','EditorPage','font-bold',value)
+
+ def VarChanged_spaceNum(self,*params):
+ value=self.spaceNum.get()
+@@ -466,6 +550,9 @@
+ def VarChanged_colour(self,*params):
+ self.OnNewColourSet()
+
++ #def VarChanged_themeFontBold(self, *params):
++ # self.OnBoldChanged()
++
+ def VarChanged_builtinTheme(self,*params):
+ value=self.builtinTheme.get()
+ self.AddChangedItem('main','Theme','name',value)
+@@ -521,11 +608,11 @@
+
+ def VarChanged_winWidth(self,*params):
+ value=self.winWidth.get()
+- self.AddChangedItem('main','EditorWindow','width',value)
++ self.AddChangedItem('main', 'EditorWindow', 'width', value)
+
+ def VarChanged_winHeight(self,*params):
+ value=self.winHeight.get()
+- self.AddChangedItem('main','EditorWindow','height',value)
++ self.AddChangedItem('main', 'EditorWindow', 'height', value)
+
+ def VarChanged_paraWidth(self,*params):
+ value=self.paraWidth.get()
+@@ -541,8 +628,16 @@
+
+ def VarChanged_encoding(self,*params):
+ value=self.encoding.get()
+- self.AddChangedItem('main','EditorWindow','encoding',value)
++ self.AddChangedItem('main','EditorPage','encoding',value)
+
++ def VarChanged_themename(self, *params):
++ value = self.themename.get()
++ self.AddChangedItem('main', 'Theme', 'displaytheme', value)
++
++ def VarChanged_fileintab(self, *params):
++ value = self.fileintab.get()
++ self.AddChangedItem('main', 'EditorWindow', 'file-in-tab', value)
++
+ def ResetChangedItems(self):
+ #When any config item is changed in this dialog, an entry
+ #should be made in the relevant section (config type) of this
+@@ -552,10 +647,10 @@
+ self.changedItems={'main':{},'highlight':{},'keys':{},'extensions':{}}
+
+ def AddChangedItem(self,type,section,item,value):
+- value=str(value) #make sure we use a string
++ value = str(value) #make sure we use a string
+ if not self.changedItems[type].has_key(section):
+- self.changedItems[type][section]={}
+- self.changedItems[type][section][item]=value
++ self.changedItems[type][section] = {}
++ self.changedItems[type][section][item] = value
+
+ def GetDefaultItems(self):
+ dItems={'main':{},'highlight':{},'keys':{},'extensions':{}}
+@@ -653,7 +748,7 @@
+ newKeys={}
+ for event in prevKeys.keys(): #add key set to changed items
+ eventName=event[2:-2] #trim off the angle brackets
+- binding=string.join(prevKeys[event])
++ binding=' '.join(prevKeys[event])
+ newKeys[eventName]=binding
+ #handle any unsaved changes to prev key set
+ if prevKeySetName in self.changedItems['keys'].keys():
+@@ -680,7 +775,7 @@
+ bindNames.sort()
+ self.listBindings.delete(0,END)
+ for bindName in bindNames:
+- key=string.join(keySet[bindName]) #make key(s) into a string
++ key=' '.join(keySet[bindName]) #make key(s) into a string
+ bindName=bindName[2:-2] #trim off the angle brackets
+ if keySetName in self.changedItems['keys'].keys():
+ #handle any unsaved changes to this key set
+@@ -694,9 +789,9 @@
+
+ def DeleteCustomKeys(self):
+ keySetName=self.customKeys.get()
+- if not tkMessageBox.askyesno('Delete Key Set','Are you sure you wish '+
+- 'to delete the key set %r ?' % (keySetName),
+- parent=self):
++ if not tkMessageBox.askyesno("Delete Key Set",
++ "Are you sure you wish to delete the key set %r ?" % keySetName,
++ parent=self):
+ return
+ #remove key set from config
+ idleConf.userCfg['keys'].remove_section(keySetName)
+@@ -705,25 +800,26 @@
+ #write changes
+ idleConf.userCfg['keys'].Save()
+ #reload user key set list
+- itemList=idleConf.GetSectionList('user','keys')
++ itemList = idleConf.GetSectionList('user', 'keys')
+ itemList.sort()
+ if not itemList:
+ self.radioKeysCustom.config(state=DISABLED)
+- self.optMenuKeysCustom.SetMenu(itemList,'- no custom keys -')
++ self.optMenuKeysCustom.SetMenu(itemList, "- no custom keys -")
+ else:
+ self.optMenuKeysCustom.SetMenu(itemList,itemList[0])
+ #revert to default key set
+- self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys','default'))
+- self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys','name'))
++ self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys',
++ 'default'))
++ self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys', 'name'))
+ #user can't back out of these changes, they must be applied now
+ self.Apply()
+ self.SetKeysType()
+
+ def DeleteCustomTheme(self):
+- themeName=self.customTheme.get()
+- if not tkMessageBox.askyesno('Delete Theme','Are you sure you wish '+
+- 'to delete the theme %r ?' % (themeName,),
+- parent=self):
++ themeName = self.customTheme.get()
++ if not tkMessageBox.askyesno("Delete Theme",
++ "Are you sure you wish to delete the theme %r ?" % themeName,
++ parent=self):
+ return
+ #remove theme from config
+ idleConf.userCfg['highlight'].remove_section(themeName)
+@@ -732,30 +828,31 @@
+ #write changes
+ idleConf.userCfg['highlight'].Save()
+ #reload user theme list
+- itemList=idleConf.GetSectionList('user','highlight')
++ itemList = idleConf.GetSectionList('user', 'highlight')
+ itemList.sort()
+ if not itemList:
+ self.radioThemeCustom.config(state=DISABLED)
+- self.optMenuThemeCustom.SetMenu(itemList,'- no custom themes -')
++ self.optMenuThemeCustom.SetMenu(itemList, "- no custom themes -")
+ else:
+- self.optMenuThemeCustom.SetMenu(itemList,itemList[0])
++ self.optMenuThemeCustom.SetMenu(itemList, itemList[0])
+ #revert to default theme
+- self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme','default'))
+- self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme','name'))
++ self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme',
++ 'default'))
++ self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme', 'name'))
+ #user can't back out of these changes, they must be applied now
+ self.Apply()
+ self.SetThemeType()
+
+ def GetColour(self):
+ target=self.highlightTarget.get()
+- prevColour=self.frameColourSet.cget('bg')
++ prevColour = self.ttkstyle.configure('Color.TFrame', 'background')
+ rgbTuplet, colourString = tkColorChooser.askcolor(parent=self,
+ title='Pick new colour for : '+target,initialcolor=prevColour)
+ if colourString and (colourString!=prevColour):
+ #user didn't cancel, and they chose a new colour
+ if self.themeIsBuiltin.get(): #current theme is a built-in
+- message=('Your changes will be saved as a new Custom Theme. '+
+- 'Enter a name for your new Custom Theme below.')
++ message=("Your changes will be saved as a new Custom Theme. "
++ "Enter a name for your new Custom Theme below.")
+ newTheme=self.GetNewThemeName(message)
+ if not newTheme: #user cancelled custom theme creation
+ return
+@@ -767,16 +864,33 @@
+
+ def OnNewColourSet(self):
+ newColour=self.colour.get()
+- self.frameColourSet.config(bg=newColour)#set sample
++ self.ttkstyle.configure('Color.TFrame', background=newColour)
+ if self.fgHilite.get(): plane='foreground'
+ else: plane='background'
+ sampleElement=self.themeElements[self.highlightTarget.get()][0]
+- self.textHighlightSample.tag_config(sampleElement, **{plane:newColour})
++ self.textHighlightSample.tag_config(sampleElement, **{plane: newColour})
+ theme=self.customTheme.get()
+ themeElement=sampleElement+'-'+plane
+ self.AddChangedItem('highlight',theme,themeElement,newColour)
+
++ #def OnBoldChanged(self):
++ # bold = self.themeFontBold.get() and tkFont.BOLD or tkFont.NORMAL
++ # sampleElement = self.themeElements[self.highlightTarget.get()][0]
++
++ # #fontName = self.fontName.get()
++ # #self.textHighlightSample.tag_config(sampleElement,
++ # # font=(fontName, self.fontSize.get(), bold))
++
++ # self.textHighlightSample.tag_config(sampleElement,
++ # font=('courier', 12, bold))
++
++ # theme = self.customTheme.get()
++ # themeElement = "%s-%s" % (sampleElement, 'bold')
++ # self.AddChangedItem('highlight', theme, themeElement,
++ # (bold == tkFont.BOLD) and 1 or 0)
++
+ def GetNewThemeName(self,message):
++ # XXX idle bug here
+ usedNames=(idleConf.GetSectionList('user','highlight')+
+ idleConf.GetSectionList('default','highlight'))
+ newTheme=GetCfgSectionNameDialog(self,'New Custom Theme',
+@@ -835,37 +949,45 @@
+ self.radioFg.config(state=NORMAL)
+ self.radioBg.config(state=NORMAL)
+ self.fgHilite.set(1)
++ #tag = self.themeElements[self.highlightTarget.get()][0]
++ #font = self.textHighlightSample.tag_cget(tag, 'font')
++ #if font:
++ # self.themeFontBold.set(font.split()[2] == tkFont.BOLD)
++ #else:
++ # self.themeFontBold.set(0)
+ self.SetColourSample()
+
+ def SetColourSampleBinding(self,*args):
+ self.SetColourSample()
+
+ def SetColourSample(self):
+- #set the colour smaple area
+- tag=self.themeElements[self.highlightTarget.get()][0]
+- if self.fgHilite.get(): plane='foreground'
+- else: plane='background'
+- colour=self.textHighlightSample.tag_cget(tag,plane)
+- self.frameColourSet.config(bg=colour)
++ # set the colour sample area
++ tag = self.themeElements[self.highlightTarget.get()][0]
++ if self.fgHilite.get():
++ plane = 'foreground'
++ else:
++ plane = 'background'
++ colour = self.textHighlightSample.tag_cget(tag, plane)
++ self.ttkstyle.configure('Color.TFrame', background=colour)
+
+ def PaintThemeSample(self):
+ if self.themeIsBuiltin.get(): #a default theme
+- theme=self.builtinTheme.get()
++ theme = self.builtinTheme.get()
+ else: #a user theme
+- theme=self.customTheme.get()
++ theme = self.customTheme.get()
+ for elementTitle in self.themeElements.keys():
+- element=self.themeElements[elementTitle][0]
+- colours=idleConf.GetHighlight(theme,element)
+- if element=='cursor': #cursor sample needs special painting
+- colours['background']=idleConf.GetHighlight(theme,
+- 'normal', fgBg='bg')
++ element = self.themeElements[elementTitle][0]
++ colours = idleConf.GetHighlight(theme, element)
++ if element == 'cursor': #cursor sample needs special painting
++ colours['background'] = idleConf.GetHighlight(theme,
++ 'normal', fgBg='bg')
+ #handle any unsaved changes to this theme
+ if theme in self.changedItems['highlight'].keys():
+- themeDict=self.changedItems['highlight'][theme]
+- if themeDict.has_key(element+'-foreground'):
+- colours['foreground']=themeDict[element+'-foreground']
+- if themeDict.has_key(element+'-background'):
+- colours['background']=themeDict[element+'-background']
++ themeDict = self.changedItems['highlight'][theme]
++ if themeDict.has_key(element + '-foreground'):
++ colours['foreground'] = themeDict[element + '-foreground']
++ if themeDict.has_key(element + '-background'):
++ colours['background'] = themeDict[element + '-background']
+ self.textHighlightSample.tag_config(element, **colours)
+ self.SetColourSample()
+
+@@ -917,7 +1039,7 @@
+ self.changedItems['main']['HelpFiles'] = {}
+ for num in range(1,len(self.userHelpList)+1):
+ self.AddChangedItem('main','HelpFiles',str(num),
+- string.join(self.userHelpList[num-1][:2],';'))
++ ';'.join(self.userHelpList[num - 1][:2]))
+
+ def LoadFontCfg(self):
+ ##base editor font selection list
+@@ -925,7 +1047,7 @@
+ fonts.sort()
+ for font in fonts:
+ self.listFontName.insert(END,font)
+- configuredFont=idleConf.GetOption('main','EditorWindow','font',
++ configuredFont=idleConf.GetOption('main','EditorPage','font',
+ default='courier')
+ lc_configuredFont = configuredFont.lower()
+ self.fontName.set(lc_configuredFont)
+@@ -936,13 +1058,13 @@
+ self.listFontName.select_set(currentFontIndex)
+ self.listFontName.select_anchor(currentFontIndex)
+ ##font size dropdown
+- fontSize=idleConf.GetOption('main','EditorWindow','font-size',
++ fontSize=idleConf.GetOption('main', 'EditorPage', 'font-size',
+ default='10')
+ self.optMenuFontSize.SetMenu(('7','8','9','10','11','12','13','14',
+ '16','18','20','22'),fontSize )
+ ##fontWeight
+- self.fontBold.set(idleConf.GetOption('main','EditorWindow',
+- 'font-bold',default=0,type='bool'))
++ self.fontBold.set(idleConf.GetOption('main', 'EditorPage',
++ 'font-bold', default=0, type='bool'))
+ ##font sample
+ self.SetFontSample()
+
+@@ -954,37 +1076,41 @@
+
+ def LoadThemeCfg(self):
+ ##current theme type radiobutton
+- self.themeIsBuiltin.set(idleConf.GetOption('main','Theme','default',
+- type='bool',default=1))
++ self.themeIsBuiltin.set(idleConf.GetOption('main', 'Theme', 'default',
++ type='bool', default=1))
+ ##currently set theme
+- currentOption=idleConf.CurrentTheme()
++ currentOption = idleConf.CurrentTheme()
+ ##load available theme option menus
+ if self.themeIsBuiltin.get(): #default theme selected
+- itemList=idleConf.GetSectionList('default','highlight')
++ itemList = idleConf.GetSectionList('default', 'highlight')
+ itemList.sort()
+- self.optMenuThemeBuiltin.SetMenu(itemList,currentOption)
+- itemList=idleConf.GetSectionList('user','highlight')
++ self.optMenuThemeBuiltin.SetMenu(itemList, currentOption)
++ itemList = idleConf.GetSectionList('user', 'highlight')
+ itemList.sort()
+ if not itemList:
+ self.radioThemeCustom.config(state=DISABLED)
+- self.customTheme.set('- no custom themes -')
++ self.customTheme.set("- no custom themes -")
+ else:
+ self.optMenuThemeCustom.SetMenu(itemList,itemList[0])
+ else: #user theme selected
+- itemList=idleConf.GetSectionList('user','highlight')
++ itemList = idleConf.GetSectionList('user', 'highlight')
+ itemList.sort()
+- self.optMenuThemeCustom.SetMenu(itemList,currentOption)
+- itemList=idleConf.GetSectionList('default','highlight')
++ self.optMenuThemeCustom.SetMenu(itemList, currentOption)
++ itemList = idleConf.GetSectionList('default', 'highlight')
+ itemList.sort()
+- self.optMenuThemeBuiltin.SetMenu(itemList,itemList[0])
++ self.optMenuThemeBuiltin.SetMenu(itemList, itemList[0])
+ self.SetThemeType()
+ ##load theme element option menu
+- themeNames=self.themeElements.keys()
++ themeNames = self.themeElements.keys()
+ themeNames.sort(self.__ThemeNameIndexCompare)
+- self.optMenuHighlightTarget.SetMenu(themeNames,themeNames[0])
++ self.optMenuHighlightTarget.SetMenu(themeNames, themeNames[0])
+ self.PaintThemeSample()
+ self.SetHighlightTarget()
+
++ if TTK:
++ displaytheme = idleConf.GetOption('main', 'Theme', 'displaytheme')
++ self.themename.set(displaytheme)
++
+ def __ThemeNameIndexCompare(self,a,b):
+ if self.themeElements[a][1]<self.themeElements[b][1]: return -1
+ elif self.themeElements[a][1]==self.themeElements[b][1]: return 0
+@@ -1028,13 +1154,17 @@
+ self.autoSave.set(idleConf.GetOption('main', 'General', 'autosave',
+ default=0, type='bool'))
+ #initial window size
+- self.winWidth.set(idleConf.GetOption('main','EditorWindow','width'))
+- self.winHeight.set(idleConf.GetOption('main','EditorWindow','height'))
++ self.winWidth.set(idleConf.GetOption('main', 'EditorWindow', 'width'))
++ self.winHeight.set(idleConf.GetOption('main', 'EditorWindow', 'height'))
+ #initial paragraph reformat size
+- self.paraWidth.set(idleConf.GetOption('main','FormatParagraph','paragraph'))
++ self.paraWidth.set(idleConf.GetOption('main','FormatParagraph',
++ 'paragraph'))
+ # default source encoding
+- self.encoding.set(idleConf.GetOption('main', 'EditorWindow',
+- 'encoding', default='none'))
++ self.encoding.set(idleConf.GetOption('main', 'EditorPage',
++ 'encoding', default='none'))
++ # open files/modules in tabs
++ self.fileintab.set(idleConf.GetOption('main', 'EditorWindow',
++ 'file-in-tab', default=1, type='bool'))
+ # additional help sources
+ self.userHelpList = idleConf.GetAllExtraHelpSourcesList()
+ for helpItem in self.userHelpList:
+@@ -1125,6 +1255,8 @@
+ instance.set_notabs_indentwidth()
+ instance.ApplyKeybindings()
+ instance.reset_help_menu_entries()
++ if TTK:
++ instance.set_theme(self.ttkstyle)
+
+ def Cancel(self):
+ self.destroy()
+@@ -1142,6 +1274,7 @@
+ pass
+
+ if __name__ == '__main__':
++ from Tkinter import Tk
+ #test the dialog
+ root=Tk()
+ Button(root,text='Dialog',
+Index: SearchEngine.py
+===================================================================
+--- SearchEngine.py (revision 63995)
++++ SearchEngine.py (revision 65541)
+@@ -1,5 +1,5 @@
+ import re
+-from Tkinter import *
++from Tkinter import StringVar, BooleanVar, TclError
+ import tkMessageBox
+
+ def get(root):
+Index: ReplaceDialog.py
+===================================================================
+--- ReplaceDialog.py (revision 63995)
++++ ReplaceDialog.py (revision 65541)
+@@ -1,4 +1,5 @@
+-from Tkinter import *
++from Tkinter import StringVar, TclError
++
+ import SearchEngine
+ from SearchDialogBase import SearchDialogBase
+
+@@ -11,9 +12,12 @@
+ dialog.open(text)
+
+ class ReplaceDialog(SearchDialogBase):
+-
+ title = "Replace Dialog"
+ icon = "Replace"
++ bottom_btns = [("Find", 'find_it'),
++ ("Replace", 'replace_it'),
++ ("Replace+Find", 'default_command', 1),
++ ("Replace All", 'replace_all')]
+
+ def __init__(self, root, engine):
+ SearchDialogBase.__init__(self, root, engine)
+@@ -36,14 +40,10 @@
+
+ def create_entries(self):
+ SearchDialogBase.create_entries(self)
+- self.replent = self.make_entry("Replace with:", self.replvar)
++ self.replent = self.make_entry("Replace with", self.replvar)
+
+ def create_command_buttons(self):
+ SearchDialogBase.create_command_buttons(self)
+- self.make_button("Find", self.find_it)
+- self.make_button("Replace", self.replace_it)
+- self.make_button("Replace+Find", self.default_command, 1)
+- self.make_button("Replace All", self.replace_all)
+
+ def find_it(self, event=None):
+ self.do_find(0)
+Index: ScriptBinding.py
+===================================================================
+--- ScriptBinding.py (revision 63995)
++++ ScriptBinding.py (revision 65541)
+@@ -46,8 +46,9 @@
+ ('Check Module', '<<check-module>>'),
+ ('Run Module', '<<run-module>>'), ]), ]
+
+- def __init__(self, editwin):
+- self.editwin = editwin
++ def __init__(self, editpage):
++ self.editpage = editpage
++ self.editwin = editpage.editwin
+ # Provide instance variables referenced by Debugger
+ # XXX This should be done differently
+ self.flist = self.editwin.flist
+@@ -70,7 +71,7 @@
+ msgtxt, (lineno, start) = msg
+ self.editwin.gotoline(lineno)
+ self.errorbox("Tabnanny Tokenizing Error",
+- "Token Error: %s" % msgtxt)
++ "Token Error: %s" % msgtxt)
+ return False
+ except tabnanny.NannyNag, nag:
+ # The error messages from tabnanny are too confusing...
+@@ -91,7 +92,7 @@
+ source = re.sub(r"\r", "\n", source)
+ if source and source[-1] != '\n':
+ source = source + '\n'
+- text = self.editwin.text
++ text = self.editpage.text
+ text.tag_remove("ERROR", "1.0", "end")
+ try:
+ try:
+@@ -113,7 +114,7 @@
+ shell.set_warning_stream(saved_stream)
+
+ def colorize_syntax_error(self, msg, lineno, offset):
+- text = self.editwin.text
++ text = self.editpage.text
+ pos = "0.0 + %d lines + %d chars" % (lineno-1, offset-1)
+ text.tag_add("ERROR", pos)
+ char = text.get(pos)
+@@ -175,20 +176,20 @@
+
+ If the user has configured IDLE for Autosave, the file will be
+ silently saved if it already exists and is dirty.
+-
+ """
+- filename = self.editwin.io.filename
+- if not self.editwin.get_saved():
++ page = self.editpage
++ filename = page.io.filename
++ if not page.get_saved():
+ autosave = idleConf.GetOption('main', 'General',
+ 'autosave', type='bool')
+ if autosave and filename:
+- self.editwin.io.save(None)
++ page.io.save(None)
+ else:
+ reply = self.ask_save_dialog()
+- self.editwin.text.focus_set()
++ page.text.focus_set()
+ if reply == "ok":
+- self.editwin.io.save(None)
+- filename = self.editwin.io.filename
++ page.io.save(None)
++ filename = page.io.filename
+ else:
+ filename = None
+ return filename
+@@ -196,14 +197,12 @@
+ def ask_save_dialog(self):
+ msg = "Source Must Be Saved\n" + 5*' ' + "OK to Save?"
+ mb = tkMessageBox.Message(title="Save Before Run or Check",
+- message=msg,
+- icon=tkMessageBox.QUESTION,
+- type=tkMessageBox.OKCANCEL,
+- default=tkMessageBox.OK,
+- master=self.editwin.text)
++ message=msg, icon=tkMessageBox.QUESTION,
++ type=tkMessageBox.OKCANCEL, default=tkMessageBox.OK,
++ master=self.editpage.text)
+ return mb.show()
+
+ def errorbox(self, title, message):
+ # XXX This should really be a function of EditorWindow...
+- tkMessageBox.showerror(title, message, master=self.editwin.text)
+- self.editwin.text.focus_set()
++ tkMessageBox.showerror(title, message, master=self.editpage.text)
++ self.editpage.text.focus_set()
+Index: tabbedpages.py
+===================================================================
+--- tabbedpages.py (revision 63995)
++++ tabbedpages.py (revision 65541)
+@@ -1,490 +1,12 @@
+-"""An implementation of tabbed pages using only standard Tkinter.
+-
+-Originally developed for use in IDLE. Based on tabpage.py.
+-
+-Classes exported:
+-TabbedPageSet -- A Tkinter implementation of a tabbed-page widget.
+-TabSet -- A widget containing tabs (buttons) in one or more rows.
+-
+-"""
+-from Tkinter import *
+-
++# Exceptions used on both versions of tabbedpages
+ class InvalidNameError(Exception): pass
+ class AlreadyExistsError(Exception): pass
+
++def get_tabbedpage():
++ """Returns the TabbedPageSet available for use."""
++ try:
++ from tabbedpages_new import TabbedPageSet
++ except ImportError:
++ from tabbedpages_old import TabbedPageSet
+
+-class TabSet(Frame):
+- """A widget containing tabs (buttons) in one or more rows.
+-
+- Only one tab may be selected at a time.
+-
+- """
+- def __init__(self, page_set, select_command,
+- tabs=None, n_rows=1, max_tabs_per_row=5,
+- expand_tabs=False, **kw):
+- """Constructor arguments:
+-
+- select_command -- A callable which will be called when a tab is
+- selected. It is called with the name of the selected tab as an
+- argument.
+-
+- tabs -- A list of strings, the names of the tabs. Should be specified in
+- the desired tab order. The first tab will be the default and first
+- active tab. If tabs is None or empty, the TabSet will be initialized
+- empty.
+-
+- n_rows -- Number of rows of tabs to be shown. If n_rows <= 0 or is
+- None, then the number of rows will be decided by TabSet. See
+- _arrange_tabs() for details.
+-
+- max_tabs_per_row -- Used for deciding how many rows of tabs are needed,
+- when the number of rows is not constant. See _arrange_tabs() for
+- details.
+-
+- """
+- Frame.__init__(self, page_set, **kw)
+- self.select_command = select_command
+- self.n_rows = n_rows
+- self.max_tabs_per_row = max_tabs_per_row
+- self.expand_tabs = expand_tabs
+- self.page_set = page_set
+-
+- self._tabs = {}
+- self._tab2row = {}
+- if tabs:
+- self._tab_names = list(tabs)
+- else:
+- self._tab_names = []
+- self._selected_tab = None
+- self._tab_rows = []
+-
+- self.padding_frame = Frame(self, height=2,
+- borderwidth=0, relief=FLAT,
+- background=self.cget('background'))
+- self.padding_frame.pack(side=TOP, fill=X, expand=False)
+-
+- self._arrange_tabs()
+-
+- def add_tab(self, tab_name):
+- """Add a new tab with the name given in tab_name."""
+- if not tab_name:
+- raise InvalidNameError("Invalid Tab name: '%s'" % tab_name)
+- if tab_name in self._tab_names:
+- raise AlreadyExistsError("Tab named '%s' already exists" %tab_name)
+-
+- self._tab_names.append(tab_name)
+- self._arrange_tabs()
+-
+- def remove_tab(self, tab_name):
+- """Remove the tab named <tab_name>"""
+- if not tab_name in self._tab_names:
+- raise KeyError("No such Tab: '%s" % page_name)
+-
+- self._tab_names.remove(tab_name)
+- self._arrange_tabs()
+-
+- def set_selected_tab(self, tab_name):
+- """Show the tab named <tab_name> as the selected one"""
+- if tab_name == self._selected_tab:
+- return
+- if tab_name is not None and tab_name not in self._tabs:
+- raise KeyError("No such Tab: '%s" % page_name)
+-
+- # deselect the current selected tab
+- if self._selected_tab is not None:
+- self._tabs[self._selected_tab].set_normal()
+- self._selected_tab = None
+-
+- if tab_name is not None:
+- # activate the tab named tab_name
+- self._selected_tab = tab_name
+- tab = self._tabs[tab_name]
+- tab.set_selected()
+- # move the tab row with the selected tab to the bottom
+- tab_row = self._tab2row[tab]
+- tab_row.pack_forget()
+- tab_row.pack(side=TOP, fill=X, expand=0)
+-
+- def _add_tab_row(self, tab_names, expand_tabs):
+- if not tab_names:
+- return
+-
+- tab_row = Frame(self)
+- tab_row.pack(side=TOP, fill=X, expand=0)
+- self._tab_rows.append(tab_row)
+-
+- for tab_name in tab_names:
+- tab = TabSet.TabButton(tab_name, self.select_command,
+- tab_row, self)
+- if expand_tabs:
+- tab.pack(side=LEFT, fill=X, expand=True)
+- else:
+- tab.pack(side=LEFT)
+- self._tabs[tab_name] = tab
+- self._tab2row[tab] = tab_row
+-
+- # tab is the last one created in the above loop
+- tab.is_last_in_row = True
+-
+- def _reset_tab_rows(self):
+- while self._tab_rows:
+- tab_row = self._tab_rows.pop()
+- tab_row.destroy()
+- self._tab2row = {}
+-
+- def _arrange_tabs(self):
+- """
+- Arrange the tabs in rows, in the order in which they were added.
+-
+- If n_rows >= 1, this will be the number of rows used. Otherwise the
+- number of rows will be calculated according to the number of tabs and
+- max_tabs_per_row. In this case, the number of rows may change when
+- adding/removing tabs.
+-
+- """
+- # remove all tabs and rows
+- for tab_name in self._tabs.keys():
+- self._tabs.pop(tab_name).destroy()
+- self._reset_tab_rows()
+-
+- if not self._tab_names:
+- return
+-
+- if self.n_rows is not None and self.n_rows > 0:
+- n_rows = self.n_rows
+- else:
+- # calculate the required number of rows
+- n_rows = (len(self._tab_names) - 1) // self.max_tabs_per_row + 1
+-
+- # not expanding the tabs with more than one row is very ugly
+- expand_tabs = self.expand_tabs or n_rows > 1
+- i = 0 # index in self._tab_names
+- for row_index in xrange(n_rows):
+- # calculate required number of tabs in this row
+- n_tabs = (len(self._tab_names) - i - 1) // (n_rows - row_index) + 1
+- tab_names = self._tab_names[i:i + n_tabs]
+- i += n_tabs
+- self._add_tab_row(tab_names, expand_tabs)
+-
+- # re-select selected tab so it is properly displayed
+- selected = self._selected_tab
+- self.set_selected_tab(None)
+- if selected in self._tab_names:
+- self.set_selected_tab(selected)
+-
+- class TabButton(Frame):
+- """A simple tab-like widget."""
+-
+- bw = 2 # borderwidth
+-
+- def __init__(self, name, select_command, tab_row, tab_set):
+- """Constructor arguments:
+-
+- name -- The tab's name, which will appear in its button.
+-
+- select_command -- The command to be called upon selection of the
+- tab. It is called with the tab's name as an argument.
+-
+- """
+- Frame.__init__(self, tab_row, borderwidth=self.bw, relief=RAISED)
+-
+- self.name = name
+- self.select_command = select_command
+- self.tab_set = tab_set
+- self.is_last_in_row = False
+-
+- self.button = Radiobutton(
+- self, text=name, command=self._select_event,
+- padx=5, pady=1, takefocus=FALSE, indicatoron=FALSE,
+- highlightthickness=0, selectcolor='', borderwidth=0)
+- self.button.pack(side=LEFT, fill=X, expand=True)
+-
+- self._init_masks()
+- self.set_normal()
+-
+- def _select_event(self, *args):
+- """Event handler for tab selection.
+-
+- With TabbedPageSet, this calls TabbedPageSet.change_page, so that
+- selecting a tab changes the page.
+-
+- Note that this does -not- call set_selected -- it will be called by
+- TabSet.set_selected_tab, which should be called when whatever the
+- tabs are related to changes.
+-
+- """
+- self.select_command(self.name)
+- return
+-
+- def set_selected(self):
+- """Assume selected look"""
+- self._place_masks(selected=True)
+-
+- def set_normal(self):
+- """Assume normal look"""
+- self._place_masks(selected=False)
+-
+- def _init_masks(self):
+- page_set = self.tab_set.page_set
+- background = page_set.pages_frame.cget('background')
+- # mask replaces the middle of the border with the background color
+- self.mask = Frame(page_set, borderwidth=0, relief=FLAT,
+- background=background)
+- # mskl replaces the bottom-left corner of the border with a normal
+- # left border
+- self.mskl = Frame(page_set, borderwidth=0, relief=FLAT,
+- background=background)
+- self.mskl.ml = Frame(self.mskl, borderwidth=self.bw,
+- relief=RAISED)
+- self.mskl.ml.place(x=0, y=-self.bw,
+- width=2*self.bw, height=self.bw*4)
+- # mskr replaces the bottom-right corner of the border with a normal
+- # right border
+- self.mskr = Frame(page_set, borderwidth=0, relief=FLAT,
+- background=background)
+- self.mskr.mr = Frame(self.mskr, borderwidth=self.bw,
+- relief=RAISED)
+-
+- def _place_masks(self, selected=False):
+- height = self.bw
+- if selected:
+- height += self.bw
+-
+- self.mask.place(in_=self,
+- relx=0.0, x=0,
+- rely=1.0, y=0,
+- relwidth=1.0, width=0,
+- relheight=0.0, height=height)
+-
+- self.mskl.place(in_=self,
+- relx=0.0, x=-self.bw,
+- rely=1.0, y=0,
+- relwidth=0.0, width=self.bw,
+- relheight=0.0, height=height)
+-
+- page_set = self.tab_set.page_set
+- if selected and ((not self.is_last_in_row) or
+- (self.winfo_rootx() + self.winfo_width() <
+- page_set.winfo_rootx() + page_set.winfo_width())
+- ):
+- # for a selected tab, if its rightmost edge isn't on the
+- # rightmost edge of the page set, the right mask should be one
+- # borderwidth shorter (vertically)
+- height -= self.bw
+-
+- self.mskr.place(in_=self,
+- relx=1.0, x=0,
+- rely=1.0, y=0,
+- relwidth=0.0, width=self.bw,
+- relheight=0.0, height=height)
+-
+- self.mskr.mr.place(x=-self.bw, y=-self.bw,
+- width=2*self.bw, height=height + self.bw*2)
+-
+- # finally, lower the tab set so that all of the frames we just
+- # placed hide it
+- self.tab_set.lower()
+-
+-class TabbedPageSet(Frame):
+- """A Tkinter tabbed-pane widget.
+-
+- Constains set of 'pages' (or 'panes') with tabs above for selecting which
+- page is displayed. Only one page will be displayed at a time.
+-
+- Pages may be accessed through the 'pages' attribute, which is a dictionary
+- of pages, using the name given as the key. A page is an instance of a
+- subclass of Tk's Frame widget.
+-
+- The page widgets will be created (and destroyed when required) by the
+- TabbedPageSet. Do not call the page's pack/place/grid/destroy methods.
+-
+- Pages may be added or removed at any time using the add_page() and
+- remove_page() methods.
+-
+- """
+- class Page(object):
+- """Abstract base class for TabbedPageSet's pages.
+-
+- Subclasses must override the _show() and _hide() methods.
+-
+- """
+- uses_grid = False
+-
+- def __init__(self, page_set):
+- self.frame = Frame(page_set, borderwidth=2, relief=RAISED)
+-
+- def _show(self):
+- raise NotImplementedError
+-
+- def _hide(self):
+- raise NotImplementedError
+-
+- class PageRemove(Page):
+- """Page class using the grid placement manager's "remove" mechanism."""
+- uses_grid = True
+-
+- def _show(self):
+- self.frame.grid(row=0, column=0, sticky=NSEW)
+-
+- def _hide(self):
+- self.frame.grid_remove()
+-
+- class PageLift(Page):
+- """Page class using the grid placement manager's "lift" mechanism."""
+- uses_grid = True
+-
+- def __init__(self, page_set):
+- super(TabbedPageSet.PageLift, self).__init__(page_set)
+- self.frame.grid(row=0, column=0, sticky=NSEW)
+- self.frame.lower()
+-
+- def _show(self):
+- self.frame.lift()
+-
+- def _hide(self):
+- self.frame.lower()
+-
+- class PagePackForget(Page):
+- """Page class using the pack placement manager's "forget" mechanism."""
+- def _show(self):
+- self.frame.pack(fill=BOTH, expand=True)
+-
+- def _hide(self):
+- self.frame.pack_forget()
+-
+- def __init__(self, parent, page_names=None, page_class=PageLift,
+- n_rows=1, max_tabs_per_row=5, expand_tabs=False,
+- **kw):
+- """Constructor arguments:
+-
+- page_names -- A list of strings, each will be the dictionary key to a
+- page's widget, and the name displayed on the page's tab. Should be
+- specified in the desired page order. The first page will be the default
+- and first active page. If page_names is None or empty, the
+- TabbedPageSet will be initialized empty.
+-
+- n_rows, max_tabs_per_row -- Parameters for the TabSet which will
+- manage the tabs. See TabSet's docs for details.
+-
+- page_class -- Pages can be shown/hidden using three mechanisms:
+-
+- * PageLift - All pages will be rendered one on top of the other. When
+- a page is selected, it will be brought to the top, thus hiding all
+- other pages. Using this method, the TabbedPageSet will not be resized
+- when pages are switched. (It may still be resized when pages are
+- added/removed.)
+-
+- * PageRemove - When a page is selected, the currently showing page is
+- hidden, and the new page shown in its place. Using this method, the
+- TabbedPageSet may resize when pages are changed.
+-
+- * PagePackForget - This mechanism uses the pack placement manager.
+- When a page is shown it is packed, and when it is hidden it is
+- unpacked (i.e. pack_forget). This mechanism may also cause the
+- TabbedPageSet to resize when the page is changed.
+-
+- """
+- Frame.__init__(self, parent, **kw)
+-
+- self.page_class = page_class
+- self.pages = {}
+- self._pages_order = []
+- self._current_page = None
+- self._default_page = None
+-
+- self.columnconfigure(0, weight=1)
+- self.rowconfigure(1, weight=1)
+-
+- self.pages_frame = Frame(self)
+- self.pages_frame.grid(row=1, column=0, sticky=NSEW)
+- if self.page_class.uses_grid:
+- self.pages_frame.columnconfigure(0, weight=1)
+- self.pages_frame.rowconfigure(0, weight=1)
+-
+- # the order of the following commands is important
+- self._tab_set = TabSet(self, self.change_page, n_rows=n_rows,
+- max_tabs_per_row=max_tabs_per_row,
+- expand_tabs=expand_tabs)
+- if page_names:
+- for name in page_names:
+- self.add_page(name)
+- self._tab_set.grid(row=0, column=0, sticky=NSEW)
+-
+- self.change_page(self._default_page)
+-
+- def add_page(self, page_name):
+- """Add a new page with the name given in page_name."""
+- if not page_name:
+- raise InvalidNameError("Invalid TabPage name: '%s'" % page_name)
+- if page_name in self.pages:
+- raise AlreadyExistsError(
+- "TabPage named '%s' already exists" % page_name)
+-
+- self.pages[page_name] = self.page_class(self.pages_frame)
+- self._pages_order.append(page_name)
+- self._tab_set.add_tab(page_name)
+-
+- if len(self.pages) == 1: # adding first page
+- self._default_page = page_name
+- self.change_page(page_name)
+-
+- def remove_page(self, page_name):
+- """Destroy the page whose name is given in page_name."""
+- if not page_name in self.pages:
+- raise KeyError("No such TabPage: '%s" % page_name)
+-
+- self._pages_order.remove(page_name)
+-
+- # handle removing last remaining, default, or currently shown page
+- if len(self._pages_order) > 0:
+- if page_name == self._default_page:
+- # set a new default page
+- self._default_page = self._pages_order[0]
+- else:
+- self._default_page = None
+-
+- if page_name == self._current_page:
+- self.change_page(self._default_page)
+-
+- self._tab_set.remove_tab(page_name)
+- page = self.pages.pop(page_name)
+- page.frame.destroy()
+-
+- def change_page(self, page_name):
+- """Show the page whose name is given in page_name."""
+- if self._current_page == page_name:
+- return
+- if page_name is not None and page_name not in self.pages:
+- raise KeyError("No such TabPage: '%s'" % page_name)
+-
+- if self._current_page is not None:
+- self.pages[self._current_page]._hide()
+- self._current_page = None
+-
+- if page_name is not None:
+- self._current_page = page_name
+- self.pages[page_name]._show()
+-
+- self._tab_set.set_selected_tab(page_name)
+-
+-if __name__ == '__main__':
+- # test dialog
+- root=Tk()
+- tabPage=TabbedPageSet(root, page_names=['Foobar','Baz'], n_rows=0,
+- expand_tabs=False,
+- )
+- tabPage.pack(side=TOP, expand=TRUE, fill=BOTH)
+- Label(tabPage.pages['Foobar'].frame, text='Foo', pady=20).pack()
+- Label(tabPage.pages['Foobar'].frame, text='Bar', pady=20).pack()
+- Label(tabPage.pages['Baz'].frame, text='Baz').pack()
+- entryPgName=Entry(root)
+- buttonAdd=Button(root, text='Add Page',
+- command=lambda:tabPage.add_page(entryPgName.get()))
+- buttonRemove=Button(root, text='Remove Page',
+- command=lambda:tabPage.remove_page(entryPgName.get()))
+- labelPgName=Label(root, text='name of page to add/remove:')
+- buttonAdd.pack(padx=5, pady=5)
+- buttonRemove.pack(padx=5, pady=5)
+- labelPgName.pack(padx=5)
+- entryPgName.pack(padx=5)
+- root.mainloop()
++ return TabbedPageSet
+Index: keybindingDialog.py
+===================================================================
+--- keybindingDialog.py (revision 63995)
++++ keybindingDialog.py (revision 65541)
+@@ -1,10 +1,18 @@
+ """
+ Dialog for building Tkinter accelerator key bindings
+ """
+-from Tkinter import *
++from Tkinter import Toplevel, Frame, Entry, Button, Checkbutton, Label, \
++ Listbox, Scrollbar, StringVar
++from Tkconstants import TOP, BOTH, BOTTOM, X, NSEW, SUNKEN, LEFT, GROOVE, W, \
++ END, EW, NS, SINGLE, VERTICAL, ANCHOR, MOVETO
+ import tkMessageBox
+ import string
+
++from idlelib.configHandler import idleConf
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++if TTK:
++ from ttk import Frame, Entry, Button, Checkbutton, Label, Scrollbar
++
+ class GetKeysDialog(Toplevel):
+ def __init__(self,parent,title,action,currentKeySequences):
+ """
+@@ -15,7 +23,7 @@
+ """
+ Toplevel.__init__(self, parent)
+ self.configure(borderwidth=5)
+- self.resizable(height=FALSE,width=FALSE)
++ self.resizable(height=False, width=False)
+ self.title(title)
+ self.transient(parent)
+ self.grab_set()
+@@ -46,10 +54,10 @@
+ self.wait_window()
+
+ def CreateWidgets(self):
+- frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
+- frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
++ frameMain = Frame(self, borderwidth=2, relief=SUNKEN)
++ frameMain.pack(side=TOP, expand=True, fill=BOTH)
+ frameButtons=Frame(self)
+- frameButtons.pack(side=BOTTOM,fill=X)
++ frameButtons.pack(side=BOTTOM, fill=X)
+ self.buttonOK = Button(frameButtons,text='OK',
+ width=8,command=self.OK)
+ self.buttonOK.grid(row=0,column=0,padx=5,pady=5)
+@@ -124,6 +132,9 @@
+ "separated by a space, eg., <Alt-v> <Meta-v>." )
+ labelHelpAdvanced.grid(row=0,column=0,sticky=NSEW)
+
++ if TTK:
++ frameButtons['style'] = 'RootColor.TFrame'
++
+ def SetModifiersForPlatform(self):
+ """Determine list of names of key modifiers for this platform.
+
+@@ -163,7 +174,7 @@
+ if finalKey:
+ finalKey = self.TranslateKey(finalKey, modifiers)
+ keyList.append(finalKey)
+- self.keyString.set('<' + string.join(keyList,'-') + '>')
++ self.keyString.set('<%s>' % '-'.join(keyList))
+
+ def GetModifiers(self):
+ modList = [variable.get() for variable in self.modifier_vars]
+@@ -258,6 +269,7 @@
+ return keysOK
+
+ if __name__ == '__main__':
++ from Tkinter import Tk
+ #test the dialog
+ root=Tk()
+ def run():
+Index: configHelpSourceEdit.py
+===================================================================
+--- configHelpSourceEdit.py (revision 63995)
++++ configHelpSourceEdit.py (revision 65541)
+@@ -1,12 +1,17 @@
+ "Dialog to specify or edit the parameters for a user configured help source."
+-
+ import os
+ import sys
+-
+-from Tkinter import *
++from Tkinter import Toplevel, Frame, Entry, Button, Label, StringVar
++from Tkconstants import GROOVE, LEFT, RIGHT, W, ACTIVE, X, BOTH, TOP, BOTTOM
+ import tkMessageBox
+ import tkFileDialog
+
++from configHandler import idleConf
++
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++if TTK:
++ from ttk import Frame, Entry, Button, Label
++
+ class GetHelpSourceDialog(Toplevel):
+ def __init__(self, parent, title, menuItem='', filePath=''):
+ """Get menu entry and url/ local file location for Additional Help
+@@ -18,13 +23,14 @@
+ """
+ Toplevel.__init__(self, parent)
+ self.configure(borderwidth=5)
+- self.resizable(height=FALSE, width=FALSE)
++ self.resizable(height=False, width=False)
+ self.title(title)
+ self.transient(parent)
+ self.grab_set()
+ self.protocol("WM_DELETE_WINDOW", self.Cancel)
+ self.parent = parent
+ self.result = None
++
+ self.CreateWidgets()
+ self.menu.set(menuItem)
+ self.path.set(filePath)
+@@ -46,33 +52,36 @@
+ self.path = StringVar(self)
+ self.fontSize = StringVar(self)
+ self.frameMain = Frame(self, borderwidth=2, relief=GROOVE)
+- self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
+ labelMenu = Label(self.frameMain, anchor=W, justify=LEFT,
+- text='Menu Item:')
+- self.entryMenu = Entry(self.frameMain, textvariable=self.menu,
+- width=30)
+- self.entryMenu.focus_set()
++ text='Menu Item:')
++ self.entryMenu = Entry(self.frameMain, textvariable=self.menu)
+ labelPath = Label(self.frameMain, anchor=W, justify=LEFT,
+- text='Help File Path: Enter URL or browse for file')
++ text='Help File Path: Enter URL or browse for file')
+ self.entryPath = Entry(self.frameMain, textvariable=self.path,
+- width=40)
++ width=30)
++ browseButton = Button(self.frameMain, text='Browse', width=8,
++ command=self.browseFile)
++ frameButtons = Frame(self)
++ self.buttonOk = Button(frameButtons, text='OK', width=8,
++ default=ACTIVE, command=self.Ok)
++ self.buttonCancel = Button(frameButtons, text='Cancel', width=8,
++ command=self.Cancel)
++
+ self.entryMenu.focus_set()
++
++ self.frameMain.pack(side=TOP, expand=True, fill=BOTH)
+ labelMenu.pack(anchor=W, padx=5, pady=3)
+- self.entryMenu.pack(anchor=W, padx=5, pady=3)
++ self.entryMenu.pack(anchor=W, padx=5, pady=3, fill=X)
+ labelPath.pack(anchor=W, padx=5, pady=3)
+- self.entryPath.pack(anchor=W, padx=5, pady=3)
+- browseButton = Button(self.frameMain, text='Browse', width=8,
+- command=self.browseFile)
+- browseButton.pack(pady=3)
+- frameButtons = Frame(self)
++ self.entryPath.pack(anchor=W, padx=5, pady=3, side=LEFT, fill=X)
++ browseButton.pack(pady=3, padx=5, side=RIGHT)
+ frameButtons.pack(side=BOTTOM, fill=X)
+- self.buttonOk = Button(frameButtons, text='OK',
+- width=8, default=ACTIVE, command=self.Ok)
+- self.buttonOk.grid(row=0, column=0, padx=5,pady=5)
+- self.buttonCancel = Button(frameButtons, text='Cancel',
+- width=8, command=self.Cancel)
+- self.buttonCancel.grid(row=0, column=1, padx=5, pady=5)
++ self.buttonOk.pack(pady=5, side=RIGHT)
++ self.buttonCancel.pack(padx=5, pady=5, side=RIGHT)
+
++ if TTK:
++ frameButtons['style'] = 'RootColor.TFrame'
++
+ def browseFile(self):
+ filetypes = [
+ ("HTML Files", "*.htm *.html", "TEXT"),
+@@ -159,6 +168,7 @@
+ self.destroy()
+
+ if __name__ == '__main__':
++ from Tkinter import Tk
+ #test the dialog
+ root = Tk()
+ def run():
+Index: WidgetRedirector.py
+===================================================================
+--- WidgetRedirector.py (revision 63995)
++++ WidgetRedirector.py (revision 65541)
+@@ -1,4 +1,4 @@
+-from Tkinter import *
++from Tkinter import TclError
+
+ class WidgetRedirector:
+
+@@ -105,6 +105,7 @@
+
+
+ def main():
++ from Tkinter import Tk, Text
+ root = Tk()
+ root.wm_protocol("WM_DELETE_WINDOW", root.quit)
+ text = Text()
+Index: GrepDialog.py
+===================================================================
+--- GrepDialog.py (revision 63995)
++++ GrepDialog.py (revision 65541)
+@@ -1,10 +1,15 @@
+ import os
+-import fnmatch
+ import sys
+-from Tkinter import *
++import fnmatch
++from Tkinter import StringVar, BooleanVar, Checkbutton
++
+ import SearchEngine
+ from SearchDialogBase import SearchDialogBase
++from configHandler import idleConf
+
++if idleConf.GetOption('main', 'General', 'use-ttk', type='int'):
++ from ttk import Checkbutton
++
+ def grep(text, io=None, flist=None):
+ root = text._root()
+ engine = SearchEngine.get(root)
+@@ -15,10 +20,10 @@
+ dialog.open(text, searchphrase, io)
+
+ class GrepDialog(SearchDialogBase):
+-
+ title = "Find in Files Dialog"
+ icon = "Grep"
+ needwrapbutton = 0
++ bottom_btns = [("Search Files", 'default_command', 1)]
+
+ def __init__(self, root, engine, flist):
+ SearchDialogBase.__init__(self, root, engine)
+@@ -40,20 +45,18 @@
+
+ def create_entries(self):
+ SearchDialogBase.create_entries(self)
+- self.globent = self.make_entry("In files:", self.globvar)
++ self.globent = self.make_entry("In files", self.globvar)
+
+ def create_other_buttons(self):
+ f = self.make_frame()
+
+- btn = Checkbutton(f, anchor="w",
+- variable=self.recvar,
++ btn = Checkbutton(f, variable=self.recvar,
+ text="Recurse down subdirectories")
+ btn.pack(side="top", fill="both")
+- btn.select()
++ btn.invoke()
+
+ def create_command_buttons(self):
+ SearchDialogBase.create_command_buttons(self)
+- self.make_button("Search Files", self.default_command, 1)
+
+ def default_command(self, event=None):
+ prog = self.engine.getprog()
+@@ -126,8 +129,3 @@
+ for subdir in subdirs:
+ list.extend(self.findfiles(subdir, base, rec))
+ return list
+-
+- def close(self, event=None):
+- if self.top:
+- self.top.grab_release()
+- self.top.withdraw()
+Index: FormatParagraph.py
+===================================================================
+--- FormatParagraph.py (revision 63995)
++++ FormatParagraph.py (revision 65541)
+@@ -25,28 +25,30 @@
+ ])
+ ]
+
+- def __init__(self, editwin):
+- self.editwin = editwin
++ def __init__(self, editpage):
++ self.editpage = editpage
+
+ def close(self):
+- self.editwin = None
++ self.editpage = None
+
+ def format_paragraph_event(self, event):
+- maxformatwidth = int(idleConf.GetOption('main','FormatParagraph','paragraph'))
+- text = self.editwin.text
+- first, last = self.editwin.get_selection_indices()
++ maxformatwidth = int(idleConf.GetOption('main', 'FormatParagraph',
++ 'paragraph'))
++ text = self.editpage.text
++ first, last = self.editpage.get_selection_indices()
+ if first and last:
+ data = text.get(first, last)
+ comment_header = ''
+ else:
+- first, last, comment_header, data = \
+- find_paragraph(text, text.index("insert"))
++ first, last, comment_header, data = find_paragraph(text,
++ text.index("insert"))
+ if comment_header:
+ # Reformat the comment lines - convert to text sans header.
+ lines = data.split("\n")
+ lines = map(lambda st, l=len(comment_header): st[l:], lines)
+ data = "\n".join(lines)
+- # Reformat to maxformatwidth chars or a 20 char width, whichever is greater.
++ # Reformat to maxformatwidth chars or a 20 char width, whichever is
++ # greater.
+ format_width = max(maxformatwidth - len(comment_header), 20)
+ newdata = reformat_paragraph(data, format_width)
+ # re-split and re-insert the comment header.
+Index: EditorWindow.py
+===================================================================
+--- EditorWindow.py (revision 63995)
++++ EditorWindow.py (revision 65541)
+@@ -1,55 +1,54 @@
+-import sys
+ import os
+ import re
+-import imp
+-from itertools import count
+-from Tkinter import *
+-import tkSimpleDialog
++import sys
++import traceback
++import webbrowser
+ import tkMessageBox
+-from MultiCall import MultiCallCreator
++import tkSimpleDialog
++from Tkinter import Menu, Scrollbar, TclError, BooleanVar
++from Tkconstants import INSERT, END, RIGHT, BOTTOM, TOP, X, Y, BOTH
+
+-import webbrowser
+-import idlever
++import macosxSupport
++import Bindings
+ import WindowList
+-import SearchDialog
+-import GrepDialog
+-import ReplaceDialog
+-import PyParse
++from editorpage import EditorPage, classifyws, filename_to_unicode
++from tabbedpages import get_tabbedpage
+ from configHandler import idleConf
+-import aboutDialog, textView, configDialog
+-import macosxSupport
++from MultiStatusBar import MultiStatusBar
+
++TabbedPageSet = get_tabbedpage()
++
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++if TTK:
++ from ttk import Scrollbar
++
+ # The default tab setting for a Text widget, in average-width characters.
+ TK_TABWIDTH_DEFAULT = 8
+
+-def _find_module(fullname, path=None):
+- """Version of imp.find_module() that handles hierarchical module names"""
+-
+- file = None
+- for tgt in fullname.split('.'):
+- if file is not None:
+- file.close() # close intermediate files
+- (file, filename, descr) = imp.find_module(tgt, path)
+- if descr[2] == imp.PY_SOURCE:
+- break # find but not load the source file
+- module = imp.load_module(tgt, file, filename, descr)
+- try:
+- path = module.__path__
+- except AttributeError:
+- raise ImportError, 'No source for module ' + module.__name__
+- return file, filename, descr
+-
+ class EditorWindow(object):
+- from Percolator import Percolator
+- from ColorDelegator import ColorDelegator
+- from UndoDelegator import UndoDelegator
+- from IOBinding import IOBinding, filesystemencoding, encoding
+- import Bindings
+- from Tkinter import Toplevel
+- from MultiStatusBar import MultiStatusBar
++ from ColorDelegator import ColorDelegator # overridden by PyShell
++ from UndoDelegator import UndoDelegator # overridden by PyShell
+
+ help_url = None
++ rmenu = None
++ rmenu_specs = [
++ # ("Label", "<<virtual-event>>"), ...
++ ("Close", "<<close-window>>"), # Example
++ ]
++ menu_specs = [
++ ("file", "_File"),
++ ("edit", "_Edit"),
++ ("format", "F_ormat"),
++ ("run", "_Run"),
++ ("options", "_Options"),
++ ("windows", "_Windows"),
++ ("help", "_Help"),
++ ]
+
++ if macosxSupport.runningAsOSXApp():
++ del menu_specs[-3]
++ menu_specs[-2] = ("windows", "_Window")
++
+ def __init__(self, flist=None, filename=None, key=None, root=None):
+ if EditorWindow.help_url is None:
+ dochome = os.path.join(sys.prefix, 'Doc', 'index.html')
+@@ -81,7 +80,7 @@
+ EditorWindow.help_url = 'file://' + EditorWindow.help_url
+ else:
+ EditorWindow.help_url = "http://www.python.org/doc/current"
+- currentTheme=idleConf.CurrentTheme()
++
+ self.flist = flist
+ root = root or flist.root
+ self.root = root
+@@ -90,94 +89,45 @@
+ except AttributeError:
+ sys.ps1 = '>>> '
+ self.menubar = Menu(root)
+- self.top = top = WindowList.ListedToplevel(root, menu=self.menubar)
++ self.top = WindowList.ListedToplevel(root, menu=self.menubar)
+ if flist:
+ self.tkinter_vars = flist.vars
+- #self.top.instance_dict makes flist.inversedict avalable to
+- #configDialog.py so it can access all EditorWindow instaces
++ # self.top.instance_dict makes flist.inversedict avalable to
++ # configDialog.py so it can access all EditorWindow instaces
+ self.top.instance_dict = flist.inversedict
+ else:
+ self.tkinter_vars = {} # keys: Tkinter event names
+ # values: Tkinter variable instances
+ self.top.instance_dict = {}
+ self.recent_files_path = os.path.join(idleConf.GetUserCfgDir(),
+- 'recent-files.lst')
+- self.text_frame = text_frame = Frame(top)
+- self.vbar = vbar = Scrollbar(text_frame, name='vbar')
+- self.width = idleConf.GetOption('main','EditorWindow','width')
+- self.text = text = MultiCallCreator(Text)(
+- text_frame, name='text', padx=5, wrap='none',
+- width=self.width,
+- height=idleConf.GetOption('main','EditorWindow','height') )
+- self.top.focused_widget = self.text
++ 'recent-files.lst')
+
+- self.createmenubar()
+- self.apply_bindings()
+-
+- self.top.protocol("WM_DELETE_WINDOW", self.close)
+- self.top.bind("<<close-window>>", self.close_event)
+- if macosxSupport.runningAsOSXApp():
+- # Command-W on editorwindows doesn't work without this.
+- text.bind('<<close-window>>', self.close_event)
+- text.bind("<<cut>>", self.cut)
+- text.bind("<<copy>>", self.copy)
+- text.bind("<<paste>>", self.paste)
+- text.bind("<<center-insert>>", self.center_insert_event)
+- text.bind("<<help>>", self.help_dialog)
+- text.bind("<<python-docs>>", self.python_docs)
+- text.bind("<<about-idle>>", self.about_dialog)
+- text.bind("<<open-config-dialog>>", self.config_dialog)
+- text.bind("<<open-module>>", self.open_module)
+- text.bind("<<do-nothing>>", lambda event: "break")
+- text.bind("<<select-all>>", self.select_all)
+- text.bind("<<remove-selection>>", self.remove_selection)
+- text.bind("<<find>>", self.find_event)
+- text.bind("<<find-again>>", self.find_again_event)
+- text.bind("<<find-in-files>>", self.find_in_files_event)
+- text.bind("<<find-selection>>", self.find_selection_event)
+- text.bind("<<replace>>", self.replace_event)
+- text.bind("<<goto-line>>", self.goto_line_event)
+- text.bind("<3>", self.right_menu_event)
+- text.bind("<<smart-backspace>>",self.smart_backspace_event)
+- text.bind("<<newline-and-indent>>",self.newline_and_indent_event)
+- text.bind("<<smart-indent>>",self.smart_indent_event)
+- text.bind("<<indent-region>>",self.indent_region_event)
+- text.bind("<<dedent-region>>",self.dedent_region_event)
+- text.bind("<<comment-region>>",self.comment_region_event)
+- text.bind("<<uncomment-region>>",self.uncomment_region_event)
+- text.bind("<<tabify-region>>",self.tabify_region_event)
+- text.bind("<<untabify-region>>",self.untabify_region_event)
+- text.bind("<<toggle-tabs>>",self.toggle_tabs_event)
+- text.bind("<<change-indentwidth>>",self.change_indentwidth_event)
+- text.bind("<Left>", self.move_at_edge_if_selection(0))
+- text.bind("<Right>", self.move_at_edge_if_selection(1))
+- text.bind("<<del-word-left>>", self.del_word_left)
+- text.bind("<<del-word-right>>", self.del_word_right)
+- text.bind("<<beginning-of-line>>", self.home_callback)
+-
+ if flist:
+ flist.inversedict[self] = key
+ if key:
+ flist.dict[key] = self
+- text.bind("<<open-new-window>>", self.new_callback)
+- text.bind("<<close-all-windows>>", self.flist.close_all_callback)
+- text.bind("<<open-class-browser>>", self.open_class_browser)
+- text.bind("<<open-path-browser>>", self.open_path_browser)
+
+- self.set_status_bar()
+- vbar['command'] = text.yview
+- vbar.pack(side=RIGHT, fill=Y)
+- text['yscrollcommand'] = vbar.set
+- fontWeight = 'normal'
+- if idleConf.GetOption('main', 'EditorWindow', 'font-bold', type='bool'):
+- fontWeight='bold'
+- text.config(font=(idleConf.GetOption('main', 'EditorWindow', 'font'),
+- idleConf.GetOption('main', 'EditorWindow', 'font-size'),
+- fontWeight))
+- text_frame.pack(side=LEFT, fill=BOTH, expand=1)
+- text.pack(side=TOP, fill=BOTH, expand=1)
+- text.focus_set()
++ self.menudict = None
+
++ # create a Notebook where the text pages for this EditorWindow will
++ # reside
++ self.text_notebook = TabbedPageSet(self.top)
++ self.text_notebook.pack(fill=BOTH, expand=True)
++ self.text_notebook.bind('<<NotebookTabChanged>>', self._update_controls)
++ self.new_tab(filename=filename, load_ext=False)
++ self.text = self.current_page.text # XXX
++ self.top.focused_widget = self.text
++ self.top.bind('<<tab-closed>>', self._post_tab_close)
++
++ # The following "width" attribute is used by PyShell, so keep it here
++ self.width = idleConf.GetOption('main', 'EditorPage', 'width')
++
++ self.top.protocol("WM_DELETE_WINDOW", self.close)
++ self.top.bind("<<close-window>>", self.close_event)
++
++ self._create_statusbar()
++ self.top.after_idle(self.set_line_and_column)
++
+ # usetabs true -> literal tab characters are used by indent and
+ # dedent cmds, possibly mixed with spaces if
+ # indentwidth is not a multiple of tabwidth,
+@@ -187,7 +137,8 @@
+ # Although use-spaces=0 can be configured manually in config-main.def,
+ # configuration of tabs v. spaces is not supported in the configuration
+ # dialog. IDLE promotes the preferred Python indentation: use spaces!
+- usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces', type='bool')
++ usespaces = idleConf.GetOption('main', 'Indent', 'use-spaces',
++ type='bool')
+ self.usetabs = not usespaces
+
+ # tabwidth is the display width of a literal tab character.
+@@ -214,37 +165,10 @@
+ # Making the initial values larger slows things down more often.
+ self.num_context_lines = 50, 500, 5000000
+
+- self.per = per = self.Percolator(text)
+-
+- self.undo = undo = self.UndoDelegator()
+- per.insertfilter(undo)
+- text.undo_block_start = undo.undo_block_start
+- text.undo_block_stop = undo.undo_block_stop
+- undo.set_saved_change_hook(self.saved_change_hook)
+-
+- # IOBinding implements file I/O and printing functionality
+- self.io = io = self.IOBinding(self)
+- io.set_filename_change_hook(self.filename_change_hook)
+-
+- # Create the recent files submenu
+- self.recent_files_menu = Menu(self.menubar)
+- self.menudict['file'].insert_cascade(3, label='Recent Files',
+- underline=0,
+- menu=self.recent_files_menu)
+- self.update_recent_files_list()
+-
+- self.color = None # initialized below in self.ResetColorizer
+- if filename:
+- if os.path.exists(filename) and not os.path.isdir(filename):
+- io.loadfile(filename)
+- else:
+- io.set_filename(filename)
+- self.ResetColorizer()
+- self.saved_change_hook()
+-
+ self.set_indentation_params(self.ispythonsource(filename))
+
+- self.load_extensions()
++ self.extensions = {}
++ self._load_extensions()
+
+ menu = self.menudict.get('windows')
+ if menu:
+@@ -262,121 +186,96 @@
+ self.askinteger = tkSimpleDialog.askinteger
+ self.showerror = tkMessageBox.showerror
+
+- def _filename_to_unicode(self, filename):
+- """convert filename to unicode in order to display it in Tk"""
+- if isinstance(filename, unicode) or not filename:
+- return filename
++ @property
++ def current_page(self):
++ """Return the active EditorPage in EditorWindow."""
++ curr_tab = self.text_notebook.select()
++ if not curr_tab:
++ return None
++
++ if TTK:
++ page = self.text_notebook.pages[self.text_notebook.tab(
++ curr_tab)['text']].editpage
+ else:
+- try:
+- return filename.decode(self.filesystemencoding)
+- except UnicodeDecodeError:
+- # XXX
+- try:
+- return filename.decode(self.encoding)
+- except UnicodeDecodeError:
+- # byte-to-byte conversion
+- return filename.decode('iso8859-1')
++ page = self.text_notebook.pages[curr_tab].editpage
++ return page
+
+- def new_callback(self, event):
+- dirname, basename = self.io.defaultfilename()
+- self.flist.new(dirname)
+- return "break"
++ def short_title(self):
++ # overriden by PyShell
++ self.current_page.short_title()
+
+- def home_callback(self, event):
+- if (event.state & 12) != 0 and event.keysym == "Home":
+- # state&1==shift, state&4==control, state&8==alt
+- return # <Modifier-Home>; fall back to class binding
++ def next_tab(self, event):
++ """Show next tab if not in the last tab already."""
++ index = self.text_notebook.index(self.text_notebook.select())
++ if index == len(self.text_notebook.tabs()) - 1:
++ return
++ self.text_notebook.select(index + 1)
+
+- if self.text.index("iomark") and \
+- self.text.compare("iomark", "<=", "insert lineend") and \
+- self.text.compare("insert linestart", "<=", "iomark"):
+- insertpt = int(self.text.index("iomark").split(".")[1])
+- else:
+- line = self.text.get("insert linestart", "insert lineend")
+- for insertpt in xrange(len(line)):
+- if line[insertpt] not in (' ','\t'):
+- break
+- else:
+- insertpt=len(line)
++ def prev_tab(self, event):
++ """Show the previous tab if not in the first tab already."""
++ index = self.text_notebook.index(self.text_notebook.select())
++ if index == 0:
++ return
++ self.text_notebook.select(index - 1)
+
+- lineat = int(self.text.index("insert").split('.')[1])
++ def new_tab(self, event=None, filename=None, load_ext=True):
++ """Create a new EditorPage and insert it into the notebook."""
++ page_title = "#%d" % (len(self.text_notebook.pages) + 1)
++ page = self.text_notebook.add_page(page_title)
+
+- if insertpt == lineat:
+- insertpt = 0
++ vbar = Scrollbar(page.frame, name='vbar')
++ page.editpage = EditorPage(page.frame, self, title=page_title,
++ name='text', padx=5, wrap='none')
+
+- dest = "insert linestart+"+str(insertpt)+"c"
++ firstpage = False # don't update window's title
++ if self.menudict is None:
++ # This EditorWindow is being created now, perform the following
++ # tasks before.
++ firstpage = True # will cause window's title to be updated
++ self.menudict = {}
++ self._createmenubar(page.editpage.text)
++ # Create the recent files submenu
++ self.recent_files_menu = Menu(self.menubar)
++ self.menudict['file'].insert_cascade(3, label='Recent Files',
++ underline=0, menu=self.recent_files_menu)
++ self.update_recent_files_list()
+
+- if (event.state&1) == 0:
+- # shift not pressed
+- self.text.tag_remove("sel", "1.0", "end")
+- else:
+- if not self.text.index("sel.first"):
+- self.text.mark_set("anchor","insert")
++ page.editpage.post_init(filename=filename, update_window_title=firstpage)
+
+- first = self.text.index(dest)
+- last = self.text.index("anchor")
++ text = page.editpage.text
++ vbar['command'] = text.yview
++ vbar.pack(side=RIGHT, fill=Y)
++ text['yscrollcommand'] = vbar.set
++ fontWeight = 'normal'
++ if idleConf.GetOption('main', 'EditorPage', 'font-bold', type='bool'):
++ fontWeight = 'bold'
++ text.config(font=(idleConf.GetOption('main', 'EditorPage', 'font'),
++ idleConf.GetOption('main', 'EditorPage', 'font-size'),
++ fontWeight))
++ text.pack(side=TOP, fill=BOTH, expand=1)
++ text.focus_set()
+
+- if self.text.compare(first,">",last):
+- first,last = last,first
++ self.apply_bindings(tab=page)
++ if load_ext:
++ self._load_extensions()
++ self.top.event_generate('<<tab-created>>')
++ return "break"
+
+- self.text.tag_remove("sel", "1.0", "end")
+- self.text.tag_add("sel", first, last)
+-
+- self.text.mark_set("insert", dest)
+- self.text.see("insert")
++ def new_callback(self, event, page):
++ dirname, basename = page.io.defaultfilename()
++ self.flist.new(dirname)
+ return "break"
+
+- def set_status_bar(self):
+- self.status_bar = self.MultiStatusBar(self.top)
+- if macosxSupport.runningAsOSXApp():
+- # Insert some padding to avoid obscuring some of the statusbar
+- # by the resize widget.
+- self.status_bar.set_label('_padding1', ' ', side=RIGHT)
+- self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
+- self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
+- self.status_bar.pack(side=BOTTOM, fill=X)
+- self.text.bind("<<set-line-and-column>>", self.set_line_and_column)
+- self.text.event_add("<<set-line-and-column>>",
+- "<KeyRelease>", "<ButtonRelease>")
+- self.text.after_idle(self.set_line_and_column)
+-
+ def set_line_and_column(self, event=None):
+- line, column = self.text.index(INSERT).split('.')
++ # Used by PyShell too
++ curr_page = self.current_page
++ if not curr_page:
++ return
++
++ line, column = curr_page.text.index(INSERT).split('.')
+ self.status_bar.set_label('column', 'Col: %s' % column)
+ self.status_bar.set_label('line', 'Ln: %s' % line)
+
+- menu_specs = [
+- ("file", "_File"),
+- ("edit", "_Edit"),
+- ("format", "F_ormat"),
+- ("run", "_Run"),
+- ("options", "_Options"),
+- ("windows", "_Windows"),
+- ("help", "_Help"),
+- ]
+-
+- if macosxSupport.runningAsOSXApp():
+- del menu_specs[-3]
+- menu_specs[-2] = ("windows", "_Window")
+-
+-
+- def createmenubar(self):
+- mbar = self.menubar
+- self.menudict = menudict = {}
+- for name, label in self.menu_specs:
+- underline, label = prepstr(label)
+- menudict[name] = menu = Menu(mbar, name=name)
+- mbar.add_cascade(label=label, menu=menu, underline=underline)
+-
+- if sys.platform == 'darwin' and '.framework' in sys.executable:
+- # Insert the application menu
+- menudict['application'] = menu = Menu(mbar, name='apple')
+- mbar.add_cascade(label='IDLE', menu=menu)
+-
+- self.fill_menus()
+- self.base_helpmenu_length = self.menudict['help'].index(END)
+- self.reset_help_menu_entries()
+-
+ def postwindowsmenu(self):
+ # Only called when Windows menu exists
+ menu = self.menudict['windows']
+@@ -387,195 +286,27 @@
+ menu.delete(self.wmenu_end+1, end)
+ WindowList.add_windows_to_menu(menu)
+
+- rmenu = None
++ def newline_and_indent_event(self, event):
++ """Call newline_and_indent_event on current EditorPage."""
++ self.current_page.newline_and_indent_event(event)
+
+- def right_menu_event(self, event):
+- self.text.tag_remove("sel", "1.0", "end")
+- self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
+- if not self.rmenu:
+- self.make_rmenu()
+- rmenu = self.rmenu
+- self.event = event
+- iswin = sys.platform[:3] == 'win'
+- if iswin:
+- self.text.config(cursor="arrow")
+- rmenu.tk_popup(event.x_root, event.y_root)
+- if iswin:
+- self.text.config(cursor="ibeam")
++ def get_selection_indices(self):
++ """Call get_selection_indices on current EditorPage."""
++ return self.current_page.get_selection_indices()
+
+- rmenu_specs = [
+- # ("Label", "<<virtual-event>>"), ...
+- ("Close", "<<close-window>>"), # Example
+- ]
++ def build_char_in_string_func(self, startindex):
++ """Call build_char_in_string_func on current EditorPage."""
++ return self.current_page.build_char_in_string_func(startindex)
+
+- def make_rmenu(self):
+- rmenu = Menu(self.text, tearoff=0)
+- for label, eventname in self.rmenu_specs:
+- def command(text=self.text, eventname=eventname):
+- text.event_generate(eventname)
+- rmenu.add_command(label=label, command=command)
+- self.rmenu = rmenu
+-
+- def about_dialog(self, event=None):
+- aboutDialog.AboutDialog(self.top,'About IDLE')
+-
+- def config_dialog(self, event=None):
+- configDialog.ConfigDialog(self.top,'Settings')
+-
+- def help_dialog(self, event=None):
+- fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),'help.txt')
+- textView.view_file(self.top,'Help',fn)
+-
+- def python_docs(self, event=None):
+- if sys.platform[:3] == 'win':
+- os.startfile(self.help_url)
+- else:
+- webbrowser.open(self.help_url)
+- return "break"
+-
+- def cut(self,event):
+- self.text.event_generate("<<Cut>>")
+- return "break"
+-
+- def copy(self,event):
+- if not self.text.tag_ranges("sel"):
+- # There is no selection, so do nothing and maybe interrupt.
+- return
+- self.text.event_generate("<<Copy>>")
+- return "break"
+-
+- def paste(self,event):
+- self.text.event_generate("<<Paste>>")
+- self.text.see("insert")
+- return "break"
+-
+- def select_all(self, event=None):
+- self.text.tag_add("sel", "1.0", "end-1c")
+- self.text.mark_set("insert", "1.0")
+- self.text.see("insert")
+- return "break"
+-
+- def remove_selection(self, event=None):
+- self.text.tag_remove("sel", "1.0", "end")
+- self.text.see("insert")
+-
+- def move_at_edge_if_selection(self, edge_index):
+- """Cursor move begins at start or end of selection
+-
+- When a left/right cursor key is pressed create and return to Tkinter a
+- function which causes a cursor move from the associated edge of the
+- selection.
+-
+- """
+- self_text_index = self.text.index
+- self_text_mark_set = self.text.mark_set
+- edges_table = ("sel.first+1c", "sel.last-1c")
+- def move_at_edge(event):
+- if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
+- try:
+- self_text_index("sel.first")
+- self_text_mark_set("insert", edges_table[edge_index])
+- except TclError:
+- pass
+- return move_at_edge
+-
+- def del_word_left(self, event):
+- self.text.event_generate('<Meta-Delete>')
+- return "break"
+-
+- def del_word_right(self, event):
+- self.text.event_generate('<Meta-d>')
+- return "break"
+-
+- def find_event(self, event):
+- SearchDialog.find(self.text)
+- return "break"
+-
+- def find_again_event(self, event):
+- SearchDialog.find_again(self.text)
+- return "break"
+-
+- def find_selection_event(self, event):
+- SearchDialog.find_selection(self.text)
+- return "break"
+-
+- def find_in_files_event(self, event):
+- GrepDialog.grep(self.text, self.io, self.flist)
+- return "break"
+-
+- def replace_event(self, event):
+- ReplaceDialog.replace(self.text)
+- return "break"
+-
+- def goto_line_event(self, event):
+- text = self.text
+- lineno = tkSimpleDialog.askinteger("Goto",
+- "Go to line number:",parent=text)
+- if lineno is None:
+- return "break"
+- if lineno <= 0:
+- text.bell()
+- return "break"
+- text.mark_set("insert", "%d.0" % lineno)
+- text.see("insert")
+-
+- def open_module(self, event=None):
+- # XXX Shouldn't this be in IOBinding or in FileList?
+- try:
+- name = self.text.get("sel.first", "sel.last")
+- except TclError:
+- name = ""
+- else:
+- name = name.strip()
+- name = tkSimpleDialog.askstring("Module",
+- "Enter the name of a Python module\n"
+- "to search on sys.path and open:",
+- parent=self.text, initialvalue=name)
+- if name:
+- name = name.strip()
+- if not name:
+- return
+- # XXX Ought to insert current file's directory in front of path
+- try:
+- (f, file, (suffix, mode, type)) = _find_module(name)
+- except (NameError, ImportError), msg:
+- tkMessageBox.showerror("Import error", str(msg), parent=self.text)
+- return
+- if type != imp.PY_SOURCE:
+- tkMessageBox.showerror("Unsupported type",
+- "%s is not a source module" % name, parent=self.text)
+- return
+- if f:
+- f.close()
+- if self.flist:
+- self.flist.open(file)
+- else:
+- self.io.loadfile(file)
+-
+- def open_class_browser(self, event=None):
+- filename = self.io.filename
+- if not filename:
+- tkMessageBox.showerror(
+- "No filename",
+- "This buffer has no associated filename",
+- master=self.text)
+- self.text.focus_set()
+- return None
+- head, tail = os.path.split(filename)
+- base, ext = os.path.splitext(tail)
+- import ClassBrowser
+- ClassBrowser.ClassBrowser(self.flist, base, [head])
+-
+- def open_path_browser(self, event=None):
+- import PathBrowser
+- PathBrowser.PathBrowser(self.flist)
+-
+ def gotoline(self, lineno):
++ page = self.current_page
++ text = page.text
++
+ if lineno is not None and lineno > 0:
+- self.text.mark_set("insert", "%d.0" % lineno)
+- self.text.tag_remove("sel", "1.0", "end")
+- self.text.tag_add("sel", "insert", "insert +1l")
+- self.center()
++ text.mark_set("insert", "%d.0" % lineno)
++ text.tag_remove("sel", "1.0", "end")
++ text.tag_add("sel", "insert", "insert +1l")
++ page.center()
+
+ def ispythonsource(self, filename):
+ if not filename or os.path.isdir(filename):
+@@ -599,82 +330,59 @@
+ def set_close_hook(self, close_hook):
+ self.close_hook = close_hook
+
+- def filename_change_hook(self):
+- if self.flist:
+- self.flist.filename_changed_edit(self)
+- self.saved_change_hook()
+- self.top.update_windowlist_registry(self)
+- self.ResetColorizer()
++ def set_theme(self, ttkstyle):
++ # called from configDialog.py
++ ttkstyle.theme_use(idleConf.GetOption('main', 'Theme', 'displaytheme'))
+
+- def _addcolorizer(self):
+- if self.color:
+- return
+- if self.ispythonsource(self.io.filename):
+- self.color = self.ColorDelegator()
+- # can add more colorizers here...
+- if self.color:
+- self.per.removefilter(self.undo)
+- self.per.insertfilter(self.color)
+- self.per.insertfilter(self.undo)
+-
+- def _rmcolorizer(self):
+- if not self.color:
+- return
+- self.color.removecolors()
+- self.per.removefilter(self.color)
+- self.color = None
+-
+ def ResetColorizer(self):
+ "Update the colour theme"
+ # Called from self.filename_change_hook and from configDialog.py
+- self._rmcolorizer()
+- self._addcolorizer()
+- theme = idleConf.GetOption('main','Theme','name')
+- normal_colors = idleConf.GetHighlight(theme, 'normal')
+- cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
+- select_colors = idleConf.GetHighlight(theme, 'hilite')
+- self.text.config(
+- foreground=normal_colors['foreground'],
+- background=normal_colors['background'],
+- insertbackground=cursor_color,
+- selectforeground=select_colors['foreground'],
+- selectbackground=select_colors['background'],
+- )
++ for page in self.text_notebook.pages.itervalues():
++ page.editpage.reset_colorizer()
+
+ def ResetFont(self):
+ "Update the text widgets' font if it is changed"
+ # Called from configDialog.py
+- fontWeight='normal'
+- if idleConf.GetOption('main','EditorWindow','font-bold',type='bool'):
+- fontWeight='bold'
+- self.text.config(font=(idleConf.GetOption('main','EditorWindow','font'),
+- idleConf.GetOption('main','EditorWindow','font-size'),
++ fontWeight = 'normal'
++ if idleConf.GetOption('main', 'EditorPage', 'font-bold', type='bool'):
++ fontWeight = 'bold'
++
++ for page in self.text_notebook.pages.itervalues():
++ text = page.editpage.text
++ text.config(font=(idleConf.GetOption('main', 'EditorPage', 'font'),
++ idleConf.GetOption('main', 'EditorPage', 'font-size'),
+ fontWeight))
+
+ def RemoveKeybindings(self):
+ "Remove the keybindings before they are changed."
+ # Called from configDialog.py
+- self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
+- for event, keylist in keydefs.items():
+- self.text.event_delete(event, *keylist)
+- for extensionName in self.get_standard_extension_names():
++ Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
++
++ for page in self.text_notebook.pages.itervalues():
++ text = page.editpage.text
++ for event, keylist in keydefs.items():
++ text.event_delete(event, *keylist)
++
++ for extensionName in self._get_standard_extension_names():
+ xkeydefs = idleConf.GetExtensionBindings(extensionName)
+ if xkeydefs:
+- for event, keylist in xkeydefs.items():
+- self.text.event_delete(event, *keylist)
++ for page in self.text_notebook.pages.itervalues():
++ text = page.editpage.text
++ for event, keylist in xkeydefs.items():
++ text.event_delete(event, *keylist)
+
+ def ApplyKeybindings(self):
+ "Update the keybindings after they are changed"
+ # Called from configDialog.py
+- self.Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
++ Bindings.default_keydefs = keydefs = idleConf.GetCurrentKeySet()
+ self.apply_bindings()
+- for extensionName in self.get_standard_extension_names():
++ for extensionName in self._get_standard_extension_names():
+ xkeydefs = idleConf.GetExtensionBindings(extensionName)
+ if xkeydefs:
+ self.apply_bindings(xkeydefs)
+ #update menu accelerators
+ menuEventDict = {}
+- for menu in self.Bindings.menudefs:
++ for menu in Bindings.menudefs:
+ menuEventDict[menu[0]] = {}
+ for item in menu[1]:
+ if item:
+@@ -695,13 +403,6 @@
+ accel = get_accelerator(keydefs, event)
+ menu.entryconfig(index, accelerator=accel)
+
+- def set_notabs_indentwidth(self):
+- "Update the indentwidth if changed and not using tabs in this window"
+- # Called from configDialog.py
+- if not self.usetabs:
+- self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
+- type='int')
+-
+ def reset_help_menu_entries(self):
+ "Update the additional help entries on the Help menu"
+ help_list = idleConf.GetAllExtraHelpSourcesList()
+@@ -719,19 +420,16 @@
+ # and update the menu dictionary
+ self.menudict['help'] = helpmenu
+
+- def __extra_help_callback(self, helpfile):
+- "Create a callback with the helpfile value frozen at definition time"
+- def display_extra_help(helpfile=helpfile):
+- if not helpfile.startswith(('www', 'http')):
+- url = os.path.normpath(helpfile)
+- if sys.platform[:3] == 'win':
+- os.startfile(helpfile)
+- else:
+- webbrowser.open(helpfile)
+- return display_extra_help
++ def set_notabs_indentwidth(self):
++ "Update the indentwidth if changed and not using tabs in this window"
++ # Called from configDialog.py
++ if not self.usetabs:
++ self.indentwidth = idleConf.GetOption('main', 'Indent','num-spaces',
++ type='int')
+
+ def update_recent_files_list(self, new_file=None):
+ "Load and update the recent files list and menus"
++ # IOBinding calls this
+ rf_list = []
+ if os.path.exists(self.recent_files_path):
+ rf_list_file = open(self.recent_files_path,'r')
+@@ -761,83 +459,18 @@
+ for instance in self.top.instance_dict.keys():
+ menu = instance.recent_files_menu
+ menu.delete(1, END) # clear, and rebuild:
+- for i, file in zip(count(), rf_list):
++ for i, file in enumerate(rf_list):
+ file_name = file[0:-1] # zap \n
+ # make unicode string to display non-ASCII chars correctly
+- ufile_name = self._filename_to_unicode(file_name)
++ ufile_name = filename_to_unicode(file_name)
+ callback = instance.__recent_file_callback(file_name)
+ menu.add_command(label=ulchars[i] + " " + ufile_name,
+ command=callback,
+ underline=0)
+
+- def __recent_file_callback(self, file_name):
+- def open_recent_file(fn_closure=file_name):
+- self.io.open(editFile=fn_closure)
+- return open_recent_file
+-
+- def saved_change_hook(self):
+- short = self.short_title()
+- long = self.long_title()
+- if short and long:
+- title = short + " - " + long
+- elif short:
+- title = short
+- elif long:
+- title = long
+- else:
+- title = "Untitled"
+- icon = short or long or title
+- if not self.get_saved():
+- title = "*%s*" % title
+- icon = "*%s" % icon
+- self.top.wm_title(title)
+- self.top.wm_iconname(icon)
+-
+ def get_saved(self):
+- return self.undo.get_saved()
++ return self.current_page.undo.get_saved() # XXX check this!
+
+- def set_saved(self, flag):
+- self.undo.set_saved(flag)
+-
+- def reset_undo(self):
+- self.undo.reset_undo()
+-
+- def short_title(self):
+- filename = self.io.filename
+- if filename:
+- filename = os.path.basename(filename)
+- # return unicode string to display non-ASCII chars correctly
+- return self._filename_to_unicode(filename)
+-
+- def long_title(self):
+- # return unicode string to display non-ASCII chars correctly
+- return self._filename_to_unicode(self.io.filename or "")
+-
+- def center_insert_event(self, event):
+- self.center()
+-
+- def center(self, mark="insert"):
+- text = self.text
+- top, bot = self.getwindowlines()
+- lineno = self.getlineno(mark)
+- height = bot - top
+- newtop = max(1, lineno - height//2)
+- text.yview(float(newtop))
+-
+- def getwindowlines(self):
+- text = self.text
+- top = self.getlineno("@0,0")
+- bot = self.getlineno("@0,65535")
+- if top == bot and text.winfo_height() == 1:
+- # Geometry manager hasn't run yet
+- height = int(text['height'])
+- bot = top + height - 1
+- return top, bot
+-
+- def getlineno(self, mark="insert"):
+- text = self.text
+- return int(float(text.index(mark)))
+-
+ def get_geometry(self):
+ "Return (width, height, x, y)"
+ geom = self.top.wm_geometry()
+@@ -848,132 +481,49 @@
+ def close_event(self, event):
+ self.close()
+
+- def maybesave(self):
+- if self.io:
+- if not self.get_saved():
+- if self.top.state()!='normal':
+- self.top.deiconify()
+- self.top.lower()
+- self.top.lift()
+- return self.io.maybesave()
+-
+ def close(self):
+- reply = self.maybesave()
+- if str(reply) != "cancel":
+- self._close()
+- return reply
++ to_check = self.text_notebook.pages.copy()
+
++ while to_check:
++ curr_tab = self.text_notebook.select()
++ if TTK:
++ page_name = self.text_notebook.tab(curr_tab)['text']
++ else:
++ page_name = curr_tab
++ page = to_check.pop(page_name)
++ editpage = page.editpage
++ reply = editpage.close_tab()
++ if reply == "cancel":
++ break
++
+ def _close(self):
+- if self.io.filename:
+- self.update_recent_files_list(new_file=self.io.filename)
+ WindowList.unregister_callback(self.postwindowsmenu)
+- self.unload_extensions()
+- self.io.close()
+- self.io = None
+- self.undo = None
+- if self.color:
+- self.color.close(False)
+- self.color = None
+- self.text = None
++ self._unload_extensions()
+ self.tkinter_vars = None
+- self.per.close()
+- self.per = None
++
++ for page in self.text_notebook.pages.itervalues():
++ page.editpage.close()
++
+ self.top.destroy()
+ if self.close_hook:
+ # unless override: unregister from flist, terminate if last window
+ self.close_hook()
+
+- def load_extensions(self):
+- self.extensions = {}
+- self.load_standard_extensions()
+-
+- def unload_extensions(self):
+- for ins in self.extensions.values():
+- if hasattr(ins, "close"):
+- ins.close()
+- self.extensions = {}
+-
+- def load_standard_extensions(self):
+- for name in self.get_standard_extension_names():
+- try:
+- self.load_extension(name)
+- except:
+- print "Failed to load extension", repr(name)
+- import traceback
+- traceback.print_exc()
+-
+- def get_standard_extension_names(self):
+- return idleConf.GetExtensions(editor_only=True)
+-
+- def load_extension(self, name):
+- try:
+- mod = __import__(name, globals(), locals(), [])
+- except ImportError:
+- print "\nFailed to import extension: ", name
+- return
+- cls = getattr(mod, name)
+- keydefs = idleConf.GetExtensionBindings(name)
+- if hasattr(cls, "menudefs"):
+- self.fill_menus(cls.menudefs, keydefs)
+- ins = cls(self)
+- self.extensions[name] = ins
+- if keydefs:
+- self.apply_bindings(keydefs)
+- for vevent in keydefs.keys():
+- methodname = vevent.replace("-", "_")
+- while methodname[:1] == '<':
+- methodname = methodname[1:]
+- while methodname[-1:] == '>':
+- methodname = methodname[:-1]
+- methodname = methodname + "_event"
+- if hasattr(ins, methodname):
+- self.text.bind(vevent, getattr(ins, methodname))
+-
+- def apply_bindings(self, keydefs=None):
++ def apply_bindings(self, keydefs=None, tab=None):
+ if keydefs is None:
+- keydefs = self.Bindings.default_keydefs
+- text = self.text
+- text.keydefs = keydefs
+- for event, keylist in keydefs.items():
+- if keylist:
+- text.event_add(event, *keylist)
++ keydefs = Bindings.default_keydefs
+
+- def fill_menus(self, menudefs=None, keydefs=None):
+- """Add appropriate entries to the menus and submenus
++ if tab:
++ iter_over = [tab]
++ else:
++ iter_over = self.text_notebook.pages.itervalues()
+
+- Menus that are absent or None in self.menudict are ignored.
+- """
+- if menudefs is None:
+- menudefs = self.Bindings.menudefs
+- if keydefs is None:
+- keydefs = self.Bindings.default_keydefs
+- menudict = self.menudict
+- text = self.text
+- for mname, entrylist in menudefs:
+- menu = menudict.get(mname)
+- if not menu:
+- continue
+- for entry in entrylist:
+- if not entry:
+- menu.add_separator()
+- else:
+- label, eventname = entry
+- checkbutton = (label[:1] == '!')
+- if checkbutton:
+- label = label[1:]
+- underline, label = prepstr(label)
+- accelerator = get_accelerator(keydefs, eventname)
+- def command(text=text, eventname=eventname):
+- text.event_generate(eventname)
+- if checkbutton:
+- var = self.get_var_obj(eventname, BooleanVar)
+- menu.add_checkbutton(label=label, underline=underline,
+- command=command, accelerator=accelerator,
+- variable=var)
+- else:
+- menu.add_command(label=label, underline=underline,
+- command=command,
+- accelerator=accelerator)
++ for page in iter_over:
++ text = page.editpage.text
++ text.keydefs = keydefs
++ for event, keylist in keydefs.items():
++ if keylist:
++ text.event_add(event, *keylist)
+
+ def getvar(self, name):
+ var = self.get_var_obj(name)
+@@ -990,52 +540,25 @@
+ else:
+ raise NameError, name
+
+- def get_var_obj(self, name, vartype=None):
++ def get_var_obj(self, name, vartype=None, text=None):
+ var = self.tkinter_vars.get(name)
+ if not var and vartype:
+ # create a Tkinter variable object with self.text as master:
+- self.tkinter_vars[name] = var = vartype(self.text)
++ self.tkinter_vars[name] = var = vartype(text or self.text)
+ return var
+
+ # Tk implementations of "virtual text methods" -- each platform
+ # reusing IDLE's support code needs to define these for its GUI's
+ # flavor of widget.
+
+- # Is character at text_index in a Python string? Return 0 for
+- # "guaranteed no", true for anything else. This info is expensive
+- # to compute ab initio, but is probably already known by the
+- # platform's colorizer.
+-
+- def is_char_in_string(self, text_index):
+- if self.color:
+- # Return true iff colorizer hasn't (re)gotten this far
+- # yet, or the character is tagged as being in a string
+- return self.text.tag_prevrange("TODO", text_index) or \
+- "STRING" in self.text.tag_names(text_index)
+- else:
+- # The colorizer is missing: assume the worst
+- return 1
+-
+- # If a selection is defined in the text widget, return (start,
+- # end) as Tkinter text indices, otherwise return (None, None)
+- def get_selection_indices(self):
+- try:
+- first = self.text.index("sel.first")
+- last = self.text.index("sel.last")
+- return first, last
+- except TclError:
+- return None, None
+-
+ # Return the text widget's current view of what a tab stop means
+ # (equivalent width in spaces).
+-
+- def get_tabwidth(self):
++ def get_tabwidth(self): # XXX depends on self.text
+ current = self.text['tabs'] or TK_TABWIDTH_DEFAULT
+ return int(current)
+
+ # Set the text widget's current view of what a tab stop means.
+-
+- def set_tabwidth(self, newtabwidth):
++ def set_tabwidth(self, newtabwidth): # XXX depends on self.text
+ text = self.text
+ if self.get_tabwidth() != newtabwidth:
+ pixels = text.tk.call("font", "measure", text["font"],
+@@ -1048,7 +571,6 @@
+ # indentwidth != tabwidth set usetabs false.
+ # In any case, adjust the Text widget's view of what a tab
+ # character means.
+-
+ def set_indentation_params(self, ispythonsource, guess=True):
+ if guess and ispythonsource:
+ i = self.guess_indent()
+@@ -1058,386 +580,187 @@
+ self.usetabs = False
+ self.set_tabwidth(self.tabwidth)
+
+- def smart_backspace_event(self, event):
+- text = self.text
+- first, last = self.get_selection_indices()
+- if first and last:
+- text.delete(first, last)
+- text.mark_set("insert", first)
+- return "break"
+- # Delete whitespace left, until hitting a real char or closest
+- # preceding virtual tab stop.
+- chars = text.get("insert linestart", "insert")
+- if chars == '':
+- if text.compare("insert", ">", "1.0"):
+- # easy: delete preceding newline
+- text.delete("insert-1c")
+- else:
+- text.bell() # at start of buffer
+- return "break"
+- if chars[-1] not in " \t":
+- # easy: delete preceding real char
+- text.delete("insert-1c")
+- return "break"
+- # Ick. It may require *inserting* spaces if we back up over a
+- # tab character! This is written to be clear, not fast.
+- tabwidth = self.tabwidth
+- have = len(chars.expandtabs(tabwidth))
+- assert have > 0
+- want = ((have - 1) // self.indentwidth) * self.indentwidth
+- # Debug prompt is multilined....
+- last_line_of_prompt = sys.ps1.split('\n')[-1]
+- ncharsdeleted = 0
+- while 1:
+- if chars == last_line_of_prompt:
+- break
+- chars = chars[:-1]
+- ncharsdeleted = ncharsdeleted + 1
+- have = len(chars.expandtabs(tabwidth))
+- if have <= want or chars[-1] not in " \t":
+- break
+- text.undo_block_start()
+- text.delete("insert-%dc" % ncharsdeleted, "insert")
+- if have < want:
+- text.insert("insert", ' ' * (want - have))
+- text.undo_block_stop()
+- return "break"
++ # Guess indentwidth from text content.
++ # Return guessed indentwidth. This should not be believed unless
++ # it's in a reasonable range (e.g., it will be 0 if no indented
++ # blocks are found).
++ def guess_indent(self): # XXX depends on self.text
++ opener, indented = IndentSearcher(self.text, self.tabwidth).run()
++ if opener and indented:
++ raw, indentsmall = classifyws(opener, self.tabwidth)
++ raw, indentlarge = classifyws(indented, self.tabwidth)
++ else:
++ indentsmall = indentlarge = 0
++ return indentlarge - indentsmall
+
+- def smart_indent_event(self, event):
+- # if intraline selection:
+- # delete it
+- # elif multiline selection:
+- # do indent-region
+- # else:
+- # indent one level
+- text = self.text
+- first, last = self.get_selection_indices()
+- text.undo_block_start()
+- try:
+- if first and last:
+- if index2line(first) != index2line(last):
+- return self.indent_region_event(event)
+- text.delete(first, last)
+- text.mark_set("insert", first)
+- prefix = text.get("insert linestart", "insert")
+- raw, effective = classifyws(prefix, self.tabwidth)
+- if raw == len(prefix):
+- # only whitespace to the left
+- self.reindent_to(effective + self.indentwidth)
+- else:
+- # tab to the next 'stop' within or to right of line's text:
+- if self.usetabs:
+- pad = '\t'
+- else:
+- effective = len(prefix.expandtabs(self.tabwidth))
+- n = self.indentwidth
+- pad = ' ' * (n - effective % n)
+- text.insert("insert", pad)
+- text.see("insert")
+- return "break"
+- finally:
+- text.undo_block_stop()
++ # Private methods/attributes
+
+- def newline_and_indent_event(self, event):
+- text = self.text
+- first, last = self.get_selection_indices()
+- text.undo_block_start()
+- try:
+- if first and last:
+- text.delete(first, last)
+- text.mark_set("insert", first)
+- line = text.get("insert linestart", "insert")
+- i, n = 0, len(line)
+- while i < n and line[i] in " \t":
+- i = i+1
+- if i == n:
+- # the cursor is in or at leading indentation in a continuation
+- # line; just inject an empty line at the start
+- text.insert("insert linestart", '\n')
+- return "break"
+- indent = line[:i]
+- # strip whitespace before insert point unless it's in the prompt
+- i = 0
+- last_line_of_prompt = sys.ps1.split('\n')[-1]
+- while line and line[-1] in " \t" and line != last_line_of_prompt:
+- line = line[:-1]
+- i = i+1
+- if i:
+- text.delete("insert - %d chars" % i, "insert")
+- # strip whitespace after insert point
+- while text.get("insert") in " \t":
+- text.delete("insert")
+- # start new line
+- text.insert("insert", '\n')
++ # extensions won't have more than one instance per window
++ _unique_extensions = ['CodeContext', 'ScriptBinding', 'FormatParagraph']
+
+- # adjust indentation for continuations and block
+- # open/close first need to find the last stmt
+- lno = index2line(text.index('insert'))
+- y = PyParse.Parser(self.indentwidth, self.tabwidth)
+- if not self.context_use_ps1:
+- for context in self.num_context_lines:
+- startat = max(lno - context, 1)
+- startatindex = `startat` + ".0"
+- rawtext = text.get(startatindex, "insert")
+- y.set_str(rawtext)
+- bod = y.find_good_parse_start(
+- self.context_use_ps1,
+- self._build_char_in_string_func(startatindex))
+- if bod is not None or startat == 1:
+- break
+- y.set_lo(bod or 0)
+- else:
+- r = text.tag_prevrange("console", "insert")
+- if r:
+- startatindex = r[1]
+- else:
+- startatindex = "1.0"
+- rawtext = text.get(startatindex, "insert")
+- y.set_str(rawtext)
+- y.set_lo(0)
++ def _unload_extensions(self):
++ for ins in self.extensions.values():
++ if hasattr(ins, "close"):
++ ins.close()
++ self.extensions = {}
+
+- c = y.get_continuation_type()
+- if c != PyParse.C_NONE:
+- # The current stmt hasn't ended yet.
+- if c == PyParse.C_STRING_FIRST_LINE:
+- # after the first line of a string; do not indent at all
+- pass
+- elif c == PyParse.C_STRING_NEXT_LINES:
+- # inside a string which started before this line;
+- # just mimic the current indent
+- text.insert("insert", indent)
+- elif c == PyParse.C_BRACKET:
+- # line up with the first (if any) element of the
+- # last open bracket structure; else indent one
+- # level beyond the indent of the line with the
+- # last open bracket
+- self.reindent_to(y.compute_bracket_indent())
+- elif c == PyParse.C_BACKSLASH:
+- # if more than one line in this stmt already, just
+- # mimic the current indent; else if initial line
+- # has a start on an assignment stmt, indent to
+- # beyond leftmost =; else to beyond first chunk of
+- # non-whitespace on initial line
+- if y.get_num_lines_in_stmt() > 1:
+- text.insert("insert", indent)
+- else:
+- self.reindent_to(y.compute_backslash_indent())
+- else:
+- assert 0, "bogus continuation type %r" % (c,)
+- return "break"
++ def _load_extension(self, name, tab):
++ ext_loaded = self.extensions.get(name, None)
+
+- # This line starts a brand new stmt; indent relative to
+- # indentation of initial line of closest preceding
+- # interesting stmt.
+- indent = y.get_base_indent_string()
+- text.insert("insert", indent)
+- if y.is_block_opener():
+- self.smart_indent_event(event)
+- elif indent and y.is_block_closer():
+- self.smart_backspace_event(event)
+- return "break"
+- finally:
+- text.see("insert")
+- text.undo_block_stop()
++ try:
++ mod = __import__(name, globals(), locals(), [])
++ except ImportError:
++ print "\nFailed to import extension: ", name
++ return
+
+- # Our editwin provides a is_char_in_string function that works
+- # with a Tk text index, but PyParse only knows about offsets into
+- # a string. This builds a function for PyParse that accepts an
+- # offset.
++ keydefs = idleConf.GetExtensionBindings(name)
+
+- def _build_char_in_string_func(self, startindex):
+- def inner(offset, _startindex=startindex,
+- _icis=self.is_char_in_string):
+- return _icis(_startindex + "+%dc" % offset)
+- return inner
++ if name not in self._unique_extensions or not ext_loaded:
++ # create a new instance
++ cls = getattr(mod, name)
++ ins = cls(tab.editpage)
++ self.extensions.setdefault(name, []).append(ins)
++ if not ext_loaded:
++ # create new items in menu only if this is the first time this
++ # extension is being loaded in this window
++ if hasattr(cls, "menudefs"):
++ self._fill_menus(cls.menudefs, keydefs)
++ elif name in self._unique_extensions and ext_loaded:
++ # get an existing instance
++ ins = self.extensions[name][0]
+
+- def indent_region_event(self, event):
+- head, tail, chars, lines = self.get_region()
+- for pos in range(len(lines)):
+- line = lines[pos]
+- if line:
+- raw, effective = classifyws(line, self.tabwidth)
+- effective = effective + self.indentwidth
+- lines[pos] = self._make_blanks(effective) + line[raw:]
+- self.set_region(head, tail, chars, lines)
+- return "break"
++ if keydefs:
++ self.apply_bindings(keydefs, tab)
++ for vevent in keydefs.keys():
++ methodname = vevent.replace("-", "_")
++ while methodname[:1] == '<':
++ methodname = methodname[1:]
++ while methodname[-1:] == '>':
++ methodname = methodname[:-1]
++ methodname = methodname + "_event"
++ if hasattr(ins, methodname):
++ tab.editpage.text.bind(vevent, getattr(ins, methodname))
+
+- def dedent_region_event(self, event):
+- head, tail, chars, lines = self.get_region()
+- for pos in range(len(lines)):
+- line = lines[pos]
+- if line:
+- raw, effective = classifyws(line, self.tabwidth)
+- effective = max(effective - self.indentwidth, 0)
+- lines[pos] = self._make_blanks(effective) + line[raw:]
+- self.set_region(head, tail, chars, lines)
+- return "break"
++ def _load_extensions(self):
++ self._load_standard_extensions(self.text_notebook.last_page())
+
+- def comment_region_event(self, event):
+- head, tail, chars, lines = self.get_region()
+- for pos in range(len(lines) - 1):
+- line = lines[pos]
+- lines[pos] = '##' + line
+- self.set_region(head, tail, chars, lines)
++ def _load_standard_extensions(self, tab):
++ for name in self._get_standard_extension_names():
++ try:
++ self._load_extension(name, tab)
++ except:
++ print "Failed to load extension", repr(name)
++ traceback.print_exc()
+
+- def uncomment_region_event(self, event):
+- head, tail, chars, lines = self.get_region()
+- for pos in range(len(lines)):
+- line = lines[pos]
+- if not line:
+- continue
+- if line[:2] == '##':
+- line = line[2:]
+- elif line[:1] == '#':
+- line = line[1:]
+- lines[pos] = line
+- self.set_region(head, tail, chars, lines)
++ def _get_standard_extension_names(self):
++ return idleConf.GetExtensions(editor_only=True)
+
+- def tabify_region_event(self, event):
+- head, tail, chars, lines = self.get_region()
+- tabwidth = self._asktabwidth()
+- for pos in range(len(lines)):
+- line = lines[pos]
+- if line:
+- raw, effective = classifyws(line, tabwidth)
+- ntabs, nspaces = divmod(effective, tabwidth)
+- lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
+- self.set_region(head, tail, chars, lines)
++ def _post_tab_close(self, event):
++ if not self.current_page:
++ # no tabs now, close window
++ self._close()
++ return
+
+- def untabify_region_event(self, event):
+- head, tail, chars, lines = self.get_region()
+- tabwidth = self._asktabwidth()
+- for pos in range(len(lines)):
+- lines[pos] = lines[pos].expandtabs(tabwidth)
+- self.set_region(head, tail, chars, lines)
++ def _update_controls(self, event):
++ curr_page = self.current_page
++ if not curr_page:
++ return
+
+- def toggle_tabs_event(self, event):
+- if self.askyesno(
+- "Toggle tabs",
+- "Turn tabs " + ("on", "off")[self.usetabs] +
+- "?\nIndent width " +
+- ("will be", "remains at")[self.usetabs] + " 8." +
+- "\n Note: a tab is always 8 columns",
+- parent=self.text):
+- self.usetabs = not self.usetabs
+- # Try to prevent inconsistent indentation.
+- # User must change indent width manually after using tabs.
+- self.indentwidth = 8
+- return "break"
++ self.text = curr_page.text
++ curr_page.saved_change_hook(True, False) # update window title
++ curr_page.text.focus_set()
++ self.set_line_and_column()
+
+- # XXX this isn't bound to anything -- see tabwidth comments
+-## def change_tabwidth_event(self, event):
+-## new = self._asktabwidth()
+-## if new != self.tabwidth:
+-## self.tabwidth = new
+-## self.set_indentation_params(0, guess=0)
+-## return "break"
++ # update references in extensions that are loaded only once
++ for ext in self._unique_extensions:
++ if ext not in self.extensions:
++ continue
++ ext = self.extensions[ext][0]
++ ext.editpage = curr_page
+
+- def change_indentwidth_event(self, event):
+- new = self.askinteger(
+- "Indent width",
+- "New indent width (2-16)\n(Always use 8 when using tabs)",
+- parent=self.text,
+- initialvalue=self.indentwidth,
+- minvalue=2,
+- maxvalue=16)
+- if new and new != self.indentwidth and not self.usetabs:
+- self.indentwidth = new
+- return "break"
++ def _create_statusbar(self):
++ self.status_bar = MultiStatusBar(self.top)
++ if macosxSupport.runningAsOSXApp():
++ # Insert some padding to avoid obscuring some of the statusbar
++ # by the resize widget.
++ self.status_bar.set_label('_padding1', ' ', side=RIGHT)
++ self.status_bar.set_label('column', 'Col: ?', side=RIGHT)
++ self.status_bar.set_label('line', 'Ln: ?', side=RIGHT)
++ self.status_bar.pack(side=BOTTOM, fill=X)
+
+- def get_region(self):
+- text = self.text
+- first, last = self.get_selection_indices()
+- if first and last:
+- head = text.index(first + " linestart")
+- tail = text.index(last + "-1c lineend +1c")
+- else:
+- head = text.index("insert linestart")
+- tail = text.index("insert lineend +1c")
+- chars = text.get(head, tail)
+- lines = chars.split("\n")
+- return head, tail, chars, lines
++ def _createmenubar(self, text):
++ mbar = self.menubar
++ menudict = self.menudict
++ for name, label in self.menu_specs:
++ underline, label = prepstr(label)
++ menudict[name] = menu = Menu(mbar, name=name, tearoff=0)
++ mbar.add_cascade(label=label, menu=menu, underline=underline)
+
+- def set_region(self, head, tail, chars, lines):
+- text = self.text
+- newchars = "\n".join(lines)
+- if newchars == chars:
+- text.bell()
+- return
+- text.tag_remove("sel", "1.0", "end")
+- text.mark_set("insert", head)
+- text.undo_block_start()
+- text.delete(head, tail)
+- text.insert(head, newchars)
+- text.undo_block_stop()
+- text.tag_add("sel", head, "insert")
++ if sys.platform == 'darwin' and '.framework' in sys.executable:
++ # Insert the application menu
++ menudict['application'] = menu = Menu(mbar, name='apple')
++ mbar.add_cascade(label='IDLE', menu=menu)
+
+- # Make string that displays as n leading blanks.
++ self._fill_menus(text=text)
++ self.base_helpmenu_length = self.menudict['help'].index(END)
++ self.reset_help_menu_entries()
+
+- def _make_blanks(self, n):
+- if self.usetabs:
+- ntabs, nspaces = divmod(n, self.tabwidth)
+- return '\t' * ntabs + ' ' * nspaces
+- else:
+- return ' ' * n
++ def _fill_menus(self, menudefs=None, keydefs=None, text=None):
++ """Add appropriate entries to the menus and submenus
+
+- # Delete from beginning of line to insert point, then reinsert
+- # column logical (meaning use tabs if appropriate) spaces.
++ Menus that are absent or None in self.menudict are ignored.
++ """
++ if menudefs is None:
++ menudefs = Bindings.menudefs
++ if keydefs is None:
++ keydefs = Bindings.default_keydefs
++ menudict = self.menudict
++ for mname, entrylist in menudefs:
++ menu = menudict.get(mname)
++ if not menu:
++ continue
++ for entry in entrylist:
++ if not entry:
++ menu.add_separator()
++ else:
++ label, eventname = entry
++ checkbutton = (label[:1] == '!')
++ if checkbutton:
++ label = label[1:]
++ underline, label = prepstr(label)
++ accelerator = get_accelerator(keydefs, eventname)
++ def command(eventname=eventname):
++ self.text.event_generate(eventname)
++ if checkbutton:
++ var = self.get_var_obj(eventname, BooleanVar, text)
++ menu.add_checkbutton(label=label, underline=underline,
++ command=command, accelerator=accelerator,
++ variable=var)
++ else:
++ menu.add_command(label=label, underline=underline,
++ command=command, accelerator=accelerator)
+
+- def reindent_to(self, column):
+- text = self.text
+- text.undo_block_start()
+- if text.compare("insert linestart", "!=", "insert"):
+- text.delete("insert linestart", "insert")
+- if column:
+- text.insert("insert", self._make_blanks(column))
+- text.undo_block_stop()
++ def __recent_file_callback(self, file_name):
++ def open_recent_file(fn_closure=file_name):
++ self.current_page.io.open(editFile=fn_closure)
++ return open_recent_file
+
+- def _asktabwidth(self):
+- return self.askinteger(
+- "Tab width",
+- "Columns per tab? (2-16)",
+- parent=self.text,
+- initialvalue=self.indentwidth,
+- minvalue=2,
+- maxvalue=16) or self.tabwidth
++ def __extra_help_callback(self, helpfile):
++ "Create a callback with the helpfile value frozen at definition time"
++ def display_extra_help(helpfile=helpfile):
++ if not helpfile.startswith(('www', 'http')):
++ url = os.path.normpath(helpfile)
++ if sys.platform[:3] == 'win':
++ os.startfile(helpfile)
++ else:
++ webbrowser.open(helpfile)
++ return display_extra_help
+
+- # Guess indentwidth from text content.
+- # Return guessed indentwidth. This should not be believed unless
+- # it's in a reasonable range (e.g., it will be 0 if no indented
+- # blocks are found).
+-
+- def guess_indent(self):
+- opener, indented = IndentSearcher(self.text, self.tabwidth).run()
+- if opener and indented:
+- raw, indentsmall = classifyws(opener, self.tabwidth)
+- raw, indentlarge = classifyws(indented, self.tabwidth)
+- else:
+- indentsmall = indentlarge = 0
+- return indentlarge - indentsmall
+-
+-# "line.col" -> line, as an int
+-def index2line(index):
+- return int(float(index))
+-
+ # Look at the leading whitespace in s.
+ # Return pair (# of leading ws characters,
+ # effective # of leading blanks after expanding
+ # tabs to width tabwidth)
+
+-def classifyws(s, tabwidth):
+- raw = effective = 0
+- for ch in s:
+- if ch == ' ':
+- raw = raw + 1
+- effective = effective + 1
+- elif ch == '\t':
+- raw = raw + 1
+- effective = (effective // tabwidth + 1) * tabwidth
+- else:
+- break
+- return raw, effective
+-
+ import tokenize
+ _tokenize = tokenize
+ del tokenize
+@@ -1534,6 +857,7 @@
+
+
+ def test():
++ from Tkinter import Tk
+ root = Tk()
+ fixwordbreaks(root)
+ root.withdraw()
+Index: help.txt
+===================================================================
+--- help.txt (revision 63995)
++++ help.txt (revision 65541)
+@@ -6,6 +6,7 @@
+ File Menu:
+
+ New Window -- Create a new editing window
++ New Tab -- Create a new editing tab
+ Open... -- Open an existing file
+ Recent Files... -- Open a list of recent files
+ Open Module... -- Open an existing module (searches sys.path)
+@@ -23,6 +24,7 @@
+ ---
+ Print Window -- Print the current window
+ ---
++ Close Tab -- Close current tab (asks to save if unsaved)
+ Close -- Close current window (asks to save if unsaved)
+ Exit -- Close all windows, quit (asks to save if unsaved)
+
+Index: editorpage.py
+===================================================================
+--- editorpage.py (revision 0)
++++ editorpage.py (revision 65541)
+@@ -0,0 +1,925 @@
++import os
++import sys
++import imp
++import webbrowser
++import tkMessageBox
++import tkSimpleDialog
++from Tkinter import Text, Menu, TclError
++
++import utils
++import textView
++import aboutDialog
++import configDialog
++import macosxSupport
++import PyParse
++import IOBinding
++import GrepDialog
++import PathBrowser
++import ClassBrowser
++import SearchDialog
++import ReplaceDialog
++from configHandler import idleConf
++from MultiCall import MultiCallCreator
++from Percolator import Percolator
++
++def classifyws(s, tabwidth):
++ raw = effective = 0
++ for ch in s:
++ if ch == ' ':
++ raw = raw + 1
++ effective = effective + 1
++ elif ch == '\t':
++ raw = raw + 1
++ effective = (effective // tabwidth + 1) * tabwidth
++ else:
++ break
++ return raw, effective
++
++def index2line(index):
++ """"line.col" -> line, as an int"""
++ return int(float(index))
++
++def filename_to_unicode(filename):
++ """Convert filename to unicode in order to display it in Tk"""
++ if isinstance(filename, unicode) or not filename:
++ return filename
++ else:
++ try:
++ return filename.decode(IOBinding.filesystemencoding)
++ except UnicodeDecodeError:
++ # XXX
++ try:
++ return filename.decode(IOBinding.encoding)
++ except UnicodeDecodeError:
++ # byte-to-byte conversion
++ return filename.decode('iso8859-1')
++
++def _find_module(fullname, path=None):
++ """Version of imp.find_module() that handles hierarchical module names"""
++ file = None
++
++ for tgt in fullname.split('.'):
++ if file is not None:
++ file.close() # close intermediate files
++ (file, filename, descr) = imp.find_module(tgt, path)
++ if descr[2] == imp.PY_SOURCE:
++ break # find but not load the source file
++ module = imp.load_module(tgt, file, filename, descr)
++ try:
++ path = module.__path__
++ except AttributeError:
++ raise ImportError('No source for module %s' % module.__name__)
++
++ return file, filename, descr
++
++class EditorPage(object):
++ def __init__(self, parent_frame, editwin, title=None, **kwargs):
++ self.editwin = editwin
++ self.title = title
++ self.tab_initialized = False
++ kwargs.setdefault('width', idleConf.GetOption('main', 'EditorPage',
++ 'width'))
++ kwargs.setdefault('height', idleConf.GetOption('main', 'EditorPage',
++ 'height'))
++
++ self.text = MultiCallCreator(Text)(parent_frame, **kwargs)
++ self.color = None # initialized in reset_colorizer
++ self.per = Percolator(self.text)
++ self.undo = self.editwin.UndoDelegator()
++ self.per.insertfilter(self.undo)
++ self.text.undo_block_start = self.undo.undo_block_start
++ self.text.undo_block_stop = self.undo.undo_block_stop
++ self.io = IOBinding.IOBinding(self)
++
++ self.undo.set_saved_change_hook(self.saved_change_hook)
++ self.io.set_filename_change_hook(self.filename_change_hook)
++ self.reset_colorizer()
++ self._setup_bindings()
++
++ def post_init(self, filename=None, update_window_title=False):
++ if filename:
++ if os.path.exists(filename) and not os.path.isdir(filename):
++ self.io.loadfile(filename)
++ else:
++ self.io.set_filename(filename)
++ self.saved_change_hook(update_window_title=update_window_title)
++ self.tab_initialized = True
++
++ def close_tab(self, event=None):
++ """Close current tab, if no more tabs present, close the window."""
++ if hasattr(self.editwin, 'interp'):
++ # this is a PyShell, don't ask to save
++ reply = 'yes'
++ else:
++ reply = str(self.maybesave())
++ if reply != "cancel":
++ if self.io.filename:
++ self.editwin.update_recent_files_list(new_file=self.io.filename)
++ self.close()
++ self.editwin.text_notebook.remove_page(self.title)
++ self.editwin.top.event_generate('<<tab-closed>>')
++
++ return reply
++
++ def close(self):
++ """Perform necessary cleanup for this page before closing it."""
++ self.io.close()
++ self.io = None
++
++ self.undo = None
++
++ if self.color:
++ self.color.close(False)
++ self.color = None
++
++ self.per.close()
++ self.per = None
++ self.text = None
++
++ # XXX (1) mark where these functions are used
++ def saved_change_hook(self,update_window_title=False,update_tab_title=True):
++ short = self.editwin.short_title()
++ long = self.long_title()
++
++ if short and long:
++ title = short + " - " + long
++ tabtitle = os.path.split(long)[-1]
++ elif short:
++ title = short
++ tabtitle = short
++ elif long:
++ title = long
++ tabtitle = os.path.split(long)[-1]
++ else:
++ title = tabtitle = "Untitled"
++ icon = short or long or title
++ if not self.get_saved():
++ title = "*%s*" % title
++ tabtitle = "*%s*" % tabtitle
++ icon = "*%s" % icon
++
++ if update_tab_title:
++ self.editwin.text_notebook.update_tabtitle(self, tabtitle)
++ if update_window_title or (
++ update_window_title is None and self.tab_initialized):
++ self.editwin.top.wm_title(title)
++ self.editwin.top.wm_iconname(icon)
++
++ def get_saved(self):
++ return self.undo.get_saved()
++
++ def set_saved(self, flag):
++ self.undo.set_saved(flag)
++
++ def filename_change_hook(self):
++ if self.editwin.flist:
++ self.editwin.flist.filename_changed_edit(self, self.editwin)
++ self.saved_change_hook(self.tab_initialized)
++ self.editwin.top.update_windowlist_registry(self.editwin)
++ self.reset_colorizer()
++
++ def reset_undo(self):
++ self.undo.reset_undo()
++
++ def reset_colorizer(self):
++ "Update the colour theme"
++ # Called from self.filename_change_hook and from configDialog.py
++ self.__rmcolorizer()
++ self.__addcolorizer()
++ theme = idleConf.GetOption('main','Theme','name')
++ normal_colors = idleConf.GetHighlight(theme, 'normal')
++ cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg')
++ select_colors = idleConf.GetHighlight(theme, 'hilite')
++
++ self.text.config(
++ foreground=normal_colors['foreground'],
++ background=normal_colors['background'],
++ insertbackground=cursor_color,
++ selectforeground=select_colors['foreground'],
++ selectbackground=select_colors['background'])
++
++ def short_title(self):
++ filename = self.io.filename
++ if filename:
++ filename = os.path.basename(filename)
++ # return unicode string to display non-ASCII chars correctly
++ return filename_to_unicode(filename)
++
++ def long_title(self):
++ # return unicode string to display non-ASCII chars correctly
++ return filename_to_unicode(self.io.filename or "")
++
++ def maybesave(self):
++ if self.io:
++ if not self.get_saved():
++ if self.editwin.top.state()!= 'normal':
++ self.editiwn.top.deiconify()
++ self.editwin.top.lower()
++ self.editwin.top.lift()
++ return self.io.maybesave()
++ # XXX (1) end
++
++ def center(self, mark="insert"):
++ # Used by EditorWindow.gotoline
++ text = self.text
++ top, bot = self._getwindowlines()
++ lineno = self._getlineno(mark)
++ height = bot - top
++ newtop = max(1, lineno - height//2)
++ text.yview(float(newtop))
++
++ def newline_and_indent_event(self, event):
++ # Used by EditorWindow.newline_and_indent_event which is used by PyShell
++ text = self.text
++ first, last = self.get_selection_indices()
++ text.undo_block_start()
++ try:
++ if first and last:
++ text.delete(first, last)
++ text.mark_set("insert", first)
++ line = text.get("insert linestart", "insert")
++ i, n = 0, len(line)
++ while i < n and line[i] in " \t":
++ i = i+1
++ if i == n:
++ # the cursor is in or at leading indentation in a continuation
++ # line; just inject an empty line at the start
++ text.insert("insert linestart", '\n')
++ return "break"
++ indent = line[:i]
++ # strip whitespace before insert point unless it's in the prompt
++ i = 0
++ last_line_of_prompt = sys.ps1.split('\n')[-1]
++ while line and line[-1] in " \t" and line != last_line_of_prompt:
++ line = line[:-1]
++ i = i+1
++ if i:
++ text.delete("insert - %d chars" % i, "insert")
++ # strip whitespace after insert point
++ while text.get("insert") in " \t":
++ text.delete("insert")
++ # start new line
++ text.insert("insert", '\n')
++
++ # adjust indentation for continuations and block
++ # open/close first need to find the last stmt
++ lno = index2line(text.index('insert'))
++ #print self.editwin.indentwidth, self.editwin.tabwidth
++ y = PyParse.Parser(self.editwin.indentwidth, self.editwin.tabwidth)
++ if not self.editwin.context_use_ps1:
++ for context in self.editwin.num_context_lines:
++ startat = max(lno - context, 1)
++ startatindex = `startat` + ".0"
++ rawtext = text.get(startatindex, "insert")
++ y.set_str(rawtext)
++ bod = y.find_good_parse_start(
++ self.editwin.context_use_ps1,
++ self.build_char_in_string_func(startatindex))
++ if bod is not None or startat == 1:
++ break
++ y.set_lo(bod or 0)
++ else:
++ r = text.tag_prevrange("console", "insert")
++ if r:
++ startatindex = r[1]
++ else:
++ startatindex = "1.0"
++ rawtext = text.get(startatindex, "insert")
++ y.set_str(rawtext)
++ y.set_lo(0)
++
++ c = y.get_continuation_type()
++ if c != PyParse.C_NONE:
++ # The current stmt hasn't ended yet.
++ if c == PyParse.C_STRING_FIRST_LINE:
++ # after the first line of a string; do not indent at all
++ pass
++ elif c == PyParse.C_STRING_NEXT_LINES:
++ # inside a string which started before this line;
++ # just mimic the current indent
++ text.insert("insert", indent)
++ elif c == PyParse.C_BRACKET:
++ # line up with the first (if any) element of the
++ # last open bracket structure; else indent one
++ # level beyond the indent of the line with the
++ # last open bracket
++ self.__reindent_to(y.compute_bracket_indent())
++ elif c == PyParse.C_BACKSLASH:
++ # if more than one line in this stmt already, just
++ # mimic the current indent; else if initial line
++ # has a start on an assignment stmt, indent to
++ # beyond leftmost =; else to beyond first chunk of
++ # non-whitespace on initial line
++ if y.get_num_lines_in_stmt() > 1:
++ text.insert("insert", indent)
++ else:
++ self.__reindent_to(y.compute_backslash_indent())
++ else:
++ assert 0, "bogus continuation type %r" % (c,)
++ return "break"
++
++ # This line starts a brand new stmt; indent relative to
++ # indentation of initial line of closest preceding
++ # interesting stmt.
++ indent = y.get_base_indent_string()
++ text.insert("insert", indent)
++ if y.is_block_opener():
++ self._smart_indent_event(event)
++ elif indent and y.is_block_closer():
++ self._smart_backspace_event(event)
++ return "break"
++ finally:
++ text.see("insert")
++ text.undo_block_stop()
++
++ # If a selection is defined in the text widget, return (start,
++ # end) as Tkinter text indices, otherwise return (None, None)
++ def get_selection_indices(self):
++ # Used by EditorWindow.get_selection_indices which is used by
++ # FormatParagraph
++ try:
++ first = self.text.index("sel.first")
++ last = self.text.index("sel.last")
++ return first, last
++ except TclError:
++ return None, None
++
++ # Our editpage provides a __is_char_in_string function that works
++ # with a Tk text index, but PyParse only knows about offsets into
++ # a string. This builds a function for PyParse that accepts an
++ # offset.
++ def build_char_in_string_func(self, startindex):
++ # Used by EditorWindow.build_char_in_string_func which is used by
++ # HyperParser
++ def inner(offset, _startindex=startindex,
++ _icis=self.__is_char_in_string):
++ return _icis(_startindex + "+%dc" % offset)
++ return inner
++
++ def _setup_bindings(self):
++ text = self.text
++ def bind_them(to_bind, prefix='_%s'):
++ for tb in to_bind:
++ prefix_size = tb.count('<')
++ method_name = tb[prefix_size:-prefix_size].replace('-', '_')
++ text.bind(tb, getattr(self, prefix % method_name.lower()))
++
++ actions = ('<<help>>', '<<python-docs>>',
++ '<<about-idle>>', '<<open-config-dialog>>', '<<open-module>>',
++ '<<cut>>', '<<copy>>', '<<paste>>', '<<select-all>>',
++ '<<remove-selection>>', '<<del-word-left>>', '<<del-word-right>>',
++ '<<beginning-of-line>>')
++ events = ('<<find>>', '<<center-insert>>', '<<find-again>>',
++ '<<find-in-files>>', '<<find-selection>>', '<<replace>>',
++ '<<goto-line>>', '<<smart-backspace>>', '<<smart-indent>>',
++ '<<indent-region>>', '<<dedent-region>>', '<<comment-region>>',
++ '<<tabify-region>>', '<<untabify-region>>', '<<toggle-tabs>>',
++ '<<change-indentwidth>>')
++ parent_actions = ('<<new-tab>>', '<<next-tab>>', '<<prev-tab>>')
++
++ bind_them(actions)
++ bind_them(events, prefix="_%s_event")
++ for action in parent_actions:
++ prefix_size = action.count('<')
++ method_name = action[prefix_size:-prefix_size].replace('-', '_')
++ text.bind(action, getattr(self.editwin, method_name))
++
++ text.bind('<<close-tab>>', self.close_tab)
++ text.bind('<<newline-and-indent>>', self.newline_and_indent_event)
++ text.bind("<<do-nothing>>", lambda event: "break")
++ text.bind("<Left>", self._move_at_edge_if_selection(0))
++ text.bind("<Right>", self._move_at_edge_if_selection(1))
++ text.bind("<3>", self._right_menu)
++ text.bind('<<set-line-and-column>>', self.editwin.set_line_and_column)
++ text.event_add("<<set-line-and-column>>",
++ "<KeyRelease>", "<ButtonRelease>")
++
++ if self.editwin.flist:
++ text.bind("<<open-new-window>>",
++ utils.callback(self.editwin.new_callback, self))
++ text.bind("<<close-all-windows>>",
++ self.editwin.flist.close_all_callback)
++ text.bind("<<open-class-browser>>", self._open_class_browser)
++ text.bind("<<open-path-browser>>", self._open_path_browser)
++
++ if macosxSupport.runningAsOSXApp():
++ # Command-W on editorwindows doesn't work without this.
++ text.bind('<<close-window>>', self.editwin.close_event)
++
++ def _help(self, event=None):
++ fn = os.path.join(os.path.abspath(os.path.dirname(__file__)),
++ 'help.txt')
++ textView.view_file(self.text, 'Help', fn)
++
++ def _python_docs(self, event=None):
++ if sys.platform[:3] == 'win':
++ os.startfile(self.editwin.help_url)
++ else:
++ webbrowser.open(self.editwin.help_url)
++ return "break"
++
++ def _about_idle(self, event=None):
++ aboutDialog.AboutDialog(self.text, 'About IDLE')
++
++ def _open_class_browser(self, event=None):
++ filename = self.io.filename
++ if not filename:
++ tkMessageBox.showerror("No filename",
++ "This buffer has no associated filename",
++ master=self.text)
++ self.text.focus_set()
++ return None
++ head, tail = os.path.split(filename)
++ base, ext = os.path.splitext(tail)
++ ClassBrowser.ClassBrowser(self.editwin.flist, base, [head])
++
++ def _open_path_browser(self, event=None):
++ PathBrowser.PathBrowser(self.editwin.flist)
++
++ def _open_config_dialog(self, event=None):
++ # When changing colors and saving it, it requires the attribute
++ # instance_dict making necessary to pass self.editwin.top as the
++ # parent
++ configDialog.ConfigDialog(self.editwin.top, 'Settings')
++
++ def _open_module(self, event=None):
++ try:
++ name = self.text.get("sel.first", "sel.last")
++ except TclError:
++ name = ""
++ else:
++ name = name.strip()
++
++ name = tkSimpleDialog.askstring("Module",
++ "Enter the name of a Python module\n"
++ "to search on sys.path and open:",
++ parent=self.text, initialvalue=name)
++
++ if name:
++ name = name.strip()
++ if not name:
++ return
++ # XXX Ought to insert current file's directory in front of path
++ try:
++ (f, file, (suffix, mode, type)) = _find_module(name)
++ except (NameError, ImportError), msg:
++ tkMessageBox.showerror("Import error", str(msg), parent=self.text)
++ return
++
++ if type != imp.PY_SOURCE:
++ tkMessageBox.showerror("Unsupported type",
++ "%s is not a source module" % name, parent=self.text)
++ return
++ if f:
++ f.close()
++ if self.editwin.flist:
++ if idleConf.GetOption('main', 'EditorWindow', 'file-in-tab',
++ default=1, type='bool'):
++ if hasattr(self.editwin, 'interp'):
++ # PyShell window must open a module in a new window
++ action = None
++ else:
++ action = self.editwin.new_tab
++ else:
++ action = None
++ self.editwin.flist.open(file, action)
++ else:
++ self.io.loadfile(file)
++
++ def _find_event(self, event):
++ SearchDialog.find(self.text)
++ return "break"
++
++ def _find_again_event(self, event):
++ SearchDialog.find_again(self.text)
++ return "break"
++
++ def _find_selection_event(self, event):
++ SearchDialog.find_selection(self.text)
++ return "break"
++
++ def _find_in_files_event(self, event):
++ # XXX not expected to work correctly for now
++ GrepDialog.grep(self.text, self.editwin.io, self.editwin.flist)
++ return "break"
++
++ def _replace_event(self, event):
++ ReplaceDialog.replace(self.text)
++ return "break"
++
++ def _center_insert_event(self, event):
++ self.center()
++
++ def _getwindowlines(self):
++ text = self.text
++ top = self._getlineno("@0,0")
++ bot = self._getlineno("@0,65535")
++ if top == bot and text.winfo_height() == 1:
++ # Geometry manager hasn't run yet
++ height = int(text['height'])
++ bot = top + height - 1
++ return top, bot
++
++ def _getlineno(self, mark="insert"):
++ return int(float(self.text.index(mark)))
++
++ def _goto_line_event(self, event):
++ text = self.text
++ lineno = tkSimpleDialog.askinteger("Goto", "Go to line number:",
++ parent=text)
++
++ if lineno is None:
++ return "break"
++
++ if lineno <= 0:
++ text.bell()
++ return "break"
++
++ text.mark_set("insert", "%d.0" % lineno)
++ text.see("insert")
++
++ def _cut(self, event):
++ self.text.event_generate("<<Cut>>")
++ return "break"
++
++ def _copy(self, event):
++ if not self.text.tag_ranges("sel"):
++ # There is no selection, so do nothing and maybe interrupt.
++ return
++
++ self.text.event_generate("<<Copy>>")
++ return "break"
++
++ def _paste(self, event):
++ self.text.event_generate("<<Paste>>")
++ self.text.see("insert")
++ return "break"
++
++ def _select_all(self, event=None):
++ self.text.tag_add("sel", "1.0", "end-1c")
++ self.text.mark_set("insert", "1.0")
++ self.text.see("insert")
++ return "break"
++
++ def _remove_selection(self, event=None):
++ self.text.tag_remove("sel", "1.0", "end")
++ self.text.see("insert")
++
++ def _del_word_left(self, event):
++ self.text.event_generate('<Meta-Delete>')
++ return "break"
++
++ def _del_word_right(self, event):
++ self.text.event_generate('<Meta-d>')
++ return "break"
++
++ def _move_at_edge_if_selection(self, edge_index):
++ """Cursor move begins at start or end of selection
++ When a left/right cursor key is pressed create and return to Tkinter a
++ function which causes a cursor move from the associated edge of the
++ selection.
++ """
++ text_index = self.text.index
++ text_mark_set = self.text.mark_set
++ edges_table = ("sel.first+1c", "sel.last-1c")
++
++ def move_at_edge(event):
++ if (event.state & 5) == 0: # no shift(==1) or control(==4) pressed
++ try:
++ text_index("sel.first")
++ text_mark_set("insert", edges_table[edge_index])
++ except TclError:
++ pass
++
++ return move_at_edge
++
++ def _beginning_of_line(self, event):
++ if (event.state & 12) != 0 and event.keysym == "Home":
++ # state&1==shift, state&4==control, state&8==alt
++ return # <Modifier-Home>; fall back to class binding
++
++ text = self.text
++
++ if text.index("iomark") and \
++ text.compare("iomark", "<=", "insert lineend") and \
++ text.compare("insert linestart", "<=", "iomark"):
++ insertpt = int(text.index("iomark").split(".")[1])
++ else:
++ line = text.get("insert linestart", "insert lineend")
++ for insertpt in xrange(len(line)):
++ if line[insertpt] not in (' ','\t'):
++ break
++ else:
++ insertpt=len(line)
++
++ lineat = int(text.index("insert").split('.')[1])
++
++ if insertpt == lineat:
++ insertpt = 0
++
++ dest = "insert linestart+%sc" % str(insertpt)
++
++ if (event.state & 1) == 0:
++ # shift not pressed
++ text.tag_remove("sel", "1.0", "end")
++ else:
++ if not text.index("sel.first"):
++ text.mark_set("anchor", "insert")
++
++ first = text.index(dest)
++ last = text.index("anchor")
++
++ if text.compare(first, ">", last):
++ first, last = last, first
++
++ text.tag_remove("sel", "1.0", "end")
++ text.tag_add("sel", first, last)
++
++ text.mark_set("insert", dest)
++ text.see("insert")
++ return "break"
++
++ def _smart_backspace_event(self, event):
++ text = self.text
++ first, last = self.get_selection_indices()
++ if first and last:
++ text.delete(first, last)
++ text.mark_set("insert", first)
++ return "break"
++ # Delete whitespace left, until hitting a real char or closest
++ # preceding virtual tab stop.
++ chars = text.get("insert linestart", "insert")
++ if chars == '':
++ if text.compare("insert", ">", "1.0"):
++ # easy: delete preceding newline
++ text.delete("insert-1c")
++ else:
++ text.bell() # at start of buffer
++ return "break"
++ if chars[-1] not in " \t":
++ # easy: delete preceding real char
++ text.delete("insert-1c")
++ return "break"
++ # Ick. It may require *inserting* spaces if we back up over a
++ # tab character! This is written to be clear, not fast.
++ tabwidth = self.editwin.tabwidth
++ have = len(chars.expandtabs(tabwidth))
++ assert have > 0
++ want = ((have - 1) //
++ self.editwin.indentwidth) * self.editwin.indentwidth
++ # Debug prompt is multilined....
++ last_line_of_prompt = sys.ps1.split('\n')[-1]
++ ncharsdeleted = 0
++ while 1:
++ if chars == last_line_of_prompt:
++ break
++ chars = chars[:-1]
++ ncharsdeleted = ncharsdeleted + 1
++ have = len(chars.expandtabs(tabwidth))
++ if have <= want or chars[-1] not in " \t":
++ break
++ text.undo_block_start()
++ text.delete("insert-%dc" % ncharsdeleted, "insert")
++ if have < want:
++ text.insert("insert", ' ' * (want - have))
++ text.undo_block_stop()
++ return "break"
++
++ def _smart_indent_event(self, event):
++ # if intraline selection:
++ # delete it
++ # elif multiline selection:
++ # do indent-region
++ # else:
++ # indent one level
++ text = self.text
++ first, last = self.get_selection_indices()
++ text.undo_block_start()
++ try:
++ if first and last:
++ if index2line(first) != index2line(last):
++ return self._indent_region_event(event)
++ text.delete(first, last)
++ text.mark_set("insert", first)
++ prefix = text.get("insert linestart", "insert")
++ raw, effective = classifyws(prefix, self.editwin.tabwidth)
++ if raw == len(prefix):
++ # only whitespace to the left
++ self.__reindent_to(effective + self.editwin.indentwidth)
++ else:
++ # tab to the next 'stop' within or to right of line's text:
++ if self.editwin.usetabs:
++ pad = '\t'
++ else:
++ effective = len(prefix.expandtabs(self.editwin.tabwidth))
++ n = self.editwin.indentwidth
++ pad = ' ' * (n - effective % n)
++ text.insert("insert", pad)
++ text.see("insert")
++ return "break"
++ finally:
++ text.undo_block_stop()
++
++ def _indent_region_event(self, event):
++ head, tail, chars, lines = self.__get_region()
++ for pos in range(len(lines)):
++ line = lines[pos]
++ if line:
++ raw, effective = classifyws(line, self.editwin.tabwidth)
++ effective = effective + self.editwin.indentwidth
++ lines[pos] = self.__make_blanks(effective) + line[raw:]
++ self.__set_region(head, tail, chars, lines)
++ return "break"
++
++ def _dedent_region_event(self, event):
++ head, tail, chars, lines = self.__get_region()
++ for pos in range(len(lines)):
++ line = lines[pos]
++ if line:
++ raw, effective = classifyws(line, self.editwin.tabwidth)
++ effective = max(effective - self.editwin.indentwidth, 0)
++ lines[pos] = self.__make_blanks(effective) + line[raw:]
++ self.__set_region(head, tail, chars, lines)
++ return "break"
++
++ def _comment_region_event(self, event):
++ head, tail, chars, lines = self.__get_region()
++ for pos in range(len(lines) - 1):
++ line = lines[pos]
++ lines[pos] = '##' + line
++ self.__set_region(head, tail, chars, lines)
++
++ def _uncomment_region_event(self, event):
++ head, tail, chars, lines = self.__get_region()
++ for pos in range(len(lines)):
++ line = lines[pos]
++ if not line:
++ continue
++ if line[:2] == '##':
++ line = line[2:]
++ elif line[:1] == '#':
++ line = line[1:]
++ lines[pos] = line
++ self.__set_region(head, tail, chars, lines)
++
++ def _tabify_region_event(self, event):
++ head, tail, chars, lines = self.__get_region()
++ tabwidth = self.__asktabwidth()
++ for pos in range(len(lines)):
++ line = lines[pos]
++ if line:
++ raw, effective = classifyws(line, tabwidth)
++ ntabs, nspaces = divmod(effective, tabwidth)
++ lines[pos] = '\t' * ntabs + ' ' * nspaces + line[raw:]
++ self.__set_region(head, tail, chars, lines)
++
++ def _untabify_region_event(self, event):
++ head, tail, chars, lines = self.__get_region()
++ tabwidth = self.__asktabwidth()
++ for pos in range(len(lines)):
++ lines[pos] = lines[pos].expandtabs(tabwidth)
++ self.__set_region(head, tail, chars, lines)
++
++ def _toggle_tabs_event(self, event):
++ if self.editwin.askyesno(
++ "Toggle tabs",
++ "Turn tabs " + ("on", "off")[self.editwin.usetabs] +
++ "?\nIndent width " +
++ ("will be", "remains at")[self.editwin.usetabs] + " 8." +
++ "\n Note: a tab is always 8 columns",
++ parent=self.text):
++ self.editwin.usetabs = not self.editwin.usetabs
++ # Try to prevent inconsistent indentation.
++ # User must change indent width manually after using tabs.
++ self.editwin.indentwidth = 8
++ return "break"
++
++ def _change_indentwidth_event(self, event):
++ new = self.editwin.askinteger(
++ "Indent width",
++ "New indent width (2-16)\n(Always use 8 when using tabs)",
++ parent=self.text,
++ initialvalue=self.editwin.indentwidth,
++ minvalue=2,
++ maxvalue=16)
++ if new and new != self.editwin.indentwidth and not self.editwin.usetabs:
++ self.editwin.indentwidth = new
++ return "break"
++
++ def _right_menu(self, event):
++ self.text.tag_remove("sel", "1.0", "end")
++ self.text.mark_set("insert", "@%d,%d" % (event.x, event.y))
++
++ if not self.editwin.rmenu:
++ self.__make_rmenu()
++
++ rmenu = self.editwin.rmenu
++ self.event = event
++ iswin = sys.platform[:3] == 'win'
++ if iswin:
++ self.text.config(cursor="arrow")
++ rmenu.tk_popup(event.x_root, event.y_root)
++ if iswin:
++ self.text.config(cursor="ibeam")
++
++ def __make_rmenu(self):
++ rmenu = Menu(self.text, tearoff=False)
++
++ for label, eventname in self.editwin.rmenu_specs:
++ def command(text=self.text, eventname=eventname):
++ text.event_generate(eventname)
++ rmenu.add_command(label=label, command=command)
++
++ self.editwin.rmenu = rmenu
++
++ def __rmcolorizer(self):
++ if not self.color:
++ return
++ self.color.removecolors()
++ self.per.removefilter(self.color)
++ self.color = None
++
++ def __addcolorizer(self):
++ if self.color:
++ return
++ if self.editwin.ispythonsource(self.io.filename):
++ self.color = self.editwin.ColorDelegator()
++
++ # can add more colorizers here...
++ if self.color:
++ self.per.removefilter(self.undo)
++ self.per.insertfilter(self.color)
++ self.per.insertfilter(self.undo)
++
++ def __asktabwidth(self):
++ return self.editwin.askinteger(
++ "Tab width",
++ "Columns per tab? (2-16)",
++ parent=self.text,
++ initialvalue=self.editwin.indentwidth,
++ minvalue=2,
++ maxvalue=16) or self.editwin.tabwidth
++
++ # Make string that displays as n leading blanks.
++ def __make_blanks(self, n):
++ if self.editwin.usetabs:
++ ntabs, nspaces = divmod(n, self.editwin.tabwidth)
++ return '\t' * ntabs + ' ' * nspaces
++ else:
++ return ' ' * n
++
++ def __get_region(self):
++ text = self.text
++ first, last = self.get_selection_indices()
++ if first and last:
++ head = text.index(first + " linestart")
++ tail = text.index(last + "-1c lineend +1c")
++ else:
++ head = text.index("insert linestart")
++ tail = text.index("insert lineend +1c")
++ chars = text.get(head, tail)
++ lines = chars.split("\n")
++ return head, tail, chars, lines
++
++ def __set_region(self, head, tail, chars, lines):
++ text = self.text
++ newchars = "\n".join(lines)
++ if newchars == chars:
++ text.bell()
++ return
++ text.tag_remove("sel", "1.0", "end")
++ text.mark_set("insert", head)
++ text.undo_block_start()
++ text.delete(head, tail)
++ text.insert(head, newchars)
++ text.undo_block_stop()
++ text.tag_add("sel", head, "insert")
++
++ # Delete from beginning of line to insert point, then reinsert
++ # column logical (meaning use tabs if appropriate) spaces.
++ def __reindent_to(self, column):
++ text = self.text
++ text.undo_block_start()
++ if text.compare("insert linestart", "!=", "insert"):
++ text.delete("insert linestart", "insert")
++ if column:
++ text.insert("insert", self.__make_blanks(column))
++ text.undo_block_stop()
++
++ # Tk implementations of "virtual text methods" -- each platform
++ # reusing IDLE's support code needs to define these for its GUI's
++ # flavor of widget.
++
++ # Is character at text_index in a Python string? Return 0 for
++ # "guaranteed no", true for anything else. This info is expensive
++ # to compute ab initio, but is probably already known by the
++ # platform's colorizer.
++ def __is_char_in_string(self, text_index):
++ if self.color:
++ # Return true iff colorizer hasn't (re)gotten this far
++ # yet, or the character is tagged as being in a string
++ return self.text.tag_prevrange("TODO", text_index) or \
++ "STRING" in self.text.tag_names(text_index)
++ else:
++ # The colorizer is missing: assume the worst
++ return 1
+Index: OutputWindow.py
+===================================================================
+--- OutputWindow.py (revision 63995)
++++ OutputWindow.py (revision 65541)
+@@ -1,8 +1,9 @@
+-from Tkinter import *
+-from EditorWindow import EditorWindow
+ import re
+ import tkMessageBox
++
++import utils
+ import IOBinding
++from EditorWindow import EditorWindow
+
+ class OutputWindow(EditorWindow):
+
+@@ -14,8 +15,15 @@
+
+ def __init__(self, *args):
+ EditorWindow.__init__(self, *args)
+- self.text.bind("<<goto-file-line>>", self.goto_file_line)
++ self.top.bind("<<tab-created>>", self.__configure_new_tab)
++ # configure the tab created in EditorWindow.__init__
++ self.__configure_new_tab()
+
++ def __configure_new_tab(self, event=None):
++ page = self.text_notebook.last_page().editpage
++ page.text.bind("<<goto-file-line>>", utils.callback(self.goto_file_line,
++ page.text))
++
+ # Customize EditorWindow
+
+ def ispythonsource(self, filename):
+@@ -34,7 +42,8 @@
+
+ # Act as output file
+
+- def write(self, s, tags=(), mark="insert"):
++ def write(self, s, tags=(), mark="insert", text=None):
++ assert text is not None
+ # Tk assumes that byte strings are Latin-1;
+ # we assume that they are in the locale's encoding
+ if isinstance(s, str):
+@@ -43,12 +52,12 @@
+ except UnicodeError:
+ # some other encoding; let Tcl deal with it
+ pass
+- self.text.insert(mark, s, tags)
+- self.text.see(mark)
+- self.text.update()
++ text.insert(mark, s, tags)
++ text.see(mark)
++ text.update()
+
+ def writelines(self, l):
+- map(self.write, l)
++ map(self.write, l, text=self.current_page.text)
+
+ def flush(self):
+ pass
+@@ -67,28 +76,26 @@
+
+ file_line_progs = None
+
+- def goto_file_line(self, event=None):
++ def goto_file_line(self, event, text):
+ if self.file_line_progs is None:
+ l = []
+ for pat in self.file_line_pats:
+ l.append(re.compile(pat, re.IGNORECASE))
+ self.file_line_progs = l
+- # x, y = self.event.x, self.event.y
+- # self.text.mark_set("insert", "@%d,%d" % (x, y))
+- line = self.text.get("insert linestart", "insert lineend")
++
++ line = text.get("insert linestart", "insert lineend")
+ result = self._file_line_helper(line)
+ if not result:
+ # Try the previous line. This is handy e.g. in tracebacks,
+ # where you tend to right-click on the displayed source line
+- line = self.text.get("insert -1line linestart",
+- "insert -1line lineend")
++ line = text.get("insert -1line linestart", "insert -1line ineend")
+ result = self._file_line_helper(line)
+ if not result:
+ tkMessageBox.showerror(
+ "No special line",
+ "The line you point at doesn't look like "
+ "a valid file name followed by a line number.",
+- master=self.text)
++ master=text)
+ return
+ filename, lineno = result
+ edit = self.flist.open(filename)
+@@ -111,47 +118,3 @@
+ return filename, int(lineno)
+ except TypeError:
+ return None
+-
+-# These classes are currently not used but might come in handy
+-
+-class OnDemandOutputWindow:
+-
+- tagdefs = {
+- # XXX Should use IdlePrefs.ColorPrefs
+- "stdout": {"foreground": "blue"},
+- "stderr": {"foreground": "#007700"},
+- }
+-
+- def __init__(self, flist):
+- self.flist = flist
+- self.owin = None
+-
+- def write(self, s, tags, mark):
+- if not self.owin:
+- self.setup()
+- self.owin.write(s, tags, mark)
+-
+- def setup(self):
+- self.owin = owin = OutputWindow(self.flist)
+- text = owin.text
+- for tag, cnf in self.tagdefs.items():
+- if cnf:
+- text.tag_configure(tag, **cnf)
+- text.tag_raise('sel')
+- self.write = self.owin.write
+-
+-#class PseudoFile:
+-#
+-# def __init__(self, owin, tags, mark="end"):
+-# self.owin = owin
+-# self.tags = tags
+-# self.mark = mark
+-
+-# def write(self, s):
+-# self.owin.write(s, self.tags, self.mark)
+-
+-# def writelines(self, l):
+-# map(self.write, l)
+-
+-# def flush(self):
+-# pass
+Index: aboutDialog.py
+===================================================================
+--- aboutDialog.py (revision 63995)
++++ aboutDialog.py (revision 65541)
+@@ -1,13 +1,18 @@
+-"""About Dialog for IDLE
++"""About Dialog for IDLE"""
++import os
++import sys
++from Tkinter import Toplevel, Frame, Button, Label, TkVersion
++from Tkconstants import LEFT, NSEW, SUNKEN, EW, W, BOTH, TOP, BOTTOM
+
+-"""
+-
+-from Tkinter import *
+-import os
+-import os.path
++import idlever
+ import textView
+-import idlever
++from stylist import PoorManStyle
++from configHandler import idleConf
+
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++if TTK:
++ from ttk import Frame, Button, Label, Style
++
+ class AboutDialog(Toplevel):
+ """Modal about dialog for idle
+
+@@ -15,12 +20,15 @@
+ def __init__(self,parent,title):
+ Toplevel.__init__(self, parent)
+ self.configure(borderwidth=5)
++
+ self.geometry("+%d+%d" % (parent.winfo_rootx()+30,
+ parent.winfo_rooty()+30))
+ self.bg = "#707070"
+ self.fg = "#ffffff"
++
++ self.SetupStyles()
+ self.CreateWidgets()
+- self.resizable(height=FALSE, width=FALSE)
++ self.resizable(height=False, width=False)
+ self.title(title)
+ self.transient(parent)
+ self.grab_set()
+@@ -31,40 +39,44 @@
+ self.bind('<Escape>',self.Ok) #dismiss dialog
+ self.wait_window()
+
++ def SetupStyles(self):
++ if TTK:
++ style = Style(self.master)
++ style.configure('Color.TLabel', foreground=self.fg,
++ background=self.bg)
++ style.configure('Color.TFrame', background=self.bg)
++ self.ttkstyle = style
++ self.style = lambda w, style: w.configure(style=style)
++ else:
++ self.style = PoorManStyle(self,
++ styles={'Color.TLabel': {'fg': self.fg, 'bg': self.bg},
++ 'Color.TFrame': {'bg': self.bg}}).style_it
++
+ def CreateWidgets(self):
+ frameMain = Frame(self, borderwidth=2, relief=SUNKEN)
+ frameButtons = Frame(self)
+- frameButtons.pack(side=BOTTOM, fill=X)
+- frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
+- self.buttonOk = Button(frameButtons, text='Close',
+- command=self.Ok)
+- self.buttonOk.pack(padx=5, pady=5)
+- #self.picture = Image('photo', data=self.pictureData)
+- frameBg = Frame(frameMain, bg=self.bg)
+- frameBg.pack(expand=TRUE, fill=BOTH)
+- labelTitle = Label(frameBg, text='IDLE', fg=self.fg, bg=self.bg,
+- font=('courier', 24, 'bold'))
++ frameButtons.pack(side=BOTTOM, pady=3)
++ frameMain.pack(side=TOP, expand=True, fill=BOTH)
++ self.buttonOk = Button(frameButtons, text='Close', command=self.Ok)
++ self.buttonOk.pack()
++ frameBg = Frame(frameMain)
++ frameBg.pack(expand=True, fill=BOTH)
++ labelTitle = Label(frameBg, text='IDLE', font=('courier', 24, 'bold'))
+ labelTitle.grid(row=0, column=0, sticky=W, padx=10, pady=10)
+- #labelPicture = Label(frameBg, text='[picture]')
+- #image=self.picture, bg=self.bg)
+- #labelPicture.grid(row=1, column=1, sticky=W, rowspan=2,
+- # padx=0, pady=3)
+ byline = "Python's Integrated DeveLopment Environment" + 5*'\n'
+- labelDesc = Label(frameBg, text=byline, justify=LEFT,
+- fg=self.fg, bg=self.bg)
++ labelDesc = Label(frameBg, text=byline, justify=LEFT)
+ labelDesc.grid(row=2, column=0, sticky=W, columnspan=3, padx=10, pady=5)
+ labelEmail = Label(frameBg, text='email: idle-dev at python.org',
+- justify=LEFT, fg=self.fg, bg=self.bg)
++ justify=LEFT)
+ labelEmail.grid(row=6, column=0, columnspan=2,
+ sticky=W, padx=10, pady=0)
+ labelWWW = Label(frameBg, text='www: http://www.python.org/idle/',
+- justify=LEFT, fg=self.fg, bg=self.bg)
++ justify=LEFT)
+ labelWWW.grid(row=7, column=0, columnspan=2, sticky=W, padx=10, pady=0)
+- Frame(frameBg, borderwidth=1, relief=SUNKEN,
+- height=2, bg=self.bg).grid(row=8, column=0, sticky=EW,
+- columnspan=3, padx=5, pady=5)
++ fbg = Frame(frameBg, borderwidth=1, relief=SUNKEN, height=2)
++ fbg.grid(row=8, column=0, sticky=EW, columnspan=3, padx=5, pady=5)
+ labelPythonVer = Label(frameBg, text='Python version: ' + \
+- sys.version.split()[0], fg=self.fg, bg=self.bg)
++ sys.version.split()[0])
+ labelPythonVer.grid(row=9, column=0, sticky=W, padx=10, pady=0)
+ # handle weird tk version num in windoze python >= 1.6 (?!?)
+ tkVer = repr(TkVersion).split('.')
+@@ -72,44 +84,50 @@
+ if tkVer[len(tkVer)-1] == '':
+ tkVer[len(tkVer)-1] = '0'
+ tkVer = '.'.join(tkVer)
+- labelTkVer = Label(frameBg, text='Tk version: '+
+- tkVer, fg=self.fg, bg=self.bg)
++ labelTkVer = Label(frameBg, text='Tk version: '+ tkVer)
+ labelTkVer.grid(row=9, column=1, sticky=W, padx=2, pady=0)
+- py_button_f = Frame(frameBg, bg=self.bg)
++ py_button_f = Frame(frameBg)
+ py_button_f.grid(row=10, column=0, columnspan=2, sticky=NSEW)
+ buttonLicense = Button(py_button_f, text='License', width=8,
+- highlightbackground=self.bg,
+ command=self.ShowLicense)
+ buttonLicense.pack(side=LEFT, padx=10, pady=10)
+ buttonCopyright = Button(py_button_f, text='Copyright', width=8,
+- highlightbackground=self.bg,
+ command=self.ShowCopyright)
+ buttonCopyright.pack(side=LEFT, padx=10, pady=10)
+ buttonCredits = Button(py_button_f, text='Credits', width=8,
+- highlightbackground=self.bg,
+ command=self.ShowPythonCredits)
+ buttonCredits.pack(side=LEFT, padx=10, pady=10)
+- Frame(frameBg, borderwidth=1, relief=SUNKEN,
+- height=2, bg=self.bg).grid(row=11, column=0, sticky=EW,
+- columnspan=3, padx=5, pady=5)
+- idle_v = Label(frameBg, text='IDLE version: ' + idlever.IDLE_VERSION,
+- fg=self.fg, bg=self.bg)
++ fbg2 = Frame(frameBg, borderwidth=1, relief=SUNKEN, height=2)
++ fbg2.grid(row=11, column=0, sticky=EW, columnspan=3, padx=5, pady=5)
++ idle_v = Label(frameBg, text='IDLE version: ' + idlever.IDLE_VERSION)
+ idle_v.grid(row=12, column=0, sticky=W, padx=10, pady=0)
+- idle_button_f = Frame(frameBg, bg=self.bg)
++ idle_button_f = Frame(frameBg)
+ idle_button_f.grid(row=13, column=0, columnspan=3, sticky=NSEW)
+ idle_about_b = Button(idle_button_f, text='README', width=8,
+- highlightbackground=self.bg,
+ command=self.ShowIDLEAbout)
+ idle_about_b.pack(side=LEFT, padx=10, pady=10)
+ idle_news_b = Button(idle_button_f, text='NEWS', width=8,
+- highlightbackground=self.bg,
+ command=self.ShowIDLENEWS)
+ idle_news_b.pack(side=LEFT, padx=10, pady=10)
+ idle_credits_b = Button(idle_button_f, text='Credits', width=8,
+- highlightbackground=self.bg,
+ command=self.ShowIDLECredits)
+ idle_credits_b.pack(side=LEFT, padx=10, pady=10)
+
++ s = self.style
++ s(frameButtons, 'RootColor.TFrame')
++ s(frameBg, 'Color.TFrame')
++ s(labelTitle, 'Color.TLabel')
++ s(labelDesc, 'Color.TLabel')
++ s(labelEmail, 'Color.TLabel')
++ s(labelWWW, 'Color.TLabel')
++ s(fbg, 'Color.TFrame')
++ s(labelPythonVer, 'Color.TLabel')
++ s(labelTkVer, 'Color.TLabel')
++ s(py_button_f, 'Color.TFrame')
++ s(fbg2, 'Color.TFrame')
++ s(idle_v, 'Color.TLabel')
++ s(idle_button_f, 'Color.TFrame')
++
+ def ShowLicense(self):
+ self.display_printer_text('About - License', license)
+
+@@ -142,9 +160,9 @@
+
+ if __name__ == '__main__':
+ # test the dialog
++ from Tkinter import Tk
+ root = Tk()
+ def run():
+- import aboutDialog
+- aboutDialog.AboutDialog(root, 'About')
++ AboutDialog(root, 'About')
+ Button(root, text='Dialog', command=run).pack()
+ root.mainloop()
+Index: config-main.def
+===================================================================
+--- config-main.def (revision 63995)
++++ config-main.def (revision 65541)
+@@ -49,8 +49,14 @@
+ print-command-posix=lpr %s
+ print-command-win=start /min notepad /p %s
+ delete-exitfunc= 1
++use-ttk = 0
+
+ [EditorWindow]
++width = 80
++height = 40
++file-in-tab = 1
++
++[EditorPage]
+ width= 80
+ height= 40
+ font= courier
+@@ -68,6 +74,7 @@
+ [Theme]
+ default= 1
+ name= IDLE Classic
++displaytheme = default
+
+ [Keys]
+ default= 1
+Index: PathBrowser.py
+===================================================================
+--- PathBrowser.py (revision 63995)
++++ PathBrowser.py (revision 65541)
+@@ -15,25 +15,29 @@
+ self.top.wm_iconname("Path Browser")
+
+ def rootnode(self):
+- return PathBrowserTreeItem()
++ return PathBrowserTreeItem(self.flist)
+
+ class PathBrowserTreeItem(TreeItem):
+
++ def __init__(self, flist):
++ self.flist = flist
++
+ def GetText(self):
+ return "sys.path"
+
+ def GetSubList(self):
+ sublist = []
+ for dir in sys.path:
+- item = DirBrowserTreeItem(dir)
++ item = DirBrowserTreeItem(dir, flist=self.flist)
+ sublist.append(item)
+ return sublist
+
+ class DirBrowserTreeItem(TreeItem):
+
+- def __init__(self, dir, packages=[]):
++ def __init__(self, dir, packages=[], flist=None):
+ self.dir = dir
+ self.packages = packages
++ self.flist = flist
+
+ def GetText(self):
+ if not self.packages:
+@@ -55,10 +59,11 @@
+ packages.sort()
+ sublist = []
+ for nn, name, file in packages:
+- item = DirBrowserTreeItem(file, self.packages + [name])
++ item = DirBrowserTreeItem(file, self.packages + [name], self.flist)
+ sublist.append(item)
+ for nn, name in self.listmodules(names):
+- item = ModuleBrowserTreeItem(os.path.join(self.dir, name))
++ item = ModuleBrowserTreeItem(os.path.join(self.dir, name),
++ self.flist)
+ sublist.append(item)
+ return sublist
+
+Index: IOBinding.py
+===================================================================
+--- IOBinding.py (revision 63995)
++++ IOBinding.py (revision 65541)
+@@ -6,17 +6,20 @@
+ # which will only understand the local convention.
+
+ import os
++import re
++import sys
+ import types
+-import sys
+ import codecs
+ import tempfile
+ import tkFileDialog
+ import tkMessageBox
+-import re
+-from Tkinter import *
++from Tkinter import Toplevel, Entry, Frame, Button, Label
++from Tkconstants import W, X, TOP, LEFT, BOTH
+ from SimpleDialog import SimpleDialog
+
+ from configHandler import idleConf
++if idleConf.GetOption('main', 'General', 'use-ttk', type='int'):
++ from ttk import Entry, Frame, Button, Label
+
+ try:
+ from codecs import BOM_UTF8
+@@ -93,11 +96,11 @@
+ # For some reason, the text is not selectable anymore if the
+ # widget is disabled.
+ # l2['state'] = DISABLED
+- l2.pack(side=TOP, anchor = W, fill=X)
++ l2.pack(side=TOP, anchor=W, fill=X)
+ l3 = Label(top, text="to your file\n"
+ "Choose OK to save this file as %s\n"
+ "Edit your general options to silence this warning" % enc)
+- l3.pack(side=TOP, anchor = W)
++ l3.pack(side=TOP, anchor=W)
+
+ buttons = Frame(top)
+ buttons.pack(side=TOP, fill=X)
+@@ -142,10 +145,14 @@
+
+
+ class IOBinding:
++ filename_change_hook = None
++ filename = None
++ dirname = None
+
+- def __init__(self, editwin):
+- self.editwin = editwin
+- self.text = editwin.text
++ def __init__(self, editpage):
++ self.editpage = editpage
++ self.editwin = editpage.editwin
++ self.text = editpage.text
+ self.__id_open = self.text.bind("<<open-window-from-file>>", self.open)
+ self.__id_save = self.text.bind("<<save-window>>", self.save)
+ self.__id_saveas = self.text.bind("<<save-window-as-file>>",
+@@ -163,27 +170,23 @@
+ self.text.unbind("<<save-copy-of-window-as-file>>", self.__id_savecopy)
+ self.text.unbind("<<print-window>>", self.__id_print)
+ # Break cycles
++ self.text = None
++ self.editpage = None
+ self.editwin = None
+- self.text = None
+ self.filename_change_hook = None
+
+ def get_saved(self):
+- return self.editwin.get_saved()
++ return self.editpage.get_saved()
+
+ def set_saved(self, flag):
+- self.editwin.set_saved(flag)
++ self.editpage.set_saved(flag)
+
+ def reset_undo(self):
+- self.editwin.reset_undo()
++ self.editpage.reset_undo()
+
+- filename_change_hook = None
+-
+ def set_filename_change_hook(self, hook):
+ self.filename_change_hook = hook
+
+- filename = None
+- dirname = None
+-
+ def set_filename(self, filename):
+ if filename and os.path.isdir(filename):
+ self.filename = None
+@@ -200,21 +203,33 @@
+ if not editFile:
+ filename = self.askopenfile()
+ else:
+- filename=editFile
++ filename = editFile
+ if filename:
+ # If the current window has no filename and hasn't been
+- # modified, we replace its contents (no loss). Otherwise
+- # we open a new window. But we won't replace the
+- # shell window (which has an interp(reter) attribute), which
+- # gets set to "not modified" at every new prompt.
++ # modified, we replace its contents (no loss). Otherwise
++ # we open a new window, or maybe open in a tab.
++ # But we won't replace the shell window (which has an
++ # interp(reter) attribute), which gets set to "not modified"
++ # at every new prompt.
+ try:
+ interp = self.editwin.interp
+ except AttributeError:
+ interp = None
++
+ if not self.filename and self.get_saved() and not interp:
+ self.editwin.flist.open(filename, self.loadfile)
+ else:
+- self.editwin.flist.open(filename)
++ if idleConf.GetOption('main', 'EditorWindow',
++ 'file-in-tab', default=1, type='bool'):
++ if interp:
++ # this is a PyShell, force file to be opened in a
++ # new window
++ action = None
++ else:
++ action = self.editwin.new_tab
++ else:
++ action = None
++ self.editwin.flist.open(filename, action)
+ else:
+ self.text.focus_set()
+ return "break"
+@@ -341,8 +356,8 @@
+ if self.writefile(self.filename):
+ self.set_saved(1)
+ try:
+- self.editwin.store_file_breaks()
+- except AttributeError: # may be a PyShell
++ self.editwin.store_file_breaks(self.editpage)
++ except AttributeError: # may not be a PyShell
+ pass
+ self.text.focus_set()
+ return "break"
+@@ -354,7 +369,7 @@
+ self.set_filename(filename)
+ self.set_saved(1)
+ try:
+- self.editwin.store_file_breaks()
++ self.editwin.store_file_breaks(self.editpage)
+ except AttributeError:
+ pass
+ self.text.focus_set()
+@@ -381,8 +396,7 @@
+ f.close()
+ return True
+ except IOError, msg:
+- tkMessageBox.showerror("I/O Error", str(msg),
+- master=self.text)
++ tkMessageBox.showerror("I/O Error", str(msg), master=self.text)
+ return False
+
+ def encode(self, chars):
+@@ -429,8 +443,7 @@
+ return BOM_UTF8 + chars.encode("utf-8")
+ # Nothing was declared, and we had not determined an encoding
+ # on loading. Recommend an encoding line.
+- config_encoding = idleConf.GetOption("main","EditorWindow",
+- "encoding")
++ config_encoding = idleConf.GetOption("main", "EditorPage", "encoding")
+ if config_encoding == 'utf-8':
+ # User has requested that we save files as UTF-8
+ return BOM_UTF8 + chars.encode("utf-8")
+@@ -563,6 +576,7 @@
+ self.editwin.update_recent_files_list(filename)
+
+ def test():
++ from Tkinter import Tk, Text
+ root = Tk()
+ class MyEditWin:
+ def __init__(self, text):
+Index: WindowList.py
+===================================================================
+--- WindowList.py (revision 63995)
++++ WindowList.py (revision 65541)
+@@ -1,4 +1,4 @@
+-from Tkinter import *
++from Tkinter import Toplevel, TclError
+
+ class WindowList:
+
+@@ -45,8 +45,8 @@
+ try:
+ callback()
+ except:
+- print "warning: callback failed in WindowList", \
+- sys.exc_type, ":", sys.exc_value
++ print ("warning: callback failed in WindowList",
++ sys.exc_type, ":", sys.exc_value)
+
+ registry = WindowList()
+
+Index: ScrolledList.py
+===================================================================
+--- ScrolledList.py (revision 63995)
++++ ScrolledList.py (revision 65541)
+@@ -1,5 +1,9 @@
+-from Tkinter import *
++from Tkinter import Frame, Menu, Listbox, Scrollbar
++from idlelib.configHandler import idleConf
+
++if idleConf.GetOption('main', 'General', 'use-ttk', type='int'):
++ from ttk import Frame, Scrollbar
++
+ class ScrolledList:
+
+ default = "(None)"
+@@ -120,6 +124,7 @@
+
+
+ def test():
++ from Tkinter import Tk
+ root = Tk()
+ root.protocol("WM_DELETE_WINDOW", root.destroy)
+ class MyScrolledList(ScrolledList):
+Index: ClassBrowser.py
+===================================================================
+--- ClassBrowser.py (revision 63995)
++++ ClassBrowser.py (revision 65541)
+@@ -14,7 +14,6 @@
+ import sys
+ import pyclbr
+
+-import PyShell
+ from WindowList import ListedToplevel
+ from TreeWidget import TreeNode, TreeItem, ScrolledCanvas
+ from configHandler import idleConf
+@@ -26,6 +25,7 @@
+ # XXX the code here is bogus!
+ self.name = name
+ self.file = os.path.join(path[0], self.name + ".py")
++ self.flist = None
+ self.init(flist)
+
+ def close(self, event=None):
+@@ -57,12 +57,13 @@
+ self.top.wm_iconname("Class Browser")
+
+ def rootnode(self):
+- return ModuleBrowserTreeItem(self.file)
++ return ModuleBrowserTreeItem(self.file, self.flist)
+
+ class ModuleBrowserTreeItem(TreeItem):
+
+- def __init__(self, file):
++ def __init__(self, file, flist):
+ self.file = file
++ self.flist = flist
+
+ def GetText(self):
+ return os.path.basename(self.file)
+@@ -73,7 +74,8 @@
+ def GetSubList(self):
+ sublist = []
+ for name in self.listclasses():
+- item = ClassBrowserTreeItem(name, self.classes, self.file)
++ item = ClassBrowserTreeItem(name, self.classes, self.file,
++ self.flist)
+ sublist.append(item)
+ return sublist
+
+@@ -82,7 +84,7 @@
+ return
+ if not os.path.exists(self.file):
+ return
+- PyShell.flist.open(self.file)
++ self.flist.open(self.file)
+
+ def IsExpandable(self):
+ return os.path.normcase(self.file[-3:]) == ".py"
+@@ -122,10 +124,11 @@
+
+ class ClassBrowserTreeItem(TreeItem):
+
+- def __init__(self, name, classes, file):
++ def __init__(self, name, classes, file, flist):
+ self.name = name
+ self.classes = classes
+ self.file = file
++ self.flist = flist
+ try:
+ self.cl = self.classes[self.name]
+ except (IndexError, KeyError):
+@@ -163,7 +166,7 @@
+ def OnDoubleClick(self):
+ if not os.path.exists(self.file):
+ return
+- edit = PyShell.flist.open(self.file)
++ edit = self.flist.open(self.file)
+ if hasattr(self.cl, 'lineno'):
+ lineno = self.cl.lineno
+ edit.gotoline(lineno)
+@@ -199,10 +202,11 @@
+ def OnDoubleClick(self):
+ if not os.path.exists(self.file):
+ return
+- edit = PyShell.flist.open(self.file)
++ edit = self.flist.open(self.file)
+ edit.gotoline(self.cl.methods[self.name])
+
+ def main():
++ import PyShell
+ try:
+ file = __file__
+ except NameError:
+Index: FileList.py
+===================================================================
+--- FileList.py (revision 63995)
++++ FileList.py (revision 65541)
+@@ -1,7 +1,15 @@
+ import os
+-from Tkinter import *
+ import tkMessageBox
+
++def _canonize(filename):
++ if not os.path.isabs(filename):
++ try:
++ pwd = os.getcwd()
++ except os.error:
++ pass
++ else:
++ filename = os.path.join(pwd, filename)
++ return os.path.normpath(filename)
+
+ class FileList:
+
+@@ -16,7 +24,7 @@
+
+ def open(self, filename, action=None):
+ assert filename
+- filename = self.canonize(filename)
++ filename = _canonize(filename)
+ if os.path.isdir(filename):
+ # This can happen when bad filename is passed on command line:
+ tkMessageBox.showerror(
+@@ -29,9 +37,10 @@
+ edit = self.dict[key]
+ edit.top.wakeup()
+ return edit
++
+ if action:
+ # Don't create window, perform 'action', e.g. open in same window
+- return action(filename)
++ return action(filename=filename)
+ else:
+ return self.EditorWindow(self, filename, key)
+
+@@ -62,20 +71,20 @@
+ if not self.inversedict:
+ self.root.quit()
+
+- def filename_changed_edit(self, edit):
+- edit.saved_change_hook()
++ def filename_changed_edit(self, page, editwin):
++ page.saved_change_hook(page.tab_initialized)
+ try:
+- key = self.inversedict[edit]
++ key = self.inversedict[editwin]
+ except KeyError:
+ print "Don't know this EditorWindow object. (rename)"
+ return
+- filename = edit.io.filename
++ filename = page.io.filename
+ if not filename:
+ if key:
+ del self.dict[key]
+- self.inversedict[edit] = None
++ self.inversedict[editwin] = None
+ return
+- filename = self.canonize(filename)
++ filename = _canonize(filename)
+ newkey = os.path.normcase(filename)
+ if newkey == key:
+ return
+@@ -86,28 +95,19 @@
+ "Name Conflict",
+ "You now have multiple edit windows open for %r" % (filename,),
+ master=self.root)
+- self.dict[newkey] = edit
+- self.inversedict[edit] = newkey
++ self.dict[newkey] = editwin
++ self.inversedict[editwin] = newkey
+ if key:
+ try:
+ del self.dict[key]
+ except KeyError:
+ pass
+
+- def canonize(self, filename):
+- if not os.path.isabs(filename):
+- try:
+- pwd = os.getcwd()
+- except os.error:
+- pass
+- else:
+- filename = os.path.join(pwd, filename)
+- return os.path.normpath(filename)
+
+-
+ def _test():
++ import sys
++ from Tkinter import Tk
+ from EditorWindow import fixwordbreaks
+- import sys
+ root = Tk()
+ fixwordbreaks(root)
+ root.withdraw()
+Index: CallTips.py
+===================================================================
+--- CallTips.py (revision 63995)
++++ CallTips.py (revision 65541)
+@@ -22,12 +22,12 @@
+ ])
+ ]
+
+- def __init__(self, editwin=None):
+- if editwin is None: # subprocess and test
+- self.editwin = None
++ def __init__(self, editpage=None):
++ if editpage is None: # subprocess and test
++ self.editpage = None
+ return
+- self.editwin = editwin
+- self.text = editwin.text
++ self.editpage = editpage
++ self.text = editpage.text
+ self.calltip = None
+ self._make_calltip_window = self._make_tk_calltip_window
+
+@@ -66,7 +66,7 @@
+ def open_calltip(self, evalfuncs):
+ self._remove_calltip_window()
+
+- hp = HyperParser(self.editwin, "insert")
++ hp = HyperParser(self.editpage, "insert")
+ sur_paren = hp.get_surrounding_brackets('(')
+ if not sur_paren:
+ return
+@@ -95,7 +95,7 @@
+
+ """
+ try:
+- rpcclt = self.editwin.flist.pyshell.interp.rpcclt
++ rpcclt = self.editpage.editwin.flist.pyshell.interp.rpcclt
+ except:
+ rpcclt = None
+ if rpcclt:
+Index: CodeContext.py
+===================================================================
+--- CodeContext.py (revision 63995)
++++ CodeContext.py (revision 65541)
+@@ -23,7 +23,7 @@
+ getspacesfirstword =\
+ lambda s, c=re.compile(r"^(\s*)(\w*)"): c.match(s).groups()
+
+-class CodeContext:
++class CodeContext(object):
+ menudefs = [('options', [('!Code Conte_xt', '<<toggle-code-context>>')])]
+ context_depth = idleConf.GetOption("extensions", "CodeContext",
+ "numlines", type="int", default=3)
+@@ -31,10 +31,10 @@
+ "bgcolor", type="str", default="LightGray")
+ fgcolor = idleConf.GetOption("extensions", "CodeContext",
+ "fgcolor", type="str", default="Black")
+- def __init__(self, editwin):
+- self.editwin = editwin
+- self.text = editwin.text
+- self.textfont = self.text["font"]
++
++ def __init__(self, editpage):
++ self.editpage = editpage
++ self.editwin = editpage.editwin
+ self.label = None
+ # self.info is a list of (line number, indent level, line text, block
+ # keyword) tuples providing the block structure associated with
+@@ -43,14 +43,11 @@
+ # starts the toplevel 'block' of the module.
+ self.info = [(0, -1, "", False)]
+ self.topvisible = 1
+- visible = idleConf.GetOption("extensions", "CodeContext",
+- "visible", type="bool", default=False)
++ visible = idleConf.GetOption("extensions", "CodeContext", "visible",
++ type="bool", default=False)
+ if visible:
+ self.toggle_code_context_event()
+ self.editwin.setvar('<<toggle-code-context>>', True)
+- # Start two update cycles, one for context lines, one for font changes.
+- self.text.after(UPDATEINTERVAL, self.timer_event)
+- self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
+
+ def toggle_code_context_event(self, event=None):
+ if not self.label:
+@@ -59,33 +56,27 @@
+ #
+ # All values are passed through int(str(<value>)), since some
+ # values may be pixel objects, which can't simply be added to ints.
+- widgets = self.editwin.text, self.editwin.text_frame
+- # Calculate the required vertical padding
+- padx = 0
+- for widget in widgets:
+- padx += int(str( widget.pack_info()['padx'] ))
+- padx += int(str( widget.cget('padx') ))
+- # Calculate the required border width
+- border = 0
+- for widget in widgets:
+- border += int(str( widget.cget('border') ))
++
++ # Calculate the required horizontal padding
++ padx = int(str(self.editwin.text_notebook.pack_info()['padx']))
++
+ self.label = Tkinter.Label(self.editwin.top,
+ text="\n" * (self.context_depth - 1),
+ anchor=W, justify=LEFT,
+ font=self.textfont,
+ bg=self.bgcolor, fg=self.fgcolor,
+ width=1, #don't request more than we get
+- padx=padx, border=border,
++ padx=padx,
+ relief=SUNKEN)
+- # Pack the label widget before and above the text_frame widget,
+- # thus ensuring that it will appear directly above text_frame
++ # Pack the label widget before and above the text_notebook widget,
++ # thus ensuring that it will appear directly above text_notebook
+ self.label.pack(side=TOP, fill=X, expand=False,
+- before=self.editwin.text_frame)
++ before=self.editwin.text_notebook)
+ else:
+ self.label.destroy()
+ self.label = None
+ idleConf.SetOption("extensions", "CodeContext", "visible",
+- str(self.label is not None))
++ str(self.label is not None))
+ idleConf.SaveUserCfgFiles()
+
+ def get_line_info(self, linenum):
+@@ -132,15 +123,12 @@
+ return lines, lastindent
+
+ def update_code_context(self):
+- """Update context information and lines visible in the context pane.
+-
+- """
++ """Update context information and lines visible in the context pane."""
+ new_topvisible = int(self.text.index("@0,0").split('.')[0])
+ if self.topvisible == new_topvisible: # haven't scrolled
+ return
+ if self.topvisible < new_topvisible: # scroll down
+- lines, lastindent = self.get_context(new_topvisible,
+- self.topvisible)
++ lines, lastindent = self.get_context(new_topvisible, self.topvisible)
+ # retain only context info applicable to the region
+ # between topvisible and new_topvisible:
+ while self.info[-1][1] >= lastindent:
+@@ -153,8 +141,7 @@
+ stopindent = self.info[-1][1]
+ del self.info[-1]
+ lines, lastindent = self.get_context(new_topvisible,
+- self.info[-1][0]+1,
+- stopindent)
++ self.info[-1][0] + 1, stopindent)
+ self.info.extend(lines)
+ self.topvisible = new_topvisible
+ # empty lines in context pane:
+@@ -174,3 +161,18 @@
+ self.textfont = newtextfont
+ self.label["font"] = self.textfont
+ self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
++
++ # Private methods
++
++ def _get_editpage(self):
++ return self._editpage
++
++ def _set_editpage(self, page):
++ self._editpage = page
++ self.text = page.text
++ self.textfont = self.text["font"]
++ # Start two update cycles, one for context lines, one for font changes.
++ self.text.after(UPDATEINTERVAL, self.timer_event)
++ self.text.after(FONTUPDATEINTERVAL, self.font_timer_event)
++
++ editpage = property(_get_editpage, _set_editpage)
+Index: textView.py
+===================================================================
+--- textView.py (revision 63995)
++++ textView.py (revision 65541)
+@@ -1,10 +1,16 @@
+-"""Simple text browser for IDLE
++"""Simple text browser for IDLE"""
+
+-"""
+-
+-from Tkinter import *
+ import tkMessageBox
++from Tkinter import Toplevel, Frame, Button, Scrollbar, Text
++from Tkconstants import DISABLED, SUNKEN, VERTICAL, WORD, RIGHT, Y, TOP, \
++ LEFT, BOTH, BOTTOM
+
++from configHandler import idleConf
++
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++if TTK:
++ from ttk import Frame, Button, Scrollbar
++
+ class TextViewer(Toplevel):
+ """A simple text viewer dialog for IDLE
+
+@@ -40,19 +46,22 @@
+ frameText = Frame(self, relief=SUNKEN, height=700)
+ frameButtons = Frame(self)
+ self.buttonOk = Button(frameButtons, text='Close',
+- command=self.Ok, takefocus=FALSE)
++ command=self.Ok, takefocus=False)
+ self.scrollbarView = Scrollbar(frameText, orient=VERTICAL,
+- takefocus=FALSE, highlightthickness=0)
+- self.textView = Text(frameText, wrap=WORD, highlightthickness=0,
+- fg=self.fg, bg=self.bg)
++ takefocus=False)
++ self.textView = Text(frameText, wrap=WORD, fg=self.fg, bg=self.bg,
++ highlightthickness=0)
+ self.scrollbarView.config(command=self.textView.yview)
+ self.textView.config(yscrollcommand=self.scrollbarView.set)
+ self.buttonOk.pack()
+ self.scrollbarView.pack(side=RIGHT,fill=Y)
+- self.textView.pack(side=LEFT,expand=TRUE,fill=BOTH)
+- frameButtons.pack(side=BOTTOM,fill=X)
+- frameText.pack(side=TOP,expand=TRUE,fill=BOTH)
++ self.textView.pack(side=LEFT,expand=True,fill=BOTH)
++ frameButtons.pack(side=BOTTOM)
++ frameText.pack(side=TOP,expand=True,fill=BOTH)
+
++ if TTK:
++ frameButtons['style'] = 'RootColor.TFrame'
++
+ def Ok(self, event=None):
+ self.destroy()
+
+@@ -68,7 +77,6 @@
+ else:
+ textFile = open(filename, 'r')
+ except IOError:
+- import tkMessageBox
+ tkMessageBox.showerror(title='File Load Error',
+ message='Unable to load file %r .' % filename,
+ parent=parent)
+@@ -77,6 +85,7 @@
+
+
+ if __name__ == '__main__':
++ from Tkinter import Tk
+ #test the dialog
+ root=Tk()
+ root.title('textView test')
+Index: SearchDialogBase.py
+===================================================================
+--- SearchDialogBase.py (revision 63995)
++++ SearchDialogBase.py (revision 65541)
+@@ -1,35 +1,42 @@
+-from Tkinter import *
++from Tkinter import Toplevel, Frame, Label, Entry, Button, Checkbutton, \
++ Radiobutton
+
++from configHandler import idleConf
++
++if idleConf.GetOption('main', 'General', 'use-ttk', type='int'):
++ from ttk import Frame, Label, Entry, Button, Checkbutton, Radiobutton
++
+ class SearchDialogBase:
+
+ title = "Search Dialog"
+ icon = "Search"
+ needwrapbutton = 1
++ bottom_btns = None
+
+ def __init__(self, root, engine):
+ self.root = root
+ self.engine = engine
+- self.top = None
++ self.ttop = None
+
+ def open(self, text, searchphrase=None):
+ self.text = text
+- if not self.top:
++ if not self.ttop:
+ self.create_widgets()
+ else:
+- self.top.deiconify()
+- self.top.tkraise()
++ self.ttop.deiconify()
++ self.ttop.tkraise()
+ if searchphrase:
+- self.ent.delete(0,"end")
+- self.ent.insert("end",searchphrase)
++ self.ent.delete(0, "end")
++ self.ent.insert("end", searchphrase)
+ self.ent.focus_set()
+ self.ent.selection_range(0, "end")
+ self.ent.icursor(0)
+- self.top.grab_set()
++ self.ttop.grab_set()
+
+ def close(self, event=None):
+- if self.top:
+- self.top.grab_release()
+- self.top.withdraw()
++ if self.ttop:
++ self.ttop.grab_release()
++ self.ttop.withdraw()
+
+ def create_widgets(self):
+ top = Toplevel(self.root)
+@@ -38,103 +45,96 @@
+ top.protocol("WM_DELETE_WINDOW", self.close)
+ top.wm_title(self.title)
+ top.wm_iconname(self.icon)
+- self.top = top
++ top.resizable(height=False, width=False)
++ self.ttop = top
++ self.top = Frame(top)
+
+ self.row = 0
+- self.top.grid_columnconfigure(0, pad=2, weight=0)
+- self.top.grid_columnconfigure(1, pad=2, minsize=100, weight=100)
++ self.top.grid(sticky='news')
+
+ self.create_entries()
+ self.create_option_buttons()
+ self.create_other_buttons()
+- return self.create_command_buttons()
++ self.create_command_buttons()
+
++
+ def make_entry(self, label, var):
+ l = Label(self.top, text=label)
+- l.grid(row=self.row, column=0, sticky="nw")
++ l.grid(row=self.row, column=0, sticky="ne", padx=6, pady=6)
+ e = Entry(self.top, textvariable=var, exportselection=0)
+- e.grid(row=self.row, column=1, sticky="nwe")
++ e.grid(row=self.row, column=1, sticky="nwe", padx=6, pady=6)
+ self.row = self.row + 1
+ return e
+
+ def make_frame(self,labeltext=None):
+ if labeltext:
+ l = Label(self.top, text=labeltext)
+- l.grid(row=self.row, column=0, sticky="nw")
++ l.grid(row=self.row, column=0, sticky="ne", padx=6, pady=6)
+ f = Frame(self.top)
+- f.grid(row=self.row, column=1, columnspan=1, sticky="nwe")
++ f.grid(row=self.row, column=1, columnspan=1, sticky="nwe",
++ padx=6, pady=6 if labeltext else 0)
+ self.row = self.row + 1
+ return f
+
+- def make_button(self, label, command, isdef=0):
+- b = Button(self.buttonframe,
+- text=label, command=command,
+- default=isdef and "active" or "normal")
+- cols,rows=self.buttonframe.grid_size()
+- b.grid(pady=1,row=rows,column=0,sticky="ew")
+- self.buttonframe.grid(rowspan=rows+1)
+- return b
+-
+ def create_entries(self):
+- self.ent = self.make_entry("Find:", self.engine.patvar)
++ self.ent = self.make_entry("Find", self.engine.patvar)
+
+ def create_option_buttons(self):
+ f = self.make_frame("Options")
+
+- btn = Checkbutton(f, anchor="w",
+- variable=self.engine.revar,
+- text="Regular expression")
++ btn = Checkbutton(f, variable=self.engine.revar,
++ text="Regular expression")
+ btn.pack(side="left", fill="both")
+ if self.engine.isre():
+- btn.select()
++ btn.invoke()
+
+- btn = Checkbutton(f, anchor="w",
+- variable=self.engine.casevar,
+- text="Match case")
++ btn = Checkbutton(f, variable=self.engine.casevar, text="Match case")
+ btn.pack(side="left", fill="both")
+ if self.engine.iscase():
+- btn.select()
++ btn.invoke()
+
+- btn = Checkbutton(f, anchor="w",
+- variable=self.engine.wordvar,
+- text="Whole word")
++ btn = Checkbutton(f, variable=self.engine.wordvar, text="Whole word")
+ btn.pack(side="left", fill="both")
+ if self.engine.isword():
+- btn.select()
++ btn.invoke()
+
+ if self.needwrapbutton:
+- btn = Checkbutton(f, anchor="w",
+- variable=self.engine.wrapvar,
+- text="Wrap around")
++ btn = Checkbutton(f, variable=self.engine.wrapvar,
++ text="Wrap around")
+ btn.pack(side="left", fill="both")
+ if self.engine.iswrap():
+- btn.select()
++ btn.invoke()
+
+ def create_other_buttons(self):
+ f = self.make_frame("Direction")
+
+- #lbl = Label(f, text="Direction: ")
+- #lbl.pack(side="left")
+-
+- btn = Radiobutton(f, anchor="w",
+- variable=self.engine.backvar, value=1,
+- text="Up")
+- btn.pack(side="left", fill="both")
++ btn = Radiobutton(f, variable=self.engine.backvar, value=1, text="Up")
++ btn.pack(side="left")
+ if self.engine.isback():
+- btn.select()
++ btn.invoke()
+
+- btn = Radiobutton(f, anchor="w",
+- variable=self.engine.backvar, value=0,
+- text="Down")
+- btn.pack(side="left", fill="both")
++ btn = Radiobutton(f, variable=self.engine.backvar, value=0, text="Down")
++ btn.pack(side="left")
+ if not self.engine.isback():
+- btn.select()
++ btn.invoke()
+
+ def create_command_buttons(self):
+- #
+- # place button frame on the right
+- f = self.buttonframe = Frame(self.top)
+- f.grid(row=0,column=2,padx=2,pady=2,ipadx=2,ipady=2)
++ self.bottom_btns = self.bottom_btns or []
++ f = Frame(self.top)
++ f.grid(row=self.row, column=0, columnspan=len(self.bottom_btns) + 1,
++ pady=6)
+
+- b = self.make_button("close", self.close)
+- b.lower()
++ column = 0
++ b = Button(f, text="Close", command=self.close)
++ b.grid(row=self.row, column=column, padx=6, pady=6)
++ column += 1
++
++ btns = {}
++ for tbtn in self.bottom_btns:
++ opts = {'text': tbtn[0], 'command': getattr(self, tbtn[1])}
++ if len(tbtn) == 3:
++ opts['default'] = tbtn[2] and 'active' or 'normal'
++
++ btns[opts['text']] = Button(f, **opts).grid(row=self.row, padx=6,
++ pady=6, column=column)
++ column += 1
+Index: CallTipWindow.py
+===================================================================
+--- CallTipWindow.py (revision 63995)
++++ CallTipWindow.py (revision 65541)
+@@ -4,8 +4,14 @@
+ Used by the CallTips IDLE extension.
+
+ """
+-from Tkinter import *
++from Tkinter import Toplevel, Label, TclError
++from Tkconstants import SOLID, LEFT
+
++from configHandler import idleConf
++
++if idleConf.GetOption('main', 'General', 'use-ttk', type='int'):
++ from ttk import Label
++
+ HIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-hide>>"
+ HIDE_SEQUENCES = ("<Key-Escape>", "<FocusOut>")
+ CHECKHIDE_VIRTUAL_EVENT_NAME = "<<calltipwindow-checkhide>>"
+@@ -142,6 +148,8 @@
+ #
+ class container: # Conceptually an editor_window
+ def __init__(self):
++ from Tkinter import Tk, Text
++ from Tkconstants import BOTH
+ root = Tk()
+ text = self.text = Text(root)
+ text.pack(side=LEFT, fill=BOTH, expand=1)
+@@ -163,6 +171,8 @@
+ def calltip_hide(self, event):
+ self.calltip.hidetip()
+
++# XXX Bugged test
++
+ def main():
+ # Test code
+ c=container()
+Index: SearchDialog.py
+===================================================================
+--- SearchDialog.py (revision 63995)
++++ SearchDialog.py (revision 65541)
+@@ -1,8 +1,8 @@
+-from Tkinter import *
++from Tkinter import TclError
++
+ import SearchEngine
+ from SearchDialogBase import SearchDialogBase
+
+-
+ def _setup(text):
+ root = text._root()
+ engine = SearchEngine.get(root)
+@@ -12,7 +12,7 @@
+
+ def find(text):
+ pat = text.get("sel.first", "sel.last")
+- return _setup(text).open(text,pat)
++ return _setup(text).open(text, pat)
+
+ def find_again(text):
+ return _setup(text).find_again(text)
+@@ -21,10 +21,10 @@
+ return _setup(text).find_selection(text)
+
+ class SearchDialog(SearchDialogBase):
++ bottom_btns = [("Find", 'default_command', 1)]
+
+ def create_widgets(self):
+- f = SearchDialogBase.create_widgets(self)
+- self.make_button("Find", self.default_command, 1)
++ SearchDialogBase.create_widgets(self)
+
+ def default_command(self, event=None):
+ if not self.engine.getprog():
+Index: idlever.py
+===================================================================
+--- idlever.py (revision 63995)
++++ idlever.py (revision 65541)
+@@ -1 +1 @@
+-IDLE_VERSION = "2.6a3"
++IDLE_VERSION = "2.6a3-gpolo"
+Index: tabbedpages_old.py
+===================================================================
+--- tabbedpages_old.py (revision 0)
++++ tabbedpages_old.py (revision 65541)
+@@ -0,0 +1,546 @@
++"""An implementation of tabbed pages using only standard Tkinter.
++
++Originally developed for use in IDLE. Based on tabpage.py.
++
++Classes exported:
++TabbedPageSet -- A Tkinter implementation of a tabbed-page widget.
++TabSet -- A widget containing tabs (buttons) in one or more rows.
++
++"""
++from Tkinter import Frame, Radiobutton
++from Tkconstants import BOTH, TOP, X, RAISED, NSEW, FLAT, LEFT
++
++from tabbedpages import InvalidNameError, AlreadyExistsError
++
++
++class TabSet(Frame):
++ """A widget containing tabs (buttons) in one or more rows.
++
++ Only one tab may be selected at a time.
++
++ """
++ def __init__(self, page_set, select_command,
++ tabs=None, n_rows=1, max_tabs_per_row=5,
++ expand_tabs=False, **kw):
++ """Constructor arguments:
++
++ select_command -- A callable which will be called when a tab is
++ selected. It is called with the name of the selected tab as an
++ argument.
++
++ tabs -- A list of strings, the names of the tabs. Should be specified in
++ the desired tab order. The first tab will be the default and first
++ active tab. If tabs is None or empty, the TabSet will be initialized
++ empty.
++
++ n_rows -- Number of rows of tabs to be shown. If n_rows <= 0 or is
++ None, then the number of rows will be decided by TabSet. See
++ _arrange_tabs() for details.
++
++ max_tabs_per_row -- Used for deciding how many rows of tabs are needed,
++ when the number of rows is not constant. See _arrange_tabs() for
++ details.
++
++ """
++ Frame.__init__(self, page_set, **kw)
++ self.select_command = select_command
++ self.n_rows = n_rows
++ self.max_tabs_per_row = max_tabs_per_row
++ self.expand_tabs = expand_tabs
++ self.page_set = page_set
++
++ self._tabs = {}
++ self._tab2row = {}
++ if tabs:
++ self._tab_names = list(tabs)
++ else:
++ self._tab_names = []
++ self._selected_tab = None
++ self._tab_rows = []
++
++ self.padding_frame = Frame(self, height=2,
++ borderwidth=0, relief=FLAT,
++ background=self.cget('background'))
++ self.padding_frame.pack(side=TOP, fill=X, expand=False)
++
++ self._arrange_tabs()
++
++ def add_tab(self, tab_name):
++ """Add a new tab with the name given in tab_name."""
++ if not tab_name:
++ raise InvalidNameError("Invalid Tab name: '%s'" % tab_name)
++ if tab_name in self._tab_names:
++ raise AlreadyExistsError("Tab named '%s' already exists" %tab_name)
++
++ self._tab_names.append(tab_name)
++ self._arrange_tabs()
++
++ def remove_tab(self, tab_name):
++ """Remove the tab named <tab_name>"""
++ if not tab_name in self._tab_names:
++ raise KeyError("No such Tab: '%s" % tab_name)
++
++ self._tab_names.remove(tab_name)
++ self._arrange_tabs()
++
++ def set_selected_tab(self, tab_name):
++ """Show the tab named <tab_name> as the selected one"""
++ if tab_name == self._selected_tab:
++ return
++ if tab_name is not None and tab_name not in self._tabs:
++ raise KeyError("No such Tab: '%s" % tab_name)
++
++ # deselect the current selected tab
++ if self._selected_tab is not None:
++ self._tabs[self._selected_tab].set_normal()
++ self._selected_tab = None
++
++ if tab_name is not None:
++ # activate the tab named tab_name
++ self._selected_tab = tab_name
++ tab = self._tabs[tab_name]
++ tab.set_selected()
++ # move the tab row with the selected tab to the bottom
++ tab_row = self._tab2row[tab]
++ tab_row.pack_forget()
++ tab_row.pack(side=TOP, fill=X, expand=0)
++
++ def _add_tab_row(self, tab_names, expand_tabs):
++ if not tab_names:
++ return
++
++ tab_row = Frame(self)
++ tab_row.pack(side=TOP, fill=X, expand=0)
++ self._tab_rows.append(tab_row)
++
++ for tab_name in tab_names:
++ tab = TabSet.TabButton(tab_name, self.select_command,
++ tab_row, self)
++ if expand_tabs:
++ tab.pack(side=LEFT, fill=X, expand=True)
++ else:
++ tab.pack(side=LEFT)
++ self._tabs[tab_name] = tab
++ self._tab2row[tab] = tab_row
++
++ # tab is the last one created in the above loop
++ tab.is_last_in_row = True
++
++ def _reset_tab_rows(self):
++ while self._tab_rows:
++ tab_row = self._tab_rows.pop()
++ tab_row.destroy()
++ self._tab2row = {}
++
++ def _arrange_tabs(self):
++ """
++ Arrange the tabs in rows, in the order in which they were added.
++
++ If n_rows >= 1, this will be the number of rows used. Otherwise the
++ number of rows will be calculated according to the number of tabs and
++ max_tabs_per_row. In this case, the number of rows may change when
++ adding/removing tabs.
++
++ """
++ # remove all tabs and rows
++ while self._tabs:
++ self._tabs.popitem()[1].destroy()
++ self._reset_tab_rows()
++
++ if not self._tab_names:
++ return
++
++ if self.n_rows is not None and self.n_rows > 0:
++ n_rows = self.n_rows
++ else:
++ # calculate the required number of rows
++ n_rows = (len(self._tab_names) - 1) // self.max_tabs_per_row + 1
++
++ # not expanding the tabs with more than one row is very ugly
++ expand_tabs = self.expand_tabs or n_rows > 1
++ i = 0 # index in self._tab_names
++ for row_index in range(n_rows):
++ # calculate required number of tabs in this row
++ n_tabs = (len(self._tab_names) - i - 1) // (n_rows - row_index) + 1
++ tab_names = self._tab_names[i:i + n_tabs]
++ i += n_tabs
++ self._add_tab_row(tab_names, expand_tabs)
++
++ # re-select selected tab so it is properly displayed
++ selected = self._selected_tab
++ self.set_selected_tab(None)
++ if selected in self._tab_names:
++ self.set_selected_tab(selected)
++
++ class TabButton(Frame):
++ """A simple tab-like widget."""
++
++ bw = 2 # borderwidth
++
++ def __init__(self, name, select_command, tab_row, tab_set):
++ """Constructor arguments:
++
++ name -- The tab's name, which will appear in its button.
++
++ select_command -- The command to be called upon selection of the
++ tab. It is called with the tab's name as an argument.
++
++ """
++ Frame.__init__(self, tab_row, borderwidth=self.bw, relief=RAISED)
++
++ self.name = name
++ self.select_command = select_command
++ self.tab_set = tab_set
++ self.is_last_in_row = False
++
++ self.button = Radiobutton(
++ self, text=name, command=self._select_event,
++ padx=5, pady=1, takefocus=False, indicatoron=False,
++ highlightthickness=0, selectcolor='', borderwidth=0)
++ self.button.pack(side=LEFT, fill=X, expand=True)
++
++ self._init_masks()
++ self.set_normal()
++
++ def _select_event(self, *args):
++ """Event handler for tab selection.
++
++ With TabbedPageSet, this calls TabbedPageSet.change_page, so that
++ selecting a tab changes the page.
++
++ Note that this does -not- call set_selected -- it will be called by
++ TabSet.set_selected_tab, which should be called when whatever the
++ tabs are related to changes.
++
++ """
++ self.select_command(self.name)
++ return
++
++ def set_selected(self):
++ """Assume selected look"""
++ self._place_masks(selected=True)
++
++ def set_normal(self):
++ """Assume normal look"""
++ self._place_masks(selected=False)
++
++ def _init_masks(self):
++ page_set = self.tab_set.page_set
++ background = page_set.pages_frame.cget('background')
++ # mask replaces the middle of the border with the background color
++ self.mask = Frame(page_set, borderwidth=0, relief=FLAT,
++ background=background)
++ # mskl replaces the bottom-left corner of the border with a normal
++ # left border
++ self.mskl = Frame(page_set, borderwidth=0, relief=FLAT,
++ background=background)
++ self.mskl.ml = Frame(self.mskl, borderwidth=self.bw,
++ relief=RAISED)
++ self.mskl.ml.place(x=0, y=-self.bw,
++ width=2*self.bw, height=self.bw*4)
++ # mskr replaces the bottom-right corner of the border with a normal
++ # right border
++ self.mskr = Frame(page_set, borderwidth=0, relief=FLAT,
++ background=background)
++ self.mskr.mr = Frame(self.mskr, borderwidth=self.bw,
++ relief=RAISED)
++
++ def _place_masks(self, selected=False):
++ height = self.bw
++ if selected:
++ height += self.bw
++
++ self.mask.place(in_=self,
++ relx=0.0, x=0,
++ rely=1.0, y=0,
++ relwidth=1.0, width=0,
++ relheight=0.0, height=height)
++
++ self.mskl.place(in_=self,
++ relx=0.0, x=-self.bw,
++ rely=1.0, y=0,
++ relwidth=0.0, width=self.bw,
++ relheight=0.0, height=height)
++
++ page_set = self.tab_set.page_set
++ if selected and ((not self.is_last_in_row) or
++ (self.winfo_rootx() + self.winfo_width() <
++ page_set.winfo_rootx() + page_set.winfo_width())
++ ):
++ # for a selected tab, if its rightmost edge isn't on the
++ # rightmost edge of the page set, the right mask should be one
++ # borderwidth shorter (vertically)
++ height -= self.bw
++
++ self.mskr.place(in_=self,
++ relx=1.0, x=0,
++ rely=1.0, y=0,
++ relwidth=0.0, width=self.bw,
++ relheight=0.0, height=height)
++
++ self.mskr.mr.place(x=-self.bw, y=-self.bw,
++ width=2*self.bw, height=height + self.bw*2)
++
++ # finally, lower the tab set so that all of the frames we just
++ # placed hide it
++ self.tab_set.lower()
++
++class TabbedPageSet(Frame):
++ """A Tkinter tabbed-pane widget.
++
++ Constains set of 'pages' (or 'panes') with tabs above for selecting which
++ page is displayed. Only one page will be displayed at a time.
++
++ Pages may be accessed through the 'pages' attribute, which is a dictionary
++ of pages, using the name given as the key. A page is an instance of a
++ subclass of Tk's Frame widget.
++
++ The page widgets will be created (and destroyed when required) by the
++ TabbedPageSet. Do not call the page's pack/place/grid/destroy methods.
++
++ Pages may be added or removed at any time using the add_page() and
++ remove_page() methods.
++
++ """
++ class Page(object):
++ """Abstract base class for TabbedPageSet's pages.
++
++ Subclasses must override the _show() and _hide() methods.
++
++ """
++ uses_grid = False
++
++ def __init__(self, page_set):
++ self.frame = Frame(page_set, borderwidth=2, relief=RAISED)
++
++ def _show(self):
++ raise NotImplementedError
++
++ def _hide(self):
++ raise NotImplementedError
++
++ class PageRemove(Page):
++ """Page class using the grid placement manager's "remove" mechanism."""
++ uses_grid = True
++
++ def _show(self):
++ self.frame.grid(row=0, column=0, sticky=NSEW)
++
++ def _hide(self):
++ self.frame.grid_remove()
++
++ class PageLift(Page):
++ """Page class using the grid placement manager's "lift" mechanism."""
++ uses_grid = True
++
++ def __init__(self, page_set):
++ super(TabbedPageSet.PageLift, self).__init__(page_set)
++ self.frame.grid(row=0, column=0, sticky=NSEW)
++ self.frame.lower()
++
++ def _show(self):
++ self.frame.lift()
++
++ def _hide(self):
++ self.frame.lower()
++
++ class PagePackForget(Page):
++ """Page class using the pack placement manager's "forget" mechanism."""
++ def _show(self):
++ self.frame.pack(fill=BOTH, expand=True)
++
++ def _hide(self):
++ self.frame.pack_forget()
++
++ def __init__(self, parent, page_names=None, page_class=PageLift,
++ n_rows=1, max_tabs_per_row=5, expand_tabs=False,
++ **kw):
++ """Constructor arguments:
++
++ page_names -- A list of strings, each will be the dictionary key to a
++ page's widget, and the name displayed on the page's tab. Should be
++ specified in the desired page order. The first page will be the default
++ and first active page. If page_names is None or empty, the
++ TabbedPageSet will be initialized empty.
++
++ n_rows, max_tabs_per_row -- Parameters for the TabSet which will
++ manage the tabs. See TabSet's docs for details.
++
++ page_class -- Pages can be shown/hidden using three mechanisms:
++
++ * PageLift - All pages will be rendered one on top of the other. When
++ a page is selected, it will be brought to the top, thus hiding all
++ other pages. Using this method, the TabbedPageSet will not be resized
++ when pages are switched. (It may still be resized when pages are
++ added/removed.)
++
++ * PageRemove - When a page is selected, the currently showing page is
++ hidden, and the new page shown in its place. Using this method, the
++ TabbedPageSet may resize when pages are changed.
++
++ * PagePackForget - This mechanism uses the pack placement manager.
++ When a page is shown it is packed, and when it is hidden it is
++ unpacked (i.e. pack_forget). This mechanism may also cause the
++ TabbedPageSet to resize when the page is changed.
++
++ """
++ Frame.__init__(self, parent, **kw)
++
++ self.page_class = page_class
++ self.pages = {}
++ self._pages_order = []
++ self._current_page = None
++ self._default_page = None
++
++ self.columnconfigure(0, weight=1)
++ self.rowconfigure(1, weight=1)
++
++ self.pages_frame = Frame(self)
++ self.pages_frame.grid(row=1, column=0, sticky=NSEW)
++ if self.page_class.uses_grid:
++ self.pages_frame.columnconfigure(0, weight=1)
++ self.pages_frame.rowconfigure(0, weight=1)
++
++ # the order of the following commands is important
++ self._tab_set = TabSet(self, self.change_page, n_rows=n_rows,
++ max_tabs_per_row=max_tabs_per_row,
++ expand_tabs=expand_tabs)
++ if page_names:
++ for name in page_names:
++ self.add_page(name)
++ self._tab_set.grid(row=0, column=0, sticky=NSEW)
++
++ self.change_page(self._default_page)
++
++ def update_tabtitle(self, tab, newtitle):
++ """Update tab title to newtitle."""
++ currpage = self.pages[tab.title]
++ old = tab.title
++
++ # resolve title duplicate
++ if newtitle in self.pages:
++ count = 1
++ temptitle = newtitle
++ while temptitle in self.pages:
++ temptitle = "%s #%d" % (newtitle, count)
++ count += 1
++ newtitle = temptitle
++
++ tab.title = newtitle
++ # now update 1 million places.. yeh..
++ self.pages[newtitle] = self.pages.pop(old)
++ self._pages_order[self._pages_order.index(old)] = newtitle
++ self._tab_set._tab_names[self._tab_set._tab_names.index(old)] = newtitle
++ self._tab_set._tabs[newtitle] = self._tab_set._tabs.pop(old)
++ self._tab_set._tabs[newtitle].button['text'] = newtitle
++ if self._tab_set._selected_tab == old:
++ self._tab_set._selected_tab = newtitle
++ if self._current_page == old:
++ self._current_page = newtitle
++ if self._default_page == old:
++ self._default_page = newtitle
++
++ def add_page(self, page_name):
++ """Add a new page with the name given in page_name."""
++ if not page_name:
++ raise InvalidNameError("Invalid TabPage name: '%s'" % page_name)
++ if page_name in self.pages:
++ raise AlreadyExistsError(
++ "TabPage named '%s' already exists" % page_name)
++
++ self.pages[page_name] = self.page_class(self.pages_frame)
++ self._pages_order.append(page_name)
++ self._tab_set.add_tab(page_name)
++
++ if len(self.pages) == 1: # adding first page
++ self._default_page = page_name
++ self.change_page(page_name)
++
++ return self.pages[page_name]
++
++ def remove_page(self, page_name):
++ """Destroy the page whose name is given in page_name."""
++ if not page_name in self.pages:
++ raise KeyError("No such TabPage: '%s" % page_name)
++
++ self._pages_order.remove(page_name)
++ # handle removing last remaining, default, or currently shown page
++ if len(self._pages_order) > 0:
++ if page_name == self._default_page:
++ # set a new default page
++ self._default_page = self._pages_order[0]
++ else:
++ self._default_page = None
++
++ if page_name == self._current_page:
++ self.change_page(self._default_page)
++
++ self._tab_set.remove_tab(page_name)
++ page = self.pages.pop(page_name)
++ page.frame.destroy()
++
++ def change_page(self, page_name):
++ """Show the page whose name is given in page_name."""
++ if self._current_page == page_name:
++ return
++ if page_name is not None and page_name not in self.pages:
++ raise KeyError("No such TabPage: '%s'" % page_name)
++
++ if self._current_page is not None:
++ self.pages[self._current_page]._hide()
++ self._current_page = None
++
++ if page_name is not None:
++ self._current_page = page_name
++ self.pages[page_name]._show()
++
++ self._tab_set.set_selected_tab(page_name)
++ self.event_generate('<<NotebookTabChanged>>') # conform to ttk.Notebook
++
++ def last_page(self):
++ return self.pages[self._pages_order[-1]]
++
++ # Some methods to make this Notebook compatible with the ttk Notebook
++
++ def select(self, page_id=None):
++ """Return the name of the currently selected page, otherwise
++ selects page_id.
++
++ page_id may be an integer or a page name."""
++ if page_id is None:
++ return self._current_page
++ elif isinstance(page_id, int):
++ self.change_page(self._pages_order[page_id])
++ elif isinstance(page_id, str):
++ self.change_page(page_id)
++
++ def index(self, page_name):
++ """Return the index of page_name."""
++ return self._pages_order.index(page_name)
++
++ def tabs(self):
++ """Return a list of page names."""
++ return self._pages_order
++
++if __name__ == '__main__':
++ from Tkinter import Tk, Label, Entry, Button
++ # test dialog
++ root=Tk()
++ tabPage=TabbedPageSet(root, page_names=['Foobar','Baz'], n_rows=0,
++ expand_tabs=False,
++ )
++ tabPage.pack(side=TOP, expand=True, fill=BOTH)
++ Label(tabPage.pages['Foobar'].frame, text='Foo', pady=20).pack()
++ Label(tabPage.pages['Foobar'].frame, text='Bar', pady=20).pack()
++ Label(tabPage.pages['Baz'].frame, text='Baz').pack()
++ entryPgName=Entry(root)
++ buttonAdd=Button(root, text='Add Page',
++ command=lambda:tabPage.add_page(entryPgName.get()))
++ buttonRemove=Button(root, text='Remove Page',
++ command=lambda:tabPage.remove_page(entryPgName.get()))
++ labelPgName=Label(root, text='name of page to add/remove:')
++ buttonAdd.pack(padx=5, pady=5)
++ buttonRemove.pack(padx=5, pady=5)
++ labelPgName.pack(padx=5)
++ entryPgName.pack(padx=5)
++ root.mainloop()
+Index: utils.py
+===================================================================
+--- utils.py (revision 0)
++++ utils.py (revision 65541)
+@@ -0,0 +1,4 @@
++def callback(func, *myargs):
++ def w(*args):
++ return func(*(args + myargs))
++ return w
+Index: TreeWidget.py
+===================================================================
+--- TreeWidget.py (revision 63995)
++++ TreeWidget.py (revision 65541)
+@@ -15,12 +15,16 @@
+ # - optimize tree redraw after expand of subnode
+
+ import os
+-from Tkinter import *
+-import imp
++from Tkinter import Tk, Label, Entry, Frame, Canvas, Scrollbar, PhotoImage
++from Tkconstants import ALL, END
+
+ import ZoomHeight
+ from configHandler import idleConf
+
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++if TTK:
++ from ttk import Label, Entry, Frame, Scrollbar
++
+ ICONDIR = "Icons"
+
+ # Look for Icons subdirectory in the same directory as this module
+@@ -248,7 +252,9 @@
+ label = self.label
+ except AttributeError:
+ # padding carefully selected (on Windows) to match Entry widget:
+- self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2)
++ self.label = Label(self.canvas, text=text)
++ if not TTK:
++ self.label.configure(bd=0, padx=2, pady=2)
+ theme = idleConf.GetOption('main','Theme','name')
+ if self.selected:
+ self.label.configure(idleConf.GetHighlight(theme, 'hilite'))
+@@ -451,7 +457,10 @@
+
+ # Testing functions
+
++# XXX Can't run these tests
++
+ def test():
++ from Tkinter import Toplevel
+ import PyShell
+ root = Toplevel(PyShell.root)
+ root.configure(bd=0, bg="yellow")
+Index: run.py
+===================================================================
+--- run.py (revision 63995)
++++ run.py (revision 65541)
+@@ -24,11 +24,13 @@
+ except ImportError:
+ pass
+ else:
+- def idle_formatwarning_subproc(message, category, filename, lineno):
++ def idle_formatwarning_subproc(message, category, filename, lineno,
++ line=None):
+ """Format warnings the IDLE way"""
+ s = "\nWarning (from warnings module):\n"
+ s += ' File \"%s\", line %s\n' % (filename, lineno)
+- line = linecache.getline(filename, lineno).strip()
++ if line is None:
++ line = linecache.getline(filename, lineno).strip()
+ if line:
+ s += " %s\n" % line
+ s += "%s: %s\n" % (category.__name__, message)
+Index: AutoExpand.py
+===================================================================
+--- AutoExpand.py (revision 63995)
++++ AutoExpand.py (revision 65541)
+@@ -15,8 +15,8 @@
+
+ wordchars = string.ascii_letters + string.digits + "_"
+
+- def __init__(self, editwin):
+- self.text = editwin.text
++ def __init__(self, editpage):
++ self.text = editpage.text
+ self.state = None
+
+ def expand_word_event(self, event):
+Index: Percolator.py
+===================================================================
+--- Percolator.py (revision 63995)
++++ Percolator.py (revision 65541)
+@@ -1,5 +1,5 @@
++from Delegator import Delegator
+ from WidgetRedirector import WidgetRedirector
+-from Delegator import Delegator
+
+ class Percolator:
+
+@@ -81,5 +81,5 @@
+ root.mainloop()
+
+ if __name__ == "__main__":
+- from Tkinter import *
++ from Tkinter import Tk, Text
+ main()
+Index: tabbedpages_new.py
+===================================================================
+--- tabbedpages_new.py (revision 0)
++++ tabbedpages_new.py (revision 65541)
+@@ -0,0 +1,116 @@
++"""Classes exported:
++
++TabbedPageSet -- A custom ttk.Notebook used by IDLE.
++"""
++from ttk import Frame, Notebook
++
++from tabbedpages import InvalidNameError, AlreadyExistsError
++
++class FramePage(object):
++ def __init__(self, notebook):
++ self.frame = Frame(notebook)
++
++class TabbedPageSet(Notebook):
++ """
++ Pages may be accessed through the 'pages' attribute, which is a dictionary
++ of pages, using the name given as the key. A page is an instance of a
++ subclass of ttk's Frame widget.
++
++ Pages may be added or removed at any time using the add_page() and
++ remove_page() methods.
++ """
++
++ def __init__(self, master, page_names=None, **kw):
++ """Constructor arguments:
++
++ page_names -- A list of strings, each will be the dictionary key to a
++ page's widget, and the name displayed on the page's tab. Should be
++ specified in the desired page order. The first page will be the default
++ and first active page. If page_names is None or empty, the
++ TabbedPageSet will be initialized empty.
++ """
++ Notebook.__init__(self, master, **kw)
++
++ self.pages = {}
++ page_names = page_names or ()
++ for name in page_names:
++ self.add_page(name)
++
++ def update_tabtitle(self, tab, newtitle):
++ """Update tab title to newtitle."""
++ currpage = self.pages[tab.title].frame
++ old = tab.title
++
++ # resolve title duplicate
++ if newtitle in self.pages and currpage != self.pages[newtitle].frame:
++ # newtitle is already present, and the current tab is not the
++ # one who owns it
++ count = 1
++ temptitle = newtitle
++ while temptitle in self.pages:
++ if currpage == self.pages[temptitle].frame:
++ break
++ temptitle = "%s #%d" % (newtitle, count)
++ count += 1
++ newtitle = temptitle
++
++ tab.title = newtitle
++ self.pages[newtitle] = self.pages.pop(old)
++ self.tab(currpage, text=newtitle)
++
++ def add_page(self, page_name):
++ """Add a new page with the name given in page_name."""
++ if not page_name:
++ raise InvalidNameError("Invalid TabPage name: '%s'" % page_name)
++ if page_name in self.pages:
++ raise AlreadyExistsError(
++ "TabPage named '%s' already exists" % page_name)
++
++ fpage = FramePage(self)
++ self.pages[page_name] = fpage
++ self.add(fpage.frame, text=page_name, padding=6)
++
++ # workaround for bug #1878298 at tktoolkit sf bug tracker
++ self.event_generate('<Expose>')
++
++ return fpage
++
++ def remove_page(self, page_name):
++ """Remove page_name from the notebook."""
++ if not page_name in self.pages:
++ raise KeyError("No such TabPage: '%s" % page_name)
++
++ self.forget(self.index(self.pages[page_name].frame))
++ del self.pages[page_name]
++
++ # workaround for bug #1878298 at tktoolkit sf bug tracker
++ self.event_generate('<Expose>')
++
++ def last_page(self):
++ """Return the last page in the notebook."""
++ return self.pages[self.tab(self.index('end') - 1)['text']]
++
++if __name__ == '__main__':
++ from Tkinter import Tk
++ from Tkconstants import TOP, BOTH
++ from ttk import Label, Entry, Button, Style
++ # test dialog
++ root=Tk()
++ style = Style()
++ style.configure('C.TLabel', padding=20)
++ tabPage=TabbedPageSet(root, page_names=['Foobar','Baz'])
++ tabPage.pack(side=TOP, expand=True, fill=BOTH)
++ Label(tabPage.pages['Foobar'].frame, text='Foo', style='C.TLabel').pack()
++ Label(tabPage.pages['Foobar'].frame, text='Bar', style='C.TLabel').pack()
++ Label(tabPage.pages['Baz'].frame, text='Baz').pack()
++ entryPgName=Entry(root)
++ buttonAdd=Button(root, text='Add Page',
++ command=lambda:tabPage.add_page(entryPgName.get()))
++ buttonRemove=Button(root, text='Remove Page',
++ command=lambda:tabPage.remove_page(entryPgName.get()))
++ labelPgName=Label(root, text='name of page to add/remove:')
++ buttonAdd.pack(padx=5, pady=5)
++ buttonRemove.pack(padx=5, pady=5)
++ labelPgName.pack(padx=5)
++ entryPgName.pack(padx=5)
++ root.mainloop()
+Index: dynOptionMenuWidget.py
+===================================================================
+--- dynOptionMenuWidget.py (revision 63995)
++++ dynOptionMenuWidget.py (revision 65541)
+@@ -2,34 +2,41 @@
+ OptionMenu widget modified to allow dynamic menu reconfiguration
+ and setting of highlightthickness
+ """
+-from Tkinter import OptionMenu
++from Tkinter import OptionMenu, Menu
+ from Tkinter import _setit
+ import copy
+
++from configHandler import idleConf
++TTK = idleConf.GetOption('main', 'General', 'use-ttk', type='int')
++if TTK:
++ from ttk import *
++
+ class DynOptionMenu(OptionMenu):
+- """
+- unlike OptionMenu, our kwargs can include highlightthickness
+- """
++ """Unlike OptionMenu, our kwargs can include highlightthickness"""
+ def __init__(self, master, variable, value, *values, **kwargs):
+ #get a copy of kwargs before OptionMenu.__init__ munges them
+ kwargsCopy=copy.copy(kwargs)
+ if 'highlightthickness' in kwargs.keys():
+ del(kwargs['highlightthickness'])
++ self.command=kwargs.get('command')
++ self.variable=variable
++
+ OptionMenu.__init__(self, master, variable, value, *values, **kwargs)
+ self.config(highlightthickness=kwargsCopy.get('highlightthickness'))
+- #self.menu=self['menu']
+- self.variable=variable
+- self.command=kwargs.get('command')
+
+- def SetMenu(self,valueList,value=None):
++ def SetMenu(self, valueList, value=None):
+ """
+ clear and reload the menu with a new set of options.
+ valueList - list of new options
+ value - initial value to set the optionmenu's menubutton to
+ """
+- self['menu'].delete(0,'end')
+- for item in valueList:
+- self['menu'].add_command(label=item,
++ if TTK:
++ self.set_menu(value, *valueList)
++ else:
++ menu = self['menu']
++ menu.delete(0,'end')
++ for item in valueList:
++ menu.add_command(label=item,
+ command=_setit(self.variable,item,self.command))
+- if value:
+- self.variable.set(value)
++ if value:
++ self.variable.set(value)
+Index: extend.txt
+===================================================================
+--- extend.txt (revision 63995)
++++ extend.txt (revision 65541)
+@@ -81,3 +81,10 @@
+
+ For further information on binding refer to the Tkinter Resources web page at
+ python.org and to the Tk Command "bind" man page.
++
++
++Note
++----
++
++Given that this branch is using tabbed pages, don't expect the previous
++description to be correct related to the code present here.
+Index: MultiStatusBar.py
+===================================================================
+--- MultiStatusBar.py (revision 63995)
++++ MultiStatusBar.py (revision 65541)
+@@ -1,5 +1,11 @@
+-from Tkinter import *
++from Tkinter import Tk, Frame, Label
++from Tkconstants import LEFT, SUNKEN, W
+
++from configHandler import idleConf
++
++if idleConf.GetOption('main', 'General', 'use-ttk', type='int'):
++ from ttk import Frame, Label
++
+ class MultiStatusBar(Frame):
+
+ def __init__(self, master=None, **kw):
+@@ -10,7 +16,7 @@
+
+ def set_label(self, name, text='', side=LEFT):
+ if not self.labels.has_key(name):
+- label = Label(self, bd=1, relief=SUNKEN, anchor=W)
++ label = Label(self, relief=SUNKEN, anchor=W)
+ label.pack(side=side)
+ self.labels[name] = label
+ else:
+@@ -18,6 +24,8 @@
+ label.config(text=text)
+
+ def _test():
++ from Tkinter import Text
++ from Tkconstants import TOP, BOTTOM, X
+ b = Frame()
+ c = Text(b)
+ c.pack(side=TOP)
+
+Property changes on: .
+___________________________________________________________________
+Name: svnmerge-integrated
+ + /python/trunk/Lib/idlelib:1-63994
+
More information about the Python-checkins
mailing list