[Python-checkins] python/dist/src/Tools/idle CREDITS.txt,NONE,1.2.2.1 HISTORY.txt,NONE,1.2.2.1 INSTALL.txt,NONE,1.2.2.1 LICENSE.txt,NONE,1.2.2.1 Makefile,NONE,1.2.2.1 RemoteDebugger.py,NONE,1.2.2.1 RemoteObjectBrowser.py,NONE,1.2.2.1 aboutDialog.py,NONE,1.2.2.1 boolcheck.py,NONE,1.2.2.1 config-extensions.def,NONE,1.2.2.1 config-highlight.def,NONE,1.2.2.1 config-keys.def,NONE,1.2.2.1 config-main.def,NONE,1.2.2.1 configDialog.py,NONE,1.2.2.1 configHandler.py,NONE,1.2.2.1 configHelpSourceEdit.py,NONE,1.2.2.1 configSectionNameDialog.py,NONE,1.2.2.1 dynOptionMenuWidget.py,NONE,1.2.2.1 interruptmodule.c,NONE,1.2.2.1 keybindingDialog.py,NONE,1.2.2.1 macosx_main.py,NONE,1.2.2.1 rpc.py,NONE,1.2.2.1 run.py,NONE,1.2.2.1 setup.cfg,NONE,1.2.2.1 tabpage.py,NONE,1.2.2.1 textView.py,NONE,1.2.2.1

gvanrossum@users.sourceforge.net gvanrossum@users.sourceforge.net
Tue, 29 Apr 2003 04:28:31 -0700


Update of /cvsroot/python/python/dist/src/Tools/idle
In directory sc8-pr-cvs1:/tmp/cvs-serv3262

Added Files:
      Tag: idlefork-merge-branch
	CREDITS.txt HISTORY.txt INSTALL.txt LICENSE.txt Makefile 
	RemoteDebugger.py RemoteObjectBrowser.py aboutDialog.py 
	boolcheck.py config-extensions.def config-highlight.def 
	config-keys.def config-main.def configDialog.py 
	configHandler.py configHelpSourceEdit.py 
	configSectionNameDialog.py dynOptionMenuWidget.py 
	interruptmodule.c keybindingDialog.py macosx_main.py rpc.py 
	run.py setup.cfg tabpage.py textView.py 
Log Message:
Add new files on the idlefork-merge-branch where they belong.

--- NEW FILE: CREDITS.txt ---
IDLEfork Credits
==================

Guido van Rossum, as well as being the creator of the Python language, is
the original creator of IDLE.  He also developed the RPC code and Remote
Debugger extension used in IDLEfork.

The IDLEfork project was initiated and brought up to version 0.7.1 primarily
by David Scherer, with help from Peter Schneider-Kamp and Nicholas Riley.
Bruce Sherwood has contributed considerable time testing and suggesting
improvements.

Besides Guido, the main developers who have been active on IDLEfork version
0.8.1 and later are Stephen M. Gava, who implemented the Configuration GUI, the
new configuration system, and the new About menu, and Kurt B. Kaiser, who
completed the integration of the RPC and remote debugger, and made a number of
usability enhancements.

Other contributors include Raymond Hettinger, Tony Lownds (Mac integration),
Neal Norwitz (code check and clean-up), and Chui Tey (RPC integration, debugger
integration and persistent breakpoints).

Hernan Foffani, Christos Georgiou, Jason Orendorff, Josh Robb, and Bruce
Sherwood have submitted useful patches.  Thanks, guys!

There are others who should be included here, especially those who contributed
to IDLE versions prior to 0.8, principally Mark Hammond, Jeremy Hylton,
Tim Peters, and Moshe Zadka.  For additional details refer to NEWS.txt and
Changelog.

Please contact the IDLEfork maintainer to have yourself included here if you
are one of those we missed! 

Contact details at http://idlefork.sourceforge.net



--- NEW FILE: HISTORY.txt ---
IDLE History
============

This file contains the release messages for previous IDLE releases.
As you read on you go back to the dark ages of IDLE's history.


IDLE 0.5 - February 2000 - Release Notes
----------------------------------------

This is an early release of IDLE, my own attempt at a Tkinter-based
IDE for Python.

(For a more detailed change log, see the file ChangeLog.)

FEATURES

IDLE has the following features:

- coded in 100% pure Python, using the Tkinter GUI toolkit (i.e. Tcl/Tk)

- cross-platform: works on Windows and Unix (on the Mac, there are
currently problems with Tcl/Tk)

- multi-window text editor with multiple undo, Python colorizing
and many other features, e.g. smart indent and call tips

- Python shell window (a.k.a. interactive interpreter)

- debugger (not complete, but you can set breakpoints, view  and step)

USAGE

The main program is in the file "idle.py"; on Unix, you should be able
to run it by typing "./idle.py" to your shell.  On Windows, you can
run it by double-clicking it; you can use idle.pyw to avoid popping up
a DOS console.  If you want to pass command line arguments on Windows,
use the batch file idle.bat.

Command line arguments: files passed on the command line are executed,
not opened for editing, unless you give the -e command line option.
Try "./idle.py -h" to see other command line options.

IDLE requires Python 1.5.2, so it is currently only usable with a
Python 1.5.2 distribution.  (An older version of IDLE is distributed
with Python 1.5.2; you can drop this version on top of it.)

COPYRIGHT

IDLE is covered by the standard Python copyright notice
(http://www.python.org/doc/Copyright.html).


New in IDLE 0.5 (2/15/2000)
---------------------------

Tons of stuff, much of it contributed by Tim Peters and Mark Hammond:

- Status bar, displaying current line/column (Moshe Zadka).

- Better stack viewer, using tree widget.  (XXX Only used by Stack
Viewer menu, not by the debugger.)

- Format paragraph now recognizes Python block comments and reformats
them correctly (MH)

- New version of pyclbr.py parses top-level functions and understands
much more of Python's syntax; this is reflected in the class and path
browsers (TP)

- Much better auto-indent; knows how to indent the insides of
multi-line statements (TP)

- Call tip window pops up when you type the name of a known function
followed by an open parenthesis.  Hit ESC or click elsewhere in the
window to close the tip window (MH)

- Comment out region now inserts ## to make it stand out more (TP)

- New path and class browsers based on a tree widget that looks
familiar to Windows users

- Reworked script running commands to be more intuitive: I/O now
always goes to the *Python Shell* window, and raw_input() works
correctly.  You use F5 to import/reload a module: this adds the module
name to the __main__ namespace.  You use Control-F5 to run a script:
this runs the script *in* the __main__ namespace.  The latter also
sets sys.argv[] to the script name


New in IDLE 0.4 (4/7/99)
------------------------

Most important change: a new menu entry "File -> Path browser", shows
a 4-column hierarchical browser which lets you browse sys.path,
directories, modules, and classes.  Yes, it's a superset of the Class
browser menu entry.  There's also a new internal module,
MultiScrolledLists.py, which provides the framework for this dialog.


New in IDLE 0.3 (2/17/99)
-------------------------

Most important changes:

- Enabled support for running a module, with or without the debugger.
Output goes to a new window.  Pressing F5 in a module is effectively a
reload of that module; Control-F5 loads it under the debugger.

- Re-enable tearing off the Windows menu, and make a torn-off Windows
menu update itself whenever a window is opened or closed.

- Menu items can now be have a checkbox (when the menu label starts
with "!"); use this for the Debugger and "Auto-open stack viewer"
(was: JIT stack viewer) menu items.

- Added a Quit button to the Debugger API.

- The current directory is explicitly inserted into sys.path.

- Fix the debugger (when using Python 1.5.2b2) to use canonical
filenames for breakpoints, so these actually work.  (There's still a
lot of work to be done to the management of breakpoints in the
debugger though.)

- Closing a window that is still colorizing now actually works.

- Allow dragging of the separator between the two list boxes in the
class browser.

- Bind ESC to "close window" of the debugger, stack viewer and class
browser.  It removes the selection highlighting in regular text
windows.  (These are standard Windows conventions.)


New in IDLE 0.2 (1/8/99)
------------------------

Lots of changes; here are the highlights:

General:

- You can now write and configure your own IDLE extension modules; see
extend.txt.


File menu:

The command to open the Python shell window is now in the File menu.


Edit menu:

New Find dialog with more options; replace dialog; find in files dialog.

Commands to tabify or untabify a region.

Command to format a paragraph.


Debug menu:

JIT (Just-In-Time) stack viewer toggle -- if set, the stack viewer
automaticall pops up when you get a traceback.

Windows menu:

Zoom height -- make the window full height.


Help menu:

The help text now show up in a regular window so you can search and
even edit it if you like.



IDLE 0.1 was distributed with the Python 1.5.2b1 release on 12/22/98.

======================================================================

--- NEW FILE: INSTALL.txt ---
IDLEfork Installation Notes
===========================

IDLEfork requires Python Version 2.2 or later.

There are several distribution files (where xx is the subversion):

IDLEfork-0.9xx.win32.exe
	This is a Windows installer which will install IDLEfork in 
	..../site-packages/idleforklib/ and place the idefork startup script
	at ..../scripts/idlefork.  Rename this to idlefork.pyw and
	point your launcher icons at it.  Installation is as idlefork
	to avoid conflict with the original Python IDLE.

IDLEfork-0.9xx-1.noarch.rpm
 	This is an rpm which is designed to install as idleforklib in an
	existing /usr/lib/python2.2 tree.  It installs as idlefork to avoid
	conflict with Python IDLE.

	Python rpms are available at http://www.python.org/2.2.2/rpms.html and
	http://www.python.org/2.2.1/rpms.html.

IDLEfork-0.9xx.tar.gz
	This is a distutils sdist (source) tarfile which can be used to make 
	installations on platforms not supported by the above files.
	** It remains configured to install as idlelib, not idleforklib. **

	Unpack in ..../Tools/, cd to the IDLEfork directory created, and
	"python setup.py install" to install in ....site-packages/idlelib.
	This will overwrite the Python IDLE installation.  

        If you don't want to overwrite Python IDLE, it is also possible to
	simply call "python idle.py" to run from the IDLEfork source directory
	without making an installation.  In this case, IDLE will not be on
	your PATH unless you are in the source directory.  Also, it is then
	advisable to remove any Python IDLE installation by removing
	..../site-packages/idlelib so the two identically named packages don't
	conflict.

	On Redhat Linux systems prior to 8.0, /usr/bin/python may be pointing
	at python1.5.  If so, change the first line in the /usr/bin/idle 
	script to read:
	       !# /usr/bin/python2.2
	       
See README.txt for more details on this version of IDLEfork. 







--- NEW FILE: LICENSE.txt ---
To apply this license to IDLE or IDLEfork, read 'IDLE' or 'IDLEfork'
for every occurence of 'Python 2.1.1' in the text below.

PSF LICENSE AGREEMENT
---------------------

1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using Python 2.1.1 software in source or binary form and its
associated documentation.

2. Subject to the terms and conditions of this License Agreement, PSF
hereby grants Licensee a nonexclusive, royalty-free, world-wide
license to reproduce, analyze, test, perform and/or display publicly,
prepare derivative works, distribute, and otherwise use Python 2.1.1
alone or in any derivative version, provided, however, that PSF's
License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
2001 Python Software Foundation; All Rights Reserved" are retained in
Python 2.1.1 alone or in any derivative version prepared by Licensee.

3. In the event Licensee prepares a derivative work that is based on
or incorporates Python 2.1.1 or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python 2.1.1.

4. PSF is making Python 2.1.1 available to Licensee on an "AS IS"
basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.1.1 WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.

5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
2.1.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.1.1,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.

6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.

7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee.  This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.

8. By copying, installing or otherwise using Python 2.1.1, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.

--- NEW FILE: Makefile ---
# Makefile to build the interrupt module, which is a C extension

PYTHON=python2.3

all: interrupt.so

interrupt.so: interruptmodule.c
	$(PYTHON) setup.py build_ext -i

--- NEW FILE: RemoteDebugger.py ---
"""Support for remote Python debugging.

Some ASCII art to describe the structure:

       IN PYTHON SUBPROCESS          #             IN IDLE PROCESS
                                     #
                                     #        oid='gui_adapter'
                 +----------+        #       +------------+          +-----+
                 | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
+-----+--calls-->+----------+        #       +------------+          +-----+
| Idb |                               #                             /
+-----+<-calls--+------------+         #      +----------+<--calls-/
                | IdbAdapter |<--remote#call--| IdbProxy |
                +------------+         #      +----------+
                oid='idb_adapter'      #

The purpose of the Proxy and Adapter classes is to translate certain
arguments and return values that cannot be transported through the RPC
barrier, in particular frame and traceback objects.

"""

import sys
import types
import rpc
import Debugger

debugging = 0

idb_adap_oid = "idb_adapter"
gui_adap_oid = "gui_adapter"

#=======================================
#
# In the PYTHON subprocess:

frametable = {}
dicttable = {}
codetable = {}
tracebacktable = {}

def wrap_frame(frame):
    fid = id(frame)
    frametable[fid] = frame
    return fid

def wrap_info(info):
    "replace info[2], a traceback instance, by its ID"
    if info is None:
        return None
    else:
        traceback = info[2]
        assert isinstance(traceback, types.TracebackType)
        traceback_id = id(traceback)
        tracebacktable[traceback_id] = traceback
        modified_info = (info[0], info[1], traceback_id)
        return modified_info

class GUIProxy:

    def __init__(self, conn, gui_adap_oid):
        self.conn = conn
        self.oid = gui_adap_oid

    def interaction(self, message, frame, info=None):
        # calls rpc.SocketIO.remotecall() via run.MyHandler instance
        # pass frame and traceback object IDs instead of the objects themselves
        self.conn.remotecall(self.oid, "interaction",
                             (message, wrap_frame(frame), wrap_info(info)),
                             {})

class IdbAdapter:

    def __init__(self, idb):
        self.idb = idb

    #----------called by an IdbProxy----------

    def set_step(self):
        self.idb.set_step()

    def set_quit(self):
        self.idb.set_quit()

    def set_continue(self):
        self.idb.set_continue()

    def set_next(self, fid):
        frame = frametable[fid]
        self.idb.set_next(frame)

    def set_return(self, fid):
        frame = frametable[fid]
        self.idb.set_return(frame)

    def get_stack(self, fid, tbid):
        ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
        frame = frametable[fid]
        if tbid is None:
            tb = None
        else:
            tb = tracebacktable[tbid]
        stack, i = self.idb.get_stack(frame, tb)
        ##print >>sys.__stderr__, "get_stack() ->", stack
        stack = [(wrap_frame(frame), k) for frame, k in stack]
        ##print >>sys.__stderr__, "get_stack() ->", stack
        return stack, i

    def run(self, cmd):
        import __main__
        self.idb.run(cmd, __main__.__dict__)

    def set_break(self, filename, lineno):
        msg = self.idb.set_break(filename, lineno)
        return msg

    def clear_break(self, filename, lineno):
        msg = self.idb.clear_break(filename, lineno)
        return msg

    def clear_all_file_breaks(self, filename):
        msg = self.idb.clear_all_file_breaks(filename)
        return msg

    #----------called by a FrameProxy----------

    def frame_attr(self, fid, name):
        frame = frametable[fid]
        return getattr(frame, name)

    def frame_globals(self, fid):
        frame = frametable[fid]
        dict = frame.f_globals
        did = id(dict)
        dicttable[did] = dict
        return did

    def frame_locals(self, fid):
        frame = frametable[fid]
        dict = frame.f_locals
        did = id(dict)
        dicttable[did] = dict
        return did

    def frame_code(self, fid):
        frame = frametable[fid]
        code = frame.f_code
        cid = id(code)
        codetable[cid] = code
        return cid

    #----------called by a CodeProxy----------

    def code_name(self, cid):
        code = codetable[cid]
        return code.co_name

    def code_filename(self, cid):
        code = codetable[cid]
        return code.co_filename

    #----------called by a DictProxy----------

    def dict_keys(self, did):
        dict = dicttable[did]
        return dict.keys()

    def dict_item(self, did, key):
        dict = dicttable[did]
        value = dict[key]
        value = repr(value)
        return value

#----------end class IdbAdapter----------


def start_debugger(rpchandler, gui_adap_oid):
    """Start the debugger and its RPC link in the Python subprocess

    Start the subprocess side of the split debugger and set up that side of the
    RPC link by instantiating the GUIProxy, Idb debugger, and IdbAdapter
    objects and linking them together.  Register the IdbAdapter with the
    RPCServer to handle RPC requests from the split debugger GUI via the
    IdbProxy.

    """
    gui_proxy = GUIProxy(rpchandler, gui_adap_oid)
    idb = Debugger.Idb(gui_proxy)
    idb_adap = IdbAdapter(idb)
    rpchandler.register(idb_adap_oid, idb_adap)
    return idb_adap_oid


#=======================================
#
# In the IDLE process:


class FrameProxy:

    def __init__(self, conn, fid):
        self._conn = conn
        self._fid = fid
        self._oid = "idb_adapter"
        self._dictcache = {}

    def __getattr__(self, name):
        if name[:1] == "_":
            raise AttributeError, name
        if name == "f_code":
            return self._get_f_code()
        if name == "f_globals":
            return self._get_f_globals()
        if name == "f_locals":
            return self._get_f_locals()
        return self._conn.remotecall(self._oid, "frame_attr",
                                     (self._fid, name), {})

    def _get_f_code(self):
        cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
        return CodeProxy(self._conn, self._oid, cid)

    def _get_f_globals(self):
        did = self._conn.remotecall(self._oid, "frame_globals",
                                    (self._fid,), {})
        return self._get_dict_proxy(did)

    def _get_f_locals(self):
        did = self._conn.remotecall(self._oid, "frame_locals",
                                    (self._fid,), {})
        return self._get_dict_proxy(did)

    def _get_dict_proxy(self, did):
        if self._dictcache.has_key(did):
            return self._dictcache[did]
        dp = DictProxy(self._conn, self._oid, did)
        self._dictcache[did] = dp
        return dp


class CodeProxy:

    def __init__(self, conn, oid, cid):
        self._conn = conn
        self._oid = oid
        self._cid = cid

    def __getattr__(self, name):
        if name == "co_name":
            return self._conn.remotecall(self._oid, "code_name",
                                         (self._cid,), {})
        if name == "co_filename":
            return self._conn.remotecall(self._oid, "code_filename",
                                         (self._cid,), {})


class DictProxy:

    def __init__(self, conn, oid, did):
        self._conn = conn
        self._oid = oid
        self._did = did

    def keys(self):
        return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})

    def __getitem__(self, key):
        return self._conn.remotecall(self._oid, "dict_item",
                                     (self._did, key), {})

    def __getattr__(self, name):
        ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
        raise AttributeError, name


class GUIAdapter:

    def __init__(self, conn, gui):
        self.conn = conn
        self.gui = gui

    def interaction(self, message, fid, modified_info):
        ##print "interaction: (%s, %s, %s)" % (message, fid, modified_info)
        frame = FrameProxy(self.conn, fid)
        self.gui.interaction(message, frame, modified_info)


class IdbProxy:

    def __init__(self, conn, shell, oid):
        self.oid = oid
        self.conn = conn
        self.shell = shell

    def call(self, methodname, *args, **kwargs):
        ##print "**IdbProxy.call %s %s %s" % (methodname, args, kwargs)
        value = self.conn.remotecall(self.oid, methodname, args, kwargs)
        ##print "**IdbProxy.call %s returns %s" % (methodname, `value`)
        return value

    def run(self, cmd, locals):
        # Ignores locals on purpose!
        seq = self.conn.asynccall(self.oid, "run", (cmd,), {})
        self.shell.interp.active_seq = seq

    def get_stack(self, frame, tbid):
        # passing frame and traceback IDs, not the objects themselves
        stack, i = self.call("get_stack", frame._fid, tbid)
        stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
        return stack, i

    def set_continue(self):
        self.call("set_continue")

    def set_step(self):
        self.call("set_step")

    def set_next(self, frame):
        self.call("set_next", frame._fid)

    def set_return(self, frame):
        self.call("set_return", frame._fid)

    def set_quit(self):
        self.call("set_quit")

    def set_break(self, filename, lineno):
        msg = self.call("set_break", filename, lineno)
        return msg

    def clear_break(self, filename, lineno):
        msg = self.call("clear_break", filename, lineno)
        return msg

    def clear_all_file_breaks(self, filename):
        msg = self.call("clear_all_file_breaks", filename)
        return msg

def start_remote_debugger(rpcclt, pyshell):
    """Start the subprocess debugger, initialize the debugger GUI and RPC link

    Request the RPCServer start the Python subprocess debugger and link.  Set
    up the Idle side of the split debugger by instantiating the IdbProxy,
    debugger GUI, and debugger GUIAdapter objects and linking them together.

    Register the GUIAdapter with the RPCClient to handle debugger GUI
    interaction requests coming from the subprocess debugger via the GUIProxy.

    The IdbAdapter will pass execution and environment requests coming from the
    Idle debugger GUI to the subprocess debugger via the IdbProxy.

    """
    global idb_adap_oid

    idb_adap_oid = rpcclt.remotecall("exec", "start_the_debugger",\
                                   (gui_adap_oid,), {})
    idb_proxy = IdbProxy(rpcclt, pyshell, idb_adap_oid)
    gui = Debugger.Debugger(pyshell, idb_proxy)
    gui_adap = GUIAdapter(rpcclt, gui)
    rpcclt.register(gui_adap_oid, gui_adap)
    return gui

def close_remote_debugger(rpcclt):
    """Shut down subprocess debugger and Idle side of debugger RPC link

    Request that the RPCServer shut down the subprocess debugger and link.
    Unregister the GUIAdapter, which will cause a GC on the Idle process
    debugger and RPC link objects.  (The second reference to the debugger GUI
    is deleted in PyShell.close_remote_debugger().)

    """
    close_subprocess_debugger(rpcclt)
    rpcclt.unregister(gui_adap_oid)

def close_subprocess_debugger(rpcclt):
    rpcclt.remotecall("exec", "stop_the_debugger", (idb_adap_oid,), {})

def restart_subprocess_debugger(rpcclt):
    idb_adap_oid_ret = rpcclt.remotecall("exec", "start_the_debugger",\
                                         (gui_adap_oid,), {})
    assert idb_adap_oid_ret == idb_adap_oid, 'Idb restarted with different oid'

--- NEW FILE: RemoteObjectBrowser.py ---
import rpc

def remote_object_tree_item(item):
    wrapper = WrappedObjectTreeItem(item)
    oid = id(wrapper)
    rpc.objecttable[oid] = wrapper
    return oid

class WrappedObjectTreeItem:
    # Lives in PYTHON subprocess

    def __init__(self, item):
        self.__item = item

    def __getattr__(self, name):
        value = getattr(self.__item, name)
        return value

    def _GetSubList(self):
        list = self.__item._GetSubList()
        return map(remote_object_tree_item, list)

class StubObjectTreeItem:
    # Lives in IDLE process

    def __init__(self, sockio, oid):
        self.sockio = sockio
        self.oid = oid

    def __getattr__(self, name):
        value = rpc.MethodProxy(self.sockio, self.oid, name)
        return value

    def _GetSubList(self):
        list = self.sockio.remotecall(self.oid, "_GetSubList", (), {})
        return [StubObjectTreeItem(self.sockio, oid) for oid in list]

--- NEW FILE: aboutDialog.py ---
"""
about box for idle
"""

from Tkinter import *
import string, os
import textView
import idlever

class AboutDialog(Toplevel):
    """
    modal about dialog for idle
    """
    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.CreateWidgets()
        self.resizable(height=FALSE,width=FALSE)
        self.title(title)
        self.transient(parent)
        self.grab_set()
        self.protocol("WM_DELETE_WINDOW", self.Ok)
        self.parent = parent
        self.buttonOk.focus_set()
        #key bindings for this dialog
        self.bind('<Alt-c>',self.CreditsButtonBinding) #credits button
        self.bind('<Alt-l>',self.LicenseButtonBinding) #license button
        self.bind('<Return>',self.Ok) #dismiss dialog
        self.bind('<Escape>',self.Ok) #dismiss dialog
        self.wait_window()

    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='Ok',
                command=self.Ok)#,default=ACTIVE
        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='IDLEfork',fg=self.fg,bg=self.bg,
                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=0,column=1,sticky=W,rowspan=2,padx=0,pady=3)
        labelVersion = Label(frameBg,text='version  '+idlever.IDLE_VERSION,
                fg=self.fg,bg=self.bg)
        labelVersion.grid(row=1,column=0,sticky=W,padx=10,pady=5)
        labelDesc = Label(frameBg,
                text="A development version of Python's lightweight\n"+
                'Integrated DeveLopment Environment, IDLE.',
                justify=LEFT,fg=self.fg,bg=self.bg)
        labelDesc.grid(row=2,column=0,sticky=W,columnspan=3,padx=10,pady=5)
        labelCopyright = Label(frameBg,
                text="Copyright (c) 2001 Python Software Foundation;\nAll Rights Reserved",
                justify=LEFT,fg=self.fg,bg=self.bg)
        labelCopyright.grid(row=3,column=0,sticky=W,columnspan=3,padx=10,pady=5)
        labelLicense = Label(frameBg,
                text='Released under the Python 2.1.1 PSF Licence',
                justify=LEFT,fg=self.fg,bg=self.bg)
        labelLicense.grid(row=4,column=0,sticky=W,columnspan=3,padx=10,pady=5)
        Frame(frameBg,height=5,bg=self.bg).grid(row=5,column=0)
        labelEmail = Label(frameBg,text='email:  idle-dev@python.org',
                justify=LEFT,fg=self.fg,bg=self.bg)
        labelEmail.grid(row=6,column=0,columnspan=2,sticky=W,padx=10,pady=0)
        labelWWW = Label(frameBg,text='www:  http://idlefork.sourceforge.net',
                justify=LEFT,fg=self.fg,bg=self.bg)
        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)
        labelPythonVer = Label(frameBg,text='Python version:  '+
                sys.version.split()[0],fg=self.fg,bg=self.bg)
        labelPythonVer.grid(row=9,column=0,sticky=W,padx=10,pady=0)
        #handle weird tk version num in windoze python >= 1.6 (?!?)
        tkVer = `TkVersion`.split('.')
        tkVer[len(tkVer)-1] = str('%.3g' % (float('.'+tkVer[len(tkVer)-1])))[2:]
        if tkVer[len(tkVer)-1] == '':
            tkVer[len(tkVer)-1] = '0'
        tkVer = string.join(tkVer,'.')
        labelTkVer = Label(frameBg,text='Tk version:  '+
                tkVer,fg=self.fg,bg=self.bg)
        labelTkVer.grid(row=9,column=1,sticky=W,padx=2,pady=0)

        self.buttonLicense = Button(frameBg,text='View License',underline=5,
                width=14,highlightbackground=self.bg,command=self.ShowLicense)#takefocus=FALSE
        self.buttonLicense.grid(row=10,column=0,sticky=W,padx=10,pady=10)
        self.buttonCredits = Button(frameBg,text='View Credits',underline=5,
                width=14,highlightbackground=self.bg,command=self.ShowCredits)#takefocus=FALSE
        self.buttonCredits.grid(row=10,column=1,columnspan=2,sticky=E,padx=10,pady=10)

    def CreditsButtonBinding(self,event):
        self.buttonCredits.invoke()

    def LicenseButtonBinding(self,event):
        self.buttonLicense.invoke()

    def ShowLicense(self):
        self.ViewFile('About - License','LICENSE.txt')

    def ShowCredits(self):
        self.ViewFile('About - Credits','CREDITS.txt')

    def ViewFile(self,viewTitle,viewFile):
        fn=os.path.join(os.path.abspath(os.path.dirname(__file__)),viewFile)
        textView.TextViewer(self,viewTitle,fn)

    def Ok(self, event=None):
        self.destroy()

if __name__ == '__main__':
    #test the dialog
    root=Tk()
    def run():
        import aboutDialog
        aboutDialog.AboutDialog(root,'About')
    Button(root,text='Dialog',command=run).pack()
    root.mainloop()

--- NEW FILE: boolcheck.py ---
"boolcheck - import this module to ensure True, False, bool() builtins exist."
try:
    True
except NameError:
    import __builtin__
    __builtin__.True = 1
    __builtin__.False = 0
    from operator import truth
    __builtin__.bool = truth

--- NEW FILE: config-extensions.def ---
# IDLE reads several config files to determine user preferences.  This 
# file is the default config file for idle extensions settings.  
#
# Each extension must have at least one section, named after the extension
# module. This section must contain an 'enable' item (=1 to enable the
# extension, =0 to disable it) and also contains any other general
# configuration items for the extension. Each extension may also define up to
# two optional sections named ExtensionName_bindings and
# ExtensionName_cfgBindings. If present, ExtensionName_bindings defines virtual
# event bindings for the extension that are not sensibly re-configurable. If
# present, ExtensionName_cfgBindings defines virtual event bindings for the
# extension that may be sensibly re-configured.

# See config-keys.def for notes on specifying keys.

[FormatParagraph]
enable=1
[FormatParagraph_cfgBindings]
format-paragraph=<Alt-Key-q>

[AutoExpand]
enable=1
[AutoExpand_cfgBindings]
expand-word=<Alt-Key-slash>

[ZoomHeight]
enable=1
[ZoomHeight_cfgBindings]
zoom-height=<Alt-Key-2>

[ScriptBinding]
enable=1
[ScriptBinding_cfgBindings]
run-module=<Key-F5>
check-module=<Alt-Key-x>

[CallTips]
enable=1
[CallTips_bindings]
paren-open=<Key-parenleft>
paren-close=<Key-parenright>
check-calltip-cancel=<KeyRelease>
calltip-cancel=<ButtonPress> <Key-Escape>

[ParenMatch]
enable=0
style= expression
flash-delay= 500
bell= 1
hilite-foreground= black
hilite-background= #43cd80
[ParenMatch_bindings]
flash-open-paren=<KeyRelease-parenright> <KeyRelease-bracketright> <KeyRelease-braceright>
check-restore=<KeyPress>

--- NEW FILE: config-highlight.def ---
# IDLE reads several config files to determine user preferences.  This 
# file is the default config file for idle highlight theme settings.  

[IDLE Classic]
normal-foreground= #000000
normal-background= #ffffff
keyword-foreground= #ff7700
keyword-background= #ffffff
comment-foreground= #dd0000
comment-background= #ffffff
string-foreground= #00aa00
string-background= #ffffff
definition-foreground= #0000ff
definition-background= #ffffff
hilite-foreground= #000000
hilite-background= gray
break-foreground= black
break-background= #ffff55
hit-foreground= #ffffff
hit-background= #000000
error-foreground= #000000
error-background= #ff7777
#cursor (only foreground can be set) 
cursor-foreground= black
#shell window
stdout-foreground= blue
stdout-background= #ffffff
stderr-foreground= red
stderr-background= #ffffff
console-foreground= #770000
console-background= #ffffff

[IDLE New]
normal-foreground= #000000
normal-background= #ffffff
keyword-foreground= #ff7700
keyword-background= #ffffff
comment-foreground= #dd0000
comment-background= #ffffff
string-foreground= #00aa00
string-background= #ffffff
definition-foreground= #0000ff
definition-background= #ffffff
hilite-foreground= #000000
hilite-background= gray
break-foreground= black
break-background= #ffff55
hit-foreground= #ffffff
hit-background= #000000
error-foreground= #000000
error-background= #ff7777
#cursor (only foreground can be set) 
cursor-foreground= black
#shell window
stdout-foreground= blue
stdout-background= #ffffff
stderr-foreground= red
stderr-background= #ffffff
console-foreground= #770000
console-background= #ffffff

--- NEW FILE: config-keys.def ---
# IDLE reads several config files to determine user preferences.  This 
# file is the default config file for idle key binding settings.  
# Where multiple keys are specified for an action: if they are separated
# by a space (eg. action=<key1> <key2>) then the keys are altenatives, if
# there is no space (eg. action=<key1><key2>) then the keys comprise a
# single 'emacs style' multi-keystoke binding. The tk event specifier 'Key'
# is used in all cases, for consistency in auto key conflict checking in the
# configuration gui.

[IDLE Classic Windows]
copy=<Control-Key-c>
cut=<Control-Key-x>
paste=<Control-Key-v>
beginning-of-line= <Key-Home>
center-insert=<Control-Key-l>
close-all-windows=<Control-Key-q>
close-window=<Alt-Key-F4> <Meta-Key-F4>
do-nothing=<Control-Key-F12>
end-of-file=<Control-Key-d>
python-docs=<Key-F1>
python-context-help=<Shift-Key-F1> 
history-next=<Alt-Key-n> <Meta-Key-n>
history-previous=<Alt-Key-p> <Meta-Key-p>
interrupt-execution=<Control-Key-c>
view-restart=<Key-F6>
restart-shell=<Control-Key-F6>
open-class-browser=<Alt-Key-c> <Meta-Key-c>
open-module=<Alt-Key-m> <Meta-Key-m>
open-new-window=<Control-Key-n>
open-window-from-file=<Control-Key-o>
plain-newline-and-indent=<Control-Key-j>
print-window=<Control-Key-p>
redo=<Control-Shift-Key-z>
remove-selection=<Key-Escape>
save-copy-of-window-as-file=<Alt-Shift-Key-s>
save-window-as-file=<Control-Shift-Key-s>
save-window=<Control-Key-s>
select-all=<Control-Key-a>
toggle-auto-coloring=<Control-Key-slash>
undo=<Control-Key-z>
find=<Control-Key-f>
find-again=<Control-Key-g> <Key-F3>
find-in-files=<Alt-Key-F3> <Meta-Key-F3>
find-selection=<Control-Key-F3>
replace=<Control-Key-h>
goto-line=<Alt-Key-g> <Meta-Key-g>
smart-backspace=<Key-BackSpace>
newline-and-indent=<Key-Return> <Key-KP_Enter>
smart-indent=<Key-Tab>
indent-region=<Control-Key-bracketright>
dedent-region=<Control-Key-bracketleft>
comment-region=<Alt-Key-3> <Meta-Key-3>
uncomment-region=<Alt-Key-4> <Meta-Key-4>
tabify-region=<Alt-Key-5> <Meta-Key-5>
untabify-region=<Alt-Key-6> <Meta-Key-6>
toggle-tabs=<Alt-Key-t> <Meta-Key-t>
change-indentwidth=<Alt-Key-u> <Meta-Key-u>

[IDLE Classic Unix]
copy=<Alt-Key-w> <Meta-Key-w>
cut=<Control-Key-w>
paste=<Control-Key-y>
beginning-of-line=<Control-Key-a> <Key-Home>
center-insert=<Control-Key-l>
close-all-windows=<Control-Key-x><Control-Key-c>
close-window=<Control-Key-x><Control-Key-0>
do-nothing=<Control-Key-x>
end-of-file=<Control-Key-d>
history-next=<Alt-Key-n> <Meta-Key-n>
history-previous=<Alt-Key-p> <Meta-Key-p>
interrupt-execution=<Control-Key-c>
view-restart=<Key-F6>
restart-shell=<Control-Key-F6>
open-class-browser=<Control-Key-x><Control-Key-b>
open-module=<Control-Key-x><Control-Key-m>
open-new-window=<Control-Key-x><Control-Key-n>
open-window-from-file=<Control-Key-x><Control-Key-f>
plain-newline-and-indent=<Control-Key-j>
print-window=<Control-x><Control-Key-p>
python-docs=<Control-Key-h> 
python-context-help=<Control-Shift-Key-h> 
redo=<Alt-Key-z> <Meta-Key-z>
remove-selection=<Key-Escape>
save-copy-of-window-as-file=<Control-Key-x><Control-Key-y>
save-window-as-file=<Control-Key-x><Control-Key-w>
save-window=<Control-Key-x><Control-Key-s>
select-all=<Alt-Key-a> <Meta-Key-a>
toggle-auto-coloring=<Control-Key-slash>
undo=<Control-Key-z>
find=<Control-Key-u><Control-Key-u><Control-Key-s>
find-again=<Control-Key-u><Control-Key-s>
find-in-files=<Alt-Key-s> <Meta-Key-s>
find-selection=<Control-Key-s>
replace=<Control-Key-r>
goto-line=<Alt-Key-g> <Meta-Key-g>
smart-backspace=<Key-BackSpace>
newline-and-indent=<Key-Return> <Key-KP_Enter>
smart-indent=<Key-Tab>
indent-region=<Control-Key-bracketright>
dedent-region=<Control-Key-bracketleft>
comment-region=<Alt-Key-3>
uncomment-region=<Alt-Key-4>
tabify-region=<Alt-Key-5>
untabify-region=<Alt-Key-6>
toggle-tabs=<Alt-Key-t>
change-indentwidth=<Alt-Key-u>

[IDLE Classic Mac]
copy=<Command-Key-c>
cut=<Command-Key-x>
paste=<Command-Key-v>
beginning-of-line= <Key-Home>
center-insert=<Control-Key-l>
close-all-windows=<Command-Key-q>
close-window=<Command-Key-w>
do-nothing=<Control-Key-F12>
end-of-file=<Control-Key-d>
python-docs=<Key-F1>
python-context-help=<Shift-Key-F1> 
history-next=<Control-Key-n>
history-previous=<Control-Key-p>
interrupt-execution=<Control-Key-c>
view-restart=<Key-F6>
restart-shell=<Control-Key-F6>
open-class-browser=<Command-Key-b>
open-module=<Command-Key-m>
open-new-window=<Command-Key-n>
open-window-from-file=<Command-Key-o>
plain-newline-and-indent=<Control-Key-j>
print-window=<Command-Key-p>
redo=<Shift-Command-Key-z> 
remove-selection=<Key-Escape>
save-window-as-file=<Shift-Command-Key-s>
save-window=<Command-Key-s>
save-copy-of-window-as-file=<Option-Command-Key-s>
select-all=<Command-Key-a>
toggle-auto-coloring=<Control-Key-slash>
undo=<Command-Key-z>
find=<Command-Key-f>
find-again=<Command-Key-g> <Key-F3>
find-in-files=<Command-Key-F3>
find-selection=<Shift-Command-Key-F3>
replace=<Command-Key-r>
goto-line=<Command-Key-j>
smart-backspace=<Key-BackSpace>
newline-and-indent=<Key-Return> <Key-KP_Enter>
smart-indent=<Key-Tab>
indent-region=<Command-Key-bracketright>
dedent-region=<Command-Key-bracketleft>
comment-region=<Control-Key-3>
uncomment-region=<Control-Key-4>
tabify-region=<Control-Key-5>
untabify-region=<Control-Key-6>
toggle-tabs=<Control-Key-t>
change-indentwidth=<Control-Key-u>

--- NEW FILE: config-main.def ---
# IDLE reads several config files to determine user preferences.  This 
# file is the default config file for general idle settings.
#  
# When IDLE starts, it will look in
# the following two sets of files, in order:
#
#     default configuration
#     ---------------------
#     config-main.def         the default general config file
#     config-extensions.def   the default extension config file
#     config-highlight.def    the default highlighting config file
#     config-keys.def         the default keybinding config file
#
#     user configuration
#     -------------------
#     ~/.idlerc/idle-main.cfg            the user general config file
#     ~/.idlerc/idle-extensions.cfg      the user extension config file
#     ~/.idlerc/idle-highlight.cfg       the user highlighting config file
#     ~/.idlerc/idle-keys.cfg            the user keybinding config file
#
# Any options the user saves through the config dialog will be saved to
# the relevant user config file. Reverting any general setting to the 
# default causes that entry to be wiped from the user file and re-read 
# from the default file. User highlighting themes or keybinding sets are
# retained unless specifically deleted within the config dialog. Choosing
# one of the default themes or keysets just applies the relevant settings 
# from the default file. 
#
# Additional help sources are listed in the [HelpFiles] section and must be
# viewable by a web browser (or the Windows Help viewer in the case of .chm
# files). These sources will be listed on the Help menu.  The pattern is 
# <sequence_number = menu item;/path/to/help/source> 
# You can't use a semi-colon in a menu item or path.  The path will be platform
# specific because of path separators, drive specs etc.
#
# It is best to use the Configuration GUI to set up additional help sources!
# Example:
#1 = My Extra Help Source;/usr/share/doc/foo/index.html
#2 = Another Help Source;/path/to/another.pdf

[General]
editor-on-startup= 0
print-command-posix=lpr %s
print-command-win=start /min notepad /p %s

[EditorWindow]
width= 80
height= 30
font= courier
font-size= 12
font-bold= 0

[Indent]
use-spaces= 1
num-spaces= 4

[Theme]
default= 1  
name= IDLE Classic

[Keys]
default= 1  
name= IDLE Classic Windows

[HelpFiles]

--- NEW FILE: configDialog.py ---
"""IDLE Configuration Dialog: support user customization of IDLE by GUI

Customize font faces, sizes, and colorization attributes.  Set indentation
defaults.  Customize keybindings.  Colorization and keybindings can be
saved as user defined sets.  Select startup options including shell/editor
and default window size.  Define additional help sources.

Note that tab width in IDLE is currently fixed at eight due to Tk issues.
Refer to comment in EditorWindow autoindent code for details.

"""
from Tkinter import *
import tkMessageBox, tkColorChooser, tkFont
import string, copy

from configHandler import idleConf
from dynOptionMenuWidget import DynOptionMenu
from tabpage import TabPageSet
from keybindingDialog import GetKeysDialog
[...1095 lines suppressed...]
        self.destroy()

    def Ok(self):
        self.Apply()
        self.destroy()

    def Apply(self):
        self.SaveAllChangedConfigs()
        self.ActivateConfigChanges()

    def Help(self):
        pass

if __name__ == '__main__':
    #test the dialog
    root=Tk()
    Button(root,text='Dialog',
            command=lambda:ConfigDialog(root,'Settings')).pack()
    root.instanceDict={}
    root.mainloop()

--- NEW FILE: configHandler.py ---
"""Provides access to stored IDLE configuration information.

Refer to the comments at the beginning of config-main.def for a description of
the available configuration files and the design implemented to update user
configuration information.  In particular, user configuration choices which
duplicate the defaults will be removed from the user's configuration files,
and if a file becomes empty, it will be deleted.

The contents of the user files may be altered using the Options/Configure IDLE
menu to access the configuration GUI (configDialog.py), or manually.

Throughout this module there is an emphasis on returning useable defaults
when a problem occurs in returning a requested configuration value back to
idle. This is to allow IDLE to continue to function in spite of errors in
the retrieval of config information. When a default is returned instead of
a requested config value, a message is printed to stderr to aid in
configuration problem notification and resolution.

"""
import os
import sys
import string
from ConfigParser import ConfigParser, NoOptionError, NoSectionError

class InvalidConfigType(Exception): pass
class InvalidConfigSet(Exception): pass
class InvalidFgBg(Exception): pass
class InvalidTheme(Exception): pass

class IdleConfParser(ConfigParser):
    """
    A ConfigParser specialised for idle configuration file handling
    """
    def __init__(self, cfgFile, cfgDefaults=None):
        """
        cfgFile - string, fully specified configuration file name
        """
        self.file=cfgFile
        ConfigParser.__init__(self,defaults=cfgDefaults)

    def Get(self, section, option, type=None, default=None):
        """
        Get an option value for given section/option or return default.
        If type is specified, return as type.
        """
        if type=='bool':
            getVal=self.getboolean
        elif type=='int':
            getVal=self.getint
        else:
            getVal=self.get
        if self.has_option(section,option):
            #return getVal(section, option, raw, vars, default)
            return getVal(section, option)
        else:
            return default

    def GetOptionList(self,section):
        """
        Get an option list for given section
        """
        if self.has_section(section):
            return self.options(section)
        else:  #return a default value
            return []

    def Load(self):
        """
        Load the configuration file from disk
        """
        self.read(self.file)

class IdleUserConfParser(IdleConfParser):
    """
    IdleConfigParser specialised for user configuration handling.
    """

    def AddSection(self,section):
        """
        if section doesn't exist, add it
        """
        if not self.has_section(section):
            self.add_section(section)

    def RemoveEmptySections(self):
        """
        remove any sections that have no options
        """
        for section in self.sections():
            if not self.GetOptionList(section):
                self.remove_section(section)

    def IsEmpty(self):
        """
        Remove empty sections and then return 1 if parser has no sections
        left, else return 0.
        """
        self.RemoveEmptySections()
        if self.sections():
            return 0
        else:
            return 1

    def RemoveOption(self,section,option):
        """
        If section/option exists, remove it.
        Returns 1 if option was removed, 0 otherwise.
        """
        if self.has_section(section):
            return self.remove_option(section,option)

    def SetOption(self,section,option,value):
        """
        Sets option to value, adding section if required.
        Returns 1 if option was added or changed, otherwise 0.
        """
        if self.has_option(section,option):
            if self.get(section,option)==value:
                return 0
            else:
                self.set(section,option,value)
                return 1
        else:
            if not self.has_section(section):
                self.add_section(section)
            self.set(section,option,value)
            return 1

    def RemoveFile(self):
        """
        Removes the user config file from disk if it exists.
        """
        if os.path.exists(self.file):
            os.remove(self.file)

    def Save(self):
        """Update user configuration file.

        Remove empty sections. If resulting config isn't empty, write the file
        to disk. If config is empty, remove the file from disk if it exists.

        """
        if not self.IsEmpty():
            cfgFile=open(self.file,'w')
            self.write(cfgFile)
        else:
            self.RemoveFile()

class IdleConf:
    """
    holds config parsers for all idle config files:
    default config files
        (idle install dir)/config-main.def
        (idle install dir)/config-extensions.def
        (idle install dir)/config-highlight.def
        (idle install dir)/config-keys.def
    user config  files
        (user home dir)/.idlerc/config-main.cfg
        (user home dir)/.idlerc/config-extensions.cfg
        (user home dir)/.idlerc/config-highlight.cfg
        (user home dir)/.idlerc/config-keys.cfg
    """
    def __init__(self):
        self.defaultCfg={}
        self.userCfg={}
        self.cfg={}
        self.CreateConfigHandlers()
        self.LoadCfgFiles()
        #self.LoadCfg()

    def CreateConfigHandlers(self):
        """
        set up a dictionary of config parsers for default and user
        configurations respectively
        """
        #build idle install path
        if __name__ != '__main__': # we were imported
            idleDir=os.path.dirname(__file__)
        else: # we were exec'ed (for testing only)
            idleDir=os.path.abspath(sys.path[0])
        userDir=self.GetUserCfgDir()
        configTypes=('main','extensions','highlight','keys')
        defCfgFiles={}
        usrCfgFiles={}
        for cfgType in configTypes: #build config file names
            defCfgFiles[cfgType]=os.path.join(idleDir,'config-'+cfgType+'.def')
            usrCfgFiles[cfgType]=os.path.join(userDir,'config-'+cfgType+'.cfg')
        for cfgType in configTypes: #create config parsers
            self.defaultCfg[cfgType]=IdleConfParser(defCfgFiles[cfgType])
            self.userCfg[cfgType]=IdleUserConfParser(usrCfgFiles[cfgType])

    def GetUserCfgDir(self):
        """
        Creates (if required) and returns a filesystem directory for storing
        user config files.
        """
        cfgDir='.idlerc'
        userDir=os.path.expanduser('~')
        if userDir != '~': #'HOME' exists as a key in os.environ
            if not os.path.exists(userDir):
                warn=('\n Warning: HOME environment variable points to\n '+
                        userDir+'\n but the path does not exist.\n')
                sys.stderr.write(warn)
                userDir='~'
        if userDir=='~': #we still don't have a home directory
            #traditionally idle has defaulted to os.getcwd(), is this adeqate?
            userDir = os.getcwd() #hack for no real homedir
        userDir=os.path.join(userDir,cfgDir)
        if not os.path.exists(userDir):
            try: #make the config dir if it doesn't exist yet
                os.mkdir(userDir)
            except IOError:
                warn=('\n Warning: unable to create user config directory\n '+
                        userDir+'\n')
                sys.stderr.write(warn)
        return userDir

    def GetOption(self, configType, section, option, default=None, type=None):
        """
        Get an option value for given config type and given general
        configuration section/option or return a default. If type is specified,
        return as type. Firstly the user configuration is checked, with a
        fallback to the default configuration, and a final 'catch all'
        fallback to a useable passed-in default if the option isn't present in
        either the user or the default configuration.
        configType must be one of ('main','extensions','highlight','keys')
        If a default is returned a warning is printed to stderr.
        """
        if self.userCfg[configType].has_option(section,option):
            return self.userCfg[configType].Get(section, option, type=type)
        elif self.defaultCfg[configType].has_option(section,option):
            return self.defaultCfg[configType].Get(section, option, type=type)
        else: #returning default, print warning
            warning=('\n Warning: configHandler.py - IdleConf.GetOption -\n'+
                       ' problem retrieving configration option '+`option`+'\n'+
                       ' from section '+`section`+'.\n'+
                       ' returning default value: '+`default`+'\n')
            sys.stderr.write(warning)
            return default

    def GetSectionList(self, configSet, configType):
        """
        Get a list of sections from either the user or default config for
        the given config type.
        configSet must be either 'user' or 'default'
        configType must be one of ('main','extensions','highlight','keys')
        """
        if not (configType in ('main','extensions','highlight','keys')):
            raise InvalidConfigType, 'Invalid configType specified'
        if configSet == 'user':
            cfgParser=self.userCfg[configType]
        elif configSet == 'default':
            cfgParser=self.defaultCfg[configType]
        else:
            raise InvalidConfigSet, 'Invalid configSet specified'
        return cfgParser.sections()

    def GetHighlight(self, theme, element, fgBg=None):
        """
        return individual highlighting theme elements.
        fgBg - string ('fg'or'bg') or None, if None return a dictionary
        containing fg and bg colours (appropriate for passing to Tkinter in,
        e.g., a tag_config call), otherwise fg or bg colour only as specified.
        """
        if self.defaultCfg['highlight'].has_section(theme):
            themeDict=self.GetThemeDict('default',theme)
        else:
            themeDict=self.GetThemeDict('user',theme)
        fore=themeDict[element+'-foreground']
        if element=='cursor': #there is no config value for cursor bg
            back=themeDict['normal-background']
        else:
            back=themeDict[element+'-background']
        highlight={"foreground": fore,"background": back}
        if not fgBg: #return dict of both colours
            return highlight
        else: #return specified colour only
            if fgBg == 'fg':
                return highlight["foreground"]
            if fgBg == 'bg':
                return highlight["background"]
            else:
                raise InvalidFgBg, 'Invalid fgBg specified'

    def GetThemeDict(self,type,themeName):
        """
        type - string, 'default' or 'user' theme type
        themeName - string, theme name
        Returns a dictionary which holds {option:value} for each element
        in the specified theme. Values are loaded over a set of ultimate last
        fallback defaults to guarantee that all theme elements are present in
        a newly created theme.
        """
        if type == 'user':
            cfgParser=self.userCfg['highlight']
        elif type == 'default':
            cfgParser=self.defaultCfg['highlight']
        else:
            raise InvalidTheme, 'Invalid theme type specified'
        #foreground and background values are provded for each theme element
        #(apart from cursor) even though all these values are not yet used
        #by idle, to allow for their use in the future. Default values are
        #generally black and white.
        theme={ 'normal-foreground':'#000000',
                'normal-background':'#ffffff',
                'keyword-foreground':'#000000',
                'keyword-background':'#ffffff',
                'comment-foreground':'#000000',
                'comment-background':'#ffffff',
                'string-foreground':'#000000',
                'string-background':'#ffffff',
                'definition-foreground':'#000000',
                'definition-background':'#ffffff',
                'hilite-foreground':'#000000',
                'hilite-background':'gray',
                'break-foreground':'#ffffff',
                'break-background':'#000000',
                'hit-foreground':'#ffffff',
                'hit-background':'#000000',
                'error-foreground':'#ffffff',
                'error-background':'#000000',
                #cursor (only foreground can be set)
                'cursor-foreground':'#000000',
                #shell window
                'stdout-foreground':'#000000',
                'stdout-background':'#ffffff',
                'stderr-foreground':'#000000',
                'stderr-background':'#ffffff',
                'console-foreground':'#000000',
                'console-background':'#ffffff' }
        for element in theme.keys():
            if not cfgParser.has_option(themeName,element):
                #we are going to return a default, print warning
                warning=('\n Warning: configHandler.py - IdleConf.GetThemeDict'+
                           ' -\n problem retrieving theme element '+`element`+
                           '\n from theme '+`themeName`+'.\n'+
                           ' returning default value: '+`theme[element]`+'\n')
                sys.stderr.write(warning)
            colour=cfgParser.Get(themeName,element,default=theme[element])
            theme[element]=colour
        return theme

    def CurrentTheme(self):
        """
        Returns the name of the currently active theme
        """
        return self.GetOption('main','Theme','name',default='')

    def CurrentKeys(self):
        """
        Returns the name of the currently active key set
        """
        return self.GetOption('main','Keys','name',default='')

    def GetExtensions(self, activeOnly=1):
        """
        Gets a list of all idle extensions declared in the config files.
        activeOnly - boolean, if true only return active (enabled) extensions
        """
        extns=self.RemoveKeyBindNames(
                self.GetSectionList('default','extensions'))
        userExtns=self.RemoveKeyBindNames(
                self.GetSectionList('user','extensions'))
        for extn in userExtns:
            if extn not in extns: #user has added own extension
                extns.append(extn)
        if activeOnly:
            activeExtns=[]
            for extn in extns:
                if self.GetOption('extensions',extn,'enable',default=1,
                    type='bool'):
                    #the extension is enabled
                    activeExtns.append(extn)
            return activeExtns
        else:
            return extns

    def RemoveKeyBindNames(self,extnNameList):
        #get rid of keybinding section names
        names=extnNameList
        kbNameIndicies=[]
        for name in names:
            if name.endswith('_bindings') or name.endswith('_cfgBindings'):
                kbNameIndicies.append(names.index(name))
        kbNameIndicies.sort()
        kbNameIndicies.reverse()
        for index in kbNameIndicies: #delete each keybinding section name
            del(names[index])
        return names

    def GetExtnNameForEvent(self,virtualEvent):
        """
        Returns the name of the extension that virtualEvent is bound in, or
        None if not bound in any extension.
        virtualEvent - string, name of the virtual event to test for, without
                       the enclosing '<< >>'
        """
        extName=None
        vEvent='<<'+virtualEvent+'>>'
        for extn in self.GetExtensions(activeOnly=0):
            for event in self.GetExtensionKeys(extn).keys():
                if event == vEvent:
                    extName=extn
        return extName

    def GetExtensionKeys(self,extensionName):
        """
        returns a dictionary of the configurable keybindings for a particular
        extension,as they exist in the dictionary returned by GetCurrentKeySet;
        that is, where previously used bindings are disabled.
        """
        keysName=extensionName+'_cfgBindings'
        activeKeys=self.GetCurrentKeySet()
        extKeys={}
        if self.defaultCfg['extensions'].has_section(keysName):
            eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
            for eventName in eventNames:
                event='<<'+eventName+'>>'
                binding=activeKeys[event]
                extKeys[event]=binding
        return extKeys

    def __GetRawExtensionKeys(self,extensionName):
        """
        returns a dictionary of the configurable keybindings for a particular
        extension, as defined in the configuration files, or an empty dictionary
        if no bindings are found
        """
        keysName=extensionName+'_cfgBindings'
        extKeys={}
        if self.defaultCfg['extensions'].has_section(keysName):
            eventNames=self.defaultCfg['extensions'].GetOptionList(keysName)
            for eventName in eventNames:
                binding=self.GetOption('extensions',keysName,
                        eventName,default='').split()
                event='<<'+eventName+'>>'
                extKeys[event]=binding
        return extKeys

    def GetExtensionBindings(self,extensionName):
        """
        Returns a dictionary of all the event bindings for a particular
        extension. The configurable keybindings are returned as they exist in
        the dictionary returned by GetCurrentKeySet; that is, where re-used
        keybindings are disabled.
        """
        bindsName=extensionName+'_bindings'
        extBinds=self.GetExtensionKeys(extensionName)
        #add the non-configurable bindings
        if self.defaultCfg['extensions'].has_section(bindsName):
            eventNames=self.defaultCfg['extensions'].GetOptionList(bindsName)
            for eventName in eventNames:
                binding=self.GetOption('extensions',bindsName,
                        eventName,default='').split()
                event='<<'+eventName+'>>'
                extBinds[event]=binding

        return extBinds

    def GetKeyBinding(self, keySetName, eventStr):
        """
        returns the keybinding for a specific event.
        keySetName - string, name of key binding set
        eventStr - string, the virtual event we want the binding for,
                   represented as a string, eg. '<<event>>'
        """
        eventName=eventStr[2:-2] #trim off the angle brackets
        binding=self.GetOption('keys',keySetName,eventName,default='').split()
        return binding

    def GetCurrentKeySet(self):
        return self.GetKeySet(self.CurrentKeys())

    def GetKeySet(self,keySetName):
        """
        Returns a dictionary of: all requested core keybindings, plus the
        keybindings for all currently active extensions. If a binding defined
        in an extension is already in use, that binding is disabled.
        """
        keySet=self.GetCoreKeys(keySetName)
        activeExtns=self.GetExtensions(activeOnly=1)
        for extn in activeExtns:
            extKeys=self.__GetRawExtensionKeys(extn)
            if extKeys: #the extension defines keybindings
                for event in extKeys.keys():
                    if extKeys[event] in keySet.values():
                        #the binding is already in use
                        extKeys[event]='' #disable this binding
                    keySet[event]=extKeys[event] #add binding
        return keySet

    def IsCoreBinding(self,virtualEvent):
        """
        returns true if the virtual event is bound in the core idle keybindings.
        virtualEvent - string, name of the virtual event to test for, without
                       the enclosing '<< >>'
        """
        return ('<<'+virtualEvent+'>>') in self.GetCoreKeys().keys()

    def GetCoreKeys(self, keySetName=None):
        """
        returns the requested set of core keybindings, with fallbacks if
        required.
        Keybindings loaded from the config file(s) are loaded _over_ these
        defaults, so if there is a problem getting any core binding there will
        be an 'ultimate last resort fallback' to the CUA-ish bindings
        defined here.
        """
        keyBindings={
            '<<copy>>': ['<Control-c>', '<Control-C>'],
            '<<cut>>': ['<Control-x>', '<Control-X>'],
            '<<paste>>': ['<Control-v>', '<Control-V>'],
            '<<beginning-of-line>>': ['<Control-a>', '<Home>'],
            '<<center-insert>>': ['<Control-l>'],
            '<<close-all-windows>>': ['<Control-q>'],
            '<<close-window>>': ['<Alt-F4>'],
            '<<do-nothing>>': ['<Control-x>'],
            '<<end-of-file>>': ['<Control-d>'],
            '<<python-docs>>': ['<F1>'],
            '<<python-context-help>>': ['<Shift-F1>'],
            '<<history-next>>': ['<Alt-n>'],
            '<<history-previous>>': ['<Alt-p>'],
            '<<interrupt-execution>>': ['<Control-c>'],
            '<<view-restart>>': ['<F6>'],
            '<<restart-shell>>': ['<Control-F6>'],
            '<<open-class-browser>>': ['<Alt-c>'],
            '<<open-module>>': ['<Alt-m>'],
            '<<open-new-window>>': ['<Control-n>'],
            '<<open-window-from-file>>': ['<Control-o>'],
            '<<plain-newline-and-indent>>': ['<Control-j>'],
            '<<print-window>>': ['<Control-p>'],
            '<<redo>>': ['<Control-y>'],
            '<<remove-selection>>': ['<Escape>'],
            '<<save-copy-of-window-as-file>>': ['<Alt-Shift-s>'],
            '<<save-window-as-file>>': ['<Alt-s>'],
            '<<save-window>>': ['<Control-s>'],
            '<<select-all>>': ['<Alt-a>'],
            '<<toggle-auto-coloring>>': ['<Control-slash>'],
            '<<undo>>': ['<Control-z>'],
            '<<find-again>>': ['<Control-g>', '<F3>'],
            '<<find-in-files>>': ['<Alt-F3>'],
            '<<find-selection>>': ['<Control-F3>'],
            '<<find>>': ['<Control-f>'],
            '<<replace>>': ['<Control-h>'],
            '<<goto-line>>': ['<Alt-g>'],
            '<<smart-backspace>>': ['<Key-BackSpace>'],
            '<<newline-and-indent>>': ['<Key-Return> <Key-KP_Enter>'],
            '<<smart-indent>>': ['<Key-Tab>'],
            '<<indent-region>>': ['<Control-Key-bracketright>'],
            '<<dedent-region>>': ['<Control-Key-bracketleft>'],
            '<<comment-region>>': ['<Alt-Key-3>'],
            '<<uncomment-region>>': ['<Alt-Key-4>'],
            '<<tabify-region>>': ['<Alt-Key-5>'],
            '<<untabify-region>>': ['<Alt-Key-6>'],
            '<<toggle-tabs>>': ['<Alt-Key-t>'],
            '<<change-indentwidth>>': ['<Alt-Key-u>']
            }
        if keySetName:
            for event in keyBindings.keys():
                binding=self.GetKeyBinding(keySetName,event)
                if binding:
                    keyBindings[event]=binding
                else: #we are going to return a default, print warning
                    warning=('\n Warning: configHandler.py - IdleConf.GetCoreKeys'+
                               ' -\n problem retrieving key binding for event '+
                               `event`+'\n from key set '+`keySetName`+'.\n'+
                               ' returning default value: '+`keyBindings[event]`+'\n')
                    sys.stderr.write(warning)
        return keyBindings

    def GetExtraHelpSourceList(self,configSet):
        """Fetch list of extra help sources from a given configSet.

        Valid configSets are 'user' or 'default'.  Return a list of tuples of
        the form (menu_item , path_to_help_file , option), or return the empty
        list.  'option' is the sequence number of the help resource.  'option'
        values determine the position of the menu items on the Help menu,
        therefore the returned list must be sorted by 'option'.

        """
        helpSources=[]
        if configSet=='user':
            cfgParser=self.userCfg['main']
        elif configSet=='default':
            cfgParser=self.defaultCfg['main']
        else:
            raise InvalidConfigSet, 'Invalid configSet specified'
        options=cfgParser.GetOptionList('HelpFiles')
        for option in options:
            value=cfgParser.Get('HelpFiles',option,default=';')
            if value.find(';')==-1: #malformed config entry with no ';'
                menuItem='' #make these empty
                helpPath='' #so value won't be added to list
            else: #config entry contains ';' as expected
                value=string.split(value,';')
                menuItem=value[0].strip()
                helpPath=value[1].strip()
            if menuItem and helpPath: #neither are empty strings
                helpSources.append( (menuItem,helpPath,option) )
        helpSources.sort(self.__helpsort)
        return helpSources

    def __helpsort(self, h1, h2):
        if int(h1[2]) < int(h2[2]):
            return -1
        elif int(h1[2]) > int(h2[2]):
            return 1
        else:
            return 0

    def GetAllExtraHelpSourcesList(self):
        """
        Returns a list of tuples containing the details of all additional help
        sources configured, or an empty list if there are none. Tuples are of
        the format returned by GetExtraHelpSourceList.
        """
        allHelpSources=( self.GetExtraHelpSourceList('default')+
                self.GetExtraHelpSourceList('user') )
        return allHelpSources

    def LoadCfgFiles(self):
        """
        load all configuration files.
        """
        for key in self.defaultCfg.keys():
            self.defaultCfg[key].Load()
            self.userCfg[key].Load() #same keys

    def SaveUserCfgFiles(self):
        """
        write all loaded user configuration files back to disk
        """
        for key in self.userCfg.keys():
            self.userCfg[key].Save()

idleConf=IdleConf()

### module test
if __name__ == '__main__':
    def dumpCfg(cfg):
        print '\n',cfg,'\n'
        for key in cfg.keys():
            sections=cfg[key].sections()
            print key
            print sections
            for section in sections:
                options=cfg[key].options(section)
                print section
                print options
                for option in options:
                    print option, '=', cfg[key].Get(section,option)
    dumpCfg(idleConf.defaultCfg)
    dumpCfg(idleConf.userCfg)
    print idleConf.userCfg['main'].Get('Theme','name')
    #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal')

--- NEW FILE: configHelpSourceEdit.py ---
"Dialog to specify or edit the parameters for a user configured help source."

import os

from Tkinter import *
import tkMessageBox
import tkFileDialog

class GetHelpSourceDialog(Toplevel):
    def __init__(self, parent, title, menuItem='', filePath=''):
        """Get menu entry and url/ local file location for Additional Help

        User selects a name for the Help resource and provides a web url
        or a local file as its source.  The user can enter a url or browse
        for the file.

        """
        Toplevel.__init__(self, parent)
        self.configure(borderwidth=5)
        self.resizable(height=FALSE, width=FALSE)
        self.title(title)
        self.transient(parent)
        self.grab_set()
        self.protocol("WM_DELETE_WINDOW", self.Cancel)
        self.parent = parent
        self.result = None
        self.CreateWidgets()
        self.menu.set(menuItem)
        self.path.set(filePath)
        self.withdraw() #hide while setting geometry
        #needs to be done here so that the winfo_reqwidth is valid
        self.update_idletasks()
        #centre dialog over parent:
        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)))))
        self.deiconify() #geometry set, unhide
        self.bind('<Return>', self.Ok)
        self.wait_window()

    def CreateWidgets(self):
        self.menu = StringVar(self)
        self.path = StringVar(self)
        self.fontSize = StringVar(self)
        self.frameMain = Frame(self, borderwidth=2, relief=GROOVE)
        self.frameMain.pack(side=TOP, expand=TRUE, fill=BOTH)
        labelMenu = Label(self.frameMain, anchor=W, justify=LEFT,
                          text='Menu Item:')
        self.entryMenu = Entry(self.frameMain, textvariable=self.menu,
                               width=30)
        self.entryMenu.focus_set()
        labelPath = Label(self.frameMain, anchor=W, justify=LEFT,
                          text='Help File Path: Enter URL or browse for file')
        self.entryPath = Entry(self.frameMain, textvariable=self.path,
                               width=40)
        self.entryMenu.focus_set()
        labelMenu.pack(anchor=W, padx=5, pady=3)
        self.entryMenu.pack(anchor=W, padx=5, pady=3)
        labelPath.pack(anchor=W, padx=5, pady=3)
        self.entryPath.pack(anchor=W, padx=5, pady=3)
        browseButton = Button(self.frameMain, text='Browse', width=8,
                              command=self.browseFile)
        browseButton.pack(pady=3)
        frameButtons = Frame(self)
        frameButtons.pack(side=BOTTOM, fill=X)
        self.buttonOk = Button(frameButtons, text='OK',
                               width=8, default=ACTIVE,  command=self.Ok)
        self.buttonOk.grid(row=0, column=0, padx=5,pady=5)
        self.buttonCancel = Button(frameButtons, text='Cancel',
                                   width=8, command=self.Cancel)
        self.buttonCancel.grid(row=0, column=1, padx=5, pady=5)

    def browseFile(self):
        filetypes = [
            ("HTML Files", "*.htm *.html", "TEXT"),
            ("PDF Files", "*.pdf", "TEXT"),
            ("Windows Help Files", "*.chm"),
            ("Text Files", "*.txt", "TEXT"),
            ("All Files", "*")]
        path = self.path.get()
        if path:
            dir, base = os.path.split(path)
        else:
            base = None
            if sys.platform.count('win') or sys.platform.count('nt'):
                dir = os.path.join(os.path.dirname(sys.executable), 'Doc')
                if not os.path.isdir(dir):
                    dir = os.getcwd()
            else:
                dir = os.getcwd()
        opendialog = tkFileDialog.Open(parent=self, filetypes=filetypes)
        file = opendialog.show(initialdir=dir, initialfile=base)
        if file:
            self.path.set(file)

    def MenuOk(self):
        "Simple validity check for a sensible menu item name"
        menuOk = True
        menu = self.menu.get()
        menu.strip()
        if not menu:
            tkMessageBox.showerror(title='Menu Item Error',
                                   message='No menu item specified',
                                   parent=self)
            self.entryMenu.focus_set()
            menuOk = False
        elif len(menu) > 30:
            tkMessageBox.showerror(title='Menu Item Error',
                                   message='Menu item too long:'
                                           '\nLimit 30 characters.',
                                   parent=self)
            self.entryMenu.focus_set()
            menuOk = False
        return menuOk

    def PathOk(self):
        "Simple validity check for menu file path"
        pathOk = True
        path = self.path.get()
        path.strip()
        if not path: #no path specified
            tkMessageBox.showerror(title='File Path Error',
                                   message='No help file path specified.',
                                   parent=self)
            self.entryPath.focus_set()
            pathOk = False
        elif path.startswith('www.') or path.startswith('http'):
            pathOk = True
        elif not os.path.exists(path):
            tkMessageBox.showerror(title='File Path Error',
                                   message='Help file path does not exist.',
                                   parent=self)
            self.entryPath.focus_set()
            pathOk = False
        return pathOk

    def Ok(self, event=None):
        if self.MenuOk() and self.PathOk():
            self.result = (self.menu.get().strip(),
                           self.path.get().strip())
            self.destroy()

    def Cancel(self, event=None):
        self.result = None
        self.destroy()

if __name__ == '__main__':
    #test the dialog
    root = Tk()
    def run():
        keySeq = ''
        dlg = GetHelpSourceDialog(root, 'Get Help Source')
        print dlg.result
    Button(root,text='Dialog', command=run).pack()
    root.mainloop()

--- NEW FILE: configSectionNameDialog.py ---
"""
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 *
import tkMessageBox

class GetCfgSectionNameDialog(Toplevel):
    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.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.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
        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)
        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)

    def NameOk(self):
        #simple validity check for a sensible
        #ConfigParser file section name
        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
            tkMessageBox.showerror(title='Name Error',
                    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.destroy()

    def Cancel(self, event=None):
        self.result=''
        self.destroy()

if __name__ == '__main__':
    #test the dialog
    root=Tk()
    def run():
        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.mainloop()

--- NEW FILE: dynOptionMenuWidget.py ---
"""
OptionMenu widget modified to allow dynamic menu reconfiguration
and setting of highlightthickness
"""
from Tkinter import OptionMenu
from Tkinter import _setit
import copy

class DynOptionMenu(OptionMenu):
    """
    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'])
        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):
        """
        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,
                    command=_setit(self.variable,item,self.command))
        if value:
            self.variable.set(value)

--- NEW FILE: interruptmodule.c ---
/***********************************************************************
 *  interruptmodule.c
 *
 *  Python extension implementing the interrupt module.
 *  
 **********************************************************************/

#include "Python.h"

#ifndef PyDoc_STR
#define PyDoc_VAR(name) static char name[]
#define PyDoc_STR(str) str
#define PyDoc_STRVAR(name,str) PyDoc_VAR(name) = PyDoc_STR(str)
#endif

/* module documentation */

PyDoc_STRVAR(module_doc,
"Provide a way to interrupt the main thread from a subthread.\n\n\
In threaded Python code the KeyboardInterrupt is always directed to\n\
the thread which raised it.  This extension provides a method,\n\
interrupt_main, which a subthread can use to raise a KeyboardInterrupt\n\
in the main thread.");

/* module functions */

static PyObject *
setinterrupt(PyObject * self, PyObject * args)
{
	PyErr_SetInterrupt();
	Py_INCREF(Py_None);
	return Py_None;
}

/* registration table */

static struct PyMethodDef methods[] = {
	{"interrupt_main", setinterrupt, METH_VARARGS,
	 PyDoc_STR("Interrupt the main thread")},
	{NULL, NULL}
};

/* module initialization */

void
initinterrupt(void)
{
	(void) Py_InitModule3("interrupt", methods, module_doc);
}

--- NEW FILE: keybindingDialog.py ---
"""
dialog for building tkinter accelerator key bindings
"""
from Tkinter import *
import tkMessageBox
import string, os

class GetKeysDialog(Toplevel):
    def __init__(self,parent,title,action,currentKeySequences):
        """
        action - string, the name of the virtual event these keys will be
                 mapped to
        currentKeys - list, a list of all key sequence lists currently mapped
                 to virtual events, for overlap checking
        """
        Toplevel.__init__(self, parent)
        self.configure(borderwidth=5)
        self.resizable(height=FALSE,width=FALSE)
        self.title(title)
        self.transient(parent)
        self.grab_set()
        self.protocol("WM_DELETE_WINDOW", self.Cancel)
        self.parent = parent
        self.action=action
        self.currentKeySequences=currentKeySequences
        self.result=''
        self.keyString=StringVar(self)
        self.keyString.set('')
        self.SetModifiersForPlatform()
        self.modifier_vars = []
        for modifier in self.modifiers:
            variable = StringVar(self)
            variable.set('')
            self.modifier_vars.append(variable)
        self.CreateWidgets()
        self.LoadFinalKeyList()
        self.withdraw() #hide while setting geometry
        self.update_idletasks()
        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
        self.deiconify() #geometry set, unhide
        self.wait_window()

    def CreateWidgets(self):
        frameMain = Frame(self,borderwidth=2,relief=SUNKEN)
        frameMain.pack(side=TOP,expand=TRUE,fill=BOTH)
        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.frameKeySeqBasic = Frame(frameMain)
        self.frameKeySeqAdvanced = Frame(frameMain)
        self.frameControlsBasic = Frame(frameMain)
        self.frameHelpAdvanced = Frame(frameMain)
        self.frameKeySeqAdvanced.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
        self.frameKeySeqBasic.grid(row=0,column=0,sticky=NSEW,padx=5,pady=5)
        self.frameKeySeqBasic.lift()
        self.frameHelpAdvanced.grid(row=1,column=0,sticky=NSEW,padx=5)
        self.frameControlsBasic.grid(row=1,column=0,sticky=NSEW,padx=5)
        self.frameControlsBasic.lift()
        self.buttonLevel = Button(frameMain,command=self.ToggleLevel,
                text='Advanced Key Binding Entry >>')
        self.buttonLevel.grid(row=2,column=0,stick=EW,padx=5,pady=5)
        labelTitleBasic = Label(self.frameKeySeqBasic,
                text="New keys for  '"+self.action+"' :")
        labelTitleBasic.pack(anchor=W)
        labelKeysBasic = Label(self.frameKeySeqBasic,justify=LEFT,
                textvariable=self.keyString,relief=GROOVE,borderwidth=2)
        labelKeysBasic.pack(ipadx=5,ipady=5,fill=X)
        self.modifier_checkbuttons = {}
        column = 0
        for modifier, variable in zip(self.modifiers, self.modifier_vars):
            label = self.modifier_label.get(modifier, modifier)
            check=Checkbutton(self.frameControlsBasic,
                command=self.BuildKeyString,
                text=label,variable=variable,onvalue=modifier,offvalue='')
            check.grid(row=0,column=column,padx=2,sticky=W)
            self.modifier_checkbuttons[modifier] = check
            column += 1
        labelFnAdvice=Label(self.frameControlsBasic,justify=LEFT,
                text="Select the desired modifier\n"+
                     "keys above, and final key\n"+
                     "from the list on the right.")
        labelFnAdvice.grid(row=1,column=0,columnspan=4,padx=2,sticky=W)
        self.listKeysFinal=Listbox(self.frameControlsBasic,width=15,height=10,
                selectmode=SINGLE)
        self.listKeysFinal.bind('<ButtonRelease-1>',self.FinalKeySelected)
        self.listKeysFinal.grid(row=0,column=4,rowspan=4,sticky=NS)
        scrollKeysFinal=Scrollbar(self.frameControlsBasic,orient=VERTICAL,
                command=self.listKeysFinal.yview)
        self.listKeysFinal.config(yscrollcommand=scrollKeysFinal.set)
        scrollKeysFinal.grid(row=0,column=5,rowspan=4,sticky=NS)
        self.buttonClear=Button(self.frameControlsBasic,
                text='Clear Keys',command=self.ClearKeySeq)
        self.buttonClear.grid(row=2,column=0,columnspan=4)
        labelTitleAdvanced = Label(self.frameKeySeqAdvanced,justify=LEFT,
                text="Enter new binding(s) for  '"+self.action+"' :\n"+
                "(will not be checked for validity)")
        labelTitleAdvanced.pack(anchor=W)
        self.entryKeysAdvanced=Entry(self.frameKeySeqAdvanced,
                textvariable=self.keyString)
        self.entryKeysAdvanced.pack(fill=X)
        labelHelpAdvanced=Label(self.frameHelpAdvanced,justify=LEFT,
            text="Key bindings are specified using tkinter key id's as\n"+
                 "in these samples: <Control-f>, <Shift-F2>, <F12>,\n"
                 "<Control-space>, <Meta-less>, <Control-Alt-Shift-x>.\n\n"+
                 "'Emacs style' multi-keystroke bindings are specified as\n"+
                 "follows: <Control-x><Control-y> or <Meta-f><Meta-g>.\n\n"+
                 "Multiple separate bindings for one action should be\n"+
                 "separated by a space, eg., <Alt-v> <Meta-v>." )
        labelHelpAdvanced.grid(row=0,column=0,sticky=NSEW)

    def SetModifiersForPlatform(self):
        """Determine list of names of key modifiers for this platform.

        The names are used to build Tk bindings -- it doesn't matter if the
        keyboard has these keys, it matters if Tk understands them. The
        order is also important: key binding equality depends on it, so
        config-keys.def must use the same ordering.
        """
        import sys
        if sys.platform == 'darwin' and sys.executable.count('.app'):
            self.modifiers = ['Shift', 'Control', 'Option', 'Command']
        else:
            self.modifiers = ['Control', 'Alt', 'Shift']
        self.modifier_label = {'Control': 'Ctrl'}

    def ToggleLevel(self):
        if  self.buttonLevel.cget('text')[:8]=='Advanced':
            self.ClearKeySeq()
            self.buttonLevel.config(text='<< Basic Key Binding Entry')
            self.frameKeySeqAdvanced.lift()
            self.frameHelpAdvanced.lift()
            self.entryKeysAdvanced.focus_set()
        else:
            self.ClearKeySeq()
            self.buttonLevel.config(text='Advanced Key Binding Entry >>')
            self.frameKeySeqBasic.lift()
            self.frameControlsBasic.lift()

    def FinalKeySelected(self,event):
        self.BuildKeyString()

    def BuildKeyString(self):
        keyList=[]
        modifiers=self.GetModifiers()
        finalKey=self.listKeysFinal.get(ANCHOR)
        if modifiers: modifiers[0]='<'+modifiers[0]
        keyList=keyList+modifiers
        if finalKey:
            if (not modifiers) and (finalKey not
                    in self.alphanumKeys+self.punctuationKeys):
                finalKey='<'+self.TranslateKey(finalKey)
            else:
                finalKey=self.TranslateKey(finalKey)
            keyList.append(finalKey+'>')
        keyStr=string.join(keyList,'-')
        self.keyString.set(keyStr)

    def GetModifiers(self):
        modList = [variable.get() for variable in self.modifier_vars]
        return filter(None, modList)

    def ClearKeySeq(self):
        self.listKeysFinal.select_clear(0,END)
        self.listKeysFinal.yview(MOVETO, '0.0')
        for variable in self.modifier_vars:
            variable.set('')
        self.keyString.set('')

    def LoadFinalKeyList(self):
        #these tuples are also available for use in validity checks
        self.functionKeys=('F1','F2','F2','F4','F5','F6','F7','F8','F9',
                'F10','F11','F12')
        self.alphanumKeys=tuple(string.ascii_lowercase+string.digits)
        self.punctuationKeys=tuple('~!@#%^&*()_-+={}[]|;:,.<>/?')
        self.whitespaceKeys=('Tab','Space','Return')
        self.editKeys=('BackSpace','Delete','Insert')
        self.moveKeys=('Home','End','Page Up','Page Down','Left Arrow',
                'Right Arrow','Up Arrow','Down Arrow')
        #make a tuple of most of the useful common 'final' keys
        keys=(self.alphanumKeys+self.punctuationKeys+self.functionKeys+
                self.whitespaceKeys+self.editKeys+self.moveKeys)
        apply(self.listKeysFinal.insert,
            (END,)+keys)

    def TranslateKey(self,key):
        #translate from key list value to tkinter key-id
        translateDict={'~':'asciitilde','!':'exclam','@':'at','#':'numbersign',
                '%':'percent','^':'asciicircum','&':'ampersand','*':'asterisk',
                '(':'parenleft',')':'parenright','_':'underscore','-':'minus',
                '+':'plus','=':'equal','{':'braceleft','}':'braceright',
                '[':'bracketleft',']':'bracketright','|':'bar',';':'semicolon',
                ':':'colon',',':'comma','.':'period','<':'less','>':'greater',
                '/':'slash','?':'question','Page Up':'Prior','Page Down':'Next',
                'Left Arrow':'Left','Right Arrow':'Right','Up Arrow':'Up',
                'Down Arrow': 'Down'}
        if key in translateDict.keys():
            key=translateDict[key]
        key='Key-'+key
        return key

    def Ok(self, event=None):
        if self.KeysOk():
            self.result=self.keyString.get()
            self.destroy()

    def Cancel(self, event=None):
        self.result=''
        self.destroy()

    def KeysOk(self):
        #simple validity check
        keysOk=1
        keys=self.keyString.get()
        keys.strip()
        finalKey=self.listKeysFinal.get(ANCHOR)
        modifiers=self.GetModifiers()
        keySequence=keys.split()#make into a key sequence list for overlap check
        if not keys: #no keys specified
            tkMessageBox.showerror(title='Key Sequence Error',
                    message='No keys specified.')
            keysOk=0
        elif not keys.endswith('>'): #no final key specified
            tkMessageBox.showerror(title='Key Sequence Error',
                    message='No final key specified.')
            keysOk=0
        elif (not modifiers) and (finalKey in
                self.alphanumKeys+self.punctuationKeys):
            #modifier required
            tkMessageBox.showerror(title='Key Sequence Error',
                    message='No modifier key(s) specified.')
            keysOk=0
        elif (modifiers==['Shift']) and (finalKey not
                in self.functionKeys+('Tab',)):
            #shift alone is only a useful modifier with a function key
            tkMessageBox.showerror(title='Key Sequence Error',
                    message='Shift alone is not a useful modifier '+
                            'when used with this final key key.')
            keysOk=0
        elif keySequence in self.currentKeySequences: #keys combo already in use
            tkMessageBox.showerror(title='Key Sequence Error',
                    message='This key combination is already in use.')
            keysOk=0
        return keysOk

if __name__ == '__main__':
    #test the dialog
    root=Tk()
    def run():
        keySeq=''
        dlg=GetKeysDialog(root,'Get Keys','find-again',[])
        print dlg.result
    Button(root,text='Dialog',command=run).pack()
    root.mainloop()

--- NEW FILE: macosx_main.py ---
#!/usr/bin/env pythonw
# IDLE.app
#
# Installation:
#   see the install_IDLE target in python/dist/src/Mac/OSX/Makefile
#
# Usage:
#
# 1. Double clicking IDLE icon will open IDLE.
# 2. Dropping file on IDLE icon will open that file in IDLE.
# 3. Launch from command line with files with this command-line:
#
#     /Applications/Python/IDLE.app/Contents/MacOS/python file1 file2 file3
#
#

# Add IDLE.app/Contents/Resources/idlelib to path.
# __file__ refers to this file when it is used as a module, sys.argv[0]
# refers to this file when it is used as a script (pythonw macosx_main.py)
import sys

from os.path import split, join, isdir
try:
    __file__
except NameError:
    __file__ = sys.argv[0]
idlelib = join(split(__file__)[0], 'idlelib')
if isdir(idlelib):
    sys.path.append(idlelib)

# see if we are being asked to execute the subprocess code
if '-p' in sys.argv:
    # run expects only the port number in sys.argv
    sys.argv.remove('-p')

    # this module will become the namespace used by the interactive
    # interpreter; remove all variables we have defined.
    del sys, __file__, split, join, isdir, idlelib
    __import__('run').main()
else:
    # Load idlelib/idle.py which starts the application.
    import idle

--- NEW FILE: rpc.py ---
"""RPC Implemention, originally written for the Python Idle IDE

For security reasons, GvR requested that Idle's Python execution server process
connect to the Idle process, which listens for the connection.  Since Idle has
has only one client per server, this was not a limitation.

   +---------------------------------+ +-------------+
   | SocketServer.BaseRequestHandler | | SocketIO    |
   +---------------------------------+ +-------------+
                   ^                   | register()  |
                   |                   | unregister()|
                   |                   +-------------+
                   |                      ^  ^
                   |                      |  |
                   | + -------------------+  |
                   | |                       |
   +-------------------------+        +-----------------+
   | RPCHandler              |        | RPCClient       |
   | [attribute of RPCServer]|        |                 |
   +-------------------------+        +-----------------+

The RPCServer handler class is expected to provide register/unregister methods.
RPCHandler inherits the mix-in class SocketIO, which provides these methods.

See the Idle run.main() docstring for further information on how this was
accomplished in Idle.

"""

import sys
import socket
import select
import SocketServer
import struct
import cPickle as pickle
import threading
import traceback
import copy_reg
import types
import marshal

def unpickle_code(ms):
    co = marshal.loads(ms)
    assert isinstance(co, types.CodeType)
    return co

def pickle_code(co):
    assert isinstance(co, types.CodeType)
    ms = marshal.dumps(co)
    return unpickle_code, (ms,)

# XXX KBK 24Aug02 function pickling capability not used in Idle
#  def unpickle_function(ms):
#      return ms

#  def pickle_function(fn):
#      assert isinstance(fn, type.FunctionType)
#      return `fn`

copy_reg.pickle(types.CodeType, pickle_code, unpickle_code)
# copy_reg.pickle(types.FunctionType, pickle_function, unpickle_function)

BUFSIZE = 8*1024

class RPCServer(SocketServer.TCPServer):

    def __init__(self, addr, handlerclass=None):
        if handlerclass is None:
            handlerclass = RPCHandler
        SocketServer.TCPServer.__init__(self, addr, handlerclass)

    def server_bind(self):
        "Override TCPServer method, no bind() phase for connecting entity"
        pass

    def server_activate(self):
        """Override TCPServer method, connect() instead of listen()

        Due to the reversed connection, self.server_address is actually the
        address of the Idle Client to which we are connecting.

        """
        self.socket.connect(self.server_address)

    def get_request(self):
        "Override TCPServer method, return already connected socket"
        return self.socket, self.server_address

    def handle_error(self, request, client_address):
        """Override TCPServer method

        Error message goes to __stderr__.  No error message if exiting
        normally or socket raised EOF.  Other exceptions not handled in
        server code will cause os._exit.

        """
        try:
            raise
        except SystemExit:
            raise
        except EOFError:
            pass
        except:
            erf = sys.__stderr__
            print>>erf, '\n' + '-'*40
            print>>erf, 'Unhandled server exception!'
            print>>erf, 'Thread: %s' % threading.currentThread().getName()
            print>>erf, 'Client Address: ', client_address
            print>>erf, 'Request: ', repr(request)
            traceback.print_exc(file=erf)
            print>>erf, '\n*** Unrecoverable, server exiting!'
            print>>erf, '-'*40
            import os
            os._exit(0)


objecttable = {}

class SocketIO:

    nextseq = 0

    def __init__(self, sock, objtable=None, debugging=None):
        self.mainthread = threading.currentThread()
        if debugging is not None:
            self.debugging = debugging
        self.sock = sock
        if objtable is None:
            objtable = objecttable
        self.objtable = objtable
        self.cvar = threading.Condition()
        self.responses = {}
        self.cvars = {}
        self.interrupted = False

    def close(self):
        sock = self.sock
        self.sock = None
        if sock is not None:
            sock.close()

    def debug(self, *args):
        if not self.debugging:
            return
        s = self.location + " " + str(threading.currentThread().getName())
        for a in args:
            s = s + " " + str(a)
        print>>sys.__stderr__, s

    def register(self, oid, object):
        self.objtable[oid] = object

    def unregister(self, oid):
        try:
            del self.objtable[oid]
        except KeyError:
            pass

    def localcall(self, request):
        self.debug("localcall:", request)
        try:
            how, (oid, methodname, args, kwargs) = request
        except TypeError:
            return ("ERROR", "Bad request format")
        assert how == "call"
        if not self.objtable.has_key(oid):
            return ("ERROR", "Unknown object id: %s" % `oid`)
        obj = self.objtable[oid]
        if methodname == "__methods__":
            methods = {}
            _getmethods(obj, methods)
            return ("OK", methods)
        if methodname == "__attributes__":
            attributes = {}
            _getattributes(obj, attributes)
            return ("OK", attributes)
        if not hasattr(obj, methodname):
            return ("ERROR", "Unsupported method name: %s" % `methodname`)
        method = getattr(obj, methodname)
        try:
            ret = method(*args, **kwargs)
            if isinstance(ret, RemoteObject):
                ret = remoteref(ret)
            return ("OK", ret)
        except SystemExit:
            raise
        except socket.error:
            pass
        except:
            self.debug("localcall:EXCEPTION")
            traceback.print_exc(file=sys.__stderr__)
            return ("EXCEPTION", None)

    def remotecall(self, oid, methodname, args, kwargs):
        self.debug("remotecall:asynccall: ", oid, methodname)
        # XXX KBK 06Feb03 self.interrupted logic may not be necessary if
        #                 subprocess is threaded.
        if self.interrupted:
            self.interrupted = False
            raise KeyboardInterrupt
        seq = self.asynccall(oid, methodname, args, kwargs)
        return self.asyncreturn(seq)

    def asynccall(self, oid, methodname, args, kwargs):
        request = ("call", (oid, methodname, args, kwargs))
        seq = self.newseq()
        self.debug(("asynccall:%d:" % seq), oid, methodname, args, kwargs)
        self.putmessage((seq, request))
        return seq

    def asyncreturn(self, seq):
        self.debug("asyncreturn:%d:call getresponse(): " % seq)
        response = self.getresponse(seq, wait=None)
        self.debug(("asyncreturn:%d:response: " % seq), response)
        return self.decoderesponse(response)

    def decoderesponse(self, response):
        how, what = response
        if how == "OK":
            return what
        if how == "EXCEPTION":
            self.debug("decoderesponse: EXCEPTION")
            return None
        if how == "ERROR":
            self.debug("decoderesponse: Internal ERROR:", what)
            raise RuntimeError, what
        raise SystemError, (how, what)

    def mainloop(self):
        """Listen on socket until I/O not ready or EOF

        Main thread pollresponse() will loop looking for seq number None, which
        never comes, and exit on EOFError.

        """
        try:
            self.getresponse(myseq=None, wait=None)
        except EOFError:
            pass

    def getresponse(self, myseq, wait):
        response = self._getresponse(myseq, wait)
        if response is not None:
            how, what = response
            if how == "OK":
                response = how, self._proxify(what)
        return response

    def _proxify(self, obj):
        if isinstance(obj, RemoteProxy):
            return RPCProxy(self, obj.oid)
        if isinstance(obj, types.ListType):
            return map(self._proxify, obj)
        # XXX Check for other types -- not currently needed
        return obj

    def _getresponse(self, myseq, wait):
        self.debug("_getresponse:myseq:", myseq)
        if threading.currentThread() is self.mainthread:
            # Main thread: does all reading of requests or responses
            # Loop here, blocking each time until socket is ready.
            while 1:
                response = self.pollresponse(myseq, wait)
                if response is not None:
                    return response
        else:
            # Auxiliary thread: wait for notification from main thread
            self.cvar.acquire()
            self.cvars[myseq] = self.cvar
            while not self.responses.has_key(myseq):
                self.cvar.wait()
            response = self.responses[myseq]
            del self.responses[myseq]
            del self.cvars[myseq]
            self.cvar.release()
            return response

    def newseq(self):
        self.nextseq = seq = self.nextseq + 2
        return seq

    def putmessage(self, message):
        self.debug("putmessage:%d:" % message[0])
        try:
            s = pickle.dumps(message)
        except:
            print >>sys.__stderr__, "Cannot pickle:", `message`
            raise
        s = struct.pack("<i", len(s)) + s
        while len(s) > 0:
            try:
                n = self.sock.send(s)
            except AttributeError:
                # socket was closed
                raise IOError
            else:
                s = s[n:]

    def ioready(self, wait=0.0):
        r, w, x = select.select([self.sock.fileno()], [], [], wait)
        return len(r)

    buffer = ""
    bufneed = 4
    bufstate = 0 # meaning: 0 => reading count; 1 => reading data

    def pollpacket(self, wait=0.0):
        self._stage0()
        if len(self.buffer) < self.bufneed:
            if not self.ioready(wait):
                return None
            try:
                s = self.sock.recv(BUFSIZE)
            except socket.error:
                raise EOFError
            if len(s) == 0:
                raise EOFError
            self.buffer += s
            self._stage0()
        return self._stage1()

    def _stage0(self):
        if self.bufstate == 0 and len(self.buffer) >= 4:
            s = self.buffer[:4]
            self.buffer = self.buffer[4:]
            self.bufneed = struct.unpack("<i", s)[0]
            self.bufstate = 1

    def _stage1(self):
        if self.bufstate == 1 and len(self.buffer) >= self.bufneed:
            packet = self.buffer[:self.bufneed]
            self.buffer = self.buffer[self.bufneed:]
            self.bufneed = 4
            self.bufstate = 0
            return packet

    def pollmessage(self, wait=0.0):
        packet = self.pollpacket(wait)
        if packet is None:
            return None
        try:
            message = pickle.loads(packet)
        except:
            print >>sys.__stderr__, "-----------------------"
            print >>sys.__stderr__, "cannot unpickle packet:", `packet`
            traceback.print_stack(file=sys.__stderr__)
            print >>sys.__stderr__, "-----------------------"
            raise
        return message

    def pollresponse(self, myseq, wait=0.0):
        """Handle messages received on the socket.

        Some messages received may be asynchronous 'call' commands, and
        some may be responses intended for other threads.

        Loop until message with myseq sequence number is received.  Save others
        in self.responses and notify the owning thread, except that 'call'
        commands are handed off to localcall() and the response sent back
        across the link with the appropriate sequence number.

        """
        while 1:
            message = self.pollmessage(wait)
            if message is None:  # socket not ready
                return None
            #wait = 0.0  # poll on subsequent passes instead of blocking
            seq, resq = message
            self.debug("pollresponse:%d:myseq:%s" % (seq, myseq))
            if resq[0] == "call":
                self.debug("pollresponse:%d:localcall:call:" % seq)
                response = self.localcall(resq)
                self.debug("pollresponse:%d:localcall:response:%s"
                           % (seq, response))
                self.putmessage((seq, response))
                continue
            elif seq == myseq:
                return resq
            else:
                self.cvar.acquire()
                cv = self.cvars.get(seq)
                # response involving unknown sequence number is discarded,
                # probably intended for prior incarnation
                if cv is not None:
                    self.responses[seq] = resq
                    cv.notify()
                self.cvar.release()
                continue

#----------------- end class SocketIO --------------------

class RemoteObject:
    # Token mix-in class
    pass

def remoteref(obj):
    oid = id(obj)
    objecttable[oid] = obj
    return RemoteProxy(oid)

class RemoteProxy:

    def __init__(self, oid):
        self.oid = oid

class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):

    debugging = False
    location = "#S"  # Server

    def __init__(self, sock, addr, svr):
        svr.current_handler = self ## cgt xxx
        SocketIO.__init__(self, sock)
        SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)

    def handle(self):
        "handle() method required by SocketServer"
        self.mainloop()

    def get_remote_proxy(self, oid):
        return RPCProxy(self, oid)

class RPCClient(SocketIO):

    debugging = False
    location = "#C"  # Client

    nextseq = 1 # Requests coming from the client are odd numbered

    def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
        self.listening_sock = socket.socket(family, type)
        self.listening_sock.setsockopt(socket.SOL_SOCKET,
                                       socket.SO_REUSEADDR, 1)
        self.listening_sock.bind(address)
        self.listening_sock.listen(1)

    def accept(self):
        working_sock, address = self.listening_sock.accept()
        if self.debugging:
            print>>sys.__stderr__, "****** Connection request from ", address
        if address[0] == '127.0.0.1':
            SocketIO.__init__(self, working_sock)
        else:
            print>>sys.__stderr__, "** Invalid host: ", address
            raise socket.error

    def get_remote_proxy(self, oid):
        return RPCProxy(self, oid)

class RPCProxy:

    __methods = None
    __attributes = None

    def __init__(self, sockio, oid):
        self.sockio = sockio
        self.oid = oid

    def __getattr__(self, name):
        if self.__methods is None:
            self.__getmethods()
        if self.__methods.get(name):
            return MethodProxy(self.sockio, self.oid, name)
        if self.__attributes is None:
            self.__getattributes()
        if not self.__attributes.has_key(name):
            raise AttributeError, name
    __getattr__.DebuggerStepThrough=1

    def __getattributes(self):
        self.__attributes = self.sockio.remotecall(self.oid,
                                                "__attributes__", (), {})

    def __getmethods(self):
        self.__methods = self.sockio.remotecall(self.oid,
                                                "__methods__", (), {})

def _getmethods(obj, methods):
    # Helper to get a list of methods from an object
    # Adds names to dictionary argument 'methods'
    for name in dir(obj):
        attr = getattr(obj, name)
        if callable(attr):
            methods[name] = 1
    if type(obj) == types.InstanceType:
        _getmethods(obj.__class__, methods)
    if type(obj) == types.ClassType:
        for super in obj.__bases__:
            _getmethods(super, methods)

def _getattributes(obj, attributes):
    for name in dir(obj):
        attr = getattr(obj, name)
        if not callable(attr):
            attributes[name] = 1

class MethodProxy:

    def __init__(self, sockio, oid, name):
        self.sockio = sockio
        self.oid = oid
        self.name = name

    def __call__(self, *args, **kwargs):
        value = self.sockio.remotecall(self.oid, self.name, args, kwargs)
        return value

#
# Self Test
#

def testServer(addr):
    # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
    class RemotePerson:
        def __init__(self,name):
            self.name = name
        def greet(self, name):
            print "(someone called greet)"
            print "Hello %s, I am %s." % (name, self.name)
            print
        def getName(self):
            print "(someone called getName)"
            print
            return self.name
        def greet_this_guy(self, name):
            print "(someone called greet_this_guy)"
            print "About to greet %s ..." % name
            remote_guy = self.server.current_handler.get_remote_proxy(name)
            remote_guy.greet("Thomas Edison")
            print "Done."
            print

    person = RemotePerson("Thomas Edison")
    svr = RPCServer(addr)
    svr.register('thomas', person)
    person.server = svr # only required if callbacks are used

    # svr.serve_forever()
    svr.handle_request()  # process once only

def testClient(addr):
    "demonstrates RPC Client"
    # XXX 25 Jul 02 KBK needs update to use rpc.py register/unregister methods
    import time
    clt=RPCClient(addr)
    thomas = clt.get_remote_proxy("thomas")
    print "The remote person's name is ..."
    print thomas.getName()
    # print clt.remotecall("thomas", "getName", (), {})
    print
    time.sleep(1)
    print "Getting remote thomas to say hi..."
    thomas.greet("Alexander Bell")
    #clt.remotecall("thomas","greet",("Alexander Bell",), {})
    print "Done."
    print
    time.sleep(2)
    # demonstrates remote server calling local instance
    class LocalPerson:
        def __init__(self,name):
            self.name = name
        def greet(self, name):
            print "You've greeted me!"
        def getName(self):
            return self.name
    person = LocalPerson("Alexander Bell")
    clt.register("alexander",person)
    thomas.greet_this_guy("alexander")
    # clt.remotecall("thomas","greet_this_guy",("alexander",), {})

def test():
    addr=("localhost",8833)
    if len(sys.argv) == 2:
        if sys.argv[1]=='-server':
            testServer(addr)
            return
    testClient(addr)

if __name__ == '__main__':
    test()

--- NEW FILE: run.py ---
import sys
import time
import socket
import traceback
import threading
import Queue

import boolcheck

import CallTips
import RemoteDebugger
import RemoteObjectBrowser
import StackViewer
import rpc
import interrupt

import __main__

# Thread shared globals: Establish a queue between a subthread (which handles
# the socket) and the main thread (which runs user code), plus global
# completion and exit flags:

server = None                # RPCServer instance
queue = Queue.Queue(0)
execution_finished = False
exit_requested = False


def main():
    """Start the Python execution server in a subprocess

    In the Python subprocess, RPCServer is instantiated with handlerclass
    MyHandler, which inherits register/unregister methods from RPCHandler via
    the mix-in class SocketIO.

    When the RPCServer 'server' is instantiated, the TCPServer initialization
    creates an instance of run.MyHandler and calls its handle() method.
    handle() instantiates a run.Executive object, passing it a reference to the
    MyHandler object.  That reference is saved as attribute rpchandler of the
    Executive instance.  The Executive methods have access to the reference and
    can pass it on to entities that they command
    (e.g. RemoteDebugger.Debugger.start_debugger()).  The latter, in turn, can
    call MyHandler(SocketIO) register/unregister methods via the reference to
    register and unregister themselves.

    """
    global queue, execution_finished, exit_requested

    port = 8833
    if sys.argv[1:]:
        port = int(sys.argv[1])
    sys.argv[:] = [""]
    sockthread = threading.Thread(target=manage_socket,
                                  name='SockThread',
                                  args=(('localhost', port),))
    sockthread.setDaemon(True)
    sockthread.start()
    while 1:
        try:
            if exit_requested:
                sys.exit()
            # XXX KBK 22Mar03 eventually check queue here!
            pass
            time.sleep(0.05)
        except KeyboardInterrupt:
            ##execution_finished = True
            continue

def manage_socket(address):
    global server, exit_requested

    for i in range(6):
        time.sleep(i)
        try:
            server = rpc.RPCServer(address, MyHandler)
            break
        except socket.error, err:
            if i < 3:
                print>>sys.__stderr__, ".. ",
            else:
                print>>sys.__stderr__,"\nPython subprocess socket error: "\
                                              + err[1] + ", retrying...."
    else:
        print>>sys.__stderr__, "\nConnection to Idle failed, exiting."
        exit_requested = True
    server.handle_request() # A single request only


class MyHandler(rpc.RPCHandler):

    def handle(self):
        """Override base method"""
        executive = Executive(self)
        self.register("exec", executive)
        sys.stdin = self.get_remote_proxy("stdin")
        sys.stdout = self.get_remote_proxy("stdout")
        sys.stderr = self.get_remote_proxy("stderr")
        rpc.RPCHandler.getresponse(self, myseq=None, wait=0.5)


class Executive:

    def __init__(self, rpchandler):
        self.rpchandler = rpchandler
        self.locals = __main__.__dict__
        self.calltip = CallTips.CallTips()

    def runcode(self, code):
        global queue, execution_finished

        execution_finished = False
        queue.put(code)
        # dequeue and run in subthread
        self.runcode_from_queue()
        while not execution_finished:
            time.sleep(0.05)

    def runcode_from_queue(self):
        global queue, execution_finished

        # poll until queue has code object, using threads, just block?
        while True:
            try:
                code = queue.get(0)
                break
            except Queue.Empty:
                time.sleep(0.05)
        try:
            exec code in self.locals
        except:
            self.flush_stdout()
            efile = sys.stderr
            typ, val, tb = info = sys.exc_info()
            sys.last_type, sys.last_value, sys.last_traceback = info
            tbe = traceback.extract_tb(tb)
            print >>efile, 'Traceback (most recent call last):'
            exclude = ("run.py", "rpc.py", "RemoteDebugger.py", "bdb.py")
            self.cleanup_traceback(tbe, exclude)
            traceback.print_list(tbe, file=efile)
            lines = traceback.format_exception_only(typ, val)
            for line in lines:
                print>>efile, line,
            execution_finished = True
        else:
            self.flush_stdout()
            execution_finished = True

    def flush_stdout(self):
        try:
            if sys.stdout.softspace:
                sys.stdout.softspace = 0
                sys.stdout.write("\n")
        except (AttributeError, EOFError):
            pass

    def cleanup_traceback(self, tb, exclude):
        "Remove excluded traces from beginning/end of tb; get cached lines"
        orig_tb = tb[:]
        while tb:
            for rpcfile in exclude:
                if tb[0][0].count(rpcfile):
                    break    # found an exclude, break for: and delete tb[0]
            else:
                break        # no excludes, have left RPC code, break while:
            del tb[0]
        while tb:
            for rpcfile in exclude:
                if tb[-1][0].count(rpcfile):
                    break
            else:
                break
            del tb[-1]
        if len(tb) == 0:
            # exception was in IDLE internals, don't prune!
            tb[:] = orig_tb[:]
            print>>sys.stderr, "** IDLE Internal Exception: "
        for i in range(len(tb)):
            fn, ln, nm, line = tb[i]
            if nm == '?':
                nm = "-toplevel-"
            if not line and fn.startswith("<pyshell#"):
                line = self.rpchandler.remotecall('linecache', 'getline',
                                                  (fn, ln), {})
            tb[i] = fn, ln, nm, line

    def interrupt_the_server(self):
        self.rpchandler.interrupted = True
        ##print>>sys.__stderr__, "** Interrupt main!"
        interrupt.interrupt_main()

    def shutdown_the_server(self):
        global exit_requested

        exit_requested = True

    def start_the_debugger(self, gui_adap_oid):
        return RemoteDebugger.start_debugger(self.rpchandler, gui_adap_oid)

    def stop_the_debugger(self, idb_adap_oid):
        "Unregister the Idb Adapter.  Link objects and Idb then subject to GC"
        self.rpchandler.unregister(idb_adap_oid)

    def get_the_calltip(self, name):
        return self.calltip.fetch_tip(name)

    def stackviewer(self, flist_oid=None):
        if not hasattr(sys, "last_traceback"):
            return None
        flist = None
        if flist_oid is not None:
            flist = self.rpchandler.get_remote_proxy(flist_oid)
        tb = sys.last_traceback
        while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
            tb = tb.tb_next
        item = StackViewer.StackTreeItem(flist, tb)
        return RemoteObjectBrowser.remote_object_tree_item(item)

--- NEW FILE: setup.cfg ---
[bdist_rpm]
release = 1
packager = Kurt B. Kaiser <kbk@shore.net>


--- NEW FILE: tabpage.py ---
"""
a couple of classes for implementing partial tabbed-page like behaviour
"""

from Tkinter import *

class InvalidTabPage(Exception): pass
class AlreadyExists(Exception): pass

class PageTab(Frame):
    """
    a 'page tab' like framed button
    """
    def __init__(self,parent):
        Frame.__init__(self, parent,borderwidth=2,relief=RIDGE)
        self.button=Radiobutton(self,padx=5,pady=5,takefocus=FALSE,
                indicatoron=FALSE,highlightthickness=0,
                borderwidth=0,selectcolor=self.cget('bg'))
        self.button.pack()

class TabPageSet(Frame):
    """
    a set of 'pages' with TabButtons for controlling their display
    """
    def __init__(self,parent,pageNames=[],**kw):
        """
        pageNames - a list of strings, each string will be the dictionary key
        to a page's data, and the name displayed on the page's tab. Should be
        specified in desired page order. The first page will be the default
        and first active page.
        """
        Frame.__init__(self, parent, kw)
        self.grid_location(0,0)
        self.columnconfigure(0,weight=1)
        self.rowconfigure(1,weight=1)
        self.tabBar=Frame(self)
        self.tabBar.grid(row=0,column=0,sticky=EW)
        self.activePage=StringVar(self)
        self.defaultPage=''
        self.pages={}
        for name in pageNames:
            self.AddPage(name)

    def ChangePage(self,pageName=None):
        if pageName:
            if pageName in self.pages.keys():
                self.activePage.set(pageName)
            else:
                raise InvalidTabPage, 'Invalid TabPage Name'
        ## pop up the active 'tab' only
        for page in self.pages.keys():
            self.pages[page]['tab'].config(relief=RIDGE)
        self.pages[self.GetActivePage()]['tab'].config(relief=RAISED)
        ## switch page
        self.pages[self.GetActivePage()]['page'].lift()

    def GetActivePage(self):
        return self.activePage.get()

    def AddPage(self,pageName):
        if pageName in self.pages.keys():
            raise AlreadyExists, 'TabPage Name Already Exists'
        self.pages[pageName]={'tab':PageTab(self.tabBar),
                'page':Frame(self,borderwidth=2,relief=RAISED)}
        self.pages[pageName]['tab'].button.config(text=pageName,
                command=self.ChangePage,variable=self.activePage,
                value=pageName)
        self.pages[pageName]['tab'].pack(side=LEFT)
        self.pages[pageName]['page'].grid(row=1,column=0,sticky=NSEW)
        if len(self.pages)==1: # adding first page
            self.defaultPage=pageName
            self.activePage.set(self.defaultPage)
            self.ChangePage()

    def RemovePage(self,pageName):
        if not pageName in self.pages.keys():
            raise InvalidTabPage, 'Invalid TabPage Name'
        self.pages[pageName]['tab'].pack_forget()
        self.pages[pageName]['page'].grid_forget()
        self.pages[pageName]['tab'].destroy()
        self.pages[pageName]['page'].destroy()
        del(self.pages[pageName])
        # handle removing last remaining, or default, or active page
        if not self.pages: # removed last remaining page
            self.defaultPage=''
            return
        if pageName==self.defaultPage: # set a new default page
            self.defaultPage=\
                self.tabBar.winfo_children()[0].button.cget('text')
        if pageName==self.GetActivePage(): # set a new active page
            self.activePage.set(self.defaultPage)
        self.ChangePage()

if __name__ == '__main__':
    #test dialog
    root=Tk()
    tabPage=TabPageSet(root,pageNames=['Foobar','Baz'])
    tabPage.pack(expand=TRUE,fill=BOTH)
    Label(tabPage.pages['Foobar']['page'],text='Foo',pady=20).pack()
    Label(tabPage.pages['Foobar']['page'],text='Bar',pady=20).pack()
    Label(tabPage.pages['Baz']['page'],text='Baz').pack()
    entryPgName=Entry(root)
    buttonAdd=Button(root,text='Add Page',
            command=lambda:tabPage.AddPage(entryPgName.get()))
    buttonRemove=Button(root,text='Remove Page',
            command=lambda:tabPage.RemovePage(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)
    tabPage.ChangePage()
    root.mainloop()

--- NEW FILE: textView.py ---
##---------------------------------------------------------------------------##
##
## idle - simple text view dialog
## elguavas
##
##---------------------------------------------------------------------------##
"""
simple text browser for idle
"""
from Tkinter import *
import tkMessageBox

class TextViewer(Toplevel):
    """
    simple text viewer dialog for idle
    """
    def __init__(self,parent,title,fileName):
        """
        fileName - string,should be an absoulute filename
        """
        Toplevel.__init__(self, parent)
        self.configure(borderwidth=5)
        self.geometry("+%d+%d" % (parent.winfo_rootx()+10,
                parent.winfo_rooty()+10))
        #elguavas - config placeholders til config stuff completed
        self.bg=None
        self.fg=None

        self.CreateWidgets()
        self.title(title)
        self.transient(parent)
        self.grab_set()
        self.protocol("WM_DELETE_WINDOW", self.Ok)
        self.parent = parent
        self.textView.focus_set()
        #key bindings for this dialog
        self.bind('<Return>',self.Ok) #dismiss dialog
        self.bind('<Escape>',self.Ok) #dismiss dialog
        self.LoadTextFile(fileName)
        self.textView.config(state=DISABLED)
        self.wait_window()

    def LoadTextFile(self, fileName):
        textFile = None
        try:
            textFile = open(fileName, 'r')
        except IOError:
            tkMessageBox.showerror(title='File Load Error',
                    message='Unable to load file '+`fileName`+' .')
        else:
            self.textView.insert(0.0,textFile.read())

    def CreateWidgets(self):
        frameText = Frame(self)
        frameButtons = Frame(self)
        self.buttonOk = Button(frameButtons,text='Ok',
                command=self.Ok,takefocus=FALSE,default=ACTIVE)
        self.scrollbarView = Scrollbar(frameText,orient=VERTICAL,
                takefocus=FALSE,highlightthickness=0)
        self.textView = Text(frameText,wrap=WORD,highlightthickness=0)
        self.scrollbarView.config(command=self.textView.yview)
        self.textView.config(yscrollcommand=self.scrollbarView.set)
        self.buttonOk.pack(padx=5,pady=5)
        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)

    def Ok(self, event=None):
        self.destroy()

if __name__ == '__main__':
    #test the dialog
    root=Tk()
    Button(root,text='View',
            command=lambda:TextViewer(root,'Text','./textView.py')).pack()
    root.mainloop()