Commit a3c253e8 authored by Guido van Rossum's avatar Guido van Rossum

Removing new files accidentally checked in on the trunk rather than on the

idlefork-merge-branch.
parent 767d9fed
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
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.
======================================================================
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.
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.
# 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
"""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'
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]
"""
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()
"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
# 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>
# 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
# 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>
# 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]
"""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
from configSectionNameDialog import GetCfgSectionNameDialog
from configHelpSourceEdit import GetHelpSourceDialog
class ConfigDialog(Toplevel):
"""
configuration dialog for idle
"""
def __init__(self,parent,title):
Toplevel.__init__(self, parent)
self.configure(borderwidth=5)
self.geometry("+%d+%d" % (parent.winfo_rootx()+20,
parent.winfo_rooty()+30))
#Theme Elements. Each theme element key is it's display name.
#The first value of the tuple is the sample area tag name.
#The second value is the display name list sort index.
self.themeElements={'Normal Text':('normal','00'),
'Python Keywords':('keyword','01'),
'Python Definitions':('definition','02'),
'Python Comments':('comment','03'),
'Python Strings':('string','04'),
'Selected Text':('hilite','05'),
'Found Text':('hit','06'),
'Cursor':('cursor','07'),
'Error Text':('error','08'),
'Shell Normal Text':('console','09'),
'Shell Stdout Text':('stdout','10'),
'Shell Stderr Text':('stderr','11')}
self.ResetChangedItems() #load initial values in changed items dict
self.CreateWidgets()
self.resizable(height=FALSE,width=FALSE)
self.transient(parent)
self.grab_set()
self.protocol("WM_DELETE_WINDOW", self.Cancel)
self.parent = parent
self.tabPages.focus_set()
#key bindings for this dialog
#self.bind('<Escape>',self.Cancel) #dismiss dialog, no save
#self.bind('<Alt-a>',self.Apply) #apply changes, save
#self.bind('<F1>',self.Help) #context help
self.LoadConfigs()
self.AttachVarCallbacks() #avoid callbacks during LoadConfigs
self.wait_window()
def CreateWidgets(self):
self.tabPages = TabPageSet(self,
pageNames=['Fonts/Tabs','Highlighting','Keys','General'])
self.tabPages.ChangePage()#activates default (first) page
frameActionButtons = Frame(self)
#action buttons
self.buttonHelp = Button(frameActionButtons,text='Help',
command=self.Help,takefocus=FALSE)
self.buttonOk = Button(frameActionButtons,text='Ok',
command=self.Ok,takefocus=FALSE)
self.buttonApply = Button(frameActionButtons,text='Apply',
command=self.Apply,takefocus=FALSE)
self.buttonCancel = Button(frameActionButtons,text='Cancel',
command=self.Cancel,takefocus=FALSE)
self.CreatePageFontTab()
self.CreatePageHighlight()
self.CreatePageKeys()
self.CreatePageGeneral()
self.buttonHelp.pack(side=RIGHT,padx=5,pady=5)
self.buttonOk.pack(side=LEFT,padx=5,pady=5)
self.buttonApply.pack(side=LEFT,padx=5,pady=5)
self.buttonCancel.pack(side=LEFT,padx=5,pady=5)
frameActionButtons.pack(side=BOTTOM)
self.tabPages.pack(side=TOP,expand=TRUE,fill=BOTH)
def CreatePageFontTab(self):
#tkVars
self.fontSize=StringVar(self)
self.fontBold=BooleanVar(self)
self.fontName=StringVar(self)
self.spaceNum=IntVar(self)
#self.tabCols=IntVar(self)
self.indentBySpaces=BooleanVar(self)
self.editFont=tkFont.Font(self,('courier',12,'normal'))
##widget creation
#body frame
frame=self.tabPages.pages['Fonts/Tabs']['page']
#body section frames
frameFont=Frame(frame,borderwidth=2,relief=GROOVE)
frameIndent=Frame(frame,borderwidth=2,relief=GROOVE)
#frameFont
labelFontTitle=Label(frameFont,text='Set Base Editor Font')
frameFontName=Frame(frameFont)
frameFontParam=Frame(frameFont)
labelFontNameTitle=Label(frameFontName,justify=LEFT,
text='Font :')
self.listFontName=Listbox(frameFontName,height=5,takefocus=FALSE,
exportselection=FALSE)
self.listFontName.bind('<ButtonRelease-1>',self.OnListFontButtonRelease)
scrollFont=Scrollbar(frameFontName)
scrollFont.config(command=self.listFontName.yview)
self.listFontName.config(yscrollcommand=scrollFont.set)
labelFontSizeTitle=Label(frameFontParam,text='Size :')
self.optMenuFontSize=DynOptionMenu(frameFontParam,self.fontSize,None,
command=self.SetFontSample)
checkFontBold=Checkbutton(frameFontParam,variable=self.fontBold,
onvalue=1,offvalue=0,text='Bold',command=self.SetFontSample)
frameFontSample=Frame(frameFont,relief=SOLID,borderwidth=1)
self.labelFontSample=Label(frameFontSample,
text='AaBbCcDdEe\nFfGgHhIiJjK\n1234567890\n#:+=(){}[]',
justify=LEFT,font=self.editFont)
#frameIndent
labelIndentTitle=Label(frameIndent,text='Set Indentation Defaults')
frameIndentType=Frame(frameIndent)
frameIndentSize=Frame(frameIndent)
labelIndentTypeTitle=Label(frameIndentType,
text='Choose indentation type :')
radioUseSpaces=Radiobutton(frameIndentType,variable=self.indentBySpaces,
value=1,text='Tab key inserts spaces')
radioUseTabs=Radiobutton(frameIndentType,variable=self.indentBySpaces,
value=0,text='Tab key inserts tabs')
labelIndentSizeTitle=Label(frameIndentSize,
text='Choose indentation size :')
labelSpaceNumTitle=Label(frameIndentSize,justify=LEFT,
text='indent width')
self.scaleSpaceNum=Scale(frameIndentSize,variable=self.spaceNum,
orient='horizontal',tickinterval=2,from_=2,to=16)
#labeltabColsTitle=Label(frameIndentSize,justify=LEFT,
# text='when tab key inserts tabs,\ncolumns per tab')
#self.scaleTabCols=Scale(frameIndentSize,variable=self.tabCols,
# orient='horizontal',tickinterval=2,from_=2,to=8)
#widget packing
#body
frameFont.pack(side=LEFT,padx=5,pady=10,expand=TRUE,fill=BOTH)
frameIndent.pack(side=LEFT,padx=5,pady=10,fill=Y)
#frameFont
labelFontTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
frameFontName.pack(side=TOP,padx=5,pady=5,fill=X)
frameFontParam.pack(side=TOP,padx=5,pady=5,fill=X)
labelFontNameTitle.pack(side=TOP,anchor=W)
self.listFontName.pack(side=LEFT,expand=TRUE,fill=X)
scrollFont.pack(side=LEFT,fill=Y)
labelFontSizeTitle.pack(side=LEFT,anchor=W)
self.optMenuFontSize.pack(side=LEFT,anchor=W)
checkFontBold.pack(side=LEFT,anchor=W,padx=20)
frameFontSample.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
self.labelFontSample.pack(expand=TRUE,fill=BOTH)
#frameIndent
labelIndentTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
frameIndentType.pack(side=TOP,padx=5,fill=X)
frameIndentSize.pack(side=TOP,padx=5,pady=5,fill=BOTH)
labelIndentTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
radioUseSpaces.pack(side=TOP,anchor=W,padx=5)
radioUseTabs.pack(side=TOP,anchor=W,padx=5)
labelIndentSizeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
labelSpaceNumTitle.pack(side=TOP,anchor=W,padx=5)
self.scaleSpaceNum.pack(side=TOP,padx=5,fill=X)
#labeltabColsTitle.pack(side=TOP,anchor=W,padx=5)
#self.scaleTabCols.pack(side=TOP,padx=5,fill=X)
return frame
def CreatePageHighlight(self):
self.builtinTheme=StringVar(self)
self.customTheme=StringVar(self)
self.fgHilite=BooleanVar(self)
self.colour=StringVar(self)
self.fontName=StringVar(self)
self.themeIsBuiltin=BooleanVar(self)
self.highlightTarget=StringVar(self)
##widget creation
#body frame
frame=self.tabPages.pages['Highlighting']['page']
#body section frames
frameCustom=Frame(frame,borderwidth=2,relief=GROOVE)
frameTheme=Frame(frame,borderwidth=2,relief=GROOVE)
#frameCustom
self.textHighlightSample=Text(frameCustom,relief=SOLID,borderwidth=1,
font=('courier',12,''),cursor='hand2',width=21,height=10,
takefocus=FALSE,highlightthickness=0,wrap=NONE)
text=self.textHighlightSample
text.bind('<Double-Button-1>',lambda e: 'break')
text.bind('<B1-Motion>',lambda e: 'break')
textAndTags=(('#you can click here','comment'),('\n','normal'),
('#to choose items','comment'),('\n','normal'),('def','keyword'),
(' ','normal'),('func','definition'),('(param):','normal'),
('\n ','normal'),('"""string"""','string'),('\n var0 = ','normal'),
("'string'",'string'),('\n var1 = ','normal'),("'selected'",'hilite'),
('\n var2 = ','normal'),("'found'",'hit'),('\n\n','normal'),
(' error ','error'),(' ','normal'),('cursor |','cursor'),
('\n ','normal'),('shell','console'),(' ','normal'),('stdout','stdout'),
(' ','normal'),('stderr','stderr'),('\n','normal'))
for txTa in textAndTags:
text.insert(END,txTa[0],txTa[1])
for element in self.themeElements.keys():
text.tag_bind(self.themeElements[element][0],'<ButtonPress-1>',
lambda event,elem=element: event.widget.winfo_toplevel()
.highlightTarget.set(elem))
text.config(state=DISABLED)
self.frameColourSet=Frame(frameCustom,relief=SOLID,borderwidth=1)
frameFgBg=Frame(frameCustom)
labelCustomTitle=Label(frameCustom,text='Set Custom Highlighting')
buttonSetColour=Button(self.frameColourSet,text='Choose Colour for :',
command=self.GetColour,highlightthickness=0)
self.optMenuHighlightTarget=DynOptionMenu(self.frameColourSet,
self.highlightTarget,None,highlightthickness=0)#,command=self.SetHighlightTargetBinding
self.radioFg=Radiobutton(frameFgBg,variable=self.fgHilite,
value=1,text='Foreground',command=self.SetColourSampleBinding)
self.radioBg=Radiobutton(frameFgBg,variable=self.fgHilite,
value=0,text='Background',command=self.SetColourSampleBinding)
self.fgHilite.set(1)
buttonSaveCustomTheme=Button(frameCustom,
text='Save as New Custom Theme',command=self.SaveAsNewTheme)
#frameTheme
labelThemeTitle=Label(frameTheme,text='Select a Highlighting Theme')
labelTypeTitle=Label(frameTheme,text='Select : ')
self.radioThemeBuiltin=Radiobutton(frameTheme,variable=self.themeIsBuiltin,
value=1,command=self.SetThemeType,text='a Built-in Theme')
self.radioThemeCustom=Radiobutton(frameTheme,variable=self.themeIsBuiltin,
value=0,command=self.SetThemeType,text='a Custom Theme')
self.optMenuThemeBuiltin=DynOptionMenu(frameTheme,
self.builtinTheme,None,command=None)
self.optMenuThemeCustom=DynOptionMenu(frameTheme,
self.customTheme,None,command=None)
self.buttonDeleteCustomTheme=Button(frameTheme,text='Delete Custom Theme',
command=self.DeleteCustomTheme)
##widget packing
#body
frameCustom.pack(side=LEFT,padx=5,pady=10,expand=TRUE,fill=BOTH)
frameTheme.pack(side=LEFT,padx=5,pady=10,fill=Y)
#frameCustom
labelCustomTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
self.frameColourSet.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=X)
frameFgBg.pack(side=TOP,padx=5,pady=0)
self.textHighlightSample.pack(side=TOP,padx=5,pady=5,expand=TRUE,
fill=BOTH)
buttonSetColour.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=4)
self.optMenuHighlightTarget.pack(side=TOP,expand=TRUE,fill=X,padx=8,pady=3)
self.radioFg.pack(side=LEFT,anchor=E)
self.radioBg.pack(side=RIGHT,anchor=W)
buttonSaveCustomTheme.pack(side=BOTTOM,fill=X,padx=5,pady=5)
#frameTheme
labelThemeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
labelTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
self.radioThemeBuiltin.pack(side=TOP,anchor=W,padx=5)
self.radioThemeCustom.pack(side=TOP,anchor=W,padx=5,pady=2)
self.optMenuThemeBuiltin.pack(side=TOP,fill=X,padx=5,pady=5)
self.optMenuThemeCustom.pack(side=TOP,fill=X,anchor=W,padx=5,pady=5)
self.buttonDeleteCustomTheme.pack(side=TOP,fill=X,padx=5,pady=5)
return frame
def CreatePageKeys(self):
#tkVars
self.bindingTarget=StringVar(self)
self.builtinKeys=StringVar(self)
self.customKeys=StringVar(self)
self.keysAreBuiltin=BooleanVar(self)
self.keyBinding=StringVar(self)
##widget creation
#body frame
frame=self.tabPages.pages['Keys']['page']
#body section frames
frameCustom=Frame(frame,borderwidth=2,relief=GROOVE)
frameKeySets=Frame(frame,borderwidth=2,relief=GROOVE)
#frameCustom
frameTarget=Frame(frameCustom)
labelCustomTitle=Label(frameCustom,text='Set Custom Key Bindings')
labelTargetTitle=Label(frameTarget,text='Action - Key(s)')
scrollTargetY=Scrollbar(frameTarget)
scrollTargetX=Scrollbar(frameTarget,orient=HORIZONTAL)
self.listBindings=Listbox(frameTarget,takefocus=FALSE,
exportselection=FALSE)
self.listBindings.bind('<ButtonRelease-1>',self.KeyBindingSelected)
scrollTargetY.config(command=self.listBindings.yview)
scrollTargetX.config(command=self.listBindings.xview)
self.listBindings.config(yscrollcommand=scrollTargetY.set)
self.listBindings.config(xscrollcommand=scrollTargetX.set)
self.buttonNewKeys=Button(frameCustom,text='Get New Keys for Selection',
command=self.GetNewKeys,state=DISABLED)
buttonSaveCustomKeys=Button(frameCustom,
text='Save as New Custom Key Set',command=self.SaveAsNewKeySet)
#frameKeySets
labelKeysTitle=Label(frameKeySets,text='Select a Key Set')
labelTypeTitle=Label(frameKeySets,text='Select : ')
self.radioKeysBuiltin=Radiobutton(frameKeySets,variable=self.keysAreBuiltin,
value=1,command=self.SetKeysType,text='a Built-in Key Set')
self.radioKeysCustom=Radiobutton(frameKeySets,variable=self.keysAreBuiltin,
value=0,command=self.SetKeysType,text='a Custom Key Set')
self.optMenuKeysBuiltin=DynOptionMenu(frameKeySets,
self.builtinKeys,None,command=None)
self.optMenuKeysCustom=DynOptionMenu(frameKeySets,
self.customKeys,None,command=None)
self.buttonDeleteCustomKeys=Button(frameKeySets,text='Delete Custom Key Set',
command=self.DeleteCustomKeys)
##widget packing
#body
frameCustom.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
frameKeySets.pack(side=LEFT,padx=5,pady=5,fill=Y)
#frameCustom
labelCustomTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
buttonSaveCustomKeys.pack(side=BOTTOM,fill=X,padx=5,pady=5)
self.buttonNewKeys.pack(side=BOTTOM,fill=X,padx=5,pady=5)
frameTarget.pack(side=LEFT,padx=5,pady=5,expand=TRUE,fill=BOTH)
#frame target
frameTarget.columnconfigure(0,weight=1)
frameTarget.rowconfigure(1,weight=1)
labelTargetTitle.grid(row=0,column=0,columnspan=2,sticky=W)
self.listBindings.grid(row=1,column=0,sticky=NSEW)
scrollTargetY.grid(row=1,column=1,sticky=NS)
scrollTargetX.grid(row=2,column=0,sticky=EW)
#frameKeySets
labelKeysTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
labelTypeTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
self.radioKeysBuiltin.pack(side=TOP,anchor=W,padx=5)
self.radioKeysCustom.pack(side=TOP,anchor=W,padx=5,pady=2)
self.optMenuKeysBuiltin.pack(side=TOP,fill=X,padx=5,pady=5)
self.optMenuKeysCustom.pack(side=TOP,fill=X,anchor=W,padx=5,pady=5)
self.buttonDeleteCustomKeys.pack(side=TOP,fill=X,padx=5,pady=5)
return frame
def CreatePageGeneral(self):
#tkVars
self.winWidth=StringVar(self)
self.winHeight=StringVar(self)
self.startupEdit=IntVar(self)
self.userHelpBrowser=BooleanVar(self)
self.helpBrowser=StringVar(self)
#widget creation
#body
frame=self.tabPages.pages['General']['page']
#body section frames
frameRun=Frame(frame,borderwidth=2,relief=GROOVE)
frameWinSize=Frame(frame,borderwidth=2,relief=GROOVE)
frameHelp=Frame(frame,borderwidth=2,relief=GROOVE)
#frameRun
labelRunTitle=Label(frameRun,text='Startup Preferences')
labelRunChoiceTitle=Label(frameRun,text='On Startup : ')
radioStartupEdit=Radiobutton(frameRun,variable=self.startupEdit,
value=1,command=self.SetKeysType,text="Open Edit Window")
radioStartupShell=Radiobutton(frameRun,variable=self.startupEdit,
value=0,command=self.SetKeysType,text='Open Shell Window')
#frameWinSize
labelWinSizeTitle=Label(frameWinSize,text='Initial Window Size'+
' (in characters)')
labelWinWidthTitle=Label(frameWinSize,text='Width')
entryWinWidth=Entry(frameWinSize,textvariable=self.winWidth,
width=3)
labelWinHeightTitle=Label(frameWinSize,text='Height')
entryWinHeight=Entry(frameWinSize,textvariable=self.winHeight,
width=3)
#frameHelp
labelHelpTitle=Label(frameHelp,text='Help Options')
frameHelpList=Frame(frameHelp)
frameHelpListButtons=Frame(frameHelpList)
labelHelpListTitle=Label(frameHelpList,text='Additional Help Sources:')
scrollHelpList=Scrollbar(frameHelpList)
self.listHelp=Listbox(frameHelpList,height=5,takefocus=FALSE,
exportselection=FALSE)
scrollHelpList.config(command=self.listHelp.yview)
self.listHelp.config(yscrollcommand=scrollHelpList.set)
self.listHelp.bind('<ButtonRelease-1>',self.HelpSourceSelected)
self.buttonHelpListEdit=Button(frameHelpListButtons,text='Edit',
state=DISABLED,width=8,command=self.HelpListItemEdit)
self.buttonHelpListAdd=Button(frameHelpListButtons,text='Add',
width=8,command=self.HelpListItemAdd)
self.buttonHelpListRemove=Button(frameHelpListButtons,text='Remove',
state=DISABLED,width=8,command=self.HelpListItemRemove)
# the following is better handled by the BROWSER environment
# variable under unix/linux
#checkHelpBrowser=Checkbutton(frameHelp,variable=self.userHelpBrowser,
# onvalue=1,offvalue=0,text='user specified (html) help browser:',
# command=self.OnCheckUserHelpBrowser)
#self.entryHelpBrowser=Entry(frameHelp,textvariable=self.helpBrowser,
# width=40)
#widget packing
#body
frameRun.pack(side=TOP,padx=5,pady=5,fill=X)
frameWinSize.pack(side=TOP,padx=5,pady=5,fill=X)
frameHelp.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
#frameRun
labelRunTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
labelRunChoiceTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
radioStartupEdit.pack(side=LEFT,anchor=W,padx=5,pady=5)
radioStartupShell.pack(side=LEFT,anchor=W,padx=5,pady=5)
#frameWinSize
labelWinSizeTitle.pack(side=LEFT,anchor=W,padx=5,pady=5)
entryWinHeight.pack(side=RIGHT,anchor=E,padx=10,pady=5)
labelWinHeightTitle.pack(side=RIGHT,anchor=E,pady=5)
entryWinWidth.pack(side=RIGHT,anchor=E,padx=10,pady=5)
labelWinWidthTitle.pack(side=RIGHT,anchor=E,pady=5)
#frameHelp
labelHelpTitle.pack(side=TOP,anchor=W,padx=5,pady=5)
frameHelpListButtons.pack(side=RIGHT,padx=5,pady=5,fill=Y)
frameHelpList.pack(side=TOP,padx=5,pady=5,expand=TRUE,fill=BOTH)
labelHelpListTitle.pack(side=TOP,anchor=W)
scrollHelpList.pack(side=RIGHT,anchor=W,fill=Y)
self.listHelp.pack(side=LEFT,anchor=E,expand=TRUE,fill=BOTH)
self.buttonHelpListEdit.pack(side=TOP,anchor=W,pady=5)
self.buttonHelpListAdd.pack(side=TOP,anchor=W)
self.buttonHelpListRemove.pack(side=TOP,anchor=W,pady=5)
#checkHelpBrowser.pack(side=TOP,anchor=W,padx=5)
#self.entryHelpBrowser.pack(side=TOP,anchor=W,padx=5,pady=5)
return frame
def AttachVarCallbacks(self):
self.fontSize.trace_variable('w',self.VarChanged_fontSize)
self.fontName.trace_variable('w',self.VarChanged_fontName)
self.fontBold.trace_variable('w',self.VarChanged_fontBold)
self.spaceNum.trace_variable('w',self.VarChanged_spaceNum)
#self.tabCols.trace_variable('w',self.VarChanged_tabCols)
self.indentBySpaces.trace_variable('w',self.VarChanged_indentBySpaces)
self.colour.trace_variable('w',self.VarChanged_colour)
self.builtinTheme.trace_variable('w',self.VarChanged_builtinTheme)
self.customTheme.trace_variable('w',self.VarChanged_customTheme)
self.themeIsBuiltin.trace_variable('w',self.VarChanged_themeIsBuiltin)
self.highlightTarget.trace_variable('w',self.VarChanged_highlightTarget)
self.keyBinding.trace_variable('w',self.VarChanged_keyBinding)
self.builtinKeys.trace_variable('w',self.VarChanged_builtinKeys)
self.customKeys.trace_variable('w',self.VarChanged_customKeys)
self.keysAreBuiltin.trace_variable('w',self.VarChanged_keysAreBuiltin)
self.winWidth.trace_variable('w',self.VarChanged_winWidth)
self.winHeight.trace_variable('w',self.VarChanged_winHeight)
self.startupEdit.trace_variable('w',self.VarChanged_startupEdit)
def VarChanged_fontSize(self,*params):
value=self.fontSize.get()
self.AddChangedItem('main','EditorWindow','font-size',value)
def VarChanged_fontName(self,*params):
value=self.fontName.get()
self.AddChangedItem('main','EditorWindow','font',value)
def VarChanged_fontBold(self,*params):
value=self.fontBold.get()
self.AddChangedItem('main','EditorWindow','font-bold',value)
def VarChanged_indentBySpaces(self,*params):
value=self.indentBySpaces.get()
self.AddChangedItem('main','Indent','use-spaces',value)
def VarChanged_spaceNum(self,*params):
value=self.spaceNum.get()
self.AddChangedItem('main','Indent','num-spaces',value)
#def VarChanged_tabCols(self,*params):
# value=self.tabCols.get()
# self.AddChangedItem('main','Indent','tab-cols',value)
def VarChanged_colour(self,*params):
self.OnNewColourSet()
def VarChanged_builtinTheme(self,*params):
value=self.builtinTheme.get()
self.AddChangedItem('main','Theme','name',value)
self.PaintThemeSample()
def VarChanged_customTheme(self,*params):
value=self.customTheme.get()
if value != '- no custom themes -':
self.AddChangedItem('main','Theme','name',value)
self.PaintThemeSample()
def VarChanged_themeIsBuiltin(self,*params):
value=self.themeIsBuiltin.get()
self.AddChangedItem('main','Theme','default',value)
if value:
self.VarChanged_builtinTheme()
else:
self.VarChanged_customTheme()
def VarChanged_highlightTarget(self,*params):
self.SetHighlightTarget()
def VarChanged_keyBinding(self,*params):
value=self.keyBinding.get()
keySet=self.customKeys.get()
event=self.listBindings.get(ANCHOR).split()[0]
if idleConf.IsCoreBinding(event):
#this is a core keybinding
self.AddChangedItem('keys',keySet,event,value)
else: #this is an extension key binding
extName=idleConf.GetExtnNameForEvent(event)
extKeybindSection=extName+'_cfgBindings'
self.AddChangedItem('extensions',extKeybindSection,event,value)
def VarChanged_builtinKeys(self,*params):
value=self.builtinKeys.get()
self.AddChangedItem('main','Keys','name',value)
self.LoadKeysList(value)
def VarChanged_customKeys(self,*params):
value=self.customKeys.get()
if value != '- no custom keys -':
self.AddChangedItem('main','Keys','name',value)
self.LoadKeysList(value)
def VarChanged_keysAreBuiltin(self,*params):
value=self.keysAreBuiltin.get()
self.AddChangedItem('main','Keys','default',value)
if value:
self.VarChanged_builtinKeys()
else:
self.VarChanged_customKeys()
def VarChanged_winWidth(self,*params):
value=self.winWidth.get()
self.AddChangedItem('main','EditorWindow','width',value)
def VarChanged_winHeight(self,*params):
value=self.winHeight.get()
self.AddChangedItem('main','EditorWindow','height',value)
def VarChanged_startupEdit(self,*params):
value=self.startupEdit.get()
self.AddChangedItem('main','General','editor-on-startup',value)
def ResetChangedItems(self):
#When any config item is changed in this dialog, an entry
#should be made in the relevant section (config type) of this
#dictionary. The key should be the config file section name and the
#value a dictionary, whose key:value pairs are item=value pairs for
#that config file section.
self.changedItems={'main':{},'highlight':{},'keys':{},'extensions':{}}
def AddChangedItem(self,type,section,item,value):
value=str(value) #make sure we use a string
if not self.changedItems[type].has_key(section):
self.changedItems[type][section]={}
self.changedItems[type][section][item]=value
def GetDefaultItems(self):
dItems={'main':{},'highlight':{},'keys':{},'extensions':{}}
for configType in dItems.keys():
sections=idleConf.GetSectionList('default',configType)
for section in sections:
dItems[configType][section]={}
options=idleConf.defaultCfg[configType].GetOptionList(section)
for option in options:
dItems[configType][section][option]=(
idleConf.defaultCfg[configType].Get(section,option))
return dItems
def SetThemeType(self):
if self.themeIsBuiltin.get():
self.optMenuThemeBuiltin.config(state=NORMAL)
self.optMenuThemeCustom.config(state=DISABLED)
self.buttonDeleteCustomTheme.config(state=DISABLED)
else:
self.optMenuThemeBuiltin.config(state=DISABLED)
self.radioThemeCustom.config(state=NORMAL)
self.optMenuThemeCustom.config(state=NORMAL)
self.buttonDeleteCustomTheme.config(state=NORMAL)
def SetKeysType(self):
if self.keysAreBuiltin.get():
self.optMenuKeysBuiltin.config(state=NORMAL)
self.optMenuKeysCustom.config(state=DISABLED)
self.buttonDeleteCustomKeys.config(state=DISABLED)
else:
self.optMenuKeysBuiltin.config(state=DISABLED)
self.radioKeysCustom.config(state=NORMAL)
self.optMenuKeysCustom.config(state=NORMAL)
self.buttonDeleteCustomKeys.config(state=NORMAL)
def GetNewKeys(self):
listIndex=self.listBindings.index(ANCHOR)
binding=self.listBindings.get(listIndex)
bindName=binding.split()[0] #first part, up to first space
if self.keysAreBuiltin.get():
currentKeySetName=self.builtinKeys.get()
else:
currentKeySetName=self.customKeys.get()
currentBindings=idleConf.GetCurrentKeySet()
if currentKeySetName in self.changedItems['keys'].keys(): #unsaved changes
keySetChanges=self.changedItems['keys'][currentKeySetName]
for event in keySetChanges.keys():
currentBindings[event]=keySetChanges[event].split()
currentKeySequences=currentBindings.values()
newKeys=GetKeysDialog(self,'Get New Keys',bindName,
currentKeySequences).result
if newKeys: #new keys were specified
if self.keysAreBuiltin.get(): #current key set is a built-in
message=('Your changes will be saved as a new Custom Key Set. '+
'Enter a name for your new Custom Key Set below.')
newKeySet=self.GetNewKeysName(message)
if not newKeySet: #user cancelled custom key set creation
self.listBindings.select_set(listIndex)
self.listBindings.select_anchor(listIndex)
return
else: #create new custom key set based on previously active key set
self.CreateNewKeySet(newKeySet)
self.listBindings.delete(listIndex)
self.listBindings.insert(listIndex,bindName+' - '+newKeys)
self.listBindings.select_set(listIndex)
self.listBindings.select_anchor(listIndex)
self.keyBinding.set(newKeys)
else:
self.listBindings.select_set(listIndex)
self.listBindings.select_anchor(listIndex)
def GetNewKeysName(self,message):
usedNames=(idleConf.GetSectionList('user','keys')+
idleConf.GetSectionList('default','keys'))
newKeySet=GetCfgSectionNameDialog(self,'New Custom Key Set',
message,usedNames).result
return newKeySet
def SaveAsNewKeySet(self):
newKeysName=self.GetNewKeysName('New Key Set Name:')
if newKeysName:
self.CreateNewKeySet(newKeysName)
def KeyBindingSelected(self,event):
self.buttonNewKeys.config(state=NORMAL)
def CreateNewKeySet(self,newKeySetName):
#creates new custom key set based on the previously active key set,
#and makes the new key set active
if self.keysAreBuiltin.get():
prevKeySetName=self.builtinKeys.get()
else:
prevKeySetName=self.customKeys.get()
prevKeys=idleConf.GetCoreKeys(prevKeySetName)
newKeys={}
for event in prevKeys.keys(): #add key set to changed items
eventName=event[2:-2] #trim off the angle brackets
binding=string.join(prevKeys[event])
newKeys[eventName]=binding
#handle any unsaved changes to prev key set
if prevKeySetName in self.changedItems['keys'].keys():
keySetChanges=self.changedItems['keys'][prevKeySetName]
for event in keySetChanges.keys():
newKeys[event]=keySetChanges[event]
#save the new theme
self.SaveNewKeySet(newKeySetName,newKeys)
#change gui over to the new key set
customKeyList=idleConf.GetSectionList('user','keys')
customKeyList.sort()
self.optMenuKeysCustom.SetMenu(customKeyList,newKeySetName)
self.keysAreBuiltin.set(0)
self.SetKeysType()
def LoadKeysList(self,keySetName):
reselect=0
newKeySet=0
if self.listBindings.curselection():
reselect=1
listIndex=self.listBindings.index(ANCHOR)
keySet=idleConf.GetKeySet(keySetName)
bindNames=keySet.keys()
bindNames.sort()
self.listBindings.delete(0,END)
for bindName in bindNames:
key=string.join(keySet[bindName]) #make key(s) into a string
bindName=bindName[2:-2] #trim off the angle brackets
if keySetName in self.changedItems['keys'].keys():
#handle any unsaved changes to this key set
if bindName in self.changedItems['keys'][keySetName].keys():
key=self.changedItems['keys'][keySetName][bindName]
self.listBindings.insert(END, bindName+' - '+key)
if reselect:
self.listBindings.see(listIndex)
self.listBindings.select_set(listIndex)
self.listBindings.select_anchor(listIndex)
def DeleteCustomKeys(self):
keySetName=self.customKeys.get()
if not tkMessageBox.askyesno('Delete Key Set','Are you sure you wish '+
'to delete the key set '+`keySetName`+' ?',
parent=self):
return
#remove key set from config
idleConf.userCfg['keys'].remove_section(keySetName)
if self.changedItems['keys'].has_key(keySetName):
del(self.changedItems['keys'][keySetName])
#write changes
idleConf.userCfg['keys'].Save()
#reload user key set list
itemList=idleConf.GetSectionList('user','keys')
itemList.sort()
if not itemList:
self.radioKeysCustom.config(state=DISABLED)
self.optMenuKeysCustom.SetMenu(itemList,'- no custom keys -')
else:
self.optMenuKeysCustom.SetMenu(itemList,itemList[0])
#revert to default key set
self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys','default'))
self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys','name'))
#user can't back out of these changes, they must be applied now
self.Apply()
self.SetKeysType()
def DeleteCustomTheme(self):
themeName=self.customTheme.get()
if not tkMessageBox.askyesno('Delete Theme','Are you sure you wish '+
'to delete the theme '+`themeName`+' ?',
parent=self):
return
#remove theme from config
idleConf.userCfg['highlight'].remove_section(themeName)
if self.changedItems['highlight'].has_key(themeName):
del(self.changedItems['highlight'][themeName])
#write changes
idleConf.userCfg['highlight'].Save()
#reload user theme list
itemList=idleConf.GetSectionList('user','highlight')
itemList.sort()
if not itemList:
self.radioThemeCustom.config(state=DISABLED)
self.optMenuThemeCustom.SetMenu(itemList,'- no custom themes -')
else:
self.optMenuThemeCustom.SetMenu(itemList,itemList[0])
#revert to default theme
self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme','default'))
self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme','name'))
#user can't back out of these changes, they must be applied now
self.Apply()
self.SetThemeType()
def GetColour(self):
target=self.highlightTarget.get()
prevColour=self.frameColourSet.cget('bg')
rgbTuplet, colourString = tkColorChooser.askcolor(parent=self,
title='Pick new colour for : '+target,initialcolor=prevColour)
if colourString and (colourString!=prevColour):
#user didn't cancel, and they chose a new colour
if self.themeIsBuiltin.get(): #current theme is a built-in
message=('Your changes will be saved as a new Custom Theme. '+
'Enter a name for your new Custom Theme below.')
newTheme=self.GetNewThemeName(message)
if not newTheme: #user cancelled custom theme creation
return
else: #create new custom theme based on previously active theme
self.CreateNewTheme(newTheme)
self.colour.set(colourString)
else: #current theme is user defined
self.colour.set(colourString)
def OnNewColourSet(self):
newColour=self.colour.get()
self.frameColourSet.config(bg=newColour)#set sample
if self.fgHilite.get(): plane='foreground'
else: plane='background'
sampleElement=self.themeElements[self.highlightTarget.get()][0]
apply(self.textHighlightSample.tag_config,
(sampleElement,),{plane:newColour})
theme=self.customTheme.get()
themeElement=sampleElement+'-'+plane
self.AddChangedItem('highlight',theme,themeElement,newColour)
def GetNewThemeName(self,message):
usedNames=(idleConf.GetSectionList('user','highlight')+
idleConf.GetSectionList('default','highlight'))
newTheme=GetCfgSectionNameDialog(self,'New Custom Theme',
message,usedNames).result
return newTheme
def SaveAsNewTheme(self):
newThemeName=self.GetNewThemeName('New Theme Name:')
if newThemeName:
self.CreateNewTheme(newThemeName)
def CreateNewTheme(self,newThemeName):
#creates new custom theme based on the previously active theme,
#and makes the new theme active
if self.themeIsBuiltin.get():
themeType='default'
themeName=self.builtinTheme.get()
else:
themeType='user'
themeName=self.customTheme.get()
newTheme=idleConf.GetThemeDict(themeType,themeName)
#apply any of the old theme's unsaved changes to the new theme
if themeName in self.changedItems['highlight'].keys():
themeChanges=self.changedItems['highlight'][themeName]
for element in themeChanges.keys():
newTheme[element]=themeChanges[element]
#save the new theme
self.SaveNewTheme(newThemeName,newTheme)
#change gui over to the new theme
customThemeList=idleConf.GetSectionList('user','highlight')
customThemeList.sort()
self.optMenuThemeCustom.SetMenu(customThemeList,newThemeName)
self.themeIsBuiltin.set(0)
self.SetThemeType()
def OnListFontButtonRelease(self,event):
self.fontName.set(self.listFontName.get(ANCHOR))
self.SetFontSample()
def SetFontSample(self,event=None):
fontName=self.fontName.get()
if self.fontBold.get():
fontWeight=tkFont.BOLD
else:
fontWeight=tkFont.NORMAL
self.editFont.config(size=self.fontSize.get(),
weight=fontWeight,family=fontName)
def SetHighlightTarget(self):
if self.highlightTarget.get()=='Cursor': #bg not possible
self.radioFg.config(state=DISABLED)
self.radioBg.config(state=DISABLED)
self.fgHilite.set(1)
else: #both fg and bg can be set
self.radioFg.config(state=NORMAL)
self.radioBg.config(state=NORMAL)
self.fgHilite.set(1)
self.SetColourSample()
def SetColourSampleBinding(self,*args):
self.SetColourSample()
def SetColourSample(self):
#set the colour smaple area
tag=self.themeElements[self.highlightTarget.get()][0]
if self.fgHilite.get(): plane='foreground'
else: plane='background'
colour=self.textHighlightSample.tag_cget(tag,plane)
self.frameColourSet.config(bg=colour)
def PaintThemeSample(self):
if self.themeIsBuiltin.get(): #a default theme
theme=self.builtinTheme.get()
else: #a user theme
theme=self.customTheme.get()
for elementTitle in self.themeElements.keys():
element=self.themeElements[elementTitle][0]
colours=idleConf.GetHighlight(theme,element)
if element=='cursor': #cursor sample needs special painting
colours['background']=idleConf.GetHighlight(theme,
'normal', fgBg='bg')
#handle any unsaved changes to this theme
if theme in self.changedItems['highlight'].keys():
themeDict=self.changedItems['highlight'][theme]
if themeDict.has_key(element+'-foreground'):
colours['foreground']=themeDict[element+'-foreground']
if themeDict.has_key(element+'-background'):
colours['background']=themeDict[element+'-background']
apply(self.textHighlightSample.tag_config,(element,),colours)
self.SetColourSample()
## def OnCheckUserHelpBrowser(self):
## if self.userHelpBrowser.get():
## self.entryHelpBrowser.config(state=NORMAL)
## else:
## self.entryHelpBrowser.config(state=DISABLED)
def HelpSourceSelected(self,event):
self.SetHelpListButtonStates()
def SetHelpListButtonStates(self):
if self.listHelp.size()<1: #no entries in list
self.buttonHelpListEdit.config(state=DISABLED)
self.buttonHelpListRemove.config(state=DISABLED)
else: #there are some entries
if self.listHelp.curselection(): #there currently is a selection
self.buttonHelpListEdit.config(state=NORMAL)
self.buttonHelpListRemove.config(state=NORMAL)
else: #there currently is not a selection
self.buttonHelpListEdit.config(state=DISABLED)
self.buttonHelpListRemove.config(state=DISABLED)
def HelpListItemAdd(self):
helpSource=GetHelpSourceDialog(self,'New Help Source').result
if helpSource:
self.userHelpList.append( (helpSource[0],helpSource[1]) )
self.listHelp.insert(END,helpSource[0])
self.UpdateUserHelpChangedItems()
self.SetHelpListButtonStates()
def HelpListItemEdit(self):
itemIndex=self.listHelp.index(ANCHOR)
helpSource=self.userHelpList[itemIndex]
newHelpSource=GetHelpSourceDialog(self,'Edit Help Source',
menuItem=helpSource[0],filePath=helpSource[1]).result
if (not newHelpSource) or (newHelpSource==helpSource):
return #no changes
self.userHelpList[itemIndex]=newHelpSource
self.listHelp.delete(itemIndex)
self.listHelp.insert(itemIndex,newHelpSource[0])
self.UpdateUserHelpChangedItems()
self.SetHelpListButtonStates()
def HelpListItemRemove(self):
itemIndex=self.listHelp.index(ANCHOR)
del(self.userHelpList[itemIndex])
self.listHelp.delete(itemIndex)
self.UpdateUserHelpChangedItems()
self.SetHelpListButtonStates()
def UpdateUserHelpChangedItems(self):
"Clear and rebuild the HelpFiles section in self.changedItems"
self.changedItems['main']['HelpFiles'] = {}
for num in range(1,len(self.userHelpList)+1):
self.AddChangedItem('main','HelpFiles',str(num),
string.join(self.userHelpList[num-1][:2],';'))
def LoadFontCfg(self):
##base editor font selection list
fonts=list(tkFont.families(self))
fonts.sort()
for font in fonts:
self.listFontName.insert(END,font)
configuredFont=idleConf.GetOption('main','EditorWindow','font',
default='courier')
self.fontName.set(configuredFont)
if configuredFont in fonts:
currentFontIndex=fonts.index(configuredFont)
self.listFontName.see(currentFontIndex)
self.listFontName.select_set(currentFontIndex)
self.listFontName.select_anchor(currentFontIndex)
##font size dropdown
fontSize=idleConf.GetOption('main','EditorWindow','font-size',
default='12')
self.optMenuFontSize.SetMenu(('7','8','9','10','11','12','13','14',
'16','18','20','22'),fontSize )
##fontWeight
self.fontBold.set(idleConf.GetOption('main','EditorWindow',
'font-bold',default=0,type='bool'))
##font sample
self.SetFontSample()
def LoadTabCfg(self):
##indent type radiobuttons
spaceIndent=idleConf.GetOption('main','Indent','use-spaces',
default=1,type='bool')
self.indentBySpaces.set(spaceIndent)
##indent sizes
spaceNum=idleConf.GetOption('main','Indent','num-spaces',
default=4,type='int')
#tabCols=idleConf.GetOption('main','Indent','tab-cols',
# default=4,type='int')
self.spaceNum.set(spaceNum)
#self.tabCols.set(tabCols)
def LoadThemeCfg(self):
##current theme type radiobutton
self.themeIsBuiltin.set(idleConf.GetOption('main','Theme','default',
type='bool',default=1))
##currently set theme
currentOption=idleConf.CurrentTheme()
##load available theme option menus
if self.themeIsBuiltin.get(): #default theme selected
itemList=idleConf.GetSectionList('default','highlight')
itemList.sort()
self.optMenuThemeBuiltin.SetMenu(itemList,currentOption)
itemList=idleConf.GetSectionList('user','highlight')
itemList.sort()
if not itemList:
self.radioThemeCustom.config(state=DISABLED)
self.customTheme.set('- no custom themes -')
else:
self.optMenuThemeCustom.SetMenu(itemList,itemList[0])
else: #user theme selected
itemList=idleConf.GetSectionList('user','highlight')
itemList.sort()
self.optMenuThemeCustom.SetMenu(itemList,currentOption)
itemList=idleConf.GetSectionList('default','highlight')
itemList.sort()
self.optMenuThemeBuiltin.SetMenu(itemList,itemList[0])
self.SetThemeType()
##load theme element option menu
themeNames=self.themeElements.keys()
themeNames.sort(self.__ThemeNameIndexCompare)
self.optMenuHighlightTarget.SetMenu(themeNames,themeNames[0])
self.PaintThemeSample()
self.SetHighlightTarget()
def __ThemeNameIndexCompare(self,a,b):
if self.themeElements[a][1]<self.themeElements[b][1]: return -1
elif self.themeElements[a][1]==self.themeElements[b][1]: return 0
else: return 1
def LoadKeyCfg(self):
##current keys type radiobutton
self.keysAreBuiltin.set(idleConf.GetOption('main','Keys','default',
type='bool',default=1))
##currently set keys
currentOption=idleConf.CurrentKeys()
##load available keyset option menus
if self.keysAreBuiltin.get(): #default theme selected
itemList=idleConf.GetSectionList('default','keys')
itemList.sort()
self.optMenuKeysBuiltin.SetMenu(itemList,currentOption)
itemList=idleConf.GetSectionList('user','keys')
itemList.sort()
if not itemList:
self.radioKeysCustom.config(state=DISABLED)
self.customKeys.set('- no custom keys -')
else:
self.optMenuKeysCustom.SetMenu(itemList,itemList[0])
else: #user key set selected
itemList=idleConf.GetSectionList('user','keys')
itemList.sort()
self.optMenuKeysCustom.SetMenu(itemList,currentOption)
itemList=idleConf.GetSectionList('default','keys')
itemList.sort()
self.optMenuKeysBuiltin.SetMenu(itemList,itemList[0])
self.SetKeysType()
##load keyset element list
keySetName=idleConf.CurrentKeys()
self.LoadKeysList(keySetName)
def LoadGeneralCfg(self):
#startup state
self.startupEdit.set(idleConf.GetOption('main','General',
'editor-on-startup',default=1,type='bool'))
#initial window size
self.winWidth.set(idleConf.GetOption('main','EditorWindow','width'))
self.winHeight.set(idleConf.GetOption('main','EditorWindow','height'))
# additional help sources
self.userHelpList = idleConf.GetAllExtraHelpSourcesList()
for helpItem in self.userHelpList:
self.listHelp.insert(END,helpItem[0])
self.SetHelpListButtonStates()
#self.userHelpBrowser.set(idleConf.GetOption('main','General',
# 'user-help-browser',default=0,type='bool'))
#self.helpBrowser.set(idleConf.GetOption('main','General',
# 'user-help-browser-command',default=''))
#self.OnCheckUserHelpBrowser()
def LoadConfigs(self):
"""
load configuration from default and user config files and populate
the widgets on the config dialog pages.
"""
### fonts / tabs page
self.LoadFontCfg()
self.LoadTabCfg()
### highlighting page
self.LoadThemeCfg()
### keys page
self.LoadKeyCfg()
### general page
self.LoadGeneralCfg()
def SaveNewKeySet(self,keySetName,keySet):
"""
save a newly created core key set.
keySetName - string, the name of the new key set
keySet - dictionary containing the new key set
"""
if not idleConf.userCfg['keys'].has_section(keySetName):
idleConf.userCfg['keys'].add_section(keySetName)
for event in keySet.keys():
value=keySet[event]
idleConf.userCfg['keys'].SetOption(keySetName,event,value)
def SaveNewTheme(self,themeName,theme):
"""
save a newly created theme.
themeName - string, the name of the new theme
theme - dictionary containing the new theme
"""
if not idleConf.userCfg['highlight'].has_section(themeName):
idleConf.userCfg['highlight'].add_section(themeName)
for element in theme.keys():
value=theme[element]
idleConf.userCfg['highlight'].SetOption(themeName,element,value)
def SetUserValue(self,configType,section,item,value):
if idleConf.defaultCfg[configType].has_option(section,item):
if idleConf.defaultCfg[configType].Get(section,item)==value:
#the setting equals a default setting, remove it from user cfg
return idleConf.userCfg[configType].RemoveOption(section,item)
#if we got here set the option
return idleConf.userCfg[configType].SetOption(section,item,value)
def SaveAllChangedConfigs(self):
"Save configuration changes to the user config file."
idleConf.userCfg['main'].Save()
for configType in self.changedItems.keys():
cfgTypeHasChanges = False
for section in self.changedItems[configType].keys():
if section == 'HelpFiles':
#this section gets completely replaced
idleConf.userCfg['main'].remove_section('HelpFiles')
cfgTypeHasChanges = True
for item in self.changedItems[configType][section].keys():
value = self.changedItems[configType][section][item]
if self.SetUserValue(configType,section,item,value):
cfgTypeHasChanges = True
if cfgTypeHasChanges:
idleConf.userCfg[configType].Save()
self.ResetChangedItems() #clear the changed items dict
def ActivateConfigChanges(self):
#things that need to be done to make
#applied config changes dynamic:
#update editor/shell font and repaint
#dynamically update indentation setttings
#update theme and repaint
#update keybindings and re-bind
#update user help sources menu
winInstances=self.parent.instanceDict.keys()
for instance in winInstances:
instance.ResetColorizer()
instance.ResetFont()
instance.ResetKeybindings()
instance.reset_help_menu_entries()
def Cancel(self):
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()
"""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')
"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()
"""
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()
"""
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)
/***********************************************************************
* 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);
}
"""
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()
#!/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
"""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()
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)
[bdist_rpm]
release = 1
packager = Kurt B. Kaiser <kbk@shore.net>
"""
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()
##---------------------------------------------------------------------------##
##
## 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()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment