#@+leo-ver=4-thin
#@+node:edream.110203113231:@thin pluginsNotes.txt
#@@nocolor
#@+all
#@+node:edream.110203113231.3:Documentation and security warnings
@nocolor

This file contains code for all plugins distributed with Leo.
#@nonl
#@+node:edream.110203113231.4:Overview of plugins and hooks
@nocolor

What is a plugin?

A plugin is a .py file that appears in Leo's plugin subdirectory. Leo tries to
import all such files while starting up. This is done at "start1" time, that
is, in the call to doHook("start1") in the run function in leo.py. Plugins
exist to define "hook code".

What is hook code?

Leo automatically calls hook code at various times during Leo's execution.
Typical hooks are:

- "start2", called at the end of Leo's startup process,
- "command1", called before executing any of Leo's commands
- "idle", called at Tk's idle time, when nothing else is happening,

See the documentation for hooks in this file for full details.

N.B. Plugins can use "start2" hooks to override any of Leo's classes. See
"Overriding functions, methods & classes" in leoPlugins.leo for several
examples of how to do this.

How do plugins work?

Plugins register themselves when Leo imports them using the following code,
which should appear as the outer level of the .py file:

@color

"""A description of this plugin"""

leoPlugins.registerHandler("xxx", onXXX)
__version__ = "1.2"
g.plugin_signon(__name__)
	
@nocolor

Line by line:

1) """A description of this plugin"""

This is a Python doc string describing the plugin. This string becomes the
value of the __name__ attribute for the module (.py file).

2) leoPlugins.registerHandler("xxx", onXXX)

This line registers a handler called onXXX for the hook called "xxx". See the
documentation for hooks for a complete list of hook names that you can use
instead of "xxx". onXXX is the name of a function or method, presumably defined
in the plugin file, that Leo will call at "xxx" time. For example:

leoPlugins.registerHandler("idle",onIdle)

will cause Leo to call the onIdle function at "idle" time.

You can pass a list of hook names as the first argument to leoPlugins.registerHandler. For
example:

leoPlugins.registerHandler(("bodykey1","bodykey2","headkey1","headkey2"), onKey)

3) __version__ = "1.2"

This assigns a version attribute to module. At present this attribute is used
by the plugin code that creates Leo's Plugins menu.

4) g.plugin_signon(__name__)

This line increases the count of loaded modules. The present code for
plugin_signon does not actually print separate signons--it seems to verbose.

About hook handlers

See the "About hooks" section of leoPlugins.leo for full documentation about
how to write hook handler routines, like onXXX or onKey in the examples above.
Basically, each hook should have this pattern:
	
@color

def onXXX (tag,keywords):
	c = keywords.get("c")
	otherKeyword = keywords.get("otherKeyword")
	<< do something with c and otherKeyword, etc. >>
	
@nocolor

In other words, keywords is a Python dictionary containing the keyword
arguments passed to the hook handler. See the See the "About hooks" section of
leoPlugins.leo for a list of the keywords passed to each hook handler.

The tag is the name of the hook. For example, tag would one of
"bodykey1","bodykey2","headkey1","headkey2", for the onKey hook handler in the
example above.

What's next?

It's one thing to know how to create a plugin. It's another to know how to
actually change Leo. Obviously, some study is needed. The place to start your
study is LeoPy.leo. In particular, study very carefully the section called
"Overview of code". Leo is a highly modular program, and Python is ideally
suited to this style of programming.
#@nonl
#@-node:edream.110203113231.4:Overview of plugins and hooks
#@+node:edream.110203113231.5:Intro to scripts
@nocolor

Scripting is fully documented in Leo's Users Guide, and the following should be enough to get you started:

@color

top() # The commander of the top (current) windows.
top().rootVnode() # The root vnode of the outline.
top().currentVnode() # The presently selected vnode.

@nocolor

If v is any vnode:
	
@color

v.headString() # is the headline of v.
v.bodyString() # is the body of v.
v.threadNext() # is node after v in outline order.

@nocolor

For example, this prints every headline of an outline:
	
@color

v = top().rootVnode()
while v:
	print v.headString()
	v = v.threadNext()
#@-node:edream.110203113231.5:Intro to scripts
#@+node:edream.110203113231.6:About hooks
At startup time Leo looks all files of the form mod_*.py in the plugins
directory. Leo assumes that these files are "plugins" containing Python modules
that customizes Leo's operation. Leo attempts to import each file.

Each module should register routines that are called at various times during
execution. Such times are identified by strings known as "tags". The code in in
one or more plugins corresponding to each tag are known as the "hook" routines
for that tag. Leo catches exceptions (including syntax errors) in hook
routines, so it is safe to hack away on this code.

Leo passes two argument to all hook routines: the tag and a keywords dictionary
containing additional information. For example, keywords["label"] indicates the
kind of command for "command1" and "command2" hooks.

For some hooks, returning anything other than None "overrides" Leo's default
action. Hooks have full access to all of Leo's source code. Just import the
relevant file. For example, top() returns the commander for the topmost Leo
window.

The following table summarizes the arguments passed to hooks: ( Overrides is
"yes" if returning anything other than None overrides Leo's normal command or
event processing.)

tag argument                                              keys in keywords
(hook name)  overrides       when called                  dictionary argument
---------    ---------       -----------                  -------------------
"bodyclick1"   yes      before normal click in body       c,v,event 
"bodyclick2"            after  normal click in body       c,v,event 
"bodydclick1"  yes      before double click in body       c,v,event 
"bodydclick2"           after  double click in body       c,v,event 
"bodykey1"     yes      before body keystrokes            c,v,ch,oldSel,undoType
"bodykey2"              after  body keystrokes            c,v,ch,oldSel,undoType
"bodyrclick1"  yes      before right click in body        c,v,event 
"bodyrclick2"           after  right click in body        c,v,event 
"boxclick1"    yes      before click in +- box            c,v,event 
"boxclick2"             after  click in +- box            c,v,event 
"command1"     yes      before each command               c,v,label (note 2)
"command2"              after  each command               c,v,label (note 2)
"drag1"        yes      before start of drag              c,v,event 
"drag2"                 after  start of drag              c,v,event 
"dragging1"    yes      before continuing to drag         c,v,event 
"dragging2"             after  continuing to drag         c,v,event 
"end1"                  start of g.app.quit()
"enddrag1"     yes      before end of drag                c,v,event 
"enddrag2"              after  end of drag                c,v,event 
"headkey1"     yes      before headline keystrokes        c,v,ch
"headkey2"              after  headline keystrokes        c,v,ch
"headclick1"   yes      before normal click in headline   c,v,event 
"headclick2"            after  normal click in headline   c,v,event 
"headrclick1"  yes      before right click in headline    c,v,event 
"headrclick2"           after  right click in headline    c,v,event 
"hypercclick1" yes      before control click in hyperlink c,v,event 
"hypercclick2"          after  control click in hyperlink c,v,event 
"hyperenter1"  yes      before entering hyperlink         c,v,event 
"hyperenter2"           after  entering hyperlink         c,v,event 
"hyperleave1"  yes      before leaving  hyperlink         c,v,event 
"hyperleave2"           after  leaving  hyperlink         c,v,event 
"iconclick1"   yes      before single click in icon box   c,v,event 
"iconclick2"            after  single click in icon box   c,v,event 
"iconrclick1"  yes      before right click in icon box    c,v,event 
"iconrclick2"           after  right click in icon box    c,v,event 
"icondclick1"  yes      before double click in icon box   c,v,event 
"icondclick2"           after  double click in icon box   c,v,event 
"idle"                  periodically (at idle time)       c,v
"menu1"        yes      before creating menus             c,v (note 3)
"menu2"        yes      before updating menus             c,v
"new"          no       during New command                old_c,new_c
"open1"        yes      before opening any file           old_c,new_c,fileName (note 4)
"open2"                 after  opening any file           old_c,new_c,fileName (note 4)
"openwith1"    yes      before Open With command          c,v,openType,arg,ext
"openwith2"             after  Open With command          c,v,openType,arg,ext
"recentfiles1" yes      before Recent Files command       c,v,fileName,closeFlag
"recentfiles2"          after  Recent Files command       c,v,fileName,closeFlag
"save1"        yes      before any Save command           c,v,fileName
"save2"                 after  any Save command           c,v,fileName
"select1"      yes      before selecting a vnode          c,new_v,old_v
"select2"               after  selecting a vnode          c,new_v,old_v
"start1"       no       after g.app.finishCreate()
"start2"                after opening first Leo window    c,v,fileName
"unselect1"    yes      before unselecting a vnode        c,new_v,old_v 
"unselect2"             after  unselecting a vnode        c,old_v,old_v 
"@url1"        yes      before double-click @url node     c,v (note 5)
"@url2"                 after  double-click @url node     c,v (note 5)

The following hooks are a new breed of hook, called "stub hooks". Conceptually,
these hooks are like calls to stub routines that can be filled in by code in
plugins.

tag argument                                                   keys in keywords
(hook name)             overrides    when called               dictionary argument
---------               ---------    -----------               -------------------

"create-optional-menus"  no          (note 8)                  (note 8)

"draw-outine-box"        yes         start of drawBox          tree,p,v,x,y (note 6)
"draw-outline-icon"      yes         start of tree.drawIcon    tree,p,v,x,y (note 6)
"draw-outline-node"      yes         start of tree.drawNode    tree,p,v,x,y (note 6)
"draw-outline-text-box"  yes         start of tree.drawText    tree,p,v,x,y (note 6)
"draw-sub-outline"       yes         start of tree.drawTree    tree,p,v,x,y,h,level,hoistFlag(note 6)
"redraw-entire-outline"  yes         start of tree.redraw      c (note 6)

"color-optional-markup"  yes *       (note 7)                  colorer,v (note 7)
"init-color-markup"      no          (note 7)                  colorer,v,s,i,j,colortag (note 7)

"new"                    no          start of New command      old_c,new_c (note 9)

"scan-directives"        no          in scanDirectives         c,v,s,old_dict,dict,pluginsList (note 10)

Notes:

(1) "activate" and "deactivate" hooks: These have been removed because they do
not work as expected.

(2) "commands" hooks: The label entry in the keywords dict contains the
"canonicalized" form of the command, that is, the lowercase name of the command
with all non-alphabetic characters removed.

Commands hooks now set the label for undo and redo commands "undo" and "redo"
rather than "cantundo" and "cantredo".

(3) "menu1" hook: Setting g.app().realMenuNameDict in this hook is an easy way of
translating menu names to other languages. Note: the "new" names created this
way affect only the actual spelling of the menu items, they do _not_ affect how
you specify shortcuts in leoConfig.txt, nor do they affect the "official"
command names passed in g.app().commandName. For example, suppose you set
g.app().realMenuNameDict["Open..."] = "Ouvre".

(4) "open1" and "open2" hooks: These are called with a keywords dict containing
the following entries:

old_c: The commander of the previously open window.
new_c: The commander of the newly opened window.
fileName: The name of the file being opened.

You can use old_c.currentVnode() and new_c.currentVnode() to get the current
vnode in the old and new windows.

Leo calls the "open1" and "open2" hooks only if the file is already open.
Leo will also call the "open1" and "open2" hooks if:
a) a file is opened using the Recent Files menu and
b) the file is not already open.

(5) "@url1" and "@url2" hooks are only executed if "icondclick1" hook returns
None.

(6) These stub hooks allow plugins to revise or completely replace how Leo
draws outlines. For example, you could change tree.drawIcon to add additional
icons. These stub hooks are really methods of the leoTree class, with the
"tree" keyword corresponding to the "self" parameter. These stub hooks are
called at the beginning of the indicated method. See the method themselves for
a description of the paramters.

(7) These stub hooks allow plugins to parse and handle markup withing doc
parts, comments and Python """ strings. Note that these hooks are _not_ called
in Python ''' strings. See the color_markup plugin for a complete example of
how to use these hooks.

(8) The "create-optional-menus" stub hook is called when Leo is creating menus,
at the place at which creating new menus is appropriate. Therefore, this hook
need only create new menus in the correct order, without worrying about the
placement of the menus in the menu bar. See the plugins_menu and scripts_menu
plugins for examples of how to use this hook. Leo now executes hooks in
alphabetical order, so that the Plugins menu will be created before the Scripts
menu.

(9) The "new" stub hook is called at the beginning of the code that implements
the New command. This provides a much easier way of recognizing when Leo is
about to create a new Leo window.

(10) The "scan-directives" stub hooks is called inside the scanDirectives
global function. The dictionary returned by scanDirectives now contains an item
whose key is "pluginsList". The value of this item is a list of tuples
(d,v,s,k) where:

- d is the spelling of the @directive, without the leading @.
- v is the vnode containing the directive, _not_ the original vnode.
- s[k:] is a string containing whatever follows the @directive. k has already
been moved past any whitespace that follows the @directive.

See the add_directives plugins directive for a complete example of how to use
the "scan-directives" stub hook.
#@nonl
#@-node:edream.110203113231.6:About hooks
#@+node:edream.110203113231.7:Hooks should never blindly Python scripts
Naively using hooks can expose you and your .leo files to malicious attacks.

** Hooks should never blindly execute Python scripts in .leo files.

It is safe to import and execute code from Leo itself, provided that you got Leo from Leo's SourceForge site.
#@nonl
#@-node:edream.110203113231.7:Hooks should never blindly Python scripts
#@+node:edream.110203113231.8:NEVER use this kind of code in a hook!!
@color
@ WARNING     ** Using the following routine exposes you malicious code in .leo files!     **

Do not EVER use code that blindly executes code in .leo files!
Someone could send you malicious code embedded in the .leo file.

WARNING 1: Changing "@onloadpythonscript" to something else will NOT protect
           you if you EVER share either your files with anyone else.

WARNING 2: Replacing exec by rexec below provides NO additional protection!
           A malicious rexec script could trash your .leo file in subtle ways.
@c
if 0: # WRONG: This blindly execute scripts found in an .leo file! NEVER DO THIS!
	def onLoadFile():
		v = top().rootVnode()
		while v:
			h = v.headString().lower()
			if match_word(h,0,"@onloadpythonscript"):
				s = v.bodyString()
				if s and len(s) > 0:
					try: # SECURITY BREACH: s may be malicious!
						exec s + '\n' in {}
					except:
						es_exception()
			v = v.threadNext()
#@nonl
#@-node:edream.110203113231.8:NEVER use this kind of code in a hook!!
#@+node:ekr.20040401054523:New coding conventions for plugins
@nocolor

Leo 4.2 uses new coding conventions internally.  I recommend using the new coding conventions in all plugins, although the old way will work inside LeoPlugins.leo.

The new way replaces:
	
from leoPlugins import *
from leoGlobals import *

with:

import leoPlugins
import leoGlobals as g

This removes a major source of namespace pollution and it makes clear where all functions are defined.  To access functions in leoGlobals.py you must preceed the names of such functions with g. N.B. script in the node called

"Script to find and replace all functions in leoGlobals.py"

does this for you. This script make replacements only in a named, top-level subtree.  To change the name of the subtree change the name of the argument in the statement:

p = g.findTopLevelNode("New or improved in 4.2")

Besides changing the imports as shown above, you should do the following:

1. Change app or app() to g.app

2. Change registerHandler(...) to leoPlugins.registerHandler(...)

3. The new standard for importing and using Tkinter is as follows:

try: import Tkinter as Tk
except ImportError: Tk = None

if Tk:
	if g.app.gui is None:
		g.app.createTkGui(__file__)
	
	if g.app.gui.guiName() == "tkinter":
		leoPlugins.registerHandler(...)
		g.plugin_signon(__name__)

This means that the code would refer to Tk.widget instead of Tkinter.widget.  Also, please avoid the use of the completely useless Tk constants such as Tk.RIGHT, Tk.TOP.  Use "right" or "top" instead.

Typical calls to functions in leoGlobals.py include the following:

g.plugin_signon(__name__)
g.es(...) # etc.

Please use @file trees unless you _must_ use an @root tree for some reason.
Please put each class, method and function in a separate node.
#@nonl
#@-node:ekr.20040401054523:New coding conventions for plugins
#@-node:edream.110203113231.3:Documentation and security warnings
#@+node:EKR.20040517074402:Diary
#@+node:ekr.20040119102021:2004
#@+node:EKR.20040602113117:June 2, 2004: registering an "idle" event now automatically calls g.enableIdleTimeHook
#@-node:EKR.20040602113117:June 2, 2004: registering an "idle" event now automatically calls g.enableIdleTimeHook
#@+node:ekr.20040325075338:March 2004
#@+node:ekr.20040325075338.1:3/25: revised for 4.2 code base
- Changed g.app() to g.app.
- Changed scanDirectives(c,v=whatever) to scanDirectives(c,p=whatever)

This is a reversion somehow.  I need to rerun the script that converts to the "import leoGlobals as g"  code style.
#@nonl
#@-node:ekr.20040325075338.1:3/25: revised for 4.2 code base
#@-node:ekr.20040325075338:March 2004
#@+node:ekr.20040201061422:February 2004
#@+node:ekr.20040226104623:2/26 Inserted @language python in all plugins
- Except @file-nosent.
#@-node:ekr.20040226104623:2/26 Inserted @language python in all plugins
#@+node:ekr.20040201061422.1:2/1
#@+node:ekr.20040201061422.2:Created status_line.py plugin
This creates a status area at the bottom of the Leo Window.  Plugins may write to this area using the status area convenience routines in leoTkinterFrame.py.
#@nonl
#@-node:ekr.20040201061422.2:Created status_line.py plugin
#@-node:ekr.20040201061422.1:2/1
#@-node:ekr.20040201061422:February 2004
#@+node:ekr.20040130165735:1/30 Fixed crashers in open_shell.py
#@-node:ekr.20040130165735:1/30 Fixed crashers in open_shell.py
#@+node:ekr.20040129151121:1/29 Activate xemacs plugin only on double-clicks
#@-node:ekr.20040129151121:1/29 Activate xemacs plugin only on double-clicks
#@+node:ekr.20040119102021.1:1/19 Added http plugin
#@-node:ekr.20040119102021.1:1/19 Added http plugin
#@-node:ekr.20040119102021:2004
#@+node:ekr.20040119102021.2:2003
#@+node:edream.031217094146:12/17
#@+node:edream.031216103123:Fix problems in plugins
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2336153
By: dsalomoni

- redefine_put.py:

Traceback (most recent call last):
  File "/user/davides/Software/leo-4.1-rc1/src/leoGlobals.py", line 1977, in
doHook
    return g.app.hookFunction(tag,keywords)
  File "/user/davides/Software/leo-4.1-rc1/src/leoPlugins.py", line 27,
in doPlugins
    return doHandlersForTag(tag,keywords)
  File "/user/davides/Software/leo-4.1-rc1/src/leoPlugins.py", line 94,
in doHandlersForTag
    ret = handle_fn(tag,keywords)
  File "/user/davides/Software/leo-4.1-rc1/plugins/redefine_put.py", line 22,
in onStart
    funcToMethod(newPut,leoFrame.LeoFrame,"put")
AttributeError: 'module' object has no attribute 'LeoFrame'

** Changed LeoFrame to leoTkinterLog

- trace_gc.py:

Traceback (most recent call last):
  File "/user/davides/Software/leo-4.1-rc1/src/leoGlobals.py", line 1977, in
doHook
    return g.app.hookFunction(tag,keywords)
  File "/user/davides/Software/leo-4.1-rc1/src/leoPlugins.py", line 27,
in doPlugins
    return doHandlersForTag(tag,keywords)
  File "/user/davides/Software/leo-4.1-rc1/src/leoPlugins.py", line 94,
in doHandlersForTag
    ret = handle_fn(tag,keywords)
  File "/user/davides/Software/leo-4.1-rc1/plugins/trace_gc.py", line 17, in
printIdleGC
    global count ; count += 1
NameError: global name 'count' is not defined

** Used gcCount (in plugin) rather than count in leoGlobals.py.
#@nonl
#@-node:edream.031216103123:Fix problems in plugins
#@-node:edream.031217094146:12/17
#@+node:edream.120603100522:12/6/03 Removed compile-time enable/disable code
leoPlugins.py now enables plugins using pluginsManager.txt.
#@nonl
#@-node:edream.120603100522:12/6/03 Removed compile-time enable/disable code
#@+node:edream.112103192146:11/21
#@+node:edream.112303075625:Added wxVersion to signon
#@-node:edream.112303075625:Added wxVersion to signon
#@+node:edream.112103192146.1:Fixed and test vim plugin
#@-node:edream.112103192146.1:Fixed and test vim plugin
#@-node:edream.112103192146:11/21
#@+node:edream.111903190444:11/19
#@+node:edream.111903190444.1:Fixed reorg bugs in plugin_menu and spelli checking plugins
#@-node:edream.111903190444.1:Fixed reorg bugs in plugin_menu and spelli checking plugins
#@+node:edream.111903105454:Moved all "protected" @others inside "if tkinter" statement
#@-node:edream.111903105454:Moved all "protected" @others inside "if tkinter" statement
#@-node:edream.111903190444:11/19
#@+node:edream.111803100856:11/18 Added new version of rst plugin from Timo Honkasalo
#@-node:edream.111803100856:11/18 Added new version of rst plugin from Timo Honkasalo
#@+node:edream.111603130528:11/17
A number of changes made to accomodate the new reorg...
#@nonl
#@-node:edream.111603130528:11/17
#@+node:edream.110703035301:11/7
Corrected crashes in the following plugins due to g.app.gui reorg:

- script_io_to_body.py 
- mod_spelling.py

Changed several other plugins so they load only if g.app.gui.guiName == "tkinter".

Converted all .ini files to @silentfile
#@nonl
#@-node:edream.110703035301:11/7
#@+node:edream.110203113231.1:10/27/02
Updated color_markup.py to use c.body.bodyCtrl.
#@nonl
#@-node:edream.110203113231.1:10/27/02
#@+node:edream.110203113231.2:10/26/03
- Made open_with.py gui-independent by calling g.app.gui dialog routines.

- Updated nav_buttons.py to use new tkinter class:
	from leoTkinterDialog import tkinterListBoxDialog
#@nonl
#@-node:edream.110203113231.2:10/26/03
#@-node:ekr.20040119102021.2:2003
#@-node:EKR.20040517074402:Diary
#@+node:edream.110203113231.9:Unfinished projects
#@+node:edream.110203113231.10:(Settings menu)
#@+node:edream.110203113231.11:To do
@nocolor

Allow different default types for each section.
Create _all_ menus from settings ?

[plugins]

pluginName = 1/0 # enables or disables plugin

# option = type (overrides defaults)

	[types]

__default_type = bool
page_width = int
tab_width = int
default_tangle_directory = string

	use type name in option?
	
	_color: color
	
	_font: font
	_key: keystroke
	_flag: bool
	everything else: bool
	
	bool: checkmark
	color: picker (or use dialog)

[menus]
child = parent
#@nonl
#@-node:edream.110203113231.11:To do
#@+node:edream.110203113231.12:Design of Settings menu
#@-node:edream.110203113231.12:Design of Settings menu
#@+node:edream.110203113231.13:settings_menu.py
"""Create a settings menu to replace LeoConfig.leo"""

import leoGlobals as g
import leoPlugins

try: import Tkinter as Tk
except ImportError: Tk = None

import leoApp,leoAtFile,leoTkinterDialog,leoFileCommands,leoFrame,leoNodes

if Tk and 0: # Register the handlers...

	settingsMenu = None

@others
	
	# leoPlugins.registerHandler("start1",onAfterFinishCreate)
	leoPlugins.registerHandler("create-optional-menus",createSettingsMenu)

	__version__ = "0.1"
	g.plugin_signon(__name__)
#@nonl
#@+node:edream.110203113231.14:createSettingsMenu
def createSettingsMenu (tag,keywords):

	c = keywords.get("c")
	
	global settingsMenu
	settingsMenu = c.frame.createNewMenu("&Settings")
#@nonl
#@-node:edream.110203113231.14:createSettingsMenu
#@-node:edream.110203113231.13:settings_menu.py
#@-node:edream.110203113231.10:(Settings menu)
#@+node:edream.112003032318:About html plugin
@nocolor
http://sourceforge.net/forum/message.php?msg_id=2295621
By: billp9619

As I was explaining my ideas about opml it occurred to me that it uses DIV tags
and a Javascript as the output of the stylesheet.

But then that is what ALL the HTML collapsing outlines use! So the html behavior
is nothing special to opml...it is a behavior of scripted DIV tags in html.

I found a style sheet named outline_leo,xsl on my computer. This is really tiny
and works pretty good. Don't know where I got it from though.
 
Anyway, as I expected the insertion of a html FORM  and a  TEXTAREA box into
one of these DIV tags would collapse out of sight then reappear when clicked
just like any other node.. Actually this is one of those odd things that maybe
noone ever thought of trying,,,,I've never seen it in one of these html outlines
before.

Interestingly, the familiar collapsing xml display in Internet Explorer is really
just a xsl stylesheet and  produces DIV tags and a script like all these other
html outlines. Somewhere I found a version with minor revisions to make it work
with standard xpath.

There is a program called xpathvisualizer that runs in IE and gives basically
the same xml display but highlights an inputted xpath expression. With this
you can view source and see the script and DIV tags.

At the end of the day the only benefit to opml is that others are using
it (standard?) and perhaps  there is something especially good about its Javascript
implementation.

BTW, to load leo into internet explorer just save a copy with an .xml file extension.
Then drag it to IE.
And of course the file  could have an embedded xml-stylesheet instruction to
use leo_outline.xsl which just shows headlines, etc,

Also BTW... I have been using Python for some time now but I don't know if i
can handle plug-in development at this stage.  :-)

(But I might be up to refining the html/script environment further. )
#@nonl
#@-node:edream.112003032318:About html plugin
#@+node:ekr.20040331072431:Custom @file plugin (prototype)
#@+node:ekr.20040331072431.1:Documentation
@nocolor
#@nonl
#@+node:ekr.20040331072431.2:Summary of the problem
Leo's @file concept is very powerful but there are numerous occasions when the user wants to read and write outlines in specialized formats which cannot be handled using the @comment, @first etc directives. Examples would be,

- exporting to restructured text (where additional markup steps are required
- exporting to MS Word
- custom file formats/XML which require restructuring before input/output

Currently there exist two approaches to get around this,

1. Use tangle_done (or other script) to post-process the file - not very efficient since all knowledge of the structure of the outline has to be reinvented by the tangle_done script

2. Use a plugin (eg rst or mod_wordexport) - more efficient but still not ideal since each plugin writer ends up spinning his/her own node traversal scheme and strategy for handling directives, <<headers>> etc


#@-node:ekr.20040331072431.2:Summary of the problem
#@+node:ekr.20040331072431.3:Proposed solution
The solution proposed here is to allow handlers to register themselves to hook into the process of deriving/underiving a file. Handlers would be dispatched on a file type basis (determined by the file extension) and would be able to take over all or part of the (un)derivation process. 

The tricky part of the proposal is to design the hooks to allow the user enough control to completely redefine the format of the derived file whilst not requiring them to rewrite the hard parts of the @file code.

Based on the proof-of-concept example presented here, the following hooks may be sufficient for writing,

1. start (begining to derive a file)
2. body (complete handling of the body)
3. sentinel (handle a sentinel)
4. line (handle a line or part of a line)
5. startnode (when a new node is being processed)
6. endnode (the node and all its children have been processed)
7. end (ready to write the file)


At this time it is not clear whether hooking the read code is actually the best way to go - it may be that the handler would have to override the entire read process.
#@nonl
#@-node:ekr.20040331072431.3:Proposed solution
#@+node:ekr.20040331072431.4:Proof-of-concept
A proof-of-concept has been built to try out the feasibility on the writing side. The example chosen is to write a FreeMind file. FreeMind (freemind.sourceforge.net) is an open source mind-mapping program which fits very well with Leo as an outlining system. A two-way link between the two would offer interesting possibilities.

The FreeMind file is an XML file which is very similar to Leo's. It would be rather easy to write a converter but it serves as a good starting point.
#@nonl
#@+node:ekr.20040331072431.5:Base Implementation
The code is implemented as a plugin which subclasses leoAtFile.baseNewDerivedFile and replaces it. The new class implements a dispatching mechanism similar to the plugin system itself, allowing handlers to register themselves to take over various stages of the derivation process.

Most of the heavy lifting is done in the _doDispatch method which checks to see if a handler is available for the current action for the current file type.

Many of the method of baseNewDerivedFile are override to pass through _doDispatch. In addition, it was necessary to wrap some methods in pre and post calls to allow, for example, the startnode and endnode functions.

One example of this is putAtOthersChild.


A slightly more ugly wrapping of the 'write' method was required since this method performs many actions which we would like to control. In this case these methods had to be switched out with dummy calls so that the handler could call them when it required.

#@+node:ekr.20040331072431.6:_doDispatch
<< class dispatchedDerivedFile methods >>=

def _doDispatch(self, ontype, default, *args, **kw):
	"""Do a dispatch"""
	base, ext = os.path.splitext(self.derivedname)
	print "Putting %s, dispatching on '%s' ('%s')" % (ontype, ext, self.derivedname)
	try:
		function = self.dispatch[ontype][ext[1:]] # [1:] to remove the "." from the extension
	except KeyError:
		function = default
	if function:
		return function(self, *args, **kw)
#@-node:ekr.20040331072431.6:_doDispatch
#@+node:ekr.20040331072431.7:putAtOthersChild
<< class dispatchedDerivedFile methods >>=

def putAtOthersChild(self, *args, **kw):
	self._handleStartNode(*args, **kw)
	leoAtFile.baseNewDerivedFile.putAtOthersChild(self, *args, **kw)
	self._handleEndNode(*args, **kw)
#@-node:ekr.20040331072431.7:putAtOthersChild
#@+node:ekr.20040331072431.8:write
<< class dispatchedDerivedFile methods >>=

def write(self, *args, **kw):
	root = args[0]
	self.derivedname = root.t.headString
	self._handleStart(*args, **kw)
	#
	# Make sure we hold the write file open
	oldclose = self.closeWriteFile
	oldreplace = self.replaceTargetFileIfDifferent
	self.closeWriteFile = lambda : None
	self.replaceTargetFileIfDifferent = lambda : None
	#
	leoAtFile.baseNewDerivedFile.write(self, *args, **kw)
	self._handleEnd(*args, **kw)
	#
	# Now we can close it
	self.closeWriteFile = oldclose
	self.closeWriteFile()
	self.replaceTargetFileIfDifferent = oldreplace
	self.replaceTargetFileIfDifferent()
#@-node:ekr.20040331072431.8:write
#@-node:ekr.20040331072431.5:Base Implementation
#@+node:ekr.20040331072431.9:Handler calls
Handler clients register themselves using the registerDispatcher call which takes three parameters

- extension (the file extension to register for)
- ontype (the handler event to hook, see declarations)
- handler (the function to recieve the call)


In addition, a convenience blank handler (ie ignore the event) can be inserted by calling the registerNullDispatcher for a particular extension and ontype.
#@nonl
#@+node:ekr.20040331072431.10:<< class dispatchedDerivedFile declarations >>
@code

"""New type of derived file which can dispatch on the file type"""

dispatch = {"startnode" : {},
			"endnode" : {},
			"body" : {},  # Only dispatch 'body' if you know what you are doing!
			"code" : {},
			"sentinel" : {},
			"start" : {},
			"end" : {},
			}

#@-node:ekr.20040331072431.10:<< class dispatchedDerivedFile declarations >>
#@+node:ekr.20040331072431.11:registerDispatcher
<< atfile_dispatch methods >>=

leoAtFile.newDerivedFile = dispatchedDerivedFile

def registerDispatcher(extension, ontype, handler):
	"""Register a handler for an file extension"""
	dispatchedDerivedFile.dispatch[ontype][extension] = handler
#@-node:ekr.20040331072431.11:registerDispatcher
#@+node:ekr.20040331072431.12:registerNullDispatcher
<< atfile_dispatch methods >>=

def registerNullDispatcher(extension, ontype):
	"""Register that a handler shouldn't be called"""
	dispatchedDerivedFile.dispatch[ontype][extension] = None
#@-node:ekr.20040331072431.12:registerNullDispatcher
#@-node:ekr.20040331072431.9:Handler calls
#@+node:ekr.20040331072431.13:FreeMind Implementation
The FreeMind example shows that the plugin begins by registering its dispatchers. It is then called during the normal process of deriving a file (with a ".mm" extension).

It hooks nearly all the events with the exception of body (see the child of this node for a discussion of why not!). The handler functions are rather simple although I took a slightly more complex route than is required in order to exercise all the events.

The implementation builds a separate node structure to represent the tree and then writes this out when the process is complete.
#@nonl
#@+node:ekr.20040331072431.14:Hooking the body call
This hooks the putBody method call. It is tough to override this as this basically means the handler must do all the hard work of traversing the tree and remembering to call v.setVisited. Finer control of this process is probably a good idea since getting someone to override this properly is quite tricky.
#@nonl
#@-node:ekr.20040331072431.14:Hooking the body call
#@-node:ekr.20040331072431.13:FreeMind Implementation
#@-node:ekr.20040331072431.4:Proof-of-concept
#@+node:ekr.20040331072431.15:Conclusions from the proof-of-concept
1. It works for writing the derived file.

2. The handler code is pretty clean and doesn't require the handler writer to know about the internals of the @file writing code.

3. Overriding "putBody" is probably a bit too tough to expect people to do.

4. It isn't clear that the same approach will work for reading derived files. This might have to be delegated completely to the handler.

#@-node:ekr.20040331072431.15:Conclusions from the proof-of-concept
#@+node:ekr.20040331072431.16:Next steps
I want to try out something on the read side (mainly because I want to see if I can get a two way link with FreeMind!). This would complete the proof-of-concept and achieve the aim, which was to demonstrate that Leo could incorporate custom handlers for derived files without any structural changes.

Following that a more complete implementation could be put together if this is thought to be a valuable way to go.
#@nonl
#@-node:ekr.20040331072431.16:Next steps
#@-node:ekr.20040331072431.1:Documentation
#@+node:ekr.20040331072431.17:Code
#@+node:ekr.20040331072431.18:atfile_dispatch.py
@ignore
@root atfile_dispatch.py
@language python
<< atfile_dispatch declarations >>
<< atfile_dispatch methods >>

	
if 1: # Register the handlers...
	g.plugin_signon(__name__)

#@+node:ekr.20040331072431.19:<< atfile_dispatch declarations >>
@code

"""Enable dispatching of @file writing to custom handlers"""

__version__ = "0.1"

import leoGlobals as g
import leoPlugins

import leoAtFile
import os
#@nonl
#@-node:ekr.20040331072431.19:<< atfile_dispatch declarations >>
#@+node:ekr.20040331072431.20:class dispatchedDerivedFile
<< atfile_dispatch methods >>=

class dispatchedDerivedFile(leoAtFile.baseNewDerivedFile):
	<< class dispatchedDerivedFile declarations >>
	<< class dispatchedDerivedFile methods >>

#@+node:ekr.20040331072431.10:<< class dispatchedDerivedFile declarations >>
@code

"""New type of derived file which can dispatch on the file type"""

dispatch = {"startnode" : {},
			"endnode" : {},
			"body" : {},  # Only dispatch 'body' if you know what you are doing!
			"code" : {},
			"sentinel" : {},
			"start" : {},
			"end" : {},
			}

#@-node:ekr.20040331072431.10:<< class dispatchedDerivedFile declarations >>
#@+node:ekr.20040331072431.6:_doDispatch
<< class dispatchedDerivedFile methods >>=

def _doDispatch(self, ontype, default, *args, **kw):
	"""Do a dispatch"""
	base, ext = os.path.splitext(self.derivedname)
	print "Putting %s, dispatching on '%s' ('%s')" % (ontype, ext, self.derivedname)
	try:
		function = self.dispatch[ontype][ext[1:]] # [1:] to remove the "." from the extension
	except KeyError:
		function = default
	if function:
		return function(self, *args, **kw)
#@-node:ekr.20040331072431.6:_doDispatch
#@+node:ekr.20040331072431.21:putBody
<< class dispatchedDerivedFile methods >>=

def putBody(self, *args, **kw):
	"""Put the body text"""
	return self._doDispatch("body", leoAtFile.baseNewDerivedFile.putBody, *args, **kw)
#@-node:ekr.20040331072431.21:putBody
#@+node:ekr.20040331072431.22:putCodeLine
<< class dispatchedDerivedFile methods >>=

def putCodeLine(self, *args, **kw):
	"""Put the code line"""
	return self._doDispatch("code", leoAtFile.baseNewDerivedFile.putCodeLine, *args, **kw)
#@-node:ekr.20040331072431.22:putCodeLine
#@+node:ekr.20040331072431.23:putSentinel
<< class dispatchedDerivedFile methods >>=

def putSentinel(self, *args, **kw):
	"""Put the sentinel line"""
	return self._doDispatch("sentinel", leoAtFile.baseNewDerivedFile.putSentinel, *args, **kw)
#@-node:ekr.20040331072431.23:putSentinel
#@+node:ekr.20040331072431.24:_handleStartNode
<< class dispatchedDerivedFile methods >>=

def _handleStartNode(self, *args, **kw):
	return self._doDispatch("startnode", None, *args, **kw)
#@-node:ekr.20040331072431.24:_handleStartNode
#@+node:ekr.20040331072431.25:_handleEndNode
<< class dispatchedDerivedFile methods >>=

def _handleEndNode(self, *args, **kw):
	return self._doDispatch("endnode", None, *args, **kw)
#@-node:ekr.20040331072431.25:_handleEndNode
#@+node:ekr.20040331072431.7:putAtOthersChild
<< class dispatchedDerivedFile methods >>=

def putAtOthersChild(self, *args, **kw):
	self._handleStartNode(*args, **kw)
	leoAtFile.baseNewDerivedFile.putAtOthersChild(self, *args, **kw)
	self._handleEndNode(*args, **kw)
#@-node:ekr.20040331072431.7:putAtOthersChild
#@+node:ekr.20040331072431.26:_handleStart
<< class dispatchedDerivedFile methods >>=

def _handleStart(self, *args, **kw):
	return self._doDispatch("start", None, *args, **kw)
#@-node:ekr.20040331072431.26:_handleStart
#@+node:ekr.20040331072431.27:_handleEnd
<< class dispatchedDerivedFile methods >>=

def _handleEnd(self, *args, **kw):
	return self._doDispatch("end", None, *args, **kw)
#@-node:ekr.20040331072431.27:_handleEnd
#@+node:ekr.20040331072431.8:write
<< class dispatchedDerivedFile methods >>=

def write(self, *args, **kw):
	root = args[0]
	self.derivedname = root.t.headString
	self._handleStart(*args, **kw)
	#
	# Make sure we hold the write file open
	oldclose = self.closeWriteFile
	oldreplace = self.replaceTargetFileIfDifferent
	self.closeWriteFile = lambda : None
	self.replaceTargetFileIfDifferent = lambda : None
	#
	leoAtFile.baseNewDerivedFile.write(self, *args, **kw)
	self._handleEnd(*args, **kw)
	#
	# Now we can close it
	self.closeWriteFile = oldclose
	self.closeWriteFile()
	self.replaceTargetFileIfDifferent = oldreplace
	self.replaceTargetFileIfDifferent()
#@-node:ekr.20040331072431.8:write
#@-node:ekr.20040331072431.20:class dispatchedDerivedFile
#@+node:ekr.20040331072431.11:registerDispatcher
<< atfile_dispatch methods >>=

leoAtFile.newDerivedFile = dispatchedDerivedFile

def registerDispatcher(extension, ontype, handler):
	"""Register a handler for an file extension"""
	dispatchedDerivedFile.dispatch[ontype][extension] = handler
#@-node:ekr.20040331072431.11:registerDispatcher
#@+node:ekr.20040331072431.12:registerNullDispatcher
<< atfile_dispatch methods >>=

def registerNullDispatcher(extension, ontype):
	"""Register that a handler shouldn't be called"""
	dispatchedDerivedFile.dispatch[ontype][extension] = None
#@-node:ekr.20040331072431.12:registerNullDispatcher
#@-node:ekr.20040331072431.18:atfile_dispatch.py
#@+node:ekr.20040331072431.28:freemind_export.py
@ignore
@root freemind_export.py
@language python
<< freemind_export declarations >>
<< freemind_export methods >>

		
if 1: # Register the handlers...
	g.plugin_signon(__name__)
	import atfile_dispatch
	# Register dispatch functions for writing
	atfile_dispatch.registerDispatcher("mm", "code", handleMindMapLine)
	atfile_dispatch.registerDispatcher("mm", "startnode", handleMindMapStartNode)
	atfile_dispatch.registerDispatcher("mm", "endnode", handleMindMapEndNode)
	atfile_dispatch.registerNullDispatcher("mm", "sentinel")
	atfile_dispatch.registerDispatcher("mm", "start", handleStart)
	atfile_dispatch.registerDispatcher("mm", "end", handleEnd)
	
#@+node:ekr.20040331072431.29:<< freemind_export declarations >>
@code

"""Export part of a Leo outline to FreeMind"""

__version__ = "0.1"

import leoGlobals as g
import leoPlugins
#@-node:ekr.20040331072431.29:<< freemind_export declarations >>
#@+node:ekr.20040331072431.30:class MindMapNode
<< freemind_export methods >>=

class MindMapNode(list):
	<< class MindMapNode declarations >>
	<< class MindMapNode methods >>

#@+node:ekr.20040331072431.31:<< class MindMapNode declarations >>
@code

"""Class representing a mind map node"""

#@-node:ekr.20040331072431.31:<< class MindMapNode declarations >>
#@+node:ekr.20040331072431.32:__init__
<< class MindMapNode methods >>=

def __init__(self, header, parent, body=""):
	"""Initialize"""
	self.header = header
	self.parent = parent
	self.body = body
#@-node:ekr.20040331072431.32:__init__
#@+node:ekr.20040331072431.33:render
<< class MindMapNode methods >>=

def render(self, at):
	"""Render to the file"""
	if not self.body:
		txt = self.header
	else:
		txt = "%s\n%s" % (self.header, self.body)
		txt = txt.replace("\n", "e&#xa;")
	at.os('<node text="%s">\n' % (txt,))
	for child in self:
		child.render(at)
	at.os('</node>\n')
#@-node:ekr.20040331072431.33:render
#@-node:ekr.20040331072431.30:class MindMapNode
#@+node:ekr.20040331072431.34:handleStart
<< freemind_export methods >>=

def handleStart(self, *args, **kw):
	"""Start of writing process"""
	print "Handling start"
	global nodes, current_node, first_node
	nodes = {} # Dictionary of nodes
	root = args[0]
	nodes[root] = current_node = MindMapNode(root.t.headString, None)
	first_node = current_node
	print "End"
#@-node:ekr.20040331072431.34:handleStart
#@+node:ekr.20040331072431.35:handleMindMapLine
<< freemind_export methods >>=

def handleMindMapLine(self, s, i):
	"""Handling body for mind mapper"""
	print "Handling body '%s'" % s
	current_node.body += s
	print "End"
#@-node:ekr.20040331072431.35:handleMindMapLine
#@+node:ekr.20040331072431.36:handleMindMapStartNode
<< freemind_export methods >>=

def handleMindMapStartNode(self, v):
	"""Handle the start header"""
	global current_node
	print "Handling start header '%s'" % (v, )
	this = MindMapNode(v.t.headString, current_node)
	nodes[v] = this
	current_node.append(this)
	current_node = this
	print "End"
#@-node:ekr.20040331072431.36:handleMindMapStartNode
#@+node:ekr.20040331072431.37:handleMindMapEndNode
<< freemind_export methods >>=

def handleMindMapEndNode(self, v):
	"""Handle the end header"""
	global current_node
	print "Handling end header '%s'" % (v, )
	current_node = current_node.parent
	print "End"
#@-node:ekr.20040331072431.37:handleMindMapEndNode
#@+node:ekr.20040331072431.38:handleEnd
<< freemind_export methods >>=

def handleEnd(self, *args, **kw):
	"""Handle the end"""
	print "Handling end"
	self.os("<map>\n")	
	first_node.render(self)
	self.os("</map>\n")
	print "End"	
#@-node:ekr.20040331072431.38:handleEnd
#@-node:ekr.20040331072431.28:freemind_export.py
#@-node:ekr.20040331072431.17:Code
#@+node:ekr.20040331072431.39:Test files
@ignore
#@nonl
#@+node:ekr.20040331072431.40:@file c:\temp\mind.mm
The main node
@others
#@+node:ekr.20040331072431.41:a
aaaa
#@nonl
#@-node:ekr.20040331072431.41:a
#@+node:ekr.20040331072431.42:b
bbb
#@nonl
#@-node:ekr.20040331072431.42:b
#@+node:ekr.20040331072431.43:c
ccc
@others
#@nonl
#@+node:ekr.20040331072431.44:c1
c1c1

@others
#@nonl
#@+node:ekr.20040331072431.45:c1a
c1ac1a
#@nonl
#@-node:ekr.20040331072431.45:c1a
#@-node:ekr.20040331072431.44:c1
#@+node:ekr.20040331072431.46:c2
c2c2
#@nonl
#@-node:ekr.20040331072431.46:c2
#@-node:ekr.20040331072431.43:c
#@+node:ekr.20040331072431.47:d
ddd
#@nonl
#@-node:ekr.20040331072431.47:d
#@-node:ekr.20040331072431.40:@file c:\temp\mind.mm
#@+node:ekr.20040331072431.48:@file c:\temp\mind2.txt
@others
#@nonl
#@+node:ekr.20040331072431.49:a
aaaa
#@nonl
#@-node:ekr.20040331072431.49:a
#@+node:ekr.20040331072431.50:b
bbb
#@nonl
#@-node:ekr.20040331072431.50:b
#@+node:ekr.20040331072431.51:c
ccc
#@nonl
#@+node:ekr.20040331072431.52:c1
c1c1
#@nonl
#@+node:ekr.20040331072431.53:c1a
c1ac1a
#@nonl
#@-node:ekr.20040331072431.53:c1a
#@-node:ekr.20040331072431.52:c1
#@+node:ekr.20040331072431.54:c2
c2c2
#@nonl
#@-node:ekr.20040331072431.54:c2
#@-node:ekr.20040331072431.51:c
#@+node:ekr.20040331072431.55:d
ddd
#@nonl
#@-node:ekr.20040331072431.55:d
#@-node:ekr.20040331072431.48:@file c:\temp\mind2.txt
#@+node:ekr.20040331072431.56:@file c:\temp\test.txt
This is some text
second line
third

@others
#@+node:ekr.20040331072431.57:Header
inside header
#@-node:ekr.20040331072431.57:Header
#@-node:ekr.20040331072431.56:@file c:\temp\test.txt
#@-node:ekr.20040331072431.39:Test files
#@-node:ekr.20040331072431:Custom @file plugin (prototype)
#@+node:ekr.20040320072156:Word completion (prototype)
#@+node:ekr.20040320075528:Documentation
@nocolor

This plugin implements a word completion scheme for Leo. In the body pane you
have two new options,

* F4 - complete a dotted word (eg type "self." followed F4 will cycle through
the methods and attributes of an object)

* F5 - complete a stand alone word (eg type "imp" followed by F5 will cycle
though some standard words ... import etc)

Pressing F4 with a partial g.match (eg "os.pa") will cycle through the methods
begining with "pa".

The plugin works by scanning the Leo file to look for words and objects. Hence
you will only get things that have been referencing in the Leo file already.
The scanning currently takes place at file load so any new things you add wont
get added to the list until you reload.

If there is more than one word suggestions then repeatedly pressing F4/F5 will
cycle through them. The order is from the most to the least used so the most
frequently used words should appear first. Once you reach the end it stops.
#@nonl
#@-node:ekr.20040320075528:Documentation
#@+node:ekr.20040320075528.1:Code notes
@nocolor

Why two keys? I tried the get the "dotted completion" to work when you press
the "." key but I couldn't stop the "." appearing in the body as well. The code
is all there so if someone knows the trick then you can just enable this by
adding and appropriate <bind>.

The completion is "python style", ie it doesn't try to know anything about the
source - it just looks for likely looking combinations. For other languages
(php, java etc) you could be smarter. If you feel inclined then you can create
a custom completer by subclassing the basic class used (Extender) and adding
another WordCompleter class.
#@nonl
#@-node:ekr.20040320075528.1:Code notes
#@+node:ekr.20040320121631:Changes made
@nocolor

Changes made:

EKR: 3/20/04

- Converted to @file.
	- Used @others where possible.
	- Removed unused sections.
- Coverted to "import leoGlobals as g" style.
	- Used g.trace rather than most calls to es.

- Fixed bug in split.
- Fixed bug in suggestWords

I don't understand the code yet, but already I love this plugin!
#@nonl
#@-node:ekr.20040320121631:Changes made
#@+node:ekr.20040320072156.1:@file-thin extender.py
import leoGlobals as g
import leoPlugins
import re

@others

if __name__ == "__main__":
	test()
#@nonl
#@+node:ekr.20040320072156.2:class Splitter
class Splitter(object):

	"""Split text into base/extension parts"""

@others
#@+node:ekr.20040320072156.4:__init__
def __init__(self):

	"""Initialise the Splitter instance"""
#@-node:ekr.20040320072156.4:__init__
#@+node:ekr.20040320072156.5:split
def split(self, text):

	"""Return a list of the base/extension pairs from a block of text"""

	raise NotImplementedError("Must override split method")
#@-node:ekr.20040320072156.5:split
#@-node:ekr.20040320072156.2:class Splitter
#@+node:ekr.20040320072156.6:class ReSplitter
class ReSplitter(Splitter):

@others
#@+node:ekr.20040320072156.7:__init__
def __init__(self, base, extension, *args):

	"""Initialise the ReSplitter instance"""

	self._base = re.compile(base,*args)
	self._extension = re.compile(extension,*args)
#@-node:ekr.20040320072156.7:__init__
#@+node:ekr.20040320072156.8:split
def split(self,text):

	"""Split the text"""

	# g.trace(len(text))

	results = []
	if not text: # EKR
		return results

	for base in self._base.finditer(text):
		ext = self._extension.match(text[base.end():])
		if ext:
			results.append((base.groups()[0],
							ext.groups()[0]))

	# g.trace("done")
	return results
#@-node:ekr.20040320072156.8:split
#@-node:ekr.20040320072156.6:class ReSplitter
#@+node:ekr.20040320072156.9:class Extender
class Extender(object):

	"""Learn about text strings and suggest ways to extend a string"""

@others
#@+node:ekr.20040320072156.10:__init__
def __init__(self, splitter):

	"""Initialise the Extender instance"""

	self._buffer = "" # The stream of characters to learn
	self._memory = {} # The store of all we have learnt
	self._splitter = splitter
	self.size = 0
#@-node:ekr.20040320072156.10:__init__
#@+node:ekr.20040320072156.11:feed
def feed(self, text):

	"""Learn from some text"""

	# g.trace(len(text))

	for base,extension in self._splitter.split(text):

		self.learn(base,extension)
#@-node:ekr.20040320072156.11:feed
#@+node:ekr.20040320072156.12:learn
def learn(self, stem, extender):

	"""Learn the relationship between the stem and the extender"""

	# g.trace(stem,extender)

	self._memory.setdefault(stem, {})
	self._memory[stem][extender] = self._memory[stem].get(extender, 0)+1
	self.size = len(self._memory)
#@-node:ekr.20040320072156.12:learn
#@+node:ekr.20040320072156.13:suggestions
def suggestions(self, base, start=None, number=None):

	"""Return a list of suggestions in order of most to least likely

	If specified, start indicates the initial characters of the match.

	By default we return all the possible suggestions but this can be
	limited by setting the number to return."""

	# g.trace()

	if start:
		allmatched = self.suggestions(base)
		result = [item for item in allmatched if item.startswith(start)]
	else:
		try:
			items = self._memory[base]
		except KeyError:
			return []
		else:
			values = [(value, key) for key, value in items.iteritems()]
			values.sort()
			values.reverse()
			result = [key for value, key in values]

	if number is None:
		return result
	else:
		return result[:number]
#@-node:ekr.20040320072156.13:suggestions
#@+node:ekr.20040320072156.14:suggestfrom
def suggestfrom(self,text,number=None):

	"""Find suggestions from a block of text

	This method returns the base string and a list of alternatives
	for how the base might be extended."""

	# Add some characters to the text so that the end will match
	trytext = "  " + text + "DDD"
	matches = self._splitter.split(trytext)
	
	g.trace(text,number,matches)

	# Make sure the last one is a match
	if not matches:
		return None, []

	base,ext = matches[-1]
	if not ext.endswith("DDD"):
		return text, []

	# Remove the dummy part
	ext = ext[:-3]
	if not ext:
		ext = None
		base_text = text
	else:
		base_text = text[:-len(ext)]

	suggestions = self.suggestions(base,ext,number)
	g.trace(base_text,len(suggestions))
	return base_text, suggestions
#@-node:ekr.20040320072156.14:suggestfrom
#@-node:ekr.20040320072156.9:class Extender
#@+node:ekr.20040320072156.15:class DottedExtender
class DottedExtender(Extender):

	"""An extender based around dotted names"""

@others
#@+node:ekr.20040320072156.16:__init__
def __init__(self):

	"""Initialise the DottedExtender instance"""

	super(DottedExtender, self).__init__(
		ReSplitter("(\w+?)\.", "(\w+)"))
#@-node:ekr.20040320072156.16:__init__
#@-node:ekr.20040320072156.15:class DottedExtender
#@+node:ekr.20040320072156.17:class WordExtender
class WordExtender(Extender):

	"""An extender based on simple words"""

@others
#@+node:ekr.20040320072156.18:__init__
def __init__(self):

	"""Initialise the WordExtender instance"""

	super(WordExtender, self).__init__(
		ReSplitter("^|\s()", "(\w+)"))
#@-node:ekr.20040320072156.18:__init__
#@-node:ekr.20040320072156.17:class WordExtender
#@+node:ekr.20040320072156.19:test
def test():

	import sys
	text = file(sys.argv[-1], "r").read()

	e = DottedExtender()
	e.feed(text)

	w = WordExtender()
	w.feed(text)
#@-node:ekr.20040320072156.19:test
#@-node:ekr.20040320072156.1:@file-thin extender.py
#@+node:ekr.20040320072156.20:@file-thin wordcompleter.py
"""Plugin to add word completion to the edit pane"""

import leoGlobals as g
import leoPlugins

try: import Tkinter as Tk
except ImportError: Tk = None

import extender

@others

if Tk:
	__version__ = "0.2" # Converted to @file by EKR.
	__name__ = "Word Completer"

	g.plugin_signon("wordcompleter")

	s = "Starting word completer"
	g.trace(s) ; g.es(s,color="purple")

	completer1 = DottedCompleter()
	completer2 = SimpleCompleter()

	#leoPlugins.registerHandler("start2", completer.addKeyBinding)
	leoPlugins.registerHandler("open2", completer1.createWordList)
#@nonl
#@+node:ekr.20040320072156.21:class WordCompleter
class WordCompleter:

	"""A class to help in suggesting words"""

	keybinding = None # Override this 
	extender_class = None # Override this

@others
#@nonl
#@+node:ekr.20040320072156.22:__init__
def __init__(self):

	"""Initialise the WordCompleter instance"""

	self.extender = self.extender_class()
	self.extender.feed("self.this self.that self.other")
	self.previous = [] # Previous suggestions
#@-node:ekr.20040320072156.22:__init__
#@+node:ekr.20040320072156.23:addKeyBinding
def addKeyBinding(self, tag, keywords):

	"""Add key bindings to intercept the hot key"""

	c = g.top()

	c.frame.body.bodyCtrl.bind(self.keybinding,self.suggestWords)

	c.frame.body.bodyCtrl.bind(".", self.suggestWords)
#@-node:ekr.20040320072156.23:addKeyBinding
#@+node:ekr.20040320072156.24:createWordList
def createWordList(self, tag, keywords):

	"""Initialize the list of words to use"""

	# g.trace(tag,keywords)

	v = g.top().rootVnode()
	while v:
		self.extender.feed(v.bodyString())
		v = v.threadNext()

	self.addKeyBinding(tag,keywords)
#@-node:ekr.20040320072156.24:createWordList
#@+node:ekr.20040320072156.25:suggestWords
def suggestWords(self, event):

	"""Suggest words to be inserted at the cursor"""

	# g.trace(self)

	# Find text on this line before the insertion point
	c = g.top()
	insert = c.frame.body.getInsertionPoint()
	thisline = insert.split(".")[0]
	start = "%s.0" % (thisline, )
	text = c.frame.body.getTextRange(start, insert)
	# If the user pressed '.' then this isn't in the text yet so we
	# manually add it -      This doesn't work!
	if event.char == ".":
		text += "."
		dotted = 1
	else:
		dotted = 0
	# Find suggestions
	base,suggestions = self.extender.suggestfrom(text)
	#import pdb; pdb.set_trace()

	if suggestions: # 3/30/04
		# Delete back from the current position to the start of the suggestion
		dot = "%s.%s" % (thisline,len(base))
		oldtext = c.frame.body.getTextRange(dot, insert)
		c.frame.body.deleteRange(dot, insert)
		# If the current text is the suggestion then we are cycling
		# through the possibilities
		if oldtext == suggestions[0] and self.previous:
			word = self.previous.pop(0)
		else:
			word = suggestions[0]
			self.previous = suggestions[1:]
		# Insert suggestion
		if dotted:
			word = "." + word
		c.frame.body.insertAtInsertPoint(word)

	return dotted
#@-node:ekr.20040320072156.25:suggestWords
#@-node:ekr.20040320072156.21:class WordCompleter
#@+node:ekr.20040320072156.26:class DottedCompleter
class DottedCompleter(WordCompleter):

	"""Completer to complete dotted.names"""

	keybinding = "<F4>"
	extender_class = extender.DottedExtender

	# No methods.
#@-node:ekr.20040320072156.26:class DottedCompleter
#@+node:ekr.20040320072156.27:class SimpleCompleter
class SimpleCompleter(WordCompleter):

	"""A completer which works on any word in a text stream"""

	keybinding = "<F5>"
	extender_class = extender.WordExtender

	# No methods
#@-node:ekr.20040320072156.27:class SimpleCompleter
#@-node:ekr.20040320072156.20:@file-thin wordcompleter.py
#@-node:ekr.20040320072156:Word completion (prototype)
#@+node:ekr.20040320091826:(problem with dot in Word completion plugin)
v.body
#@nonl
#@+node:ekr.20040320072156.23:addKeyBinding
def addKeyBinding(self, tag, keywords):

	"""Add key bindings to intercept the hot key"""

	c = g.top()

	c.frame.body.bodyCtrl.bind(self.keybinding,self.suggestWords)

	c.frame.body.bodyCtrl.bind(".", self.suggestWords)
#@-node:ekr.20040320072156.23:addKeyBinding
#@+node:ekr.20040320072156.14:suggestfrom
def suggestfrom(self,text,number=None):

	"""Find suggestions from a block of text

	This method returns the base string and a list of alternatives
	for how the base might be extended."""

	# Add some characters to the text so that the end will match
	trytext = "  " + text + "DDD"
	matches = self._splitter.split(trytext)
	
	g.trace(text,number,matches)

	# Make sure the last one is a match
	if not matches:
		return None, []

	base,ext = matches[-1]
	if not ext.endswith("DDD"):
		return text, []

	# Remove the dummy part
	ext = ext[:-3]
	if not ext:
		ext = None
		base_text = text
	else:
		base_text = text[:-len(ext)]

	suggestions = self.suggestions(base,ext,number)
	g.trace(base_text,len(suggestions))
	return base_text, suggestions
#@-node:ekr.20040320072156.14:suggestfrom
#@+node:ekr.20040320072156.13:suggestions
def suggestions(self, base, start=None, number=None):

	"""Return a list of suggestions in order of most to least likely

	If specified, start indicates the initial characters of the match.

	By default we return all the possible suggestions but this can be
	limited by setting the number to return."""

	# g.trace()

	if start:
		allmatched = self.suggestions(base)
		result = [item for item in allmatched if item.startswith(start)]
	else:
		try:
			items = self._memory[base]
		except KeyError:
			return []
		else:
			values = [(value, key) for key, value in items.iteritems()]
			values.sort()
			values.reverse()
			result = [key for value, key in values]

	if number is None:
		return result
	else:
		return result[:number]
#@-node:ekr.20040320072156.13:suggestions
#@-node:ekr.20040320091826:(problem with dot in Word completion plugin)
#@-node:edream.110203113231.9:Unfinished projects
#@+node:EKR.20040517075110:replaceLeoGlobals
"""Script to find and replace all functions in leoGlobals.py."""
@color
@language python
@tabwidth -4

import leoGlobals as g
import leoPlugins

import string

c = g.top() ; p = c.currentPosition()

@others

if 1:
    << set nameList to the list of functions in leoGlobals.py >>
else:
    p = g.findNodeAnywhere("@file leoGlobals.py")
    nameList = findFunctionsInTree(p)

    nameList.sort() ; g.enl()
    for name in nameList: g.es("'%s'," % name)
    
    s = "%d functions in leoGlobals.py" % len(nameList)
    print s ; g.es(s)

if 1:
    g.enl() ; g.enl()
    count = prependNamesInTree(p,nameList,"g.",replace=True) # Just prints if replace==False.
    s = "%d --- done --- " % count
    print s ; g.es(s)
#@+node:EKR.20040517075110.1:<< set nameList to the list of functions in leoGlobals.py >>
nameList = (
'alert',
'angleBrackets',
'appendToList',
'callerName',
'CheckVersion',
'choose',
'clearAllIvars',
'clear_stats',
'collectGarbage',
'computeLeadingWhitespace',
'computeWidth',
'computeWindowTitle',
'createTopologyList',
'create_temp_name',
'disableIdleTimeHook',
'doHook',
'dump',
'ecnl',
'ecnls',
'enableIdleTimeHook',
'enl',
'ensure_extension',
'es',
'esDiffTime',
'es_error',
'es_event_exception',
'es_exception',
'escaped',
'executeScript',
'file_date',
'findNodeAnywhere',
'findNodeInTree',
'findTopLevelNode',
'findReference',
'find_line_start',
'find_on_line',
'flattenList',
'funcToMethod',
'getBaseDirectory',
'getOutputNewline',
'getTime',
'get_Sherlock_args',
'get_directives_dict',
'get_leading_ws',
'get_line',
'get_line_after',
'getpreferredencoding',
'idleTimeHookHandler',
'importFromPath',
'initScriptFind',
'init_sherlock',
'init_trace',
'isUnicode',
'isValidEncoding',
'is_c_id',
'is_nl',
'is_special',
'is_ws',
'is_ws_or_nl',
'joinLines',
'listToString',
'makeAllNonExistentDirectories',
'makeDict',
'match',
'match_c_word',
'match_ignoring_case',
'match_word',
'module_date',
'openWithFileName',
'optimizeLeadingWhitespace',
'os_path_abspath',
'os_path_basename',
'os_path_dirname',
'os_path_exists',
'os_path_getmtime',
'os_path_isabs',
'os_path_isdir',
'os_path_isfile',
'os_path_join',
'os_path_norm',
'os_path_normcase',
'os_path_normpath',
'os_path_split',
'os_path_splitext',
'pause',
'plugin_date',
'plugin_signon',
'printDiffTime',
'printGc',
'printGcRefs',
'printGlobals',
'printLeoModules',
'print_bindings',
'print_stats',
'readlineForceUnixNewline',
'redirectStderr',
'redirectStdout',
'removeLeadingWhitespace',
'removeTrailingWs',
'reportBadChars',
'restoreStderr',
'restoreStdout',
'sanitize_filename',
'scanAtEncodingDirective',
'scanAtFileOptions',
'scanAtLineendingDirective',
'scanAtPagewidthDirective',
'scanAtRootOptions',
'scanAtTabwidthDirective',
'scanDirectives',
'scanError',
'scanf',
'set_delims_from_language',
'set_delims_from_string',
'set_language',
'shortFileName',
'skip_blank_lines',
'skip_block_comment',
'skip_braces',
'skip_c_id',
'skip_heredoc_string',
'skip_leading_ws',
'skip_leading_ws_with_indent',
'skip_line',
'skip_long',
'skip_matching_delims',
'skip_nl',
'skip_non_ws',
'skip_parens',
'skip_pascal_begin_end',
'skip_pascal_block_comment',
'skip_pascal_braces',
'skip_pascal_string',
'skip_php_braces',
'skip_pp_directive',
'skip_pp_if',
'skip_pp_part',
'skip_python_string',
'skip_string',
'skip_to_char',
'skip_to_end_of_line',
'skip_to_semicolon',
'skip_typedef',
'skip_ws',
'skip_ws_and_nl',
'splitLines',
'stat',
'stdErrIsRedirected',
'stdOutIsRedirected',
'toEncodedString',
'toUnicode',
'toUnicodeFileEncoding',
'top',
'trace',
'trace_tag',
'update_file_if_changed',
'utils_rename',
'windows',
'wrap_lines')
#@nonl
#@-node:EKR.20040517075110.1:<< set nameList to the list of functions in leoGlobals.py >>
#@+node:EKR.20040517075110.2:findFunctionsInTree
def findFunctionsInTree(p):
    
    nameList = []
    for p in p.self_and_subtree_iter():
        names = findDefs(p.bodyString())
        if names:
            for name in names:
                if name not in nameList:
                    nameList.append(name)
    return nameList
#@nonl
#@-node:EKR.20040517075110.2:findFunctionsInTree
#@+node:EKR.20040517075110.3:findDefs
def findDefs(body):
    
    lines = body.split('\n')
    names = []
    for s in lines:
        i = g.skip_ws(s,0)
        if g.match(s,i,"class"):
            return [] # The classes are defined in a single node.
        if g.match(s,i,"def"):
            i = g.skip_ws(s,i+3)
            j = g.skip_c_id(s,i)
            if j > i:
                name = s[i:j]
                if g.match(name,0,"__init__"): 
                    return [] # Disallow other class methods.
                names.append(name)
    return names
#@nonl
#@-node:EKR.20040517075110.3:findDefs
#@+node:EKR.20040517075110.4:prependNamesInTree
def prependNamesInTree(p,nameList,prefix,replace=False):
    
    c = p.c
    
    assert(len(prefix) > 0)
    ch1 = string.letters + '_'
    ch2 = string.letters + string.digits + '_'
    def_s = "def " ; def_n = len(def_s)
    prefix_n = len(prefix)
    total = 0
    c.beginUpdate()
    for p in p.self_and_subtree_iter():
        count = 0 ; s = p.bodyString()
        printFlag = False
        if s:
            for name in nameList:
                i = 0 ; n = len(name)
                while 1:
                    << look for name followed by '(' >>
            if count and replace:
                if 0:
                    << print before and after >>
                p.setBodyStringOrPane(s)
                p.setDirty()
        g.es("%3d %s" % (count,p.headString()))
        total += count
    c.endUpdate()
    return total
#@nonl
#@+node:EKR.20040517075110.5:<< look for name followed by '(' >>
i = s.find(name,i)
if i == -1:
    break
elif g.match(s,i-1,'.'):
    i += n # Already an attribute.
elif g.match(s,i-prefix_n,prefix):
    i += n # Already preceded by the prefix.
elif g.match(s,i-def_n,def_s):
    i += n # preceded by "def"
elif i > 0 and s[i-1] in ch1:
    i += n # Not a word match.
elif i+n < len(s) and s[i+n] in ch2:
    i += n # Not a word match.
else:
    j = i + n
    j = g.skip_ws(s,j)
    if j >= len(s) or s[j] != '(':
        i += n
    else: # Replace name by prefix+name
        s = s[:i] + prefix + name + s[i+n:]
        i += n ; count += 1
        # g.es('.',newline=False)
        if 1:
            if not printFlag:
                printFlag = True
                # print p.headString()
            print g.get_line(s,i-n)
#@nonl
#@-node:EKR.20040517075110.5:<< look for name followed by '(' >>
#@+node:EKR.20040517075110.6:<< print before and after >>
print "-"*10,count,p.headString()
print "before..."
print p.bodyString()
print "-"*10,"after..."
print s
#@nonl
#@-node:EKR.20040517075110.6:<< print before and after >>
#@-node:EKR.20040517075110.4:prependNamesInTree
#@-node:EKR.20040517075110:replaceLeoGlobals
#@+node:EKR.20040608102548:(Fixed unicode bug in plugins_menu.py)
#@+node:EKR.20040608102548.1:Report
@nocolor

By: Maxim Krikun - tws5
 Non-ASCII in plugin config   
2004-03-25 23:34  

 I run Leo 4.1 final, build 1.77 ,
Python 2.3.0, Tk 8.4.3, on windows 98.

For some reason i had to enter non-ascii string in plugin preferences box (header_style for word export plugin, since ms word is localized and has style names different from "Header"). 

I found that the dialog doesn't close on OK button, and the ini file gets broken -- not all options remain there. 

As i investigated, ConfigParser.write() method when writing to a file claimed it can't convert unicode string to ascii. This problem was fixed by setting system default locale in site.py, (s/if: 0/if: 1/ at line 321).

The fix:

in << Create widgets for each section and option >>:

e = Tk.Entry(b)
e.insert(0, unicode(config.get(section, option)))
#@nonl
#@-node:EKR.20040608102548.1:Report
#@+node:EKR.20040608103502:Unicode chars
楢
#@-node:EKR.20040608103502:Unicode chars
#@+node:EKR.20040608104417:Traceback
Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\PYTHON23\lib\lib-tk\Tkinter.py", line 1345, in __call__
    return self.func(*args)
  File "C:\prog\leoCVS\leo\plugins\plugins_menu.py", line 208, in onOk
    self.writeConfiguration()
  File "C:\prog\leoCVS\leo\plugins\plugins_menu.py", line 223, in writeConfiguration
    self.config.write(f)
  File "C:\PYTHON23\lib\ConfigParser.py", line 366, in write
    fp.write("%s = %s\n" %
UnicodeEncodeError: 'ascii' codec can't encode character u'\u6962' in position 0: ordinal not in range(128)
#@-node:EKR.20040608104417:Traceback
#@+node:EKR.20040517080555.13:<< create the frame from the configuration data >>
root = g.app.root

<< Create the top level and the main frame >>
<< Create widgets for each section and option >>
<< Create Ok, Cancel and Apply buttons >>

g.app.gui.center_dialog(top) # Do this after packing.
top.grab_set() # Make the dialog a modal dialog.
top.focus_force() # Get all keystrokes.
root.wait_window(top)
#@nonl
#@+node:EKR.20040517080555.14:<< Create the top level and the main frame >>
self.top = top = Tk.Toplevel(root)
g.app.gui.attachLeoIcon(self.top)
top.title("Properties of "+ plugin.name)
top.resizable(0,0) # neither height or width is resizable.
    
self.frame = frame = Tk.Frame(top)
frame.pack(side="top")
#@nonl
#@-node:EKR.20040517080555.14:<< Create the top level and the main frame >>
#@+node:EKR.20040517080555.15:<< Create widgets for each section and option >>
# Create all the entry boxes on the screen to allow the user to edit the properties
sections = config.sections()
sections.sort()
for section in sections:
    # Create a frame for the section.
    f = Tk.Frame(top, relief="groove",bd=2)
    f.pack(side="top",padx=5,pady=5)
    Tk.Label(f, text=section.capitalize()).pack(side="top")
    # Create an inner frame for the options.
    b = Tk.Frame(f)
    b.pack(side="top",padx=2,pady=2)
    # Create a Tk.Label and Tk.Entry for each option.
    options = config.options(section)
    options.sort()
    row = 0
    for option in options:
        e = Tk.Entry(b)
        e.insert(0, unicode(config.get(section,option))) # 6/8/04
        Tk.Label(b, text=option).grid(row=row, column=0, sticky="e", pady=4)
        e.grid(row=row, column=1, sticky="ew", pady = 4)
        row += 1
        self.entries.append((section, option, e))
#@nonl
#@-node:EKR.20040517080555.15:<< Create widgets for each section and option >>
#@+node:EKR.20040517080555.16:<< Create Ok, Cancel and Apply buttons >>
box = Tk.Frame(top, borderwidth=5)
box.pack(side="bottom")

list = [("OK",self.onOk),("Cancel",top.destroy)]
if plugin.hasapply:
    list.append(("Apply",self.onApply),)

for text,f in list:
    Tk.Button(box,text=text,width=6,command=f).pack(side="left",padx=5)
#@nonl
#@-node:EKR.20040517080555.16:<< Create Ok, Cancel and Apply buttons >>
#@-node:EKR.20040517080555.13:<< create the frame from the configuration data >>
#@+node:EKR.20040517080555.18:writeConfiguration
def writeConfiguration(self):
    
    """Write the configuration to disk"""

    # Set values back into the config item.
    for section, option, entry in self.entries:
        s = entry.get()
        s = g.toEncodedString(s,"ascii",reportErrors=True) # Config params had better be ascii.
        self.config.set(section,option,s)

    # Write out to the file.
    f = open(self.filename, "w")
    self.config.write(f)
    f.close()
#@nonl
#@-node:EKR.20040517080555.18:writeConfiguration
#@-node:EKR.20040608102548:(Fixed unicode bug in plugins_menu.py)
#@+node:ekr.20040918165427:To do in 4.3
#@+node:ekr.20040918165144:4.3 Prototypes as plugins
@
Each can be done without impacting other code.
Each can be done relatively easily: one week or less
#@nonl
#@+node:EKR.20040611044600:Mulder undo
#@+node:bwmulder.20040601212737:basic_undo.py
"""
Define a general purpose monitor module.

Can be used for classes derived from "object" to intercept assignment to instance attributes.

For lists and dictionaries, it also offers drop-in replacements which monitor all changes to those list and mappings.

This module also includes a basic undo / redo mechanism.

For this undo / redo mechanism, it is important that the undo / redo steps do not trigger any monitoring calls. 
This module should fullfill that requirement.

For scalars, we put "scalar_monitor" into the attribute slot to intercept accesses to the attribute. The values
themselves live in a differnt, "private" attribute. These private attributes are accessed directly for the
undo / redo steps.

For dictionaries and lists, we extend the base types. The undo / redo mechanism 
uses the methods of the basic type.

If you assign a list or a dictionary to a monitored attribute, the list or mapping is automatically converted to
a monitored list or mapping (but only one level deep).

[Question: do we need a convenience function which does this recursively?]
"""

@language python
@tabwidth -4

@others
#@+node:bwmulder.20040601212737.1:class passthrough
class passthrough(object):
    """
    Instances of this class are used to disable monitoring.
    The values are just passed through.
    """
    @others

#@+node:bwmulder.20040601212737.2:__init__
def __init__(self, private_attributename):
   self.private_attributename = private_attributename
#@-node:bwmulder.20040601212737.2:__init__
#@+node:bwmulder.20040601212737.3:__set__
def __set__( self, instance, value):
   setattr(instance, self.private_attributename, value)
#@-node:bwmulder.20040601212737.3:__set__
#@+node:bwmulder.20040601212737.4:__get__
def __get__(self, instance, owner):
   return getattr(instance, self.private_attributename)
#@-node:bwmulder.20040601212737.4:__get__
#@-node:bwmulder.20040601212737.1:class passthrough
#@+node:bwmulder.20040601212737.5:class scalar_monitor
   
class scalar_monitor(object):
    """
   Monitor a scalar attribute.
   A scalar attribute is an attribute whose values do not have internal structure.
   Used for Integers and references.
    """
    @others
#@+node:bwmulder.20040601212737.6:__init__
def __init__(self, external_attributename, monitor_object):
    self.external_attributename = external_attributename
    self.private_attributename = '__' + external_attributename
    self.monitor_object = monitor_object
#@-node:bwmulder.20040601212737.6:__init__
#@+node:bwmulder.20040601212737.7:__set__
def __set__( self, instance, value):
    private_attributename = self.private_attributename
    external_attributename = self.external_attributename
    try:
        oldvalue = getattr(instance, private_attributename)
    except AttributeError:
        self.monitor_object.scalar_set(instance, private_attributename, external_attributename)
    else:
        if oldvalue != value:
            self.monitor_object.scalar_modify(instance, private_attributename, external_attributename, oldvalue)
    setattr(instance, private_attributename, value)

#@-node:bwmulder.20040601212737.7:__set__
#@+node:bwmulder.20040601212737.8:__get__
def __get__(self, instance, owner):
   return getattr(instance, self.private_attributename)

#@-node:bwmulder.20040601212737.8:__get__
#@-node:bwmulder.20040601212737.5:class scalar_monitor
#@+node:bwmulder.20040601212737.9:class list_monitor
class list_monitor(list):
    """
    Monitor changes to a list.
    
    Calls a "monitor_object" whenever changes are made to a list.
    
    You can use this class whenever you want to track changes to a list.
    """
    @others
#@+node:bwmulder.20040605231305:__init__
def __init__(self, value, monitor):
    list.__init__(self, value)
    self.set_monitor_object(monitor)
#@-node:bwmulder.20040605231305:__init__
#@+node:bwmulder.20040601212737.10:set_monitor_object
def set_monitor_object(self, monitor_object):
    """
    All changes to this list will trigger calls to monitor_object methods
    """
    self.monitor_object = monitor_object

#@-node:bwmulder.20040601212737.10:set_monitor_object
#@+node:bwmulder.20040601212737.11:__setitem__
def __setitem__( self, key, value):
    """
    Intercept the l[key]=value operations.
    Also covers slice assignment.
    """
    try:
        oldvalue = self.__getitem__(key)
    except KeyError:
        self.monitor_object.list_create(self, key)
    else:
        self.monitor_object.list_set(self, key, oldvalue)
    list.__setitem__(self, key, value)
#@-node:bwmulder.20040601212737.11:__setitem__
#@+node:bwmulder.20040601212737.12:__delitem__
def __delitem__( self, key):
   oldvalue = list.__getitem__(self, key)
   self.monitor_object.list_del(self, key, oldvalue)
   list.__delitem__(self, key)
#@-node:bwmulder.20040601212737.12:__delitem__
#@+node:bwmulder.20040601212737.13:append
def append(self, value):
   self.monitor_object.list_append(self)
   list.append(self, value)


#@-node:bwmulder.20040601212737.13:append
#@+node:bwmulder.20040602152548:pop
def pop(self):
    oldvalue = list.pop(self)
    self.monitor_object.list_pop(self, oldvalue)
#@-node:bwmulder.20040602152548:pop
#@-node:bwmulder.20040601212737.9:class list_monitor
#@+node:bwmulder.20040601212737.14:class list_monitor_in_instance
class list_monitor_in_instance(object):
    """
    Monitor instance attributes which contain a list as a value.

    Assignments to this attributes, which must be lists, are replaced by instances of 'list_monitor'.
   """
    @others
#@+node:bwmulder.20040601212737.15:__init__
def __init__(self, external_attributename, monitor_object):
    self.external_attributename = external_attributename
    self.internal_attributename = '__' + external_attributename
    self.monitor_object = monitor_object
#@-node:bwmulder.20040601212737.15:__init__
#@+node:bwmulder.20040601212737.16:__set__
def __set__(self, instance, value):
    """Intercept assignments to the external attribute"""
    assert isinstance(value, type([]))
    if isinstance(value, list_monitor):
        newvalue = value
        # if the value is already a list monitor, assume that this value
        # is already monitored. Do not create a new value.
    else:
        newvalue = list_monitor(value, self.monitor_object)
    internal_attributename = self.internal_attributename
    try:
        oldvalue = getattr(instance, internal_attributename)
    except AttributeError:
        self.monitor_object.list_assignment_new(instance, internal_attributename)
    else:
        self.monitor_object.list_assignment_replace(instance, internal_attributename, oldvalue)
    setattr(instance, self.internal_attributename, newvalue)

#@-node:bwmulder.20040601212737.16:__set__
#@+node:bwmulder.20040601212737.17:__get__
def __get__(self, instance, owner):
   try:
      return instance.__dict__[self.internal_attributename]
   except KeyError: 
      return instance.__dict__[self.external_attributename]

#@-node:bwmulder.20040601212737.17:__get__
#@-node:bwmulder.20040601212737.14:class list_monitor_in_instance
#@+node:bwmulder.20040602153618:class dict_monitor
class dict_monitor(dict):
    """
    Overwrite dictionaries so that we can monitor them.
    """
    @others
#@+node:bwmulder.20040605231401:__init__
def __init__(self, value, monitor):
    dict.__init__(self, value)
    self.set_monitor_object(monitor)
#@-node:bwmulder.20040605231401:__init__
#@+node:bwmulder.20040602153642:set_monitor_object
def set_monitor_object(self, monitor_object):
    """
    All changes to this dictionary will trigger calls to monitor_object methods
    """
    self.monitor_object = monitor_object

#@-node:bwmulder.20040602153642:set_monitor_object
#@+node:bwmulder.20040602153723:__setitem__
def __setitem__( self, key, value):
    """
    Intercept the l[key]=value operations.
    Also covers slice assignment.
    """
    try:
        oldvalue = self.__getitem__(key)
    except KeyError:
        self.monitor_object.dict_create(self, key, value)
    else:
        self.monitor_object.dict_set(self, key, oldvalue)
    dict.__setitem__(self, key, value)
#@-node:bwmulder.20040602153723:__setitem__
#@+node:bwmulder.20040602153835:__delitem__
def __delitem__( self, key):
   oldvalue = dict.__getitem__(self, key)
   self.monitor_object.dict_del(self, key, oldvalue)
   dict.__delitem__(self, key)
#@-node:bwmulder.20040602153835:__delitem__
#@-node:bwmulder.20040602153618:class dict_monitor
#@+node:bwmulder.20040602154259:class  dict_monitor_in_instance
class dict_monitor_in_instance(object):
    """
    Monitor instance attributes which contain a list as a value.

    Assignments to this attributes, which must be lists, are replaced by instances of 'list_monitor'.
   """
    @others
#@+node:bwmulder.20040602154259.1:__init__
def __init__(self, external_attributename, monitor_object):
    self.external_attributename = external_attributename
    self.internal_attributename = '__' + external_attributename
    self.monitor_object = monitor_object
#@-node:bwmulder.20040602154259.1:__init__
#@+node:bwmulder.20040602154259.2:__set__
def __set__(self, instance, value):
    """Intercept assignments to the external attribute"""
    assert isinstance(value, type({}))
    if isinstance(value, dict_monitor):
        newvalue = value
        # if the value is already a dict_monitor,
        # assume that the value is already monitored.
    else:
        newvalue = dict_monitor(value, self.monitor_object)
    internal_attributename = self.internal_attributename
    try:
        oldvalue = getattr(instance, internal_attributename)
    except AttributeError:
        self.monitor_object.list_assignment_new(instance, internal_attributename)
    else:
        self.monitor_object.list_assignment_replace(instance, internal_attributename, oldvalue)
    setattr(instance, self.internal_attributename, newvalue)
#@-node:bwmulder.20040602154259.2:__set__
#@+node:bwmulder.20040602154259.3:__get__
def __get__(self, instance, owner):
   try:
      return instance.__dict__[self.internal_attributename]
   except KeyError: 
      return instance.__dict__[self.external_attributename]

#@-node:bwmulder.20040602154259.3:__get__
#@-node:bwmulder.20040602154259:class  dict_monitor_in_instance
#@+node:bwmulder.20040601212737.18:class monitor
class monitor:
    """
   Monitor changes to (new style) classes.

   To use:
      1. 
            a) Call monitor_scalar(klass, external_attributename, internal_attributename)
                    for each scalar attribute you want to monitor.

                A scalar attribute is an attribute without internal structure (int and reference).

                The normal Python comparison operation (=) is used to check if a new value is stored
                in an instance attribute.
                
            b) Call monitor_list(klass, external_attributename, internal_attributename) for each
               list attribute you want to monitor.
                
           c) Call monitor_dict(klass, external_attributename, internal_attributename) for each
               dict attribute you want to monitor.
         
      2. 
            Call enable / disable to enable / disable monitoring.

   This is an abstract class.
   
   Concrete subclasses are the classes "tracer" and "basic_undomechanism". The latter does most
   (almost all) of the work of a (fairly) general undo mechanism.

   Limitations:
   
      Assumes that instance attributes are used consistently with certain types.
      
   """
    @others
#@+node:bwmulder.20040601212737.19:__init__
def __init__(self):
   self.monitored_scalar_attributes = []
   self.monitored_list_attributes = []
   self.monitored_dict_attributes = []
   self.removed_functions = []
   
   self.monitor_object = self
   # See enable_category.
   
   # Overwriting this one attribute allows
   # Clients of this module to implemente tracing of all
   # calls to this module.
   
#@-node:bwmulder.20040601212737.19:__init__
#@+node:bwmulder.20040601214251:scalars
#@+node:bwmulder.20040601212737.20:monitor_scalar
def monitor_scalar(self, klass, external_attributename):
   """
   Put in a hook so that we can monitor modications to instances of classref'
   with respect to the attribute "attributename".

   It is assumed that the attribute only contains scalar objects. A scalar
   object is an object which is unstructured, and not shared.
   """
   self.monitored_scalar_attributes.append(
      (klass, external_attributename))
#@-node:bwmulder.20040601212737.20:monitor_scalar
#@+node:bwmulder.20040601212737.24:scalar_set
def scalar_set(self, instance, private_attributename, external_attributename):
   raise notImplementedError
#@-node:bwmulder.20040601212737.24:scalar_set
#@+node:bwmulder.20040601212737.25:scalar_modify
def scalar_modify(self, instance, private_attributename, external_attributename, oldvalue):
   raise notImplementedError
#@-node:bwmulder.20040601212737.25:scalar_modify
#@-node:bwmulder.20040601214251:scalars
#@+node:bwmulder.20040601215339:lists
#@+node:bwmulder.20040601212737.21:monitor_list_attribute_in_class
def monitor_list_attribute_in_class(self, klass, external_attributename):
   self.monitored_list_attributes.append(
      (klass, external_attributename))
#@-node:bwmulder.20040601212737.21:monitor_list_attribute_in_class
#@+node:bwmulder.20040601212737.26:list_create
def list_create(self, array, key, value):
   raise notImplementedError
#@-node:bwmulder.20040601212737.26:list_create
#@+node:bwmulder.20040601212737.27:list_set
def list_set(self, array, key, value):
   raise notImplementedError
#@-node:bwmulder.20040601212737.27:list_set
#@+node:bwmulder.20040601212737.28:list_del
def list_del(self, array, key, oldvalue):
   raise notImplementedError
#@-node:bwmulder.20040601212737.28:list_del
#@+node:bwmulder.20040601212737.29:list_append
def list_append(self, array, value):
   raise notImplementedError
#@-node:bwmulder.20040601212737.29:list_append
#@-node:bwmulder.20040601215339:lists
#@+node:bwmulder.20040602165344:dicts
#@+node:bwmulder.20040602165402:monitor_dict_attribute_in_class
def monitor_dict_attribute_in_class(self, klass, external_attributename):
   self.monitored_dict_attributes.append(
      (klass, external_attributename))
#@-node:bwmulder.20040602165402:monitor_dict_attribute_in_class
#@+node:bwmulder.20040602165506:dict_create
def dict_create(self, array, key, value):
   raise notImplementedError
#@-node:bwmulder.20040602165506:dict_create
#@+node:bwmulder.20040602165513:dict_set
def dict_set(self, array, key, value):
   raise notImplementedError
#@-node:bwmulder.20040602165513:dict_set
#@+node:bwmulder.20040602165553:dict_del
def dict_del(self, array, key, oldvalue):
   raise notImplementedError
#@-node:bwmulder.20040602165553:dict_del
#@-node:bwmulder.20040602165344:dicts
#@+node:bwmulder.20040601215339.1:switching on and off
@doc
Delayed activation of the monitor mechanism is probably useful.

Not clear of switching off and on is useful, though.
#@nonl
#@+node:bwmulder.20040601212737.22:enable
def enable(self):
    for l, klass in ((self.monitored_scalar_attributes, scalar_monitor),
             (self.monitored_list_attributes, list_monitor_in_instance),
             (self.monitored_dict_attributes, dict_monitor_in_instance)):
        self.enable_category(l, klass)
        
    self.enable_put_in_removed_functions()
    

#@-node:bwmulder.20040601212737.22:enable
#@+node:bwmulder.20040602164627:enable_put_in_removed_functions
def enable_put_in_removed_functions(self):
    """
    Reinstate the functions which were removed from list_monitor and dict_monitor.
    """
    for klass, attribute, function in self.removed_functions:
        setattr(klass, attribute, function)
#@-node:bwmulder.20040602164627:enable_put_in_removed_functions
#@+node:bwmulder.20040602154259.4:enable_category
def enable_category(self, l, monitor_class):
   for klass, external_attributename in l:
      setattr(klass, external_attributename, monitor_class(
         external_attributename, self.monitor_object))
    
#@nonl
#@-node:bwmulder.20040602154259.4:enable_category
#@+node:bwmulder.20040602161525:disable_category
def disable_category(self, l, monitor_class):
   for klass, external_attributename in l:
      setattr(klass, external_attributename, passthrough (internal_attributename))
    
#@-node:bwmulder.20040602161525:disable_category
#@+node:bwmulder.20040601212737.23:disable
def disable(self):
    """
    Disable monitoring (temporarily).
    """
    for l, klass in ((self.monitored_scalar_attributes, scalar_monitor),
                     (self.monitored_list_attributes, list_monitor_in_instance),
                     (self.monitored_dict_attributes, dict_monitor_in_instance)):
        self.disable_category(l, klass)
    
    self.remove_overrides_in_list_and_dict_monitor()
    
#@-node:bwmulder.20040601212737.23:disable
#@+node:bwmulder.20040603081718:remove_overrides_in_list_and_dict_monitor
def remove_overrides_in_list_and_dict_monitor(self):
    """
    Deletes all function definitions in list_monitor and dict_monitor.
    The net effect of this is that instances of these classes should
    behave like regular lists and dictionaries.
    """	
    import inspect
    for klass in (list_monitor, dict_monitor):
        for attribute in dir(klass):
            try:
                entity = getattr(klass, attribute)
            except AttributeError:
                pass
            else:
                if inspect.isfunction(entity):
                    delattr(klass, attribute)
                    self.removed_functions.append(klass, attribute, entity)
#@nonl
#@-node:bwmulder.20040603081718:remove_overrides_in_list_and_dict_monitor
#@-node:bwmulder.20040601215339.1:switching on and off
#@-node:bwmulder.20040601212737.18:class monitor
#@+node:bwmulder.20040601222230:class basic_undomechanism
class basic_undomechanism(monitor):
    """
    This class provides the basic operations for undoable operations.
    
    Records a list of changes which it will undo or redo one by one.
    
    The granularity of the undo / redo operations is determined by calls to
    the 'mark' procedure. Only immediately after the 'mark' call can undo be called.
    Redo can only be called after calling undo.
    
    'rollback' is a special case of undo: it is not redoable. The envisioned usage of
    this facility is in error recovery: if a command does not go through, you can call
    this command to undo all your changes (and leave the application in a consistent state).
    
    Uses the monitor_scalar, monitor_list_attribute_in_class and monitor_dict_attribute_in_class
    methods to make assignment to instance variables undoable.
    
    Call 'enable' to activate the undo mechanism, 'disable' to temporarily
    stop the undo mechanism from collecting information about changes.
    
    The individual changes are bundled into "_commands'. The boundaries of
    these _commands are marked by a call to the procedure "mark".
    
    The procedure 'reset' can be called externally to erase all undo information.
    
    Individual lists and dictionaries can also be monitored for change with the
    list_monitor and dict_monitor classes.
    
    Possible optimizations later: special handling for string attributes.
    """
    @others
#@nonl
#@+node:bwmulder.20040601222230.1:__init__
def __init__(self):

    monitor.__init__(self)
    self.reset()
    
#@nonl
#@-node:bwmulder.20040601222230.1:__init__
#@+node:bwmulder.20040602171606:reset
def reset(self):

    self._steps = []
    self._commands = [None, None]
    self._index = 0
    


#@-node:bwmulder.20040602171606:reset
#@+node:bwmulder.20040601222230.2:scalars
#@+node:bwmulder.20040601222230.3:scalar_set
def scalar_set(self, instance, private_attributename, external_attributename):
   self._steps.append((self.scalar_set_undo, (instance, private_attributename)))
    

#@-node:bwmulder.20040601222230.3:scalar_set
#@+node:bwmulder.20040601222230.4:scalar_set_undo
def scalar_set_undo(self, instance, private_attributename):

    """Undo the changes done by the assignment of an instance"""
    newvalue = getattr(instance, private_attributename)
    delattr(instance, private_attributename)
    return self.scalar_set_redo, (instance, private_attributename, newvalue)
#@-node:bwmulder.20040601222230.4:scalar_set_undo
#@+node:bwmulder.20040601222230.5:scalar_set_redo
def scalar_set_redo(self, instance, private_attributename, newvalue):

    setattr(instance, private_attributename, newvalue)
    return self.scalar_set_undo, (instance, private_attributename)
#@-node:bwmulder.20040601222230.5:scalar_set_redo
#@+node:bwmulder.20040601222230.6:scalar_modify
def scalar_modify(self, instance, private_attributename, external_attributename, oldvalue):

  self._steps.append((self.scalar_modify_undo, (instance, private_attributename, oldvalue)))
#@-node:bwmulder.20040601222230.6:scalar_modify
#@+node:bwmulder.20040601222431:scalar_modify_undo
def scalar_modify_undo(self, instance, private_attributename, oldvalue):

    new_value = getattr(instance, private_attributename)
    setattr(instance, private_attributename, oldvalue)
    return self.scalar_modify_undo, (instance, private_attributename, new_value)
#@nonl
#@-node:bwmulder.20040601222431:scalar_modify_undo
#@-node:bwmulder.20040601222230.2:scalars
#@+node:bwmulder.20040602075341:lists
#@+node:bwmulder.20040602084701.1:creation
#@+node:bwmulder.20040602175523:list_assignment_replace
def list_assignment_replace(self, instance, attributename, oldvalue):
    self._steps.append((self.list_assignment_replace_undo, (instance, attributename, oldvalue)))
#@nonl
#@-node:bwmulder.20040602175523:list_assignment_replace
#@+node:bwmulder.20040602175523.1:list_assignment_replace_undo
def list_assignment_replace_undo(self, instance, attributename, oldvalue):
    newvalue = getattr(instance, attributename)
    setattr(instance, attributename, oldvalue)
    return self.list_assignment_replace_redo, (instance, attributename, newvalue)
#@nonl
#@-node:bwmulder.20040602175523.1:list_assignment_replace_undo
#@+node:bwmulder.20040602175740:list_assignment_replace_redo
def list_assignment_replace_redo(self, instance, attributename, newvalue):
    oldvalue = getattr(instance, attributename)
    setattr(instance, attributename, newvalue)
    return self.list_assignment_replace_undo, (instance, attributename, oldvalue)
#@-node:bwmulder.20040602175740:list_assignment_replace_redo
#@+node:bwmulder.20040602183806:list_assignment_new
def list_assignment_new(self, instance, attributename):
    self._steps.append((self.list_assignment_new_undo, (instance, attributename)))
#@nonl
#@-node:bwmulder.20040602183806:list_assignment_new
#@+node:bwmulder.20040602183814:list_assignment_new_undo
def list_assignment_new_undo(self, instance, attributename):
    newvalue = getattr(instance, attributename)
    delattr(instance, attributename)
    return self.list_assignment_new_redo, (instance, attributename, newvalue)
#@nonl
#@-node:bwmulder.20040602183814:list_assignment_new_undo
#@+node:bwmulder.20040602183911:list_assignment_new_redo
def list_assignment_new_redo(self, instance, attributename, newvalue):
    setattr(instance, attributename, newvalue)
    return self.list_assignment_new_undo, (instance, attributename)
#@-node:bwmulder.20040602183911:list_assignment_new_redo
#@+node:bwmulder.20040602075341.1:list_create
def list_create(self, array, key):
    self._steps.append((self.list_create_undo, (array, key)))

#@-node:bwmulder.20040602075341.1:list_create
#@+node:bwmulder.20040602084701.2:list_create_undo
def list_create_undo(self, array, key):
    value = list.__getitem__(array, key)
    list.__delitem__(array, key)
    return self.list_create_redo, (array, key, value)

#@-node:bwmulder.20040602084701.2:list_create_undo
#@+node:bwmulder.20040602084701.3:list_create_redo
def list_create_redo(self, array, key, value):
    list.__setitem__(array, key, value)
    return self.list_create_undo, (array, key)
#@nonl
#@-node:bwmulder.20040602084701.3:list_create_redo
#@-node:bwmulder.20040602084701.1:creation
#@+node:bwmulder.20040602084701.4:setting
#@+node:bwmulder.20040602085955:list_set
def list_set(self, array, key, oldvalue):
    self._steps.append((self.list_set_undo, (array, key, oldvalue)))

#@-node:bwmulder.20040602085955:list_set
#@+node:bwmulder.20040602085955.1:list_set_undo
def list_set_undo(self, array, key, value):
    oldvalue = list.__getitem__(array, key)
    list.__setitem__(array, key, value)
    return self.list_set_undo, (array, key, oldvalue)
#@-node:bwmulder.20040602085955.1:list_set_undo
#@-node:bwmulder.20040602084701.4:setting
#@+node:bwmulder.20040602085955.2:deletion
#@+node:bwmulder.20040602085955.3:list_del
def list_del(self, array, key, oldvalue):
   self._steps.append((self.list_del_undo, (array, key, oldvalue)))
   
#@-node:bwmulder.20040602085955.3:list_del
#@+node:bwmulder.20040602085955.4:list_del_undo
def list_del_undo(self, array, key, oldvalue):
    if type(key) == type(1):
        list.__setitem__(array, slice(key, key), [oldvalue])
    else:
        list.__setitem__(array, key, oldvalue)
    return self.list_del_redo, (array, key, oldvalue)
#@-node:bwmulder.20040602085955.4:list_del_undo
#@+node:bwmulder.20040602085955.5:list_del_redo
def list_del_redo(self, array, key, oldvalue):
    oldvalue = list.__getitem__(array, key)
    list.__delitem__(array, key)
    return self.list_del_undo, (array, key, oldvalue)
#@-node:bwmulder.20040602085955.5:list_del_redo
#@-node:bwmulder.20040602085955.2:deletion
#@+node:bwmulder.20040602151709:append
#@+node:bwmulder.20040602152051:list_append
def list_append(self, array):
    self._steps.append((self.list_append_undo, (array,)))
    


#@-node:bwmulder.20040602152051:list_append
#@+node:bwmulder.20040602152051.1:list_append_undo
def list_append_undo(self, array):
    oldvalue = list.pop(array)
    return self.list_append_redo, (array, oldvalue)
#@-node:bwmulder.20040602152051.1:list_append_undo
#@+node:bwmulder.20040602152051.2:list_append_redo
def list_append_redo(self, array, oldvalue):
    list.append(array, oldvalue)
    return self.list_append_undo, (array,)
#@nonl
#@-node:bwmulder.20040602152051.2:list_append_redo
#@-node:bwmulder.20040602151709:append
#@+node:bwmulder.20040602152548.1:pop
#@+node:bwmulder.20040602152548.2:list_pop
def list_pop(self, array, oldvalue):
    self._steps.append((self.list_append_redo, (array, oldvalue)))
#@-node:bwmulder.20040602152548.2:list_pop
#@-node:bwmulder.20040602152548.1:pop
#@-node:bwmulder.20040602075341:lists
#@+node:bwmulder.20040602171032:dictionaries
#@+node:bwmulder.20040602171032.1:creation
#@+node:bwmulder.20040602175801:dictionary creation
# Dictionary monitors are are really handled like
# list monitors.
# For now, just use the same methods.
dict_assignment_new = list_assignment_new
dict_assignment_new_undo = list_assignment_new_undo
dict_assignment_new_redo = list_assignment_new_redo

dict_assignment_replace      = list_assignment_replace
dict_assignment_replace_undo = list_assignment_replace_undo
dict_assignment_replace_redo = list_assignment_replace_redo
#@nonl
#@-node:bwmulder.20040602175801:dictionary creation
#@+node:bwmulder.20040602171032.2:dict_create
def dict_create(self, dictionary, key, value):
    self._steps.append((self.dict_create_undo, (dictionary, key)))

#@-node:bwmulder.20040602171032.2:dict_create
#@+node:bwmulder.20040602171032.3:dict_create_undo
def dict_create_undo(self, dictionary, key):
    value = dict.__getitem__(dictionary, key)
    dict.__delitem__(dictionary, key)
    return self.dict_create_redo, (dictionary, key, value)

#@-node:bwmulder.20040602171032.3:dict_create_undo
#@+node:bwmulder.20040602171032.4:dict_create_redo
def dict_create_redo(self, dictionary, key, value):
    dict.__setitem__(dictionary, key, value)
    return self.dict_create_undo, (dictionary, key,)
#@-node:bwmulder.20040602171032.4:dict_create_redo
#@-node:bwmulder.20040602171032.1:creation
#@+node:bwmulder.20040602171032.5:setting
#@+node:bwmulder.20040602171032.6:dict_set
def dict_set(self, dictionary, key, oldvalue):
    self._steps.append((self.dict_set_undo, (dictionary, key, oldvalue)))

#@-node:bwmulder.20040602171032.6:dict_set
#@+node:bwmulder.20040602171032.7:dict_set_undo
def dict_set_undo(self, dictionary, key, value):
    oldvalue = dict.__getitem__(dictionary, key)
    dict.__setitem__(dictionary, key, value)
    return self.dict_set_undo, (dictionary, key, oldvalue)
#@-node:bwmulder.20040602171032.7:dict_set_undo
#@-node:bwmulder.20040602171032.5:setting
#@+node:bwmulder.20040602171032.8:deletion
#@+node:bwmulder.20040602171032.9:dict_del
def dict_del(self, dictionary, key, oldvalue):
   self._steps.append((self.dict_del_undo, (dictionary, key, oldvalue)))
   
#@-node:bwmulder.20040602171032.9:dict_del
#@+node:bwmulder.20040602171032.10:dict_del_undo
def dict_del_undo(self, dictionary, key, oldvalue ):
    dict.__setitem__(dictionary, key, oldvalue)
    return self.dict_del_redo, (dictionary, key, oldvalue)
#@-node:bwmulder.20040602171032.10:dict_del_undo
#@+node:bwmulder.20040602171032.11:dict_del_redo
def dict_del_redo(self, dictionary, key, oldvalue ):
    oldvalue = dict.__getitem__(dictionary, key)
    dict.__delitem__(dictionary, key)
    return self.dict_del_undo, (dictionary, key, oldvalue)
#@-node:bwmulder.20040602171032.11:dict_del_redo
#@-node:bwmulder.20040602171032.8:deletion
#@-node:bwmulder.20040602171032:dictionaries
#@+node:bwmulder.20040601224447:the undo machinery
#@+node:bwmulder.20040603211921:queries
#@+node:bwmulder.20040601224447.5:canUndo
def canUndo(self):
    
    return self._commands[self._index] is not None and len(self._steps) == 0
#@-node:bwmulder.20040601224447.5:canUndo
#@+node:bwmulder.20040601224447.6:canRedo
def canRedo(self):
    return self._commands[self._index+1] is not None and len(self._steps) == 0
#@nonl
#@-node:bwmulder.20040601224447.6:canRedo
#@+node:bwmulder.20040603212552:commands
def commands(self):

    return len(self._commands) - 2
#@nonl
#@-node:bwmulder.20040603212552:commands
#@+node:bwmulder.20040603212612:commands_to_undo
def commands_to_undo(self):

    return self._index
#@nonl
#@-node:bwmulder.20040603212612:commands_to_undo
#@+node:bwmulder.20040603212713:commands_to_redo
def commands_to_redo(self):
    return self.commands() - self._index
#@nonl
#@-node:bwmulder.20040603212713:commands_to_redo
#@+node:bwmulder.20040604165011:steps_stored
def steps_stored(self):
    """
    Return the total number of steps stored in the undoer.
    """
    result = 0
    for command in self._commands[1:-1]:
        result += len(command)
    return result
#@nonl
#@-node:bwmulder.20040604165011:steps_stored
#@+node:bwmulder.20040605220919:print_commands
def print_commands(self, comment):
    """
    rint a readable list of all commands
    """
    print "===== Commands: %s ========" % comment
    i = 0
    while i < len(self._commands):
        print "Command", i
        steps = self._commands[i]
        if steps:
            for step in steps:
                function, args = step
                print "  ",function.__name__, args
        if i == self._index:
            print "---------------------"
        i += 1
    print "========================"
#@-node:bwmulder.20040605220919:print_commands
#@-node:bwmulder.20040603211921:queries
#@+node:bwmulder.20040601224447.2:mark
def mark(self):
    
    """Mark the end of the current commmand."""

    if self._steps:
        self._commands[self._index+1:] = [self._steps, None]
        self._index += 1
        self._steps = []
#@nonl
#@-node:bwmulder.20040601224447.2:mark
#@+node:bwmulder.20040601224447.3:undo
def undo(self):

    assert self.canUndo()
    self._commands[self._index] = self.run_commands(self._commands[self._index])
    self._index -= 1
    
#@-node:bwmulder.20040601224447.3:undo
#@+node:bwmulder.20040601224447.4:redo
def redo(self):

    assert self.canRedo()
    self._commands[self._index+1] = self.run_commands(self._commands[self._index+1])
    self._index += 1
#@nonl
#@-node:bwmulder.20040601224447.4:redo
#@+node:bwmulder.20040603212934:rollback
def rollback(self):

    self.run_commands(self._steps)
#@-node:bwmulder.20040603212934:rollback
#@+node:bwmulder.20040601222649:run_commands
def run_commands(self, steps):
    
    """
    Run the undo / redo _steps.
    Returns the list of steps to redo / undo the steps just made.
    """

    steps.reverse()
    return [func(*args) for func, args in steps]
#@-node:bwmulder.20040601222649:run_commands
#@-node:bwmulder.20040601224447:the undo machinery
#@-node:bwmulder.20040601222230:class basic_undomechanism
#@-node:bwmulder.20040601212737:basic_undo.py
#@+node:bwmulder.20040602221559:basic_undo_test.py
<< imports >>

undo_tracing = False

@others

if __name__ == '__main__':
    test_main()
#@nonl
#@+node:bwmulder.20040602223236:<< imports >>
from basic_undo import monitor, basic_undomechanism, list_monitor, dict_monitor
import unittest
from test import test_support
#@-node:bwmulder.20040602223236:<< imports >>
#@+node:bwmulder.20040602223236.1:class tracer
class tracer(monitor):
    
    # The output format could be improved, but this is only for testing.
    """Simple class which can be used to trace all calls made from a monitor."""

    @others

#@+node:bwmulder.20040602223236.2:__getattr__
def __getattr__(self, attributename):
    self.attributename = attributename
    return self.catchall
#@nonl
#@-node:bwmulder.20040602223236.2:__getattr__
#@+node:bwmulder.20040602223906:catchall
def catchall(self, *args, **kwrds):

    print "Tracer:", self.attributename, ":", args, kwds
#@nonl
#@-node:bwmulder.20040602223906:catchall
#@-node:bwmulder.20040602223236.1:class tracer
#@+node:bwmulder.20040602224231:class delegator
class delegator(object):
    """
    Simple class to print all arguments passed to a monitor.
    Allows you to watch the undoer in action...
    """
    @others
#@nonl
#@+node:bwmulder.20040602224413:__init__
def __init__(self):
    
    self.undoer = basic_undomechanism()
    self.undoer.monitor_object = self
    

#@-node:bwmulder.20040602224413:__init__
#@+node:bwmulder.20040602224332:__getattr__
def __getattr__(self, attributename):
    self.attributename = attributename
    return self.catchall
#@nonl
#@-node:bwmulder.20040602224332:__getattr__
#@+node:bwmulder.20040602231914:catchall
def catchall(self, *args, **kwrds):
    print "delegator:", self.attributename, ":", args, kwrds
    getattr(self.undoer, self.attributename) (*args, **kwrds)
#@-node:bwmulder.20040602231914:catchall
#@+node:bwmulder.20040602231934:enable_category
def enable_category(self, l, monitor_class):
    
   for klass, external_attributename, internal_attributename in l:
      setattr(klass, external_attributename, monitor_class(
         external_attributename, internal_attributename, self))
    

#@-node:bwmulder.20040602231934:enable_category
#@-node:bwmulder.20040602224231:class delegator
#@+node:bwmulder.20040602225640:class basic_scalar_test
class basic_scalar_test_class(object):
    """Simple class for the basic_scalar_test"""
    pass

class basic_scalar_test(unittest.TestCase):
    @others
#@nonl
#@+node:bwmulder.20040602230124:test_scalar_basic
def test_scalar_basic(self):
    """
    Some basic testing for the scalar undoer.
    """
    if undo_tracing:
        self.m = m = delegator()
    else:
        self.m = m = basic_undomechanism()
    m.monitor_scalar(basic_scalar_test_class, "x")
    m.enable()
    # command 1
    c = basic_scalar_test_class()
    c.x = "First value"
    m.mark()
    # command 2	
    c.x = "second value"
    m.mark()
    # command 3
    c.x = "third value"
    m.mark()
    # command 4
    # Check that the second assignment can be undone
    assert c.x == "third value", c.x
    m.undo()
    # command 2
    assert c.x == "second value"
    m.undo()
    # command 1
    assert c.x == "First value", c.x
    m.undo()
    assert not hasattr(c, "x")
#@-node:bwmulder.20040602230124:test_scalar_basic
#@+node:bwmulder.20040604184443:test_linked_list
def test_linked_list(self):
    """
    Shows that the basic undo mechanism can be used to undo the construction of a linked list.
    """
    if undo_tracing:
        self.m = m = delegator()
    else:
        self.m = m = basic_undomechanism()

    # Create a linked list and check that the link operations can be undone.
    class root_class(object):
        pass
        
    class x(object):
        def __init__(self, name, next=None):
            self.name = name
            self.next = next
    
    def p(root):
        result = []
        r = root.root
        while r:
            result.append(r.name)
            r = r.next
        return result
            
    undoer = basic_undomechanism()
    
    m.monitor_scalar(root_class, "root")
    m.monitor_scalar(x, "name")
    m.monitor_scalar(x, "next")
    m.enable()
    
    root = root_class()
    root.root = x("first")
    root.root.next = x("second")
    m.mark()
    root.root.next.next = x("third")
    root.root.next.next.next = x("fourth")
    m.mark()
    assert p(root) == ["first", "second", "third", "fourth"]
    m.undo()
    assert  p(root) == ["first", "second"]
    m.redo()
    assert p(root) ==["first", "second", "third", "fourth"]
    
    # now try a few things with dictionaries.
    m.monitor_dict_attribute_in_class(root_class, "d")
    m.enable()
    root.d = {}
    m.mark()
    root.d = {'Some dict': 1}
    m.mark()
    root.d[1] = 2
    assert root.d == {'Some dict': 1, 1: 2}, root.d
    m.mark()
    m.undo()
    assert root.d == {'Some dict': 1}, root.d
    m.undo()
    assert root.d == {}, root.d
    
    
    
#@nonl
#@-node:bwmulder.20040604184443:test_linked_list
#@-node:bwmulder.20040602225640:class basic_scalar_test
#@+node:bwmulder.20040605104941:class basic_list_test
class basic_list_test_class(object):
    """Simple class to test the list undoer"""
    pass

class basic_list_test(unittest.TestCase):
    """
    Test the basic operations of the list class
    """
    @others
        
#@+node:bwmulder.20040605105746:test_lists_basic
def test_lists_basic(self):
    """
    Some basic testing for the list undoer.
    
    """
    if undo_tracing:
        self.m = m = delegator()
    else:
        self.m = m = basic_undomechanism()
    m.monitor_list_attribute_in_class(basic_list_test_class, "a")
    m.enable()
    c = basic_list_test_class()
    c.a = [1, 2, 3]
    c.a = c.a
    m.mark()
    assert m.commands_to_undo() == 1, m.commands_to_undo()
    assert m.commands_to_redo() == 0, m.commands_to_redo()
    c.a.append(5)
    m.mark()
    assert m.commands_to_undo() == 2, m.commands_to_undo()
    assert m.commands_to_redo() == 0, m.commands_to_redo()
    assert c.a == [1, 2, 3, 5], c.a
    m.undo()
    assert m.commands_to_undo() == 1, m.commands_to_undo()
    assert m.commands_to_redo() == 1, m.commands_to_redo()
    assert c.a == [1, 2, 3], c.a
    m.redo()
    assert m.commands_to_undo() == 2, m.commands_to_undo()
    assert m.commands_to_redo() == 0, m.commands_to_redo()
    assert c.a == [1, 2, 3, 5], c.a
    m.undo()
    assert m.commands_to_undo() == 1, m.commands_to_undo()
    assert m.commands_to_redo() == 1, m.commands_to_redo()
    m.undo()
    assert m.commands_to_undo() == 0, m.commands_to_undo()
    assert m.commands_to_redo() == 2, m.commands_to_redo()
    assert not hasattr(c, "a"), "A should not exist here"
    assert m.steps_stored() == 3, m.steps_stored()
    m.redo()
    m.redo()
    assert c.a == [1, 2, 3, 5], c.a
    del c.a[2]
    assert c.a == [1, 2, 5], c.a # 1
    m.mark()
    m.undo()
    assert c.a == [1, 2, 3, 5], c.a # 2
    m.redo()
    assert c.a == [1, 2, 5], c.a # 3
    x = c.a.pop()
    m.mark()
    assert c.a == [1, 2]
    m.undo()
    assert c.a == [1, 2, 5]

    

    


#@-node:bwmulder.20040605105746:test_lists_basic
#@+node:bwmulder.20040605174728:test_lists_replace
def test_lists_replace(self):
    """
    
    Test that list assignment works for an instance attribute that is put under the
    undo mechanism.
    
    """
    if undo_tracing:
        self.m = m = delegator()
    else:
        self.m = m = basic_undomechanism()
    c = basic_list_test_class()
    m.monitor_list_attribute_in_class(basic_list_test_class, "a")
    m.enable()
    c.a = [1, 2, 3]
    m.mark()
    m.undo()
    assert not hasattr(c,"a")
    m.redo()
    c.a.append(4)
    c.a.append(5)
    assert c.a == [1, 2, 3, 4, 5]
    m.mark()
    m.undo()
    assert c.a == [1, 2, 3]
    m.redo()
    assert c.a == [1, 2, 3, 4, 5]
    b = list_monitor(('a', 'b', 'c'), m)
    m.enable()
    b.append('d')
    assert b == ['a', 'b', 'c', 'd']
    m.mark()
    m.undo()
    assert b == ['a', 'b', 'c']
    m.redo()
    assert b == ['a', 'b', 'c', 'd']
    
    
#@-node:bwmulder.20040605174728:test_lists_replace
#@-node:bwmulder.20040605104941:class basic_list_test
#@+node:bwmulder.20040605180204:class basic_dict_test
class basic_dict_test_class(object):
    """Simple class to test the list undoer"""
    pass

class basic_dict_test(unittest.TestCase):
    """
    Test the basic operations of the list class
    """
    @others
        

#@+node:bwmulder.20040605180225:test_dicts_basic
def test_dicts_basic(self):
    """
    Some basic testing for the dict undoer.
    
    """
    trace_commands = False
    if undo_tracing:
        self.m = m = delegator()
    else:
        self.m = m = basic_undomechanism()
    m.monitor_dict_attribute_in_class(basic_dict_test_class, "a")
    m.enable()
    c = basic_dict_test_class()
    c.a = {1:1, 2:2, 3:3}
    c.a = c.a
    m.mark()
    if trace_commands:  print; m.print_commands(1)
    assert m.commands_to_undo() == 1, m.commands_to_undo()
    assert m.commands_to_redo() == 0, m.commands_to_redo()
    c.a[5] = 5
    m.mark()
    if trace_commands: m.print_commands(2)
    assert m.commands_to_undo() == 2, m.commands_to_undo()
    assert m.commands_to_redo() == 0, m.commands_to_redo()
    assert c.a == {1:1, 2:2, 3:3, 5:5}, c.a # 1
    m.undo()
    if trace_commands: m.print_commands(3)
    assert m.commands_to_undo() == 1, m.commands_to_undo()
    assert m.commands_to_redo() == 1, m.commands_to_redo()
    assert c.a == {1:1, 2:2, 3:3}, c.a # 2
    m.redo()
    if trace_commands: m.print_commands(4)
    assert m.commands_to_undo() == 2, m.commands_to_undo()
    assert m.commands_to_redo() == 0, m.commands_to_redo()
    assert c.a == {1:1, 2:2, 3:3, 5:5}, c.a # 3
    m.undo()
    if trace_commands: m.print_commands(5)
    assert m.commands_to_undo() == 1, m.commands_to_undo()
    assert m.commands_to_redo() == 1, m.commands_to_redo()
    assert c.a == {1:1, 2:2, 3:3}, c.a # 4
    m.undo()
    if trace_commands: m.print_commands(6)
    assert m.commands_to_undo() == 0, m.commands_to_undo()
    assert m.commands_to_redo() == 2, m.commands_to_redo()
    assert not hasattr(c, "a"), "A should not exist here"
    assert m.steps_stored() == 3, m.steps_stored()
    m.redo()
    if trace_commands: m.print_commands(7)
    assert c.a == {1:1, 2:2, 3:3}, c.a # 5
    m.redo()
    if trace_commands: m.print_commands(8)
    assert c.a == {1:1, 2:2, 3:3, 5:5}, c.a # 6
    del c.a[5]
    assert c.a == {1:1, 2:2, 3:3}, c.a # 7
    m.mark()
    if trace_commands: m.print_commands(9)
    m.undo()
    if trace_commands: m.print_commands(10)
    assert c.a == {1:1, 2:2, 3:3, 5:5}, c.a # 8
    m.redo()
    if trace_commands: m.print_commands(11)
    assert c.a == {1:1, 2:2, 3:3}, c.a # 9
    del c.a[2]
    m.mark()
    if trace_commands: m.print_commands(12)
    assert c.a == {1:1, 3:3}, c.a # 10
    m.undo()
    if trace_commands: m.print_commands(13)
    assert c.a == {1:1, 2:2, 3:3}, c.a # 11
#@nonl
#@-node:bwmulder.20040605180225:test_dicts_basic
#@+node:bwmulder.20040605180245:test_dicts_replace
def test_dicts_replace(self):
    """
    
    Test that dict assignment works for an instance attribute that is put under the
    undo mechanism.
    
    """
    if undo_tracing:
        self.m = m = delegator()
    else:
        self.m = m = basic_undomechanism()
    c = basic_dict_test_class()
    m.monitor_dict_attribute_in_class(basic_dict_test_class, "a")
    m.enable()
    c.a = {1:1, 2:2, 3:3}
    m.mark()
    m.undo()
    assert not hasattr(c,"a")
    m.redo()
    c.a[4] = 4
    c.a[5] = 5
    assert c.a == {1:1, 2:2, 3:3, 4:4, 5:5}, c.a # 1
    m.mark()
    m.undo()
    assert c.a == {1:1, 2:2, 3:3}, c.a # 2
    m.redo()
    assert c.a ==  {1:1, 2:2, 3:3, 4:4, 5:5}, c.a # 3
    b = dict_monitor({'a':'a', 'b':'b', 'c':'c'}, m)
    m.enable()
    b['d'] = 'd'
    assert b == {'a':'a', 'b':'b', 'c':'c', 'd':'d'}, b # 4
    m.mark()
    m.undo()
    assert b == {'a':'a', 'b':'b', 'c':'c'}, b # 5
    m.redo()
    assert b == {'a':'a', 'b':'b', 'c':'c', 'd':'d'}, b # 6
    
    
#@nonl
#@-node:bwmulder.20040605180245:test_dicts_replace
#@-node:bwmulder.20040605180204:class basic_dict_test
#@+node:bwmulder.20040602230426:test_main
def test_main():

    test_support.run_unittest(
        basic_scalar_test,
        basic_list_test,					
        basic_dict_test)
#@nonl
#@-node:bwmulder.20040602230426:test_main
#@-node:bwmulder.20040602221559:basic_undo_test.py
#@-node:EKR.20040611044600:Mulder undo
#@+node:ekr.20040916073636:@thin ConceptualSort.py
"""
A plugin to enhance the EditAttributes.py plugin.
Puts ConceptualSort command in the Outline Menu.
"""

@language python
@tabwidth -4

<< about this plugin >>
__version__ = "0.3"
<< version history >>
<< imports >>

@others

haveseen = weakref.WeakKeyDictionary()

if Tk and Pmw and weakref:
    leoPlugins.registerHandler(('new2','open2'), addCommand)
    __version__ = ".2"
    g.plugin_signon( __name__ )
#@nonl
#@+node:ekr.20040916073636.1:<< about this plugin >>
@

This plugin is to be used with the EditAttributes.py plugin. What it does is:
1. Puts a command in Outline called ConceptualSort. This will prompt you for a concept to sort by.
2. It will sort by uA's in all the siblings of the current node.

This gives the user some more flexibility in how they want to arrange their nodes. Nodes without the attribute in question go to the bottom of the sort. :)

Version .2 improvements:
- Now supports level 0

New features:
1. The dialog has been redone. The user can:
a. Select which attribute he wants to sort on by clicking on the Attribute box.
b. Select the type of sort he wants by clicking on the radio buttons. which are
1. Normal. just like the previous version
2. Reversed. Like normal but the results are reversed.
3. Used defined. For advanced users. The text box is where a user can type in their own python code to sort the nodes-attributes. There is no need for a def. That gets appended to the beginning of the code. It prototype looks like:
def( a, b, att ):

where a and b are nodes and att is dictionary of the nodes and the respective value of the selected attribute.

There is no need to indent on the first level since indentation is added at compile time. :)
#@nonl
#@-node:ekr.20040916073636.1:<< about this plugin >>
#@+node:ekr.20040916075741:<< version history >>
@

0.3 EKR:
    - Converted to outline.
    - Style improvements.
    - Changes for 4.2 code base in hit().
    - Use 'new2' instead of 'start2' hook.
#@nonl
#@-node:ekr.20040916075741:<< version history >>
#@+node:ekr.20040916073636.2:<< imports >>
import leoGlobals as g
import leoPlugins

try:
    import Tkinter as Tk
except ImportError:
    Tk = g.cantImport("Tk")

try:
    import Pmw
except:
    Pmw = g.cantImport("Pmw" )
    
try:
    import weakref
except ImportError:
    weakref = g.cantImport("weakref")
#@nonl
#@-node:ekr.20040916073636.2:<< imports >>
#@+node:ekr.20040916074337:class CSFrontend:
class CSFrontend:
    
    @others
#@nonl
#@+node:ekr.20040916074337.1:__init__
def __init__( self , c ):
    self.c = c
    self.dialog = Pmw.Dialog( buttons = ( 'Cancel', 'Sort' ),
    title = 'Conceptual Sort', command = self.execute )
    hull = self.dialog.component( 'hull' )
    f = Tk.Frame( hull )
    f.pack( side = 'top' )
    self.parent = p = c.currentVnode().parent()
    children = self.children = []
    self.hasparent = 1
    if p:
        nc = p.numberOfChildren()
        for i in xrange( nc ):
            children.append( p.nthChild( i ) )
    else:
        p = c.rootVnode()
        self.hasparent = 0
        while p:
            children.append( p )
            p = p.next()

    alist = list( self._makeAttrList( children ) )
    self.omuatts = Pmw.OptionMenu( f, labelpos = 'n', label_text = 'Attribute:',
    items = alist )
    self.omuatts.pack( side = 'left')
    self.rdbutts = Pmw.RadioSelect( f, buttontype = 'radiobutton', orient = 'vertical',
    labelpos = 'n',
    label_text = 'Sort type' ,
    hull_borderwidth = 2,
    hull_relief = 'ridge')
    map( lambda a: self.rdbutts.add( a ), ( 'Normal', 'Reversed', 'User defined' ) )
    self.rdbutts.setvalue( 'Normal' )
    self.rdbutts.pack( side = 'right' )
    self.stxt = Pmw.ScrolledText( hull,
    labelpos = 'nw',
    label_text = 'def user_sort( a, b , atts):',
    usehullsize = 1,
    hull_width = 200, hull_height = 100 )
    txt = self.stxt.component( 'text' )
    txt.configure( background = 'white', foreground = 'blue' )
    self.stxt.pack( side = 'top' )
    self.dialog.activate( globalMode = 'nograb' )
#@nonl
#@-node:ekr.20040916074337.1:__init__
#@+node:ekr.20040916074337.2:_makeAttrList
def _makeAttrList( self, nodes ):
    from sets import Set
    atts = Set()
    for child in nodes:
        if str(child.__class__) == 'leoNodes.vnode':
            t = child.t
        else:
            t = child.v.t
        if hasattr( t, 'unknownAttributes' ):
            uAs = t.unknownAttributes.keys()
            map( lambda a: atts.add( a ), uAs )
    return atts
#@nonl
#@-node:ekr.20040916074337.2:_makeAttrList
#@+node:ekr.20040916074337.3:execute
def execute( self, name ):
    if name == 'Cancel':
        self.dialog.destroy()
    else:
        self.targetAttr = self.omuatts.getvalue()
        if self.targetAttr == '':
            self.dialog.destroy()
            return
        self.type = self.rdbutts.getvalue()
        atts = buildAttList( self.children, self.targetAttr )
        lcsort = lambda a, b, atts = atts: csort( a,b ,atts )
        if self.type == 'Normal':
            self.children.sort( lcsort )
        elif self.type == 'Reversed':
            self.children.sort( lcsort )
            self.children.reverse()
        else:
            bcode = self.stxt.get( '1.0', 'end' )
            bcode = bcode.split( '\n' )
            bcode = map( lambda a: ' ' + a, bcode )
            bcode = '\n'.join( bcode )
            bcode = bcode + '\n'
            header = 'def user_sort( a, b, atts):\n'
            code = header + bcode
            usort = compile( code, 'user_sort', 'exec' )
            def lcsort( a, b, atts =atts ):
                z = {}
                exec usort in {}, z
                rv = z[ 'user_sort' ]( a, b, atts )
                return rv
            self.children.sort( lcsort )
        if self.hasparent:
            move( self.c, self.children, self.parent )
        else:
            root = self.c.rootVnode()
            move2( self.c, self.children, root )
        self.dialog.destroy()
#@nonl
#@-node:ekr.20040916074337.3:execute
#@-node:ekr.20040916074337:class CSFrontend:
#@+node:ekr.20040916074337.4:getConcept
def getConcept( c ):

    CSFrontend( c )

#@-node:ekr.20040916074337.4:getConcept
#@+node:ekr.20040916074337.5:move
def move( c, children , parent):

    c.beginUpdate()
    for n, ch in enumerate( children ):
        ch.moveToNthChildOf( parent, n )
    c.endUpdate()

#@-node:ekr.20040916074337.5:move
#@+node:ekr.20040916074337.6:move2
def move2( c, children , oroot):

    c.beginUpdate()
    children[ 0 ].moveToRoot( oroot )
    z1 = children[ 0 ]
    for z in children[ 1: ]:
        z.moveAfter( z1 )
        z1 = z
    c.endUpdate()

#@-node:ekr.20040916074337.6:move2
#@+node:ekr.20040916074337.7:buildAttList
def buildAttList( children, concept ):

    atts = {}
    for child in children:
        if str(child.__class__) == 'leoNodes.vnode':
            t = child.t
        else:
            t = child.v.t
        atts[ child ] = None
        if hasattr( t, 'unknownAttributes' ):
            uA = t.unknownAttributes
            atts[ child ] = uA.get( concept )
    return atts

#@-node:ekr.20040916074337.7:buildAttList
#@+node:ekr.20040916074337.8:getChildren
def getChildren( v ):

    i = v.numberOfChildren()
    children = []
    for z in xrange( i ):
        chi = v.nthChild( z )
        children.append( chi )
    return children

#@-node:ekr.20040916074337.8:getChildren
#@+node:ekr.20040916074337.9:csort
def csort( a, b, atdict ):
    
    a1 = atdict[ a ]
    b1 = atdict[ b ]
    if a1 == None and b1 != None: return 1
    elif a1 != None and b1 == None: return -1
    elif a1 > b1: return 1
    elif a1 < b1: return -1
    else: return 0


#@-node:ekr.20040916074337.9:csort
#@+node:ekr.20040916074337.10:addCommand
def addCommand( tag, keywords ):
    
    c = keywords.get('c') or keywords.get('new_c')
    if haveseen.has_key( c ): return
    
    table = ( ( "Conceptual Sort" , None, lambda c = c: getConcept( c )), )
    men = c.frame.menu
    men.createMenuItemsFromTable( "Outline" , table )
#@-node:ekr.20040916074337.10:addCommand
#@-node:ekr.20040916073636:@thin ConceptualSort.py
#@+node:ekr.20040722115324:Create a configurable pretty printer plugin
#@-node:ekr.20040722115324:Create a configurable pretty printer plugin
#@+node:ekr.20040918165144.1:Draw config dialog
#@-node:ekr.20040918165144.1:Draw config dialog
#@+node:ekr.20040918165144.2:Parse jEdit description files
#@-node:ekr.20040918165144.2:Parse jEdit description files
#@+node:ekr.20040918165144.3:Convert to Python indices
#@-node:ekr.20040918165144.3:Convert to Python indices
#@+node:ekr.20040918165144.4:Convert to new Undo
#@-node:ekr.20040918165144.4:Convert to new Undo
#@+node:ekr.20040918165144.5:New syntax coloring (threaded)
#@-node:ekr.20040918165144.5:New syntax coloring (threaded)
#@+node:ekr.20040918165144.6:Check Import feature
#@-node:ekr.20040918165144.6:Check Import feature
#@+node:ekr.20040918165144.7:Emacs-style keystroke handling
#@-node:ekr.20040918165144.7:Emacs-style keystroke handling
#@+node:ekr.20040918165144.8:Maybe:  add translation feature to g.es
#@-node:ekr.20040918165144.8:Maybe:  add translation feature to g.es
#@+node:ekr.20040918165144.9:Write script to find args to g.es()
#@-node:ekr.20040918165144.9:Write script to find args to g.es()
#@+node:ekr.20040918165144.10:New atFile organization (user tangle/untangle)
#@-node:ekr.20040918165144.10:New atFile organization (user tangle/untangle)
#@+node:ekr.20040918165350:Create status line class
#@-node:ekr.20040918165350:Create status line class
#@-node:ekr.20040918165144:4.3 Prototypes as plugins
#@+node:ekr.20040918165427.2:** Add incremental search to search plugin (make find panel smaller)
#@-node:ekr.20040918165427.2:** Add incremental search to search plugin (make find panel smaller)
#@+node:ekr.20040918165427.4:Autocompletion dictionary files
@nocolor

Developers
By: jasonic ( Jason Cunliffe ) 
 @dictionary   
2003-08-21 07:51

Auto-completion dictionary files would be excellent.
Ideally each Leo language extension could just point to a separate .dict file. 

Hopefully we can build some Leo plugin utilities to generate these .dict files by parsing any file you'd like to use a 'source' for Leo dictionary. They might need manual cleanup, but vcould be big timesaver, especially for XML formats and the like. Great to be able to sahre these easily. 

Interesting uses for Leo dictionaries beyond just autocompletion. 
I am thinking they might open the door to some powerful macro/template behavior. 

For example, you load a special dictionary to help certain kinds of repetitive formatted content. CSS and XSLT could be good candidates, but also any kind of mild databases or lists. Documentation.

So first we need basic dicts for Leo supported languages: Python, Perl, Javascript etc..

Then we need to consider that any Leo Node could have its own dictionary defined inline..

@dictionary filepath-to-custom-leo-dict

With collaborative LeoN this would be very useful because connected Leo sessions could invoke each other's dictionaries!!

- Jason  
#@-node:ekr.20040918165427.4:Autocompletion dictionary files
#@+node:ekr.20040918165427.5:Leo to docbook xml plugin: Leo2AsciiDoc
http://sourceforge.net/forum/message.php?msg_id=2388444
By: mdawson

I use DocBook XML for my computer documentation, and lately for
publishing just about any text document.  Naturally, I wanted an
easy way to use DocBook to publish Leo outlines.

I've written a small Leo module, called Leo2AsciiDoc, that enables
automatic publication of a plain text Leo outline to HTML or PDF, or
as a web site or man page.  Stuart Rackham's AsciiDoc program (in
Python) is what makes this possible.
    Leo2AsciiDoc exports a Leo outline to a text file, from whence
it can be converted to DocBook XML by AsciiDoc, and then
automatically published via DocBook to HTML or PDF.

One Leo outline can contain any number of documents, or web sites
(via DocBook Website).

I'm also learning Literate Programming, and am happy to be able to
automatically publish (via make) a program's source and
documentation from Leo.

An example of the product is the paged HTML documentation for the
module at:
    http://devguide.leo.marshallresearch.ca

The web page for Leo2AsciiDoc is
at:
    http://leo.marshallresearch.ca

That web site is produced from a Leo outline.

    ----------------------------------------
    Michael Dawson
#@nonl
#@-node:ekr.20040918165427.5:Leo to docbook xml plugin: Leo2AsciiDoc
#@+node:ekr.20040918165427.6:Finish wx plugin & improve how Leo uses indices
#@-node:ekr.20040918165427.6:Finish wx plugin & improve how Leo uses indices
#@-node:ekr.20040918165427:To do in 4.3
#@-all
#@nonl
#@-node:edream.110203113231:@thin pluginsNotes.txt
#@-leo
