#@+leo-ver=4-thin
#@+node:ekr.20040117181936:@thin ../doc/leoToDo.txt
#@@nocolor

#@+all
#@+node:EKR.20040609091327:To do: 4.3: configuration, plugins & translation
#@+node:ekr.20040917080403:Fix bugs reported late in 4.2 cycle
#@+node:ekr.20040917080403.1:Clean up code in runOpenFileDialog
@

Replace the try/except code with

    if multiple and g.CheckVersion(sys.version,"2.3") and g.CheckVersion(self.root.getvar("tk_patchLevel"),"8.4"):
#@nonl
#@+node:ekr.20031218072017.4057:app.gui.Tkinter file dialogs
def runOpenFileDialog(self,title,filetypes,defaultextension,multiple=False):

    """Create and run an Tkinter open file dialog ."""
    
    # askopenfilenames only exists in Python 2.3 or later.
    # Not only that, askopenfilenames apparently doesn't work with Tk versions earlier than 8.4.
    
    # if multiple and g.CheckVersion(sys.version,"2.3") and g.CheckVersion(self.root.getvar("tk_patchLevel"),"8.4"):
    if multiple and g.CheckVersion(sys.version,"2.3"):
        try:
            files = tkFileDialog.askopenfilenames(
                title=title, filetypes=filetypes)
            return list(files)
        except:
            # Work around an apparent Linux bug in askopenfilenames.
            file = tkFileDialog.askopenfilename(
                title=title, filetypes=filetypes)
            return [file]
    else:
        file = tkFileDialog.askopenfilename(
            title=title, filetypes=filetypes)
        if multiple: return [file]
        else:        return file
    
        # DTHEIN 2004.01.31: remove default extension on open,
        # so that we can open files without extensions

def runSaveFileDialog(self,initialfile,title,filetypes,defaultextension):

    """Create and run an Tkinter save file dialog ."""

    return tkFileDialog.asksaveasfilename(
        initialfile=initialfile,
        title=title,
        filetypes=filetypes)
    # EKR: 2004.01.31: remove default extensions on save too.
    # defaultextension=defaultextension)
#@nonl
#@-node:ekr.20031218072017.4057:app.gui.Tkinter file dialogs
#@-node:ekr.20040917080403.1:Clean up code in runOpenFileDialog
#@+node:ekr.20040917063309:Fix bug re non-existent filename on command line
@killcolor
When I start leo from the command line (linux) with e.g.

[davides@icarus utilities]$ leodev test.leo

(leodev is an alias to the latest cvs version)

and "test.leo" does not exist, the Log pane says

File not found: /localstore/computer/davides/SW/test/utilities/test.leo

but then I would expect that the current file name were set to "test.leo", so that when I do a save, "test.leo" gets saved. It is set to "untitled" instead, so that in essence in this case Leo ignores my command line argument. I would also change the log message to say something like "New file" or similar. Cf. what happens when you do e.g. "vi newfile.txt" (or "emacs", or even "edit" in ms-dos).

Davide 
#@nonl
#@-node:ekr.20040917063309:Fix bug re non-existent filename on command line
#@+node:ekr.20040918093558:Fix Font dialog problems on the Mac
@killcolor

There seems to be a bug when setting font preferences on the Mac.  If I 
check more than one box and then apply a font change, the change is set 
correctly when I restart Leo but doesn't always preview correctly 
before that.  I set headline and body text to the same font and the 
headlines did not change to show the new font until I started up again. 
  Not a big deal.

-- Dan Windler
#@-node:ekr.20040918093558:Fix Font dialog problems on the Mac
#@-node:ekr.20040917080403:Fix bugs reported late in 4.2 cycle
#@+node:ekr.20040919101448:Do first: can't be done in plugins
#@+node:ekr.20040916122326.1:Undo...
#@+node:EKR.20040601133022.1:(Finish the new undo)
#@+node:EKR.20040606195417.5:To do
@nocolor

- (done) Create u.saveNode, u.saveNodeAndChildren, u.saveListOfNodes.
- Create u.appendSavedNode.
- Use u.appendSavedNode in Change All command.
- Unit tests.
#@nonl
#@-node:EKR.20040606195417.5:To do
#@+node:EKR.20040528075307:u.saveTree
def saveTree (self,p,treeInfo=None):
    
    """Create all info needed to handle a general undo operation."""

    # WARNING: read this before doing anything "clever"
    << about u.saveTree >>
    
    u = self ; topLevel = (treeInfo == None)
    if topLevel: treeInfo = []

    # Add info for p.v and p.v.t.  Duplicate tnode info is harmless.
    data = (p.v,p.v.createUndoInfo(),p.v.t.createUndoInfo())
    treeInfo.append(data)

    # Recursively add info for the subtree.
    child = p.firstChild()
    while child:
        self.saveTree(child,treeInfo)
        child = child.next()

    # if topLevel: g.trace(treeInfo)
    return treeInfo
#@+node:EKR.20040530114124:<< about u.saveTree >>
@ 
The old code made a free-standing copy of the tree using v.copy and t.copy.  This looks "elegant" and is WRONG.  The problem is that it can not handle clones properly, especially when some clones were in the "undo" tree and some were not.   Moreover, it required complex adjustments to t.vnodeLists.

Instead of creating new nodes, the new code creates all information needed to properly restore the vnodes and tnodes.  It creates a list of tuples, on tuple for each vnode in the tree.  Each tuple has the form,

(vnodeInfo, tnodeInfo) where vnodeInfo and tnodeInfo are dicts contain all info needed to recreate the nodes.  The v.createUndoInfoDict and t.createUndoInfoDict methods correspond to the old v.copy and t.copy methods.

Aside:  Prior to 4.2 Leo used a scheme that was equivalent to the createUndoInfoDict info, but quite a bit uglier.
#@-node:EKR.20040530114124:<< about u.saveTree >>
#@-node:EKR.20040528075307:u.saveTree
#@+node:EKR.20040606195417.2:u.saveNodeAndChildren
def saveNodeAndChildren (self,p):
    
    """Create all info needed for a node and all its immediate children."""

    u = self
    treeInfo = []

    # Add info for p.v and p.v.t.  Duplicate tnode info is harmless.
    data = (p.v,p.v.createUndoInfo(),p.v.t.createUndoInfo())
    treeInfo.append(data)

    # Add info for all children.
    child = p.firstChild()
    while child:
        data = (child.v,child.v.createUndoInfo(),child.v.t.createUndoInfo())
        treeInfo.append(data)
        child = child.next()

    return treeInfo

#@-node:EKR.20040606195417.2:u.saveNodeAndChildren
#@+node:EKR.20040606195417.1:u.saveNode
def saveNode (self,p):
    
    """Create all info for a single vnode."""
    
    u = self

    treeInfo = (p.v,p.v.createUndoInfo(),p.v.t.createUndoInfo())
    return treeInfo
#@nonl
#@-node:EKR.20040606195417.1:u.saveNode
#@+node:EKR.20040606195417.3:u.saveListOfNodes
def saveListOfNodes (self,listOfVnodes):
    
    """Create all info for a list of vnodes."""
    
    u = self ; treeInfo = []

    for v in listOfVnodes:
        data = (v,v.createUndoInfo(),v.t.createUndoInfo())
        treeInfo.append(data)

    return treeInfo
#@nonl
#@-node:EKR.20040606195417.3:u.saveListOfNodes
#@-node:EKR.20040601133022.1:(Finish the new undo)
#@+node:ekr.20040323084434:(Add granularity setting for undo)
#@+node:ekr.20040328111626:Notes
@nocolor

- "node" and "line" granularity are easy:
	- Node: just replace "new" text with present text.
	- Line: create a new node if the present line isn't the same as the "line" attribute of the undo node.
		(Very similar to node granularity.

- "word" granularity is still fairly easy:
	- inWord flag is True or False.
	- create a new node if we are starting a new word.

- copy/paste always start new undo typing nodes.
	
#@nonl
#@-node:ekr.20040328111626:Notes
#@+node:ekr.20031218072017.3606:undo.__init__ & clearIvars
def __init__ (self,c):
    
    u = self ; u.c = c
    
    # Ivars to transition to new undo scheme...
    u.debug = False # True: enable debugging code in new undo scheme.
    u.debug_print = False # True: enable print statements in debug code.
    u.new_undo = True # True: enable new debug code.

    # Statistics comparing old and new ways (only if u.debug is on).
    u.new_mem = 0
    u.old_mem = 0

    # State ivars...
    u.undoType = "Can't Undo"
    # These must be set here, _not_ in clearUndoState.
    u.redoMenuLabel = "Can't Redo"
    u.undoMenuLabel = "Can't Undo"
    u.realRedoMenuLabel = "Can't Redo"
    u.realUndoMenuLabel = "Can't Undo"
    u.undoing = False # True if executing an Undo command.
    u.redoing = False # True if executing a Redo command.
    
    # New in 4.2...
    << Define optional ivars >>
    << define redoDispatchDict >>
    << define undoDispatchDict >>
    u.updateSetChangedFlag = True
    u.redrawFlag = True
#@nonl
#@+node:ekr.20031218072017.3607:clearIvars
def clearIvars (self):
    
    u = self
    
    u.p = None # The position/node being operated upon for undo and redo.
    for ivar in u.optionalIvars:
        setattr(u,ivar,None)
#@nonl
#@-node:ekr.20031218072017.3607:clearIvars
#@+node:ekr.20031218072017.3604:<< Define optional ivars >>
# New in 4.2: this is now an ivar, not a global, and it's a list, not a tuple.

u.optionalIvars = [
    "lastChild",
    "parent","oldParent",
    "back","oldBack",
    "n","oldN","oldV",
    "oldText","newText",
    "oldSel","newSel",
    "sort","select",
    "oldTree","newTree", # Added newTree 10/14/03
    "yview",
    # For incremental undo typing...
    "leading","trailing",
    "oldMiddleLines","newMiddleLines",
    "oldNewlines","newNewlines" ]
#@nonl
#@-node:ekr.20031218072017.3604:<< Define optional ivars >>
#@+node:EKR.20040526072519:<< define redoDispatchDict >>
u.redoDispatchDict = {
    "Change":             u.redoTyping,
    "Change All":         u.redoChangeAll,
    "Change Headline":    u.redoChangeHeadline,
    "Clone Node":         u.redoClone,
    "Convert All Blanks": u.redoReplaceNodesContents,
    "Convert All Tabs":   u.redoReplaceNodesContents,
    "Convert Blanks":     u.redoTyping,
    "Convert Tabs":       u.redoTyping,
    "Cut":                u.redoTyping,
    "Cut Node":           u.redoDeleteNode,
    "De-Hoist":           u.redoDehoist,
    "Delete":             u.redoTyping,
    "Delete Node":        u.redoDeleteNode,
    "Demote":             u.redoDemote,
    "Drag":               u.redoMoveNode,
    "Drag & Clone":       u.redoClone,
    "Extract":            u.redoReplaceNodes,
    "Extract Names":      u.redoReplaceNodes,
    "Extract Section":    u.redoReplaceNodes,
    "Hoist":              u.redoHoist,
    "Import":             u.redoInsertNodes,
    "Indent":             u.redoTyping,
    "Insert Node":        u.redoInsertNodes,
    "Move Down":          u.redoMoveNode,
    "Move Left":          u.redoMoveNode,
    "Move Right":         u.redoMoveNode,
    "Move Up":            u.redoMoveNode,
    "Paste":              u.redoTyping,
    "Paste Node":         u.redoInsertNodes,
    "Pretty Print":       u.redoChangeAll,
    "Promote":            u.redoPromote,
    "Read @file Nodes":   u.redoReplaceNodes,
    "Reformat Paragraph": u.redoTyping,
    "Sort Children":      u.redoSortChildren,
    "Sort Siblings":      u.redoSortSiblings,
    "Sort Top Level":     u.redoSortTopLevel,
    "Typing":             u.redoTyping,
    "Undent":             u.redoTyping }
#@nonl
#@-node:EKR.20040526072519:<< define redoDispatchDict >>
#@+node:EKR.20040526075238:<< define undoDispatchDict >>
u.undoDispatchDict = {
    "Change":             u.undoTyping,
    "Change All":         u.undoChangeAll,
    "Change Headline":    u.undoChangeHeadline,
    "Clone Node":         u.undoClone,
    "Convert All Blanks": u.undoReplaceNodesContents,
    "Convert All Tabs":   u.undoReplaceNodesContents,
    "Convert Blanks":     u.undoTyping,
    "Convert Tabs":       u.undoTyping,
    "Cut":                u.undoTyping,
    "Cut Node":           u.undoDeleteNode,
    "De-Hoist":           u.undoDehoist,
    "Delete":             u.undoTyping,
    "Delete Node":        u.undoDeleteNode,
    "Demote":             u.undoDemote,
    "Drag":               u.undoMoveNode,
    "Drag & Clone":       u.undoDragClone, # redo uses redoClone.
    "Extract":            u.undoReplaceNodes,
    "Extract Names":      u.undoReplaceNodes,
    "Extract Section":    u.undoReplaceNodes,
    "Hoist":              u.undoHoist,
    "Import":             u.undoInsertNodes,
    "Indent":             u.undoTyping,
    "Insert Node":        u.undoInsertNodes,
    "Move Down":          u.undoMoveNode,
    "Move Left":          u.undoMoveNode,
    "Move Right":         u.undoMoveNode,
    "Move Up":            u.undoMoveNode,
    "Paste":              u.undoTyping,
    "Paste Node":         u.undoInsertNodes,
    "Pretty Print":       u.undoChangeAll,
    "Promote":            u.undoPromote,
    "Read @file Nodes":   u.undoReplaceNodes,
    "Reformat Paragraph": u.undoTyping,
    "Sort Children":      u.undoSortChildren,
    "Sort Siblings":      u.undoSortSiblings,
    "Sort Top Level":     u.undoSortTopLevel,
    "Typing":             u.undoTyping,
    "Undent":             u.undoTyping }
#@nonl
#@-node:EKR.20040526075238:<< define undoDispatchDict >>
#@-node:ekr.20031218072017.3606:undo.__init__ & clearIvars
#@+node:ekr.20031218072017.1421:<< get config options >>
@ Rewritten 10/11/02 as follows:

1. We call initConfigParam and initBooleanConfigParam to get the values.

The general purpose code will enter all these values into configDict.  This allows update() to write the configuration section without special case code.  configDict is not accessible by the user.  Rather, for greater speed the user access these values via the ivars of this class.

2. We pass the ivars themselves as params so that default initialization is done in the ctor, as would normally be expected.
@c

self.at_root_bodies_start_in_doc_mode = self.initBooleanConfigParam(
    "at_root_bodies_start_in_doc_mode",self.at_root_bodies_start_in_doc_mode)
    
encoding = self.initConfigParam(
    "config_encoding",self.config_encoding)
    
if g.isValidEncoding(encoding):
    self.config_encoding = encoding
else:
    g.es("bad config_encoding: " + encoding)
    
self.create_nonexistent_directories = self.initBooleanConfigParam(
    "create_nonexistent_directories",self.create_nonexistent_directories)
    
encoding = self.initConfigParam(
    "default_derived_file_encoding",self.default_derived_file_encoding)

if g.isValidEncoding(encoding):
    self.default_derived_file_encoding = encoding
else:
    g.es("bad default_derived_file_encoding: " + encoding)
    
encoding = self.initConfigParam(
    "new_leo_file_encoding",
    self.new_leo_file_encoding)

if g.isValidEncoding(encoding):
    self.new_leo_file_encoding = encoding
else:
    g.es("bad new_leo_file_encoding: " + encoding)

self.output_initial_comment = self.initConfigParam(
    "output_initial_comment",self.output_initial_comment)

self.output_newline = self.initConfigParam(
    "output_newline",self.output_newline)

self.read_only = self.initBooleanConfigParam(
    "read_only",self.read_only)

self.relative_path_base_directory = self.initConfigParam(
    "relative_path_base_directory",self.relative_path_base_directory)
    
self.redirect_execute_script_output_to_log_pane = self.initBooleanConfigParam(
    "redirect_execute_script_output_to_log_pane",
    self.redirect_execute_script_output_to_log_pane)
    
self.remove_sentinels_extension = self.initConfigParam(
    "remove_sentinels_extension",self.remove_sentinels_extension)

self.save_clears_undo_buffer = self.initBooleanConfigParam(
    "save_clears_undo_buffer",self.save_clears_undo_buffer)
    
self.stylesheet = self.initConfigParam(
    "stylesheet",self.stylesheet)
    
encoding = self.initConfigParam(
    "tk_encoding",self.tkEncoding)
    
if encoding and len(encoding) > 0: # May be None.
    if g.isValidEncoding(encoding):
        self.tkEncoding = encoding
    else:
        g.es("bad tk_encoding: " + encoding)
        
# New in 4.2
self.trailing_body_newlines = self.initConfigParam(
    "trailing_body_newlines",self.trailing_body_newlines)

# TO BE REMOVED
g.app.use_gnx = self.initBooleanConfigParam(
    "use_gnx",g.app.use_gnx)
    
self.use_plugins = self.initBooleanConfigParam(
    "use_plugins",self.use_plugins)

self.use_psyco = self.initBooleanConfigParam(
    "use_psyco",self.use_psyco)
    
self.undo_granularity = self.initConfigParam(
    "undo_granularity",self.undo_granularity)
    
# TO BE REMOVED
self.write_old_format_derived_files = self.initBooleanConfigParam(
    "write_old_format_derived_files",self.write_old_format_derived_files)

# New in 4.2
self.write_strips_blank_lines = self.initBooleanConfigParam(
    "write_strips_blank_lines",self.write_strips_blank_lines)
    
#g.trace("write_strips_blank_lines",self.write_strips_blank_lines)
#g.trace("trailing_body_newlines",self.trailing_body_newlines)
#@nonl
#@-node:ekr.20031218072017.1421:<< get config options >>
#@+node:ekr.20031218072017.1490:setUndoTypingParams
@ This routine saves enough information so a typing operation can be undone and redone.

We do nothing when called from the undo/redo logic because the Undo and Redo commands merely reset the bead pointer.
@c

def setUndoTypingParams (self,p,undo_type,oldText,newText,oldSel,newSel,oldYview=None):
    
    # g.trace(undo_type,p,"old:",oldText,"new:",newText)
    u = self ; c = u.c
    << return if there is nothing to do >>
    << init the undo params >>
    << compute leading, middle & trailing  lines >>
    << save undo text info >>
    << save the selection and scrolling position >>
    << adjust the undo stack, clearing all forward entries >>
    u.setUndoTypes() # Recalculate the menu labels.
    return d
#@nonl
#@+node:ekr.20040324061854:<< return if there is nothing to do >>
if u.redoing or u.undoing:
    return None

if undo_type == None:
    return None

if undo_type == "Can't Undo":
    u.clearUndoState()
    return None

if oldText == newText:
    # g.trace("no change")
    return None
#@nonl
#@-node:ekr.20040324061854:<< return if there is nothing to do >>
#@+node:ekr.20040324061854.1:<< init the undo params >>
# Clear all optional params.
for ivar in u.optionalIvars:
    setattr(u,ivar,None)

# Set the params.
u.undoType = undo_type
u.p = p
#@nonl
#@-node:ekr.20040324061854.1:<< init the undo params >>
#@+node:ekr.20031218072017.1491:<< compute leading, middle & trailing  lines >>
@ Incremental undo typing is similar to incremental syntax coloring.  We compute the number of leading and trailing lines that match, and save both the old and new middle lines.

NB: the number of old and new middle lines may be different.
@c

old_lines = string.split(oldText,'\n')
new_lines = string.split(newText,'\n')
new_len = len(new_lines)
old_len = len(old_lines)
min_len = min(old_len,new_len)

i = 0
while i < min_len:
    if old_lines[i] != new_lines[i]:
        break
    i += 1
leading = i

if leading == new_len:
    # This happens when we remove lines from the end.
    # The new text is simply the leading lines from the old text.
    trailing = 0
else:
    i = 0
    while i < min_len - leading:
        if old_lines[old_len-i-1] != new_lines[new_len-i-1]:
            break
        i += 1
    trailing = i
    
# NB: the number of old and new middle lines may be different.
if trailing == 0:
    old_middle_lines = old_lines[leading:]
    new_middle_lines = new_lines[leading:]
else:
    old_middle_lines = old_lines[leading:-trailing]
    new_middle_lines = new_lines[leading:-trailing]
    
# Remember how many trailing newlines in the old and new text.
i = len(oldText) - 1 ; old_newlines = 0
while i >= 0 and oldText[i] == '\n':
    old_newlines += 1
    i -= 1

i = len(newText) - 1 ; new_newlines = 0
while i >= 0 and newText[i] == '\n':
    new_newlines += 1
    i -= 1

if u.debug_print:
    g.trace()
    print "lead,trail",leading,trailing
    print "old mid,nls:",len(old_middle_lines),old_newlines,oldText
    print "new mid,nls:",len(new_middle_lines),new_newlines,newText
    #print "lead,trail:",leading,trailing
    #print "old mid:",old_middle_lines
    #print "new mid:",new_middle_lines
    print "---------------------"
#@nonl
#@-node:ekr.20031218072017.1491:<< compute leading, middle & trailing  lines >>
#@+node:ekr.20031218072017.1492:<< save undo text info >>
@ This is the start of the incremental undo algorithm.

We must save enough info to do _both_ of the following:

Undo: Given newText, recreate oldText.
Redo: Given oldText, recreate oldText.

The "given" texts for the undo and redo routines are simply v.bodyString().
@c

if u.new_undo:
    if u.debug:
        # Remember the complete text for comparisons...
        u.oldText = oldText
        u.newText = newText
        # Compute statistics comparing old and new ways...
        # The old doesn't often store the old text, so don't count it here.
        u.old_mem += len(newText)
        s1 = string.join(old_middle_lines,'\n')
        s2 = string.join(new_middle_lines,'\n')
        u.new_mem += len(s1) + len(s2)
    else:
        u.oldText = None
        u.newText = None
else:
    u.oldText = oldText
    u.newText = newText

self.leading = leading
self.trailing = trailing
self.oldMiddleLines = old_middle_lines
self.newMiddleLines = new_middle_lines
self.oldNewlines = old_newlines
self.newNewlines = new_newlines
#@nonl
#@-node:ekr.20031218072017.1492:<< save undo text info >>
#@+node:ekr.20040324061854.2:<< save the selection and scrolling position >>
#Remember the selection.
u.oldSel = oldSel
u.newSel = newSel

# Remember the scrolling position.
if oldYview:
    u.yview = oldYview
else:
    u.yview = c.frame.body.getYScrollPosition()
#@-node:ekr.20040324061854.2:<< save the selection and scrolling position >>
#@+node:ekr.20040324061854.3:<< adjust the undo stack, clearing all forward entries >>
# Push params on undo stack, clearing all forward entries.
u.bead += 1
d = u.setBead(u.bead)
u.beads[u.bead:] = [d]

# g.trace(len(u.beads), u.bead)
#@nonl
#@-node:ekr.20040324061854.3:<< adjust the undo stack, clearing all forward entries >>
#@-node:ekr.20031218072017.1490:setUndoTypingParams
#@+node:ekr.20031218072017.2038:<< redo typing cases >>
elif redoType in ( "Typing",
	"Change","Convert Blanks","Convert Tabs","Cut",
	"Delete","Indent","Paste","Reformat Paragraph","Undent"):

	# g.trace(redoType,u.p)
	# selectVnode causes recoloring, so avoid if possible.
	if current != u.p:
		c.selectVnode(u.p)
	elif redoType in ("Cut","Paste"):
		c.frame.body.forceFullRecolor()

	self.undoRedoText(
		u.p,u.leading,u.trailing,
		u.newMiddleLines,u.oldMiddleLines,
		u.newNewlines,u.oldNewlines,
		tag="redo",undoType=redoType)
	
	if u.newSel:
		c.frame.body.setTextSelection(u.newSel)
	if u.yview:
		c.frame.body.setYScrollPosition(u.yview)
	redrawFlag = (current != u.p)
		
elif redoType == "Change All":

	count = 0
	while 1:
		u.bead += 1
		d = u.getBead(u.bead+1)
		assert(d)
		redoType = u.undoType
		# g.trace(redoType,u.p,u.newText)
		if redoType == "Change All":
			c.selectVnode(u.p)
			break
		elif redoType == "Change":
			u.p.v.setTnodeText(u.newText)
			u.p.setDirty()
			count += 1
		elif redoType == "Change Headline":
			u.p.initHeadString(u.newText)
			count += 1
		else: assert(False)
	g.es("redo %d instances" % count)

elif redoType == "Change Headline":
	
	# g.trace(redoType,u.p,u.newText)
	u.p.setHeadStringOrHeadline(u.newText)
	c.selectVnode(u.p)
#@nonl
#@-node:ekr.20031218072017.2038:<< redo typing cases >>
#@+node:ekr.20031218072017.2047:<< undo typing cases >>
@ When making "large" changes to text, we simply save the old and new text for undo and redo.  This happens rarely, so the expense is minor.

But for typical typing situations, where we are typing a single character, saving both the old and new text wastes a huge amount of space and puts extreme stress on the garbage collector.  This in turn can cause big performance problems.
@c
	
elif undoType in ( "Typing",
	"Change","Convert Blanks","Convert Tabs","Cut",
	"Delete","Indent","Paste","Reformat Paragraph","Undent"):

	# g.trace(undoType,u.p)
	# selectVnode causes recoloring, so don't do this unless needed.
	if current != u.p:
		c.selectVnode(u.p)
	elif undoType in ("Cut","Paste"):
		c.frame.body.forceFullRecolor()

	self.undoRedoText(
		u.p,u.leading,u.trailing,
		u.oldMiddleLines,u.newMiddleLines,
		u.oldNewlines,u.newNewlines,
		tag="undo",undoType=undoType)
	if u.oldSel:
		c.frame.body.setTextSelection(u.oldSel)
	if u.yview:
		c.frame.body.setYScrollPosition(u.yview)
	redrawFlag = (current != u.p)
		
elif undoType == "Change All":

	count = 0
	while 1:
		u.bead -= 1
		d = u.getBead(u.bead)
		assert(d)
		undoType = u.undoType
		# g.trace(undoType,u.p,u.oldText)
		if undoType == "Change All":
			c.selectVnode(u.p)
			break
		elif undoType == "Change":
			u.p.setTnodeText(u.oldText)  # p.setTnodeText
			count += 1
			u.p.setDirty()
		elif undoType == "Change Headline":
			u.p.initHeadString(u.oldText)  # p.initHeadString
			count += 1
		else: assert(False)
	g.es("undo %d instances" % count)
		
elif undoType == "Change Headline":
	
	# g.trace(u.oldText)
	u.p.setHeadStringOrHeadline(u.oldText)
	c.selectVnode(u.p)
#@nonl
#@-node:ekr.20031218072017.2047:<< undo typing cases >>
#@+node:ekr.20031218072017.1493:undoRedoText
# Handle text undo and redo.
# The terminology is for undo: converts _new_ text into _old_ text.

def undoRedoText (self,p,
    leading,trailing, # Number of matching leading & trailing lines.
    oldMidLines,newMidLines, # Lists of unmatched lines.
    oldNewlines,newNewlines, # Number of trailing newlines.
    tag="undo", # "undo" or "redo"
    undoType=None):

    u = self ; c = u.c
    assert(p == c.currentPosition())
    v = p.v

    << Incrementally update the Tk.Text widget >>
    << Compute the result using v's body text >>
    # g.trace(v)
    # g.trace("old:",v.bodyString())
    v.setTnodeText(result)
    # g.trace("new:",v.bodyString())
    << Get textResult from the Tk.Text widget >>
    if textResult == result:
        if undoType in ("Cut","Paste"):
            # g.trace("non-incremental undo")
            c.frame.body.recolor(p,incremental=False)
        else:
            # g.trace("incremental undo:",leading,trailing)
            c.frame.body.recolor_range(p,leading,trailing)
    else: # 11/19/02: # Rewrite the pane and do a full recolor.
        if u.debug_print:
            << print mismatch trace >>
        # g.trace("non-incremental undo")
        p.setBodyStringOrPane(result)
#@nonl
#@+node:ekr.20031218072017.1494:<< Incrementally update the Tk.Text widget >>
# Only update the changed lines.
mid_text = string.join(oldMidLines,'\n')
new_mid_len = len(newMidLines)
# Maybe this could be simplified, and it is good to treat the "end" with care.
if trailing == 0:
    c.frame.body.deleteLine(leading)
    if leading > 0:
        c.frame.body.insertAtEnd('\n')
    c.frame.body.insertAtEnd(mid_text)
else:
    if new_mid_len > 0:
        c.frame.body.deleteLines(leading,new_mid_len)
    elif leading > 0:
        c.frame.body.insertAtStartOfLine(leading,'\n')
    c.frame.body.insertAtStartOfLine(leading,mid_text)
# Try to end the Tk.Text widget with oldNewlines newlines.
# This may be off by one, and we don't care because
# we never use body text to compute undo results!
s = c.frame.body.getAllText()
newlines = 0 ; i = len(s) - 1
while i >= 0 and s[i] == '\n':
    newlines += 1 ; i -= 1
while newlines > oldNewlines:
    c.frame.body.deleteLastChar()
    newlines -= 1
if oldNewlines > newlines:
    c.frame.body.insertAtEnd('\n'*(oldNewlines-newlines))
#@nonl
#@-node:ekr.20031218072017.1494:<< Incrementally update the Tk.Text widget >>
#@+node:ekr.20031218072017.1495:<< Compute the result using v's body text >>
# Recreate the text using the present body text.
body = v.bodyString()
body = g.toUnicode(body,"utf-8")
body_lines = body.split('\n')
s = []
if leading > 0:
    s.extend(body_lines[:leading])
if len(oldMidLines) > 0:
    s.extend(oldMidLines)
if trailing > 0:
    s.extend(body_lines[-trailing:])
s = string.join(s,'\n')
# Remove trailing newlines in s.
while len(s) > 0 and s[-1] == '\n':
    s = s[:-1]
# Add oldNewlines newlines.
if oldNewlines > 0:
    s = s + '\n' * oldNewlines
result = s
if u.debug_print:
    print "body:  ",body
    print "result:",result
#@nonl
#@-node:ekr.20031218072017.1495:<< Compute the result using v's body text >>
#@+node:ekr.20031218072017.1496:<< Get textResult from the Tk.Text widget >>
textResult = c.frame.body.getAllText()

if textResult != result:
    # Remove the newline from textResult if that is the only difference.
    if len(textResult) > 0 and textResult[:-1] == result:
        textResult = result
#@nonl
#@-node:ekr.20031218072017.1496:<< Get textResult from the Tk.Text widget >>
#@+node:ekr.20031218072017.1497:<< print mismatch trace >>
print "undo mismatch"
print "expected:",result
print "actual  :",textResult
#@nonl
#@-node:ekr.20031218072017.1497:<< print mismatch trace >>
#@-node:ekr.20031218072017.1493:undoRedoText
#@-node:ekr.20040323084434:(Add granularity setting for undo)
#@-node:ekr.20040916122326.1:Undo...
#@+node:ekr.20040720073339:Remove use_gnx and write_old_format_derived_files options
This implies:
    
- Deprecating old-style file formats.
- Removing commands that write old-style formats.
#@nonl
#@-node:ekr.20040720073339:Remove use_gnx and write_old_format_derived_files options
#@+node:ekr.20040701152235:Fix shawdow warnings ?
@killcolor

cmp,dict,dir,file,id,type are all global functions.
#@+node:ekr.20040701152235.2:leoApp
c:\prog\leoCVS\leo\src\leoApp.py:209: (dict) shadows builtin
c:\prog\leoCVS\leo\src\leoApp.py:224: (dict) shadows builtin
c:\prog\leoCVS\leo\src\leoApp.py:368: (dir) shadows builtin
c:\prog\leoCVS\leo\src\leoApp.py:401: (dir) shadows builtin
#@nonl
#@-node:ekr.20040701152235.2:leoApp
#@+node:ekr.20040701152235.3:leoPlugins
C:\prog\leoCVS\leo\src\leoPlugins.py:40: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoPlugins.py:53: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoPlugins.py:67: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoPlugins.py:72: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoPlugins.py:74: (file) shadows builtin
#@nonl
#@-node:ekr.20040701152235.3:leoPlugins
#@+node:ekr.20040701152235.4:leoColor
C:\prog\leoCVS\leo\src\leoColor.py:1948: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoColor.py:2059: (dict) shadows builtin
#@nonl
#@-node:ekr.20040701152235.4:leoColor
#@+node:ekr.20040701152235.5:leoNodes
C:\prog\leoCVS\leo\src\leoNodes.py:578: (type) shadows builtin
C:\prog\leoCVS\leo\src\leoNodes.py:1372: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoNodes.py:1394: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoNodes.py:1411: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoNodes.py:1412: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoNodes.py:1419: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoNodes.py:1443: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoNodes.py:1446: (id) shadows builtin
#@nonl
#@-node:ekr.20040701152235.5:leoNodes
#@+node:ekr.20040701152235.6:leoCompare
C:\prog\leoCVS\leo\src\leoCompare.py:44: (cmp) shadows builtin
C:\prog\leoCVS\leo\src\leoCompare.py:397: (cmp) shadows builtin
C:\prog\leoCVS\leo\src\leoCompare.py:468: (dir) shadows builtin
#@nonl
#@-node:ekr.20040701152235.6:leoCompare
#@+node:ekr.20040701152235.8:leoFind
c:\prog\leoCVS\leo\src\leoFind.py:864: (type) shadows builtin
#@nonl
#@-node:ekr.20040701152235.8:leoFind
#@+node:ekr.20040701152235.9:leoMenu
c:\prog\leoCVS\leo\src\leoMenu.py:656: (dict) shadows builtin
c:\prog\leoCVS\leo\src\leoMenu.py:700: (dict) shadows builtin
#@nonl
#@-node:ekr.20040701152235.9:leoMenu
#@+node:ekr.20040701152235.10:leoTkinterTree
C:\prog\leoCVS\leo\src\leoTkinterTree.py:162: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:167: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:586: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:639: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:783: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:844: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:883: (type) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:893: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:911: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:949: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:1390: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:1588: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterTree.py:1590: (id) shadows builtin
#@nonl
#@-node:ekr.20040701152235.10:leoTkinterTree
#@+node:ekr.20040701152235.11:leoTkinterFrame
C:\prog\leoCVS\leo\src\leoTkinterFrame.py:807: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoTkinterFrame.py:1233: (file) shadows builtin

c:\prog\leoCVS\leo\src\leoFrame.py:522: (type) shadows builtin
c:\prog\leoCVS\leo\src\leoFrame.py:566: (dict) shadows builtin
#@nonl
#@-node:ekr.20040701152235.11:leoTkinterFrame
#@+node:ekr.20040701152235.12:leoTkinterPrefs
c:\prog\leoCVS\leo\src\leoTkinterPrefs.py:295: (dir) shadows builtin
#@nonl
#@-node:ekr.20040701152235.12:leoTkinterPrefs
#@+node:ekr.20040701152235.13:leoTkinterGui
c:\prog\leoCVS\leo\src\leoTkinterGui.py:78: (file) shadows builtin
#@nonl
#@-node:ekr.20040701152235.13:leoTkinterGui
#@+node:ekr.20040701152235.14:leoAtFile
C:\prog\leoCVS\leo\src\leoAtFile.py:336: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoAtFile.py:473: (dir) shadows builtin
C:\prog\leoCVS\leo\src\leoAtFile.py:490: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoAtFile.py:549: (dir) shadows builtin
C:\prog\leoCVS\leo\src\leoAtFile.py:551: (dir) shadows builtin
C:\prog\leoCVS\leo\src\leoAtFile.py:2142: (dir) shadows builtin
C:\prog\leoCVS\leo\src\leoAtFile.py:2157: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoAtFile.py:2278: (dir) shadows builtin
C:\prog\leoCVS\leo\src\leoAtFile.py:2280: (dir) shadows builtin
#@-node:ekr.20040701152235.14:leoAtFile
#@+node:ekr.20040701152235.15:leoFileCommands
C:\prog\leoCVS\leo\src\leoFileCommands.py:90: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoFileCommands.py:866: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoFileCommands.py:990: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoFileCommands.py:1191: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoFileCommands.py:1228: (dir) shadows builtin
C:\prog\leoCVS\leo\src\leoFileCommands.py:1269: (dir) shadows builtin
C:\prog\leoCVS\leo\src\leoFileCommands.py:1318: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoFileCommands.py:1756: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoFileCommands.py:2018: (dir) shadows builtin
#@nonl
#@-node:ekr.20040701152235.15:leoFileCommands
#@+node:ekr.20040701152235.16:leoImport
C:\prog\leoCVS\leo\src\leoImport.py:52: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:273: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:473: (type) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:1626: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:1648: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:1650: (id) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:2303: (type) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:2348: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:2371: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:2398: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:2426: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:2476: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:2727: (type) shadows builtin
C:\prog\leoCVS\leo\src\leoImport.py:2792: (dict) shadows builtin
#@nonl
#@-node:ekr.20040701152235.16:leoImport
#@+node:ekr.20040701152235.17:leoTangle
c:\prog\leoCVS\leo\src\leoTangle.py:540: (dir) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:541: (dir) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:666: (dict) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:718: (dict) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:911: (dict) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:924: (dict) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:2038: (type) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:3563: (dict) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:3656: (dir) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:3667: (dir) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:3780: (dir) shadows builtin
c:\prog\leoCVS\leo\src\leoTangle.py:3865: (type) shadows builtin
#@nonl
#@-node:ekr.20040701152235.17:leoTangle
#@+node:ekr.20040701152235.18:leoCommands
C:\prog\leoCVS\leo\src\leoCommands.py:324: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:340: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:346: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:465: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:468: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:480: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:495: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:734: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:1238: (file) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:1677: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:1721: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:1770: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:1800: (dict) shadows builtin
C:\prog\leoCVS\leo\src\leoCommands.py:2280: (dict) shadows builtin
#@nonl
#@-node:ekr.20040701152235.18:leoCommands
#@-node:ekr.20040701152235:Fix shawdow warnings ?
#@-node:ekr.20040919101448:Do first: can't be done in plugins
#@+node:ekr.20040919182750:Top 3
#@+node:ekr.20040916122326.1:Undo...
#@+node:EKR.20040601133022.1:(Finish the new undo)
#@+node:EKR.20040606195417.5:To do
@nocolor

- (done) Create u.saveNode, u.saveNodeAndChildren, u.saveListOfNodes.
- Create u.appendSavedNode.
- Use u.appendSavedNode in Change All command.
- Unit tests.
#@nonl
#@-node:EKR.20040606195417.5:To do
#@+node:EKR.20040528075307:u.saveTree
def saveTree (self,p,treeInfo=None):
    
    """Create all info needed to handle a general undo operation."""

    # WARNING: read this before doing anything "clever"
    << about u.saveTree >>
    
    u = self ; topLevel = (treeInfo == None)
    if topLevel: treeInfo = []

    # Add info for p.v and p.v.t.  Duplicate tnode info is harmless.
    data = (p.v,p.v.createUndoInfo(),p.v.t.createUndoInfo())
    treeInfo.append(data)

    # Recursively add info for the subtree.
    child = p.firstChild()
    while child:
        self.saveTree(child,treeInfo)
        child = child.next()

    # if topLevel: g.trace(treeInfo)
    return treeInfo
#@+node:EKR.20040530114124:<< about u.saveTree >>
@ 
The old code made a free-standing copy of the tree using v.copy and t.copy.  This looks "elegant" and is WRONG.  The problem is that it can not handle clones properly, especially when some clones were in the "undo" tree and some were not.   Moreover, it required complex adjustments to t.vnodeLists.

Instead of creating new nodes, the new code creates all information needed to properly restore the vnodes and tnodes.  It creates a list of tuples, on tuple for each vnode in the tree.  Each tuple has the form,

(vnodeInfo, tnodeInfo) where vnodeInfo and tnodeInfo are dicts contain all info needed to recreate the nodes.  The v.createUndoInfoDict and t.createUndoInfoDict methods correspond to the old v.copy and t.copy methods.

Aside:  Prior to 4.2 Leo used a scheme that was equivalent to the createUndoInfoDict info, but quite a bit uglier.
#@-node:EKR.20040530114124:<< about u.saveTree >>
#@-node:EKR.20040528075307:u.saveTree
#@+node:EKR.20040606195417.2:u.saveNodeAndChildren
def saveNodeAndChildren (self,p):
    
    """Create all info needed for a node and all its immediate children."""

    u = self
    treeInfo = []

    # Add info for p.v and p.v.t.  Duplicate tnode info is harmless.
    data = (p.v,p.v.createUndoInfo(),p.v.t.createUndoInfo())
    treeInfo.append(data)

    # Add info for all children.
    child = p.firstChild()
    while child:
        data = (child.v,child.v.createUndoInfo(),child.v.t.createUndoInfo())
        treeInfo.append(data)
        child = child.next()

    return treeInfo

#@-node:EKR.20040606195417.2:u.saveNodeAndChildren
#@+node:EKR.20040606195417.1:u.saveNode
def saveNode (self,p):
    
    """Create all info for a single vnode."""
    
    u = self

    treeInfo = (p.v,p.v.createUndoInfo(),p.v.t.createUndoInfo())
    return treeInfo
#@nonl
#@-node:EKR.20040606195417.1:u.saveNode
#@+node:EKR.20040606195417.3:u.saveListOfNodes
def saveListOfNodes (self,listOfVnodes):
    
    """Create all info for a list of vnodes."""
    
    u = self ; treeInfo = []

    for v in listOfVnodes:
        data = (v,v.createUndoInfo(),v.t.createUndoInfo())
        treeInfo.append(data)

    return treeInfo
#@nonl
#@-node:EKR.20040606195417.3:u.saveListOfNodes
#@-node:EKR.20040601133022.1:(Finish the new undo)
#@+node:ekr.20040323084434:(Add granularity setting for undo)
#@+node:ekr.20040328111626:Notes
@nocolor

- "node" and "line" granularity are easy:
	- Node: just replace "new" text with present text.
	- Line: create a new node if the present line isn't the same as the "line" attribute of the undo node.
		(Very similar to node granularity.

- "word" granularity is still fairly easy:
	- inWord flag is True or False.
	- create a new node if we are starting a new word.

- copy/paste always start new undo typing nodes.
	
#@nonl
#@-node:ekr.20040328111626:Notes
#@+node:ekr.20031218072017.3606:undo.__init__ & clearIvars
def __init__ (self,c):
    
    u = self ; u.c = c
    
    # Ivars to transition to new undo scheme...
    u.debug = False # True: enable debugging code in new undo scheme.
    u.debug_print = False # True: enable print statements in debug code.
    u.new_undo = True # True: enable new debug code.

    # Statistics comparing old and new ways (only if u.debug is on).
    u.new_mem = 0
    u.old_mem = 0

    # State ivars...
    u.undoType = "Can't Undo"
    # These must be set here, _not_ in clearUndoState.
    u.redoMenuLabel = "Can't Redo"
    u.undoMenuLabel = "Can't Undo"
    u.realRedoMenuLabel = "Can't Redo"
    u.realUndoMenuLabel = "Can't Undo"
    u.undoing = False # True if executing an Undo command.
    u.redoing = False # True if executing a Redo command.
    
    # New in 4.2...
    << Define optional ivars >>
    << define redoDispatchDict >>
    << define undoDispatchDict >>
    u.updateSetChangedFlag = True
    u.redrawFlag = True
#@nonl
#@+node:ekr.20031218072017.3607:clearIvars
def clearIvars (self):
    
    u = self
    
    u.p = None # The position/node being operated upon for undo and redo.
    for ivar in u.optionalIvars:
        setattr(u,ivar,None)
#@nonl
#@-node:ekr.20031218072017.3607:clearIvars
#@+node:ekr.20031218072017.3604:<< Define optional ivars >>
# New in 4.2: this is now an ivar, not a global, and it's a list, not a tuple.

u.optionalIvars = [
    "lastChild",
    "parent","oldParent",
    "back","oldBack",
    "n","oldN","oldV",
    "oldText","newText",
    "oldSel","newSel",
    "sort","select",
    "oldTree","newTree", # Added newTree 10/14/03
    "yview",
    # For incremental undo typing...
    "leading","trailing",
    "oldMiddleLines","newMiddleLines",
    "oldNewlines","newNewlines" ]
#@nonl
#@-node:ekr.20031218072017.3604:<< Define optional ivars >>
#@+node:EKR.20040526072519:<< define redoDispatchDict >>
u.redoDispatchDict = {
    "Change":             u.redoTyping,
    "Change All":         u.redoChangeAll,
    "Change Headline":    u.redoChangeHeadline,
    "Clone Node":         u.redoClone,
    "Convert All Blanks": u.redoReplaceNodesContents,
    "Convert All Tabs":   u.redoReplaceNodesContents,
    "Convert Blanks":     u.redoTyping,
    "Convert Tabs":       u.redoTyping,
    "Cut":                u.redoTyping,
    "Cut Node":           u.redoDeleteNode,
    "De-Hoist":           u.redoDehoist,
    "Delete":             u.redoTyping,
    "Delete Node":        u.redoDeleteNode,
    "Demote":             u.redoDemote,
    "Drag":               u.redoMoveNode,
    "Drag & Clone":       u.redoClone,
    "Extract":            u.redoReplaceNodes,
    "Extract Names":      u.redoReplaceNodes,
    "Extract Section":    u.redoReplaceNodes,
    "Hoist":              u.redoHoist,
    "Import":             u.redoInsertNodes,
    "Indent":             u.redoTyping,
    "Insert Node":        u.redoInsertNodes,
    "Move Down":          u.redoMoveNode,
    "Move Left":          u.redoMoveNode,
    "Move Right":         u.redoMoveNode,
    "Move Up":            u.redoMoveNode,
    "Paste":              u.redoTyping,
    "Paste Node":         u.redoInsertNodes,
    "Pretty Print":       u.redoChangeAll,
    "Promote":            u.redoPromote,
    "Read @file Nodes":   u.redoReplaceNodes,
    "Reformat Paragraph": u.redoTyping,
    "Sort Children":      u.redoSortChildren,
    "Sort Siblings":      u.redoSortSiblings,
    "Sort Top Level":     u.redoSortTopLevel,
    "Typing":             u.redoTyping,
    "Undent":             u.redoTyping }
#@nonl
#@-node:EKR.20040526072519:<< define redoDispatchDict >>
#@+node:EKR.20040526075238:<< define undoDispatchDict >>
u.undoDispatchDict = {
    "Change":             u.undoTyping,
    "Change All":         u.undoChangeAll,
    "Change Headline":    u.undoChangeHeadline,
    "Clone Node":         u.undoClone,
    "Convert All Blanks": u.undoReplaceNodesContents,
    "Convert All Tabs":   u.undoReplaceNodesContents,
    "Convert Blanks":     u.undoTyping,
    "Convert Tabs":       u.undoTyping,
    "Cut":                u.undoTyping,
    "Cut Node":           u.undoDeleteNode,
    "De-Hoist":           u.undoDehoist,
    "Delete":             u.undoTyping,
    "Delete Node":        u.undoDeleteNode,
    "Demote":             u.undoDemote,
    "Drag":               u.undoMoveNode,
    "Drag & Clone":       u.undoDragClone, # redo uses redoClone.
    "Extract":            u.undoReplaceNodes,
    "Extract Names":      u.undoReplaceNodes,
    "Extract Section":    u.undoReplaceNodes,
    "Hoist":              u.undoHoist,
    "Import":             u.undoInsertNodes,
    "Indent":             u.undoTyping,
    "Insert Node":        u.undoInsertNodes,
    "Move Down":          u.undoMoveNode,
    "Move Left":          u.undoMoveNode,
    "Move Right":         u.undoMoveNode,
    "Move Up":            u.undoMoveNode,
    "Paste":              u.undoTyping,
    "Paste Node":         u.undoInsertNodes,
    "Pretty Print":       u.undoChangeAll,
    "Promote":            u.undoPromote,
    "Read @file Nodes":   u.undoReplaceNodes,
    "Reformat Paragraph": u.undoTyping,
    "Sort Children":      u.undoSortChildren,
    "Sort Siblings":      u.undoSortSiblings,
    "Sort Top Level":     u.undoSortTopLevel,
    "Typing":             u.undoTyping,
    "Undent":             u.undoTyping }
#@nonl
#@-node:EKR.20040526075238:<< define undoDispatchDict >>
#@-node:ekr.20031218072017.3606:undo.__init__ & clearIvars
#@+node:ekr.20031218072017.1421:<< get config options >>
@ Rewritten 10/11/02 as follows:

1. We call initConfigParam and initBooleanConfigParam to get the values.

The general purpose code will enter all these values into configDict.  This allows update() to write the configuration section without special case code.  configDict is not accessible by the user.  Rather, for greater speed the user access these values via the ivars of this class.

2. We pass the ivars themselves as params so that default initialization is done in the ctor, as would normally be expected.
@c

self.at_root_bodies_start_in_doc_mode = self.initBooleanConfigParam(
    "at_root_bodies_start_in_doc_mode",self.at_root_bodies_start_in_doc_mode)
    
encoding = self.initConfigParam(
    "config_encoding",self.config_encoding)
    
if g.isValidEncoding(encoding):
    self.config_encoding = encoding
else:
    g.es("bad config_encoding: " + encoding)
    
self.create_nonexistent_directories = self.initBooleanConfigParam(
    "create_nonexistent_directories",self.create_nonexistent_directories)
    
encoding = self.initConfigParam(
    "default_derived_file_encoding",self.default_derived_file_encoding)

if g.isValidEncoding(encoding):
    self.default_derived_file_encoding = encoding
else:
    g.es("bad default_derived_file_encoding: " + encoding)
    
encoding = self.initConfigParam(
    "new_leo_file_encoding",
    self.new_leo_file_encoding)

if g.isValidEncoding(encoding):
    self.new_leo_file_encoding = encoding
else:
    g.es("bad new_leo_file_encoding: " + encoding)

self.output_initial_comment = self.initConfigParam(
    "output_initial_comment",self.output_initial_comment)

self.output_newline = self.initConfigParam(
    "output_newline",self.output_newline)

self.read_only = self.initBooleanConfigParam(
    "read_only",self.read_only)

self.relative_path_base_directory = self.initConfigParam(
    "relative_path_base_directory",self.relative_path_base_directory)
    
self.redirect_execute_script_output_to_log_pane = self.initBooleanConfigParam(
    "redirect_execute_script_output_to_log_pane",
    self.redirect_execute_script_output_to_log_pane)
    
self.remove_sentinels_extension = self.initConfigParam(
    "remove_sentinels_extension",self.remove_sentinels_extension)

self.save_clears_undo_buffer = self.initBooleanConfigParam(
    "save_clears_undo_buffer",self.save_clears_undo_buffer)
    
self.stylesheet = self.initConfigParam(
    "stylesheet",self.stylesheet)
    
encoding = self.initConfigParam(
    "tk_encoding",self.tkEncoding)
    
if encoding and len(encoding) > 0: # May be None.
    if g.isValidEncoding(encoding):
        self.tkEncoding = encoding
    else:
        g.es("bad tk_encoding: " + encoding)
        
# New in 4.2
self.trailing_body_newlines = self.initConfigParam(
    "trailing_body_newlines",self.trailing_body_newlines)

# TO BE REMOVED
g.app.use_gnx = self.initBooleanConfigParam(
    "use_gnx",g.app.use_gnx)
    
self.use_plugins = self.initBooleanConfigParam(
    "use_plugins",self.use_plugins)

self.use_psyco = self.initBooleanConfigParam(
    "use_psyco",self.use_psyco)
    
self.undo_granularity = self.initConfigParam(
    "undo_granularity",self.undo_granularity)
    
# TO BE REMOVED
self.write_old_format_derived_files = self.initBooleanConfigParam(
    "write_old_format_derived_files",self.write_old_format_derived_files)

# New in 4.2
self.write_strips_blank_lines = self.initBooleanConfigParam(
    "write_strips_blank_lines",self.write_strips_blank_lines)
    
#g.trace("write_strips_blank_lines",self.write_strips_blank_lines)
#g.trace("trailing_body_newlines",self.trailing_body_newlines)
#@nonl
#@-node:ekr.20031218072017.1421:<< get config options >>
#@+node:ekr.20031218072017.1490:setUndoTypingParams
@ This routine saves enough information so a typing operation can be undone and redone.

We do nothing when called from the undo/redo logic because the Undo and Redo commands merely reset the bead pointer.
@c

def setUndoTypingParams (self,p,undo_type,oldText,newText,oldSel,newSel,oldYview=None):
    
    # g.trace(undo_type,p,"old:",oldText,"new:",newText)
    u = self ; c = u.c
    << return if there is nothing to do >>
    << init the undo params >>
    << compute leading, middle & trailing  lines >>
    << save undo text info >>
    << save the selection and scrolling position >>
    << adjust the undo stack, clearing all forward entries >>
    u.setUndoTypes() # Recalculate the menu labels.
    return d
#@nonl
#@+node:ekr.20040324061854:<< return if there is nothing to do >>
if u.redoing or u.undoing:
    return None

if undo_type == None:
    return None

if undo_type == "Can't Undo":
    u.clearUndoState()
    return None

if oldText == newText:
    # g.trace("no change")
    return None
#@nonl
#@-node:ekr.20040324061854:<< return if there is nothing to do >>
#@+node:ekr.20040324061854.1:<< init the undo params >>
# Clear all optional params.
for ivar in u.optionalIvars:
    setattr(u,ivar,None)

# Set the params.
u.undoType = undo_type
u.p = p
#@nonl
#@-node:ekr.20040324061854.1:<< init the undo params >>
#@+node:ekr.20031218072017.1491:<< compute leading, middle & trailing  lines >>
@ Incremental undo typing is similar to incremental syntax coloring.  We compute the number of leading and trailing lines that match, and save both the old and new middle lines.

NB: the number of old and new middle lines may be different.
@c

old_lines = string.split(oldText,'\n')
new_lines = string.split(newText,'\n')
new_len = len(new_lines)
old_len = len(old_lines)
min_len = min(old_len,new_len)

i = 0
while i < min_len:
    if old_lines[i] != new_lines[i]:
        break
    i += 1
leading = i

if leading == new_len:
    # This happens when we remove lines from the end.
    # The new text is simply the leading lines from the old text.
    trailing = 0
else:
    i = 0
    while i < min_len - leading:
        if old_lines[old_len-i-1] != new_lines[new_len-i-1]:
            break
        i += 1
    trailing = i
    
# NB: the number of old and new middle lines may be different.
if trailing == 0:
    old_middle_lines = old_lines[leading:]
    new_middle_lines = new_lines[leading:]
else:
    old_middle_lines = old_lines[leading:-trailing]
    new_middle_lines = new_lines[leading:-trailing]
    
# Remember how many trailing newlines in the old and new text.
i = len(oldText) - 1 ; old_newlines = 0
while i >= 0 and oldText[i] == '\n':
    old_newlines += 1
    i -= 1

i = len(newText) - 1 ; new_newlines = 0
while i >= 0 and newText[i] == '\n':
    new_newlines += 1
    i -= 1

if u.debug_print:
    g.trace()
    print "lead,trail",leading,trailing
    print "old mid,nls:",len(old_middle_lines),old_newlines,oldText
    print "new mid,nls:",len(new_middle_lines),new_newlines,newText
    #print "lead,trail:",leading,trailing
    #print "old mid:",old_middle_lines
    #print "new mid:",new_middle_lines
    print "---------------------"
#@nonl
#@-node:ekr.20031218072017.1491:<< compute leading, middle & trailing  lines >>
#@+node:ekr.20031218072017.1492:<< save undo text info >>
@ This is the start of the incremental undo algorithm.

We must save enough info to do _both_ of the following:

Undo: Given newText, recreate oldText.
Redo: Given oldText, recreate oldText.

The "given" texts for the undo and redo routines are simply v.bodyString().
@c

if u.new_undo:
    if u.debug:
        # Remember the complete text for comparisons...
        u.oldText = oldText
        u.newText = newText
        # Compute statistics comparing old and new ways...
        # The old doesn't often store the old text, so don't count it here.
        u.old_mem += len(newText)
        s1 = string.join(old_middle_lines,'\n')
        s2 = string.join(new_middle_lines,'\n')
        u.new_mem += len(s1) + len(s2)
    else:
        u.oldText = None
        u.newText = None
else:
    u.oldText = oldText
    u.newText = newText

self.leading = leading
self.trailing = trailing
self.oldMiddleLines = old_middle_lines
self.newMiddleLines = new_middle_lines
self.oldNewlines = old_newlines
self.newNewlines = new_newlines
#@nonl
#@-node:ekr.20031218072017.1492:<< save undo text info >>
#@+node:ekr.20040324061854.2:<< save the selection and scrolling position >>
#Remember the selection.
u.oldSel = oldSel
u.newSel = newSel

# Remember the scrolling position.
if oldYview:
    u.yview = oldYview
else:
    u.yview = c.frame.body.getYScrollPosition()
#@-node:ekr.20040324061854.2:<< save the selection and scrolling position >>
#@+node:ekr.20040324061854.3:<< adjust the undo stack, clearing all forward entries >>
# Push params on undo stack, clearing all forward entries.
u.bead += 1
d = u.setBead(u.bead)
u.beads[u.bead:] = [d]

# g.trace(len(u.beads), u.bead)
#@nonl
#@-node:ekr.20040324061854.3:<< adjust the undo stack, clearing all forward entries >>
#@-node:ekr.20031218072017.1490:setUndoTypingParams
#@+node:ekr.20031218072017.2038:<< redo typing cases >>
elif redoType in ( "Typing",
	"Change","Convert Blanks","Convert Tabs","Cut",
	"Delete","Indent","Paste","Reformat Paragraph","Undent"):

	# g.trace(redoType,u.p)
	# selectVnode causes recoloring, so avoid if possible.
	if current != u.p:
		c.selectVnode(u.p)
	elif redoType in ("Cut","Paste"):
		c.frame.body.forceFullRecolor()

	self.undoRedoText(
		u.p,u.leading,u.trailing,
		u.newMiddleLines,u.oldMiddleLines,
		u.newNewlines,u.oldNewlines,
		tag="redo",undoType=redoType)
	
	if u.newSel:
		c.frame.body.setTextSelection(u.newSel)
	if u.yview:
		c.frame.body.setYScrollPosition(u.yview)
	redrawFlag = (current != u.p)
		
elif redoType == "Change All":

	count = 0
	while 1:
		u.bead += 1
		d = u.getBead(u.bead+1)
		assert(d)
		redoType = u.undoType
		# g.trace(redoType,u.p,u.newText)
		if redoType == "Change All":
			c.selectVnode(u.p)
			break
		elif redoType == "Change":
			u.p.v.setTnodeText(u.newText)
			u.p.setDirty()
			count += 1
		elif redoType == "Change Headline":
			u.p.initHeadString(u.newText)
			count += 1
		else: assert(False)
	g.es("redo %d instances" % count)

elif redoType == "Change Headline":
	
	# g.trace(redoType,u.p,u.newText)
	u.p.setHeadStringOrHeadline(u.newText)
	c.selectVnode(u.p)
#@nonl
#@-node:ekr.20031218072017.2038:<< redo typing cases >>
#@+node:ekr.20031218072017.2047:<< undo typing cases >>
@ When making "large" changes to text, we simply save the old and new text for undo and redo.  This happens rarely, so the expense is minor.

But for typical typing situations, where we are typing a single character, saving both the old and new text wastes a huge amount of space and puts extreme stress on the garbage collector.  This in turn can cause big performance problems.
@c
	
elif undoType in ( "Typing",
	"Change","Convert Blanks","Convert Tabs","Cut",
	"Delete","Indent","Paste","Reformat Paragraph","Undent"):

	# g.trace(undoType,u.p)
	# selectVnode causes recoloring, so don't do this unless needed.
	if current != u.p:
		c.selectVnode(u.p)
	elif undoType in ("Cut","Paste"):
		c.frame.body.forceFullRecolor()

	self.undoRedoText(
		u.p,u.leading,u.trailing,
		u.oldMiddleLines,u.newMiddleLines,
		u.oldNewlines,u.newNewlines,
		tag="undo",undoType=undoType)
	if u.oldSel:
		c.frame.body.setTextSelection(u.oldSel)
	if u.yview:
		c.frame.body.setYScrollPosition(u.yview)
	redrawFlag = (current != u.p)
		
elif undoType == "Change All":

	count = 0
	while 1:
		u.bead -= 1
		d = u.getBead(u.bead)
		assert(d)
		undoType = u.undoType
		# g.trace(undoType,u.p,u.oldText)
		if undoType == "Change All":
			c.selectVnode(u.p)
			break
		elif undoType == "Change":
			u.p.setTnodeText(u.oldText)  # p.setTnodeText
			count += 1
			u.p.setDirty()
		elif undoType == "Change Headline":
			u.p.initHeadString(u.oldText)  # p.initHeadString
			count += 1
		else: assert(False)
	g.es("undo %d instances" % count)
		
elif undoType == "Change Headline":
	
	# g.trace(u.oldText)
	u.p.setHeadStringOrHeadline(u.oldText)
	c.selectVnode(u.p)
#@nonl
#@-node:ekr.20031218072017.2047:<< undo typing cases >>
#@+node:ekr.20031218072017.1493:undoRedoText
# Handle text undo and redo.
# The terminology is for undo: converts _new_ text into _old_ text.

def undoRedoText (self,p,
    leading,trailing, # Number of matching leading & trailing lines.
    oldMidLines,newMidLines, # Lists of unmatched lines.
    oldNewlines,newNewlines, # Number of trailing newlines.
    tag="undo", # "undo" or "redo"
    undoType=None):

    u = self ; c = u.c
    assert(p == c.currentPosition())
    v = p.v

    << Incrementally update the Tk.Text widget >>
    << Compute the result using v's body text >>
    # g.trace(v)
    # g.trace("old:",v.bodyString())
    v.setTnodeText(result)
    # g.trace("new:",v.bodyString())
    << Get textResult from the Tk.Text widget >>
    if textResult == result:
        if undoType in ("Cut","Paste"):
            # g.trace("non-incremental undo")
            c.frame.body.recolor(p,incremental=False)
        else:
            # g.trace("incremental undo:",leading,trailing)
            c.frame.body.recolor_range(p,leading,trailing)
    else: # 11/19/02: # Rewrite the pane and do a full recolor.
        if u.debug_print:
            << print mismatch trace >>
        # g.trace("non-incremental undo")
        p.setBodyStringOrPane(result)
#@nonl
#@+node:ekr.20031218072017.1494:<< Incrementally update the Tk.Text widget >>
# Only update the changed lines.
mid_text = string.join(oldMidLines,'\n')
new_mid_len = len(newMidLines)
# Maybe this could be simplified, and it is good to treat the "end" with care.
if trailing == 0:
    c.frame.body.deleteLine(leading)
    if leading > 0:
        c.frame.body.insertAtEnd('\n')
    c.frame.body.insertAtEnd(mid_text)
else:
    if new_mid_len > 0:
        c.frame.body.deleteLines(leading,new_mid_len)
    elif leading > 0:
        c.frame.body.insertAtStartOfLine(leading,'\n')
    c.frame.body.insertAtStartOfLine(leading,mid_text)
# Try to end the Tk.Text widget with oldNewlines newlines.
# This may be off by one, and we don't care because
# we never use body text to compute undo results!
s = c.frame.body.getAllText()
newlines = 0 ; i = len(s) - 1
while i >= 0 and s[i] == '\n':
    newlines += 1 ; i -= 1
while newlines > oldNewlines:
    c.frame.body.deleteLastChar()
    newlines -= 1
if oldNewlines > newlines:
    c.frame.body.insertAtEnd('\n'*(oldNewlines-newlines))
#@nonl
#@-node:ekr.20031218072017.1494:<< Incrementally update the Tk.Text widget >>
#@+node:ekr.20031218072017.1495:<< Compute the result using v's body text >>
# Recreate the text using the present body text.
body = v.bodyString()
body = g.toUnicode(body,"utf-8")
body_lines = body.split('\n')
s = []
if leading > 0:
    s.extend(body_lines[:leading])
if len(oldMidLines) > 0:
    s.extend(oldMidLines)
if trailing > 0:
    s.extend(body_lines[-trailing:])
s = string.join(s,'\n')
# Remove trailing newlines in s.
while len(s) > 0 and s[-1] == '\n':
    s = s[:-1]
# Add oldNewlines newlines.
if oldNewlines > 0:
    s = s + '\n' * oldNewlines
result = s
if u.debug_print:
    print "body:  ",body
    print "result:",result
#@nonl
#@-node:ekr.20031218072017.1495:<< Compute the result using v's body text >>
#@+node:ekr.20031218072017.1496:<< Get textResult from the Tk.Text widget >>
textResult = c.frame.body.getAllText()

if textResult != result:
    # Remove the newline from textResult if that is the only difference.
    if len(textResult) > 0 and textResult[:-1] == result:
        textResult = result
#@nonl
#@-node:ekr.20031218072017.1496:<< Get textResult from the Tk.Text widget >>
#@+node:ekr.20031218072017.1497:<< print mismatch trace >>
print "undo mismatch"
print "expected:",result
print "actual  :",textResult
#@nonl
#@-node:ekr.20031218072017.1497:<< print mismatch trace >>
#@-node:ekr.20031218072017.1493:undoRedoText
#@-node:ekr.20040323084434:(Add granularity setting for undo)
#@-node:ekr.20040916122326.1:Undo...
#@+node:ekr.20031218072017.828:Rewrite the config manager
http://sourceforge.net/forum/message.php?msg_id=2329053
By: dsalomoni

I would be reluctant to run this script in a linux environment, for the following
reasons:

1) chmod 666 on the leo config file makes it writable by anybody. 

2) on the other hand, leo is currently IMHO not suitable for generic linux
installations, because it does not support per-user config files, so you actually
need to either carve leoconfig.txt in stone (not realistic), or make it writable
by any user as above (not advisable).

3) having a single dummy leoID defeats its main purpose in a multi-user environment.
If you could set per-user config files, the leoID could by default simply derived
from the username.

As a matter of fact, I've been suggesting to friends etc wanting to try out
leo/install it on their systems not to do that globally.

So, my view is that we could:

1) in the short term, provide a simple per-user installation based for example
on the script above, but with path defaults pointing to the user's home dir
(and of course w/o the requirement to be root) -- with leoID generated by default
looking at the username.

2) in the medium term, provide per-user config possibilities -- this would possibly
allow for a system-wide installation of leo and still allow configuration, per-user
plugins, etc. But this is linked I guess to the decommissioning of/changes to
the leoConfig.leo machinery.

Davide
#@nonl
#@+node:ekr.20040922073523:comments from e
@killcolor
http://sourceforge.net/forum/message.php?msg_id=2769775
By: nobody

Leo should make it a priority to determine leoID
and users HOME and other dir of interest
before plugins are loaded so thay can have access.
at 'after-create-leo-frame' g.app.leoID not defined
but g.app.loadDir and many others are.
if there will be more than one config, a system,
a per user in HOME, and maybe per currentdir or
in the same dir as the leo, they should be known.

g.app.homeDir = os.getenv('HOME', 
                    default= os.path.abspath('./'))
on windows this can point many places
best to set HOME if you want it in a specific place.
otherwise it can be %WINDIR%/APPDATA or %ROOT%/APPDATA
APPDATA can be an env variable, probably win2k or later.

# abspath resolves './' sometimes returned curdir
import user, os
print os.path.abspath(user.home) 
print os.path.abspath(os.path.expanduser("~"))

obviously this will need allot of work to get
crossplatform and bulletproof. you would think
it would be easy to know where everything should go
or how to nail down how to find everything.
no such luck, all I've seen is bits and pieces.

e
#@-node:ekr.20040922073523:comments from e
#@+node:ekr.20040105080119:Search for settings in various places: Rodrigo
@nocolor

https://sourceforge.net/forum/message.php?msg_id=2355843
By: rodrigo_b

At least pluginManager should be there too	

(or both files could be merged... who knows)

The better would be to have

$HOME/.leo/
$HOME/.leo/leoConfig.txt
$HOME/.leo/plugins/pluginManager.txt
$HOME/.leo/plugins/<myplugins>.py

look for .leoConfig.txt, then leoConfig.txt
#@nonl
#@+node:ekr.20040919075839:Stephen Schaefer
By: Stephen Schaefer - thyrsus
RE: 4.2 rc1 released  
2004-09-18 05:32
It took me two hours to track it down, but I did manage to get leoConfit.txt and .leoID.txt to be taken from $HOME/.leoconfig

Mostly it took so long because the documenation assumed a knowledge of Python operation I didn't posess.

First, the current leo requires Python 2.3, and I'm running on a Red Hat 7.3 system where the "native" python is 1.5.2, and "python2" is 2.2.2. It's far cheaper to add another python than to make sure a different python doesn't break things, so my python 2.3 is rooted at /apps/python/python-2.3.2. There is a file that python looks for but is not present when it is installed from source:

/apps/python/python-2.3.2/lib/python2.3/sitecusomize.py

I created that file with the following contents (. for leading space):

import sys
import os
try:
....sys.leo_config_directory = os.environ['HOME'] + '/.leoconfig'
except KeyError:
....sys.leo_config_directory = None

If I were more sophisticated, I would check that $HOME/.leoconfig was a directory, and if not also set sys.leo_config_directory to None.

It doesn't seem appropriate to insist that anyone using python 2.3.2 for any reason whatsoever would be forced to have a .leoconfig in their home directory (think of a limited account that doesn't have a writable home directory), but it would be appropriate for leo to create $HOME/.leoconfig if it weren't present and set sys.leo_config_directory at that point -- indeed the entire logic could be handled outside of sitecustomize.py. I'll post code for the above if someone else doesn't beat me to it :-).
#@-node:ekr.20040919075839:Stephen Schaefer
#@-node:ekr.20040105080119:Search for settings in various places: Rodrigo
#@+node:ekr.20040208112836:Specify shortcuts, ampersand bindings & translations in the same place
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2415127
By: edream

I have just hacked the code to allow & in entries in leoConfig.txt that describe
shortcuts.

This is a hack because it probably interacts poorly with the present mechanism
that allows menu entries to be translated.  I haven't looked into this in detail:
possibly everything works when menus are translated.  More likely things break.
If so, not using & in leoConfig.txt will probably "restore" things to their
former not-very-good state.

It appears that I shall have to revisit this whole gruesome topic when
the configuration code gets rewritten.
#@nonl
#@-node:ekr.20040208112836:Specify shortcuts, ampersand bindings & translations in the same place
#@+node:ekr.20040212094034:Another comment
@nocolor
http://sourceforge.net/forum/message.php?msg_id=2420426
By: dalcolmo

> One copy of leoConfig.txt might specify installation options; other copies
might specify per-folder or per-file options.

Maybe I misunderstand this, but would the .leo file be the place for any per-file
options?

> "inheritable" copies of leoConfig.txt.
Yes! I want a copy per user, so I won't have to manually update the latest
installation of leo with my configurations. The typical global rc file (in /etc
or in the leo program dir) and local config file in ~/.leo would do fine. As
far as I am concerned, that would also be my preferred setup under Windows,
but I am sure most people would disagree.

Please move all global configuration options to one place. Currently, besides
having to edit leoConfig.leo, I have also to edit leoPlugins.leo or
pluginsManager.txt
I believe, these should also be configuration settings.

Best regards - Josef Dalcolmo
#@nonl
#@-node:ekr.20040212094034:Another comment
#@+node:ekr.20040213060739:Config stuff
@nocolor

By: nobody ( Nobody/Anonymous ) 
 RE: 4.1rc4 Plug-in Bug?   
2004-02-13 03:32  

 if you write protect leoconfig.txt you also notice via traceback that it opens for write on entry too, not sure if it actually writes anything at that time.

looking forward to what the team dreams up for config options.
checkbox or dropdown & edit of available options
with spin boxes for font size etc
a positive way to lock all the panes ito the same scheme or allow individual choice.
and all the other per user options maybe an override on the command line to enable or disable a plugin or select a particular config.txt
 
#@-node:ekr.20040213060739:Config stuff
#@+node:ekr.20040216153659.1:Find some way to translate all other Leo messages.
Including, e.g., Undo messages.
#@nonl
#@-node:ekr.20040216153659.1:Find some way to translate all other Leo messages.
#@+node:ekr.20031218072017.651:Suggestion from RodrigoB
http://sourceforge.net/forum/message.php?msg_id=2312787
By: nobody

I had the idea some days ago, and I think I have not posted it yet.

The idea is simple but usefull, it is related about how to get a better configuration
interface for Leo.
The idea is:

let the configuration interface be a Plugin. This plugin define the
@configuration-file nodes. Under that nodes (and they childs), when selecting
the body, the body text is parsed to construct a tk dialog directly into the
body pane (replacing the text view, or as a window element in the text). When
modifying the menu, the text is changed. If something go wrong with the text
format, an error is raised and the original text is show.

The advantage of this plugin is that it is easier to devellop and integrate
very well with the actual scheme. It allow to define the configuration as a
hierarchy of nodes, similar to the linux kernel configuration, and use the power
of Leo.

The text format could be

option1 = 1 # checkbox <= the plugin infere that it have to show the option1
as a checkbox
option2 = bla # [bla, blo, bli] <= the plugin infere that it have to show a
combobox for option2, with options bla, blo, bli
option3 = rodrigo # text   <= trivial, etc...

The comment after the option could allow to specify python code for conditions,
etc, etc	
Obviously the comment can be placed, the line before, or the line after, or
under what ever format required.

That is the idea, it is implementable, and, to my eyes, would solve the problem.

RodrigoB.


#@-node:ekr.20031218072017.651:Suggestion from RodrigoB
#@+node:ekr.20031218072017.652:Add settings menu
- Add Settings menu.
	- Create Settings menu dynamically from leoConfig.txt
- Write leoConfig.txt by hand.

- Remove Open LeoConfig.leo command
- Remove Apply Settings command (will be done with apply, cancel, ok, revert buttons in all Settings submenus)
#@nonl
#@+node:ekr.20031218072017.653:Notes
@nocolor

It would be an understatement to say that the way Leo handles configuration settings could be improved.  The present scheme involves editing leoConfig.leo, then remembering to save all .leo files before tangling leoConfig.leo.  Moreover, even when doing this properly, not all changes to settings "take" immediately.  Furthermore, there are no easy analogies to the typical "apply" or "revert" or "revert to default" buttons commonly seen in options dialogs.

Last night I studied the way the jEdit editor handles options.  See http://www.jedit.org/
Visually, jEdit's "Global Options" dialogs are very impressive.  There is a tree view on the left, there is a unique panel on the left for each item in the tree view.  All options are set visually.

Something like this could be done in Leo, but actually I like the opening up leoConfig.leo and setting options in a typical Leo window.  The advantage of leoConfig.leo is that there is plenty of room to explain what each setting does.

Leo's Set Colors and Set Font dialogs can and should affect the settings in leoConfig.leo, but at present they only affect leoConfig.txt, which is most annoying, and basically wrong.

This morning I realized that a large part of Leo's difficulties with configuration options stems from me trying to work around the limitations of Python's ConfigParser module.  Relying on this module may be the worst mistake I have made in the Python version of Leo.  This mistake has had several ramifications:

-  leoConfig.leo uses @root trees rather than @file trees because ConfigParser deletes all comments when writing leoConfig.txt.  Suppose instead that Leo would read and write leoConfig.txt without the "help" of the ConfigParser module.  For reading, all that is needed is that Leo parse leoConfig.txt into a single configuration dictionary.  This would, in fact, be very easy to do.

- Leo needs to do a better job of ensuring that leoConfig.txt always matches the settings in effect.  This can be done if Leo can _rewrite_ leoConfig.txt as it was (with all comments and especially sentinel lines), merely substituting new settings for old.  This is only slightly harder to do.

Other improvements come to mind, not directly related to the problems with ConfigParser:

- There should be a separate Settings Menu.  This would have the Set Colors and Set Font commands, as well as the following commands: Edit Settings (Same as present Open leoConfig.leo command) and Use Default Settings command (rewrites leoConfig.txt using preset defaults) and possibly Set Default Settings, Apply Settings and Revert Settings commands.

- All classes that use configuration settings should implement a configure method that immediately updates settings to the values just written to leoConfig.txt.  This includes the commands, frame and tree classes, and others.

With this long background, there are two main approaches to improving how Leo handles options:

1. Use a graphical scheme like jEdit does, and dispense with leoConfig.leo entirely.  leoConfig.txt would be the only repository for options.  This graphical scheme would use typical Apply, Revert, OK and Cancel buttons, much like the present Set Colors and Set Font dialogs.

2. Improve how Leo handles leoConfig.leo and leoConfig.txt so that leoConfig.leo can use @file trees and so that settings are _reliably_ updated when the user would expect them to be.

At present, I favor the second scheme.  It is simple to implement, it is the most Leonine, and moreover it allows for full discussion of all options.  True, the graphical way is good looking, but that is about all it has going for it.  I suppose a help feature could be added to the graphical way, but we are talking about a lot of effort for very little real value to the user.

Actually though, the issues of keeping leoConfig.txt up-to-date and of applying settings immediately remain mostly the same regardless of which way is chosen.  In particular, without the "help" of the ConfigParser module Leo could maintain options much more easily.

Anyway, this is how I see matters.  Any comments?

Edward
#@-node:ekr.20031218072017.653:Notes
#@+node:ekr.20031218072017.654:Cleanup prefs code?
I'm not at all sure that it is worthwhile now.
#@nonl
#@-node:ekr.20031218072017.654:Cleanup prefs code?
#@-node:ekr.20031218072017.652:Add settings menu
#@+node:ekr.20031218072017.655:multiple copies of leoConfig.txt
@nocolor

> It just leaves still the problem of merging ones personal settings of leoConfig.txt 
with the ones in the new distribution. 

I am going to work on this just after 3.11b1 goes out the door. I think what I shall do is have Leo looks for several different files: first leoConfig.txt, then leoSiteConfig.txt, with the latter overriding the former. That way you can have stable settings (leoSiteConfig.txt won't be part of distributions). 

I may also have Leo look for leoLinuxConfig.txt, leoWinConfig.txt and leoMacConfig.txt, depending on the platform, so you can have stable platform settings as well. 
#@nonl
#@-node:ekr.20031218072017.655:multiple copies of leoConfig.txt
#@+node:ekr.20031218072017.656:Different fonts for Linux/Mac
@nocolor

By: sanori ( Joo-won Jung ) 
 How about split font config for win and unix?   
2003-02-10 12:02  
Developer Forum

How about split the font's configurations like IDLE, python IDE? 
Because the 10pt size in Windows and Unix (X window, exactly) is not the same. Moreover, the font set that the OS provides does not the same. 

I'm using leo on both windows and Linux, and leo is in the vfat partition to use it both OS. Of course, I can change the font size by using the font dialog. But, I want leo to be more comportable. :)

-Sanori 
#@-node:ekr.20031218072017.656:Different fonts for Linux/Mac
#@+node:ekr.20040226105601:Design for new config system (alpaha, etc.) Tom
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2434323
By: nobody

Okay, so 4.2 is to address a graphical interface to configuring Leo, so I'm
going to go over some of the ways the Alpha, AlphaX, and AlphaTk family of
editors handle this. These are editors that use Tcl as the core scripting
language, much as elisp does for Emacs. In AlphaTk, all of the code is in Tcl &
Tk, so it run on any system that support Tcl/Tk 8.3+, and shows off the type of
dialogs that can be given to Leo as it also (currently) uses the Tk half of
this. It is available for Windows, and has a 45 day free demo period. It works
fine with The Tcl/Tk we use to run Leo so you only need to download AlphaTk.exe
via this link: http://www.santafe.edu/~vince/alphatk/download.html

In Alpha, like Emacs, their are "modes" specfic to each language that give you
useful functionality when you are editing code in that language. Each mode has
its own namespace (e.g. 'pyth' for the python Mode) that hold all the variables
and functions that provide the functionality mentioned above. There is a set of
variables and functions that form a sort of "global mode" that would be useful
in editing plain text, and provides values and simple editting bahavior if no
such simularly named things exists in the pyth namespace, if these do exist,
them overide these global values & functions when in that editting mode.

Modes are held in a directory named "Modes", in Leo this would probably be a
subdirectory of 'plugins'. In Alpha a simple mode can be defined in a single
file, Leo should proabably use the multi-file method, each mode is given its own
subdirectory (e.g. ...\plugins\Modes\Python Mode\), then various files to define
that mode's functionality would be placed in that directory. Thus you could
include the syntax files directly from jEdit here. 

Alpha only concerns itself with what it needs to know to accomplish what its
user is trying to do, thus, if a user never edits a c++ file in a session, Alpha
never loads in the details of the C Mode (which includes c++), it knows it has a
C Mode, what file extensions it should activate for & has a short description to
give if the users ask for help on the mode, but only loads the rest of the code
that defines the mode if it is called on to edit a c or c++ file. It does this
by scanning all of the subdirectories files for a function call that has all
this info as parameters. These are gathered into a cache (a script file) that
gets executed when alpha starts up. This cache has a dictionary of filenames to
modification dates so that the cache remains valid as long as the files scanned
are already in the dictionary and have the same modification dates. Any conflict
gets caught at start-up and triggers a rescanning. 

Alpha has a function, 'newPref', that builds dictionaries of various kinds of
variables, consisting of variable_name to a tuple. That tuple varies according
to the type of variable, but has as a minimum, the default value and the
namespace it is to be created in. Other parts of the tuple might include a list
of the possible values, or a series of tuples (<text to present in drop down
box>, <value>). Any change by the user to a value other than the default gets
that choice written to a cache that gets loaded the first time a mode is
activated. When newPref executes it checks to see if the variable already
exists, and only creates that variable and intializes it to the default if it
doesn't. A further wrinkle is that any comment immediately preceding a newPref
call gets stored as help text for that variable.

These dictionaries are used to allow the configuration dialog for a mode be
automatically created on the fly. Flag dictionaries get their keys dumped and
sorted to create an array of checkboxes, their stored comments forms a "tooltip"
box of text displayed when the arrow hovers over the checkbox.

Other dictionaries drive other types of widgets in the dialog panes.

More later, but much of this could be adapted to Leo. Other things to check out
are the keysetting dialogs, an example of which is under "Config->Special
Keys...".

Tom
#@+node:ekr.20040227053137:More alpha stuff
https://sourceforge.net/forum/message.php?msg_id=2444789
By: nobody

To continue on things that Alpha contribute to Leo, let me tell you the some of
the reasons behind the design.

File structure: Alpha has the equivalent of a plugin directory, however, the
actual plug-ins are organized in subdirectories; files that tell alpha how to
support languages are placed in the "Modes" directory, files that add
functionality exposed primarily through an add-in menu are placed in the "Menus"
directory (Leo might use "Menus & Widgets" to include things like the new search
plug-in), other code that provides functionality that is not tied to a
particular language, and is not primarily invoked via a menu are placed in the
"Packages" directory, (e.g. the completion package that provide general word
completion).

This helps out when someone wants to roll their own code, if you want to add a
language look at the examples that are in the 'Modes' directory, you can quickly
cobble together elementary support and then extend it over time.

Installing a plug-in is really just a matter of putting the files in the right
place and have Alpha update its indexes. Now that does not mean that the new
features are enabled (other than for a language, those activate whenever Alpha
realises that it is editting a file of that language), if you go to
"Config->Global-Setup->Features" or "Config->Global-Setup->Menus", you get a
panel of checkboxes for Features or Menus that you can check to enable Globally,
(i.e. no matter what mode is currently active). Note that a tooltip box giving a
description is available whenever you hover the mouse over the feature/menu
name. These are built dynamically, are sorted alphabetically, and continued on
another page whenever their are too many to position on a single page. This
means no carefully redesign of a dialog box is needed to accomadate new
features.

The preferences for a language are only settable when a given mode is active.
Although in Alpha this is dependent on the file you are editing, Leo can be
thought of as in a given language mode dependent on what language directive is
in effect for the current cursor position. Once you are in a mode, the
"Config-><mode> Mode Prefs" Menu becomes active, Allowing you to choose what
Menus & Feature you would like available when you are in this mode. You also
have access to the Preferences that are available in that mode.

Note that any preference, feature set, menus that you chose are all store in a
cache of code that sets them to your chosen values whenever you start Alpha.
These values can be saved and used when you upgade to a new version (of Alpha or
any of its packages), conversely, discarded the Prefs directory restores you to
all the defaults as orginally specified. Mechanisms exist to prune out cached
values that are no longer used, and to ensure that a new variable that has been
added is set if it needs to be before some code gets executed.

Tom

#@-node:ekr.20040227053137:More alpha stuff
#@-node:ekr.20040226105601:Design for new config system (alpaha, etc.) Tom
#@+node:ekr.20040327050211:Fix problems with updating leoConfig.txt
#@+node:ekr.20040330092305:Preserve comments in config files (so we can use sentinels!)
@nocolor

> I don't know how to read [an @file-nosent config] file into the body of that node.

You can't, at least not automatically.  Leo needs sentinels in order to read a derived file, that is, in order to update the outline using information in a derived file.

I've been dithering about exactly what to do about configuration files.  Your question reminds me that we would prefer to have sentinels in config files.  In order to do that, Leo must preserve comments in config files, which of course would be much better than the present pathetic situation in which Python's configParser module strips comments.  As I have said before, using configParser was a blunder.  Somehow I'll fix this for 4.2.

Edward
#@nonl
#@-node:ekr.20040330092305:Preserve comments in config files (so we can use sentinels!)
#@+node:ekr.20040124073801:Add nullConfig class?
#@-node:ekr.20040124073801:Add nullConfig class?
#@+node:EKR.20040606193130:(Fix problems with negative tab width in prefs)
#@+node:EKR.20040606193323:Report
@nocolor

Open discussion
http://sourceforge.net/forum/message.php?msg_id=2603738

I always set tabs to -4 in the config. when you open the prefs it says 4 but
there is a checkmark for convert tabs to space. open a new leo, open prefs. its
not checked! open another leo, its checked.

The problem is that tab_width is usually stored in leoConfig.txt, not the .leo file.
I'm going to ignore this problem for now...
#@nonl
#@-node:EKR.20040606193323:Report
#@+node:ekr.20031218072017.2062:getPrefs
def getPrefs (self):

    c = self.c ; config = g.app.config
    
    if self.getOpenTag("<preferences"):
        return # <preferences/> seeen

    table = (
        ("allow_rich_text",None,None), # Ignored.
        ("tab_width","tab_width",self.getLong),
        ("page_width","page_width",self.getLong),
        ("tangle_bat","tangle_batch_flag",self.getBool),
        ("untangle_bat","untangle_batch_flag",self.getBool),
        ("output_doc_chunks","output_doc_flag",self.getBool),
        ("noweb_flag",None,None), # Ignored.
        ("extended_noweb_flag",None,None), # Ignored.
        ("defaultTargetLanguage","target_language",self.getTargetLanguage),
        ("use_header_flag","use_header_flag",self.getBool))
    
    done = False
    while 1:
        found = False
        for tag,var,f in table:
            if self.matchTag("%s=" % tag):
                if var:
                    self.getDquote() ; val = f() ; self.getDquote()
                    setattr(c,var,val)
                else:
                    self.getDqString()
                found = True ; break
        if not found:
            if self.matchTag("/>"):
                done = True ; break
            if self.matchTag(">"):
                break
            else: # New in 4.1: ignore all other tags.
                self.getUnknownTag()

    if not done: # 8/31/04
        while 1:
            if self.matchTag("<defaultDirectory>"):
                # New in version 0.16.
                c.tangle_directory = self.getEscapedString()
                self.getTag("</defaultDirectory>")
                if not g.os_path_exists(c.tangle_directory):
                    g.es("default tangle directory not found:" + c.tangle_directory)
            elif self.matchTag("<TSyntaxMemo_options>"):
                self.getEscapedString() # ignored
                self.getTag("</TSyntaxMemo_options>")
            else: break
        self.getTag("</preferences>")

    # Override .leo file's preferences if settings are in leoConfig.txt.
    if config.configsExist:
        config.setCommandsIvars(c)
#@nonl
#@+node:ekr.20031218072017.2063:getTargetLanguage
def getTargetLanguage (self):
    
    # Must match longer tags before short prefixes.
    for name in g.app.language_delims_dict.keys():
        if self.matchTagWordIgnoringCase(name):
            language = name.replace("/","")
            # self.getDquote()
            return language
            
    return "c" # default
#@nonl
#@-node:ekr.20031218072017.2063:getTargetLanguage
#@-node:ekr.20031218072017.2062:getPrefs
#@-node:EKR.20040606193130:(Fix problems with negative tab width in prefs)
#@+node:ekr.20040225061559:Look at older config files when reading config data the first time for a new install
@nocolor

Is there a way that a "new" Leo installation could query the older one for config data?

Every time I get a new copy, I need to do a side-by-side compare and edit with the previous leoConfig.leo in order to change the settings to the way I want them. It would be really nice if this could be automated in some fashion.
#@nonl
#@-node:ekr.20040225061559:Look at older config files when reading config data the first time for a new install
#@-node:ekr.20040327050211:Fix problems with updating leoConfig.txt
#@+node:EKR.20040422132037.4:config problem on Linux
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2373816
By: sbeards

A related issue is that leoConfig.txt gets auto-tangled and overwritten on startup.
This will be a problem if Leo is run by a non-root user or the file is not readable.
For example, I get the following traceback after starting Leo (when leoConfig.txt
is read-only):

Traceback (most recent call last):
  File "/usr/local/share/leo-4.1-rc3/src/leoConfig.py", line 672, in update
    cf = open(self.configFileName,mode)
IOError: [Errno 13] Permission denied:
'/usr/local/share/leo-4.1-rc3/src/../config/leoConfig.txt'

Is there a reason leoConfig.txt gets auto-tangled on startup? Is there anyway
to disable this? Is this by design?
#@-node:EKR.20040422132037.4:config problem on Linux
#@+node:ekr.20031218072017.829:gui interface for plugins manager
@nocolor

Interim design:
	
- use pluginsManager.txt in plugins directory.
	- Load file, scan for non-comment lines whose files are in plugins directory
- load plugins only if they are in pluginsManager.txt
- (later?) do a graphical interface for pluginsManager.txt

Simplest graphical interface:

- Listbox of all files.
- Enable, Disable, Revert, Cancel, OK buttons, Enable All, Disable All.
- Selecting an item in the Listbox enables one of the Enable/Disable buttons
- Selecting OK rewrites the file "in place" without altering comment lines.
- Opening file creates one entry for each .py file in plugins directory.

This is like the Spell-check plugin.
#@nonl
#@-node:ekr.20031218072017.829:gui interface for plugins manager
#@-node:ekr.20031218072017.828:Rewrite the config manager
#@+node:ekr.20040919182750.1:Make sure Leo works with CVS
#@+node:ekr.20040916122326:Check Import command
#@-node:ekr.20040916122326:Check Import command
#@+node:ekr.20040919101930.2:Resolve CVS Conflicts command
#@-node:ekr.20040919101930.2:Resolve CVS Conflicts command
#@-node:ekr.20040919182750.1:Make sure Leo works with CVS
#@-node:ekr.20040919182750:Top 3
#@+node:ekr.20040916121944:Can be done in plugins
#@+node:ekr.20040919101930:Most important
#@+node:ekr.20031218072017.828:Rewrite the config manager
http://sourceforge.net/forum/message.php?msg_id=2329053
By: dsalomoni

I would be reluctant to run this script in a linux environment, for the following
reasons:

1) chmod 666 on the leo config file makes it writable by anybody. 

2) on the other hand, leo is currently IMHO not suitable for generic linux
installations, because it does not support per-user config files, so you actually
need to either carve leoconfig.txt in stone (not realistic), or make it writable
by any user as above (not advisable).

3) having a single dummy leoID defeats its main purpose in a multi-user environment.
If you could set per-user config files, the leoID could by default simply derived
from the username.

As a matter of fact, I've been suggesting to friends etc wanting to try out
leo/install it on their systems not to do that globally.

So, my view is that we could:

1) in the short term, provide a simple per-user installation based for example
on the script above, but with path defaults pointing to the user's home dir
(and of course w/o the requirement to be root) -- with leoID generated by default
looking at the username.

2) in the medium term, provide per-user config possibilities -- this would possibly
allow for a system-wide installation of leo and still allow configuration, per-user
plugins, etc. But this is linked I guess to the decommissioning of/changes to
the leoConfig.leo machinery.

Davide
#@nonl
#@+node:ekr.20040922073523:comments from e
@killcolor
http://sourceforge.net/forum/message.php?msg_id=2769775
By: nobody

Leo should make it a priority to determine leoID
and users HOME and other dir of interest
before plugins are loaded so thay can have access.
at 'after-create-leo-frame' g.app.leoID not defined
but g.app.loadDir and many others are.
if there will be more than one config, a system,
a per user in HOME, and maybe per currentdir or
in the same dir as the leo, they should be known.

g.app.homeDir = os.getenv('HOME', 
                    default= os.path.abspath('./'))
on windows this can point many places
best to set HOME if you want it in a specific place.
otherwise it can be %WINDIR%/APPDATA or %ROOT%/APPDATA
APPDATA can be an env variable, probably win2k or later.

# abspath resolves './' sometimes returned curdir
import user, os
print os.path.abspath(user.home) 
print os.path.abspath(os.path.expanduser("~"))

obviously this will need allot of work to get
crossplatform and bulletproof. you would think
it would be easy to know where everything should go
or how to nail down how to find everything.
no such luck, all I've seen is bits and pieces.

e
#@-node:ekr.20040922073523:comments from e
#@+node:ekr.20040105080119:Search for settings in various places: Rodrigo
@nocolor

https://sourceforge.net/forum/message.php?msg_id=2355843
By: rodrigo_b

At least pluginManager should be there too	

(or both files could be merged... who knows)

The better would be to have

$HOME/.leo/
$HOME/.leo/leoConfig.txt
$HOME/.leo/plugins/pluginManager.txt
$HOME/.leo/plugins/<myplugins>.py

look for .leoConfig.txt, then leoConfig.txt
#@nonl
#@+node:ekr.20040919075839:Stephen Schaefer
By: Stephen Schaefer - thyrsus
RE: 4.2 rc1 released  
2004-09-18 05:32
It took me two hours to track it down, but I did manage to get leoConfit.txt and .leoID.txt to be taken from $HOME/.leoconfig

Mostly it took so long because the documenation assumed a knowledge of Python operation I didn't posess.

First, the current leo requires Python 2.3, and I'm running on a Red Hat 7.3 system where the "native" python is 1.5.2, and "python2" is 2.2.2. It's far cheaper to add another python than to make sure a different python doesn't break things, so my python 2.3 is rooted at /apps/python/python-2.3.2. There is a file that python looks for but is not present when it is installed from source:

/apps/python/python-2.3.2/lib/python2.3/sitecusomize.py

I created that file with the following contents (. for leading space):

import sys
import os
try:
....sys.leo_config_directory = os.environ['HOME'] + '/.leoconfig'
except KeyError:
....sys.leo_config_directory = None

If I were more sophisticated, I would check that $HOME/.leoconfig was a directory, and if not also set sys.leo_config_directory to None.

It doesn't seem appropriate to insist that anyone using python 2.3.2 for any reason whatsoever would be forced to have a .leoconfig in their home directory (think of a limited account that doesn't have a writable home directory), but it would be appropriate for leo to create $HOME/.leoconfig if it weren't present and set sys.leo_config_directory at that point -- indeed the entire logic could be handled outside of sitecustomize.py. I'll post code for the above if someone else doesn't beat me to it :-).
#@-node:ekr.20040919075839:Stephen Schaefer
#@-node:ekr.20040105080119:Search for settings in various places: Rodrigo
#@+node:ekr.20040208112836:Specify shortcuts, ampersand bindings & translations in the same place
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2415127
By: edream

I have just hacked the code to allow & in entries in leoConfig.txt that describe
shortcuts.

This is a hack because it probably interacts poorly with the present mechanism
that allows menu entries to be translated.  I haven't looked into this in detail:
possibly everything works when menus are translated.  More likely things break.
If so, not using & in leoConfig.txt will probably "restore" things to their
former not-very-good state.

It appears that I shall have to revisit this whole gruesome topic when
the configuration code gets rewritten.
#@nonl
#@-node:ekr.20040208112836:Specify shortcuts, ampersand bindings & translations in the same place
#@+node:ekr.20040212094034:Another comment
@nocolor
http://sourceforge.net/forum/message.php?msg_id=2420426
By: dalcolmo

> One copy of leoConfig.txt might specify installation options; other copies
might specify per-folder or per-file options.

Maybe I misunderstand this, but would the .leo file be the place for any per-file
options?

> "inheritable" copies of leoConfig.txt.
Yes! I want a copy per user, so I won't have to manually update the latest
installation of leo with my configurations. The typical global rc file (in /etc
or in the leo program dir) and local config file in ~/.leo would do fine. As
far as I am concerned, that would also be my preferred setup under Windows,
but I am sure most people would disagree.

Please move all global configuration options to one place. Currently, besides
having to edit leoConfig.leo, I have also to edit leoPlugins.leo or
pluginsManager.txt
I believe, these should also be configuration settings.

Best regards - Josef Dalcolmo
#@nonl
#@-node:ekr.20040212094034:Another comment
#@+node:ekr.20040213060739:Config stuff
@nocolor

By: nobody ( Nobody/Anonymous ) 
 RE: 4.1rc4 Plug-in Bug?   
2004-02-13 03:32  

 if you write protect leoconfig.txt you also notice via traceback that it opens for write on entry too, not sure if it actually writes anything at that time.

looking forward to what the team dreams up for config options.
checkbox or dropdown & edit of available options
with spin boxes for font size etc
a positive way to lock all the panes ito the same scheme or allow individual choice.
and all the other per user options maybe an override on the command line to enable or disable a plugin or select a particular config.txt
 
#@-node:ekr.20040213060739:Config stuff
#@+node:ekr.20040216153659.1:Find some way to translate all other Leo messages.
Including, e.g., Undo messages.
#@nonl
#@-node:ekr.20040216153659.1:Find some way to translate all other Leo messages.
#@+node:ekr.20031218072017.651:Suggestion from RodrigoB
http://sourceforge.net/forum/message.php?msg_id=2312787
By: nobody

I had the idea some days ago, and I think I have not posted it yet.

The idea is simple but usefull, it is related about how to get a better configuration
interface for Leo.
The idea is:

let the configuration interface be a Plugin. This plugin define the
@configuration-file nodes. Under that nodes (and they childs), when selecting
the body, the body text is parsed to construct a tk dialog directly into the
body pane (replacing the text view, or as a window element in the text). When
modifying the menu, the text is changed. If something go wrong with the text
format, an error is raised and the original text is show.

The advantage of this plugin is that it is easier to devellop and integrate
very well with the actual scheme. It allow to define the configuration as a
hierarchy of nodes, similar to the linux kernel configuration, and use the power
of Leo.

The text format could be

option1 = 1 # checkbox <= the plugin infere that it have to show the option1
as a checkbox
option2 = bla # [bla, blo, bli] <= the plugin infere that it have to show a
combobox for option2, with options bla, blo, bli
option3 = rodrigo # text   <= trivial, etc...

The comment after the option could allow to specify python code for conditions,
etc, etc	
Obviously the comment can be placed, the line before, or the line after, or
under what ever format required.

That is the idea, it is implementable, and, to my eyes, would solve the problem.

RodrigoB.


#@-node:ekr.20031218072017.651:Suggestion from RodrigoB
#@+node:ekr.20031218072017.652:Add settings menu
- Add Settings menu.
	- Create Settings menu dynamically from leoConfig.txt
- Write leoConfig.txt by hand.

- Remove Open LeoConfig.leo command
- Remove Apply Settings command (will be done with apply, cancel, ok, revert buttons in all Settings submenus)
#@nonl
#@+node:ekr.20031218072017.653:Notes
@nocolor

It would be an understatement to say that the way Leo handles configuration settings could be improved.  The present scheme involves editing leoConfig.leo, then remembering to save all .leo files before tangling leoConfig.leo.  Moreover, even when doing this properly, not all changes to settings "take" immediately.  Furthermore, there are no easy analogies to the typical "apply" or "revert" or "revert to default" buttons commonly seen in options dialogs.

Last night I studied the way the jEdit editor handles options.  See http://www.jedit.org/
Visually, jEdit's "Global Options" dialogs are very impressive.  There is a tree view on the left, there is a unique panel on the left for each item in the tree view.  All options are set visually.

Something like this could be done in Leo, but actually I like the opening up leoConfig.leo and setting options in a typical Leo window.  The advantage of leoConfig.leo is that there is plenty of room to explain what each setting does.

Leo's Set Colors and Set Font dialogs can and should affect the settings in leoConfig.leo, but at present they only affect leoConfig.txt, which is most annoying, and basically wrong.

This morning I realized that a large part of Leo's difficulties with configuration options stems from me trying to work around the limitations of Python's ConfigParser module.  Relying on this module may be the worst mistake I have made in the Python version of Leo.  This mistake has had several ramifications:

-  leoConfig.leo uses @root trees rather than @file trees because ConfigParser deletes all comments when writing leoConfig.txt.  Suppose instead that Leo would read and write leoConfig.txt without the "help" of the ConfigParser module.  For reading, all that is needed is that Leo parse leoConfig.txt into a single configuration dictionary.  This would, in fact, be very easy to do.

- Leo needs to do a better job of ensuring that leoConfig.txt always matches the settings in effect.  This can be done if Leo can _rewrite_ leoConfig.txt as it was (with all comments and especially sentinel lines), merely substituting new settings for old.  This is only slightly harder to do.

Other improvements come to mind, not directly related to the problems with ConfigParser:

- There should be a separate Settings Menu.  This would have the Set Colors and Set Font commands, as well as the following commands: Edit Settings (Same as present Open leoConfig.leo command) and Use Default Settings command (rewrites leoConfig.txt using preset defaults) and possibly Set Default Settings, Apply Settings and Revert Settings commands.

- All classes that use configuration settings should implement a configure method that immediately updates settings to the values just written to leoConfig.txt.  This includes the commands, frame and tree classes, and others.

With this long background, there are two main approaches to improving how Leo handles options:

1. Use a graphical scheme like jEdit does, and dispense with leoConfig.leo entirely.  leoConfig.txt would be the only repository for options.  This graphical scheme would use typical Apply, Revert, OK and Cancel buttons, much like the present Set Colors and Set Font dialogs.

2. Improve how Leo handles leoConfig.leo and leoConfig.txt so that leoConfig.leo can use @file trees and so that settings are _reliably_ updated when the user would expect them to be.

At present, I favor the second scheme.  It is simple to implement, it is the most Leonine, and moreover it allows for full discussion of all options.  True, the graphical way is good looking, but that is about all it has going for it.  I suppose a help feature could be added to the graphical way, but we are talking about a lot of effort for very little real value to the user.

Actually though, the issues of keeping leoConfig.txt up-to-date and of applying settings immediately remain mostly the same regardless of which way is chosen.  In particular, without the "help" of the ConfigParser module Leo could maintain options much more easily.

Anyway, this is how I see matters.  Any comments?

Edward
#@-node:ekr.20031218072017.653:Notes
#@+node:ekr.20031218072017.654:Cleanup prefs code?
I'm not at all sure that it is worthwhile now.
#@nonl
#@-node:ekr.20031218072017.654:Cleanup prefs code?
#@-node:ekr.20031218072017.652:Add settings menu
#@+node:ekr.20031218072017.655:multiple copies of leoConfig.txt
@nocolor

> It just leaves still the problem of merging ones personal settings of leoConfig.txt 
with the ones in the new distribution. 

I am going to work on this just after 3.11b1 goes out the door. I think what I shall do is have Leo looks for several different files: first leoConfig.txt, then leoSiteConfig.txt, with the latter overriding the former. That way you can have stable settings (leoSiteConfig.txt won't be part of distributions). 

I may also have Leo look for leoLinuxConfig.txt, leoWinConfig.txt and leoMacConfig.txt, depending on the platform, so you can have stable platform settings as well. 
#@nonl
#@-node:ekr.20031218072017.655:multiple copies of leoConfig.txt
#@+node:ekr.20031218072017.656:Different fonts for Linux/Mac
@nocolor

By: sanori ( Joo-won Jung ) 
 How about split font config for win and unix?   
2003-02-10 12:02  
Developer Forum

How about split the font's configurations like IDLE, python IDE? 
Because the 10pt size in Windows and Unix (X window, exactly) is not the same. Moreover, the font set that the OS provides does not the same. 

I'm using leo on both windows and Linux, and leo is in the vfat partition to use it both OS. Of course, I can change the font size by using the font dialog. But, I want leo to be more comportable. :)

-Sanori 
#@-node:ekr.20031218072017.656:Different fonts for Linux/Mac
#@+node:ekr.20040226105601:Design for new config system (alpaha, etc.) Tom
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2434323
By: nobody

Okay, so 4.2 is to address a graphical interface to configuring Leo, so I'm
going to go over some of the ways the Alpha, AlphaX, and AlphaTk family of
editors handle this. These are editors that use Tcl as the core scripting
language, much as elisp does for Emacs. In AlphaTk, all of the code is in Tcl &
Tk, so it run on any system that support Tcl/Tk 8.3+, and shows off the type of
dialogs that can be given to Leo as it also (currently) uses the Tk half of
this. It is available for Windows, and has a 45 day free demo period. It works
fine with The Tcl/Tk we use to run Leo so you only need to download AlphaTk.exe
via this link: http://www.santafe.edu/~vince/alphatk/download.html

In Alpha, like Emacs, their are "modes" specfic to each language that give you
useful functionality when you are editing code in that language. Each mode has
its own namespace (e.g. 'pyth' for the python Mode) that hold all the variables
and functions that provide the functionality mentioned above. There is a set of
variables and functions that form a sort of "global mode" that would be useful
in editing plain text, and provides values and simple editting bahavior if no
such simularly named things exists in the pyth namespace, if these do exist,
them overide these global values & functions when in that editting mode.

Modes are held in a directory named "Modes", in Leo this would probably be a
subdirectory of 'plugins'. In Alpha a simple mode can be defined in a single
file, Leo should proabably use the multi-file method, each mode is given its own
subdirectory (e.g. ...\plugins\Modes\Python Mode\), then various files to define
that mode's functionality would be placed in that directory. Thus you could
include the syntax files directly from jEdit here. 

Alpha only concerns itself with what it needs to know to accomplish what its
user is trying to do, thus, if a user never edits a c++ file in a session, Alpha
never loads in the details of the C Mode (which includes c++), it knows it has a
C Mode, what file extensions it should activate for & has a short description to
give if the users ask for help on the mode, but only loads the rest of the code
that defines the mode if it is called on to edit a c or c++ file. It does this
by scanning all of the subdirectories files for a function call that has all
this info as parameters. These are gathered into a cache (a script file) that
gets executed when alpha starts up. This cache has a dictionary of filenames to
modification dates so that the cache remains valid as long as the files scanned
are already in the dictionary and have the same modification dates. Any conflict
gets caught at start-up and triggers a rescanning. 

Alpha has a function, 'newPref', that builds dictionaries of various kinds of
variables, consisting of variable_name to a tuple. That tuple varies according
to the type of variable, but has as a minimum, the default value and the
namespace it is to be created in. Other parts of the tuple might include a list
of the possible values, or a series of tuples (<text to present in drop down
box>, <value>). Any change by the user to a value other than the default gets
that choice written to a cache that gets loaded the first time a mode is
activated. When newPref executes it checks to see if the variable already
exists, and only creates that variable and intializes it to the default if it
doesn't. A further wrinkle is that any comment immediately preceding a newPref
call gets stored as help text for that variable.

These dictionaries are used to allow the configuration dialog for a mode be
automatically created on the fly. Flag dictionaries get their keys dumped and
sorted to create an array of checkboxes, their stored comments forms a "tooltip"
box of text displayed when the arrow hovers over the checkbox.

Other dictionaries drive other types of widgets in the dialog panes.

More later, but much of this could be adapted to Leo. Other things to check out
are the keysetting dialogs, an example of which is under "Config->Special
Keys...".

Tom
#@+node:ekr.20040227053137:More alpha stuff
https://sourceforge.net/forum/message.php?msg_id=2444789
By: nobody

To continue on things that Alpha contribute to Leo, let me tell you the some of
the reasons behind the design.

File structure: Alpha has the equivalent of a plugin directory, however, the
actual plug-ins are organized in subdirectories; files that tell alpha how to
support languages are placed in the "Modes" directory, files that add
functionality exposed primarily through an add-in menu are placed in the "Menus"
directory (Leo might use "Menus & Widgets" to include things like the new search
plug-in), other code that provides functionality that is not tied to a
particular language, and is not primarily invoked via a menu are placed in the
"Packages" directory, (e.g. the completion package that provide general word
completion).

This helps out when someone wants to roll their own code, if you want to add a
language look at the examples that are in the 'Modes' directory, you can quickly
cobble together elementary support and then extend it over time.

Installing a plug-in is really just a matter of putting the files in the right
place and have Alpha update its indexes. Now that does not mean that the new
features are enabled (other than for a language, those activate whenever Alpha
realises that it is editting a file of that language), if you go to
"Config->Global-Setup->Features" or "Config->Global-Setup->Menus", you get a
panel of checkboxes for Features or Menus that you can check to enable Globally,
(i.e. no matter what mode is currently active). Note that a tooltip box giving a
description is available whenever you hover the mouse over the feature/menu
name. These are built dynamically, are sorted alphabetically, and continued on
another page whenever their are too many to position on a single page. This
means no carefully redesign of a dialog box is needed to accomadate new
features.

The preferences for a language are only settable when a given mode is active.
Although in Alpha this is dependent on the file you are editing, Leo can be
thought of as in a given language mode dependent on what language directive is
in effect for the current cursor position. Once you are in a mode, the
"Config-><mode> Mode Prefs" Menu becomes active, Allowing you to choose what
Menus & Feature you would like available when you are in this mode. You also
have access to the Preferences that are available in that mode.

Note that any preference, feature set, menus that you chose are all store in a
cache of code that sets them to your chosen values whenever you start Alpha.
These values can be saved and used when you upgade to a new version (of Alpha or
any of its packages), conversely, discarded the Prefs directory restores you to
all the defaults as orginally specified. Mechanisms exist to prune out cached
values that are no longer used, and to ensure that a new variable that has been
added is set if it needs to be before some code gets executed.

Tom

#@-node:ekr.20040227053137:More alpha stuff
#@-node:ekr.20040226105601:Design for new config system (alpaha, etc.) Tom
#@+node:ekr.20040327050211:Fix problems with updating leoConfig.txt
#@+node:ekr.20040330092305:Preserve comments in config files (so we can use sentinels!)
@nocolor

> I don't know how to read [an @file-nosent config] file into the body of that node.

You can't, at least not automatically.  Leo needs sentinels in order to read a derived file, that is, in order to update the outline using information in a derived file.

I've been dithering about exactly what to do about configuration files.  Your question reminds me that we would prefer to have sentinels in config files.  In order to do that, Leo must preserve comments in config files, which of course would be much better than the present pathetic situation in which Python's configParser module strips comments.  As I have said before, using configParser was a blunder.  Somehow I'll fix this for 4.2.

Edward
#@nonl
#@-node:ekr.20040330092305:Preserve comments in config files (so we can use sentinels!)
#@+node:ekr.20040124073801:Add nullConfig class?
#@-node:ekr.20040124073801:Add nullConfig class?
#@+node:EKR.20040606193130:(Fix problems with negative tab width in prefs)
#@+node:EKR.20040606193323:Report
@nocolor

Open discussion
http://sourceforge.net/forum/message.php?msg_id=2603738

I always set tabs to -4 in the config. when you open the prefs it says 4 but
there is a checkmark for convert tabs to space. open a new leo, open prefs. its
not checked! open another leo, its checked.

The problem is that tab_width is usually stored in leoConfig.txt, not the .leo file.
I'm going to ignore this problem for now...
#@nonl
#@-node:EKR.20040606193323:Report
#@+node:ekr.20031218072017.2062:getPrefs
def getPrefs (self):

    c = self.c ; config = g.app.config
    
    if self.getOpenTag("<preferences"):
        return # <preferences/> seeen

    table = (
        ("allow_rich_text",None,None), # Ignored.
        ("tab_width","tab_width",self.getLong),
        ("page_width","page_width",self.getLong),
        ("tangle_bat","tangle_batch_flag",self.getBool),
        ("untangle_bat","untangle_batch_flag",self.getBool),
        ("output_doc_chunks","output_doc_flag",self.getBool),
        ("noweb_flag",None,None), # Ignored.
        ("extended_noweb_flag",None,None), # Ignored.
        ("defaultTargetLanguage","target_language",self.getTargetLanguage),
        ("use_header_flag","use_header_flag",self.getBool))
    
    done = False
    while 1:
        found = False
        for tag,var,f in table:
            if self.matchTag("%s=" % tag):
                if var:
                    self.getDquote() ; val = f() ; self.getDquote()
                    setattr(c,var,val)
                else:
                    self.getDqString()
                found = True ; break
        if not found:
            if self.matchTag("/>"):
                done = True ; break
            if self.matchTag(">"):
                break
            else: # New in 4.1: ignore all other tags.
                self.getUnknownTag()

    if not done: # 8/31/04
        while 1:
            if self.matchTag("<defaultDirectory>"):
                # New in version 0.16.
                c.tangle_directory = self.getEscapedString()
                self.getTag("</defaultDirectory>")
                if not g.os_path_exists(c.tangle_directory):
                    g.es("default tangle directory not found:" + c.tangle_directory)
            elif self.matchTag("<TSyntaxMemo_options>"):
                self.getEscapedString() # ignored
                self.getTag("</TSyntaxMemo_options>")
            else: break
        self.getTag("</preferences>")

    # Override .leo file's preferences if settings are in leoConfig.txt.
    if config.configsExist:
        config.setCommandsIvars(c)
#@nonl
#@+node:ekr.20031218072017.2063:getTargetLanguage
def getTargetLanguage (self):
    
    # Must match longer tags before short prefixes.
    for name in g.app.language_delims_dict.keys():
        if self.matchTagWordIgnoringCase(name):
            language = name.replace("/","")
            # self.getDquote()
            return language
            
    return "c" # default
#@nonl
#@-node:ekr.20031218072017.2063:getTargetLanguage
#@-node:ekr.20031218072017.2062:getPrefs
#@-node:EKR.20040606193130:(Fix problems with negative tab width in prefs)
#@+node:ekr.20040225061559:Look at older config files when reading config data the first time for a new install
@nocolor

Is there a way that a "new" Leo installation could query the older one for config data?

Every time I get a new copy, I need to do a side-by-side compare and edit with the previous leoConfig.leo in order to change the settings to the way I want them. It would be really nice if this could be automated in some fashion.
#@nonl
#@-node:ekr.20040225061559:Look at older config files when reading config data the first time for a new install
#@-node:ekr.20040327050211:Fix problems with updating leoConfig.txt
#@+node:EKR.20040422132037.4:config problem on Linux
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2373816
By: sbeards

A related issue is that leoConfig.txt gets auto-tangled and overwritten on startup.
This will be a problem if Leo is run by a non-root user or the file is not readable.
For example, I get the following traceback after starting Leo (when leoConfig.txt
is read-only):

Traceback (most recent call last):
  File "/usr/local/share/leo-4.1-rc3/src/leoConfig.py", line 672, in update
    cf = open(self.configFileName,mode)
IOError: [Errno 13] Permission denied:
'/usr/local/share/leo-4.1-rc3/src/../config/leoConfig.txt'

Is there a reason leoConfig.txt gets auto-tangled on startup? Is there anyway
to disable this? Is this by design?
#@-node:EKR.20040422132037.4:config problem on Linux
#@+node:ekr.20031218072017.829:gui interface for plugins manager
@nocolor

Interim design:
	
- use pluginsManager.txt in plugins directory.
	- Load file, scan for non-comment lines whose files are in plugins directory
- load plugins only if they are in pluginsManager.txt
- (later?) do a graphical interface for pluginsManager.txt

Simplest graphical interface:

- Listbox of all files.
- Enable, Disable, Revert, Cancel, OK buttons, Enable All, Disable All.
- Selecting an item in the Listbox enables one of the Enable/Disable buttons
- Selecting OK rewrites the file "in place" without altering comment lines.
- Opening file creates one entry for each .py file in plugins directory.

This is like the Spell-check plugin.
#@nonl
#@-node:ekr.20031218072017.829:gui interface for plugins manager
#@-node:ekr.20031218072017.828:Rewrite the config manager
#@+node:EKR.20040507095329.1:More flexible atFile classes
#@+node:ekr.20040722132104.1:Write support stuff to write files to nodes for testing
#@-node:ekr.20040722132104.1:Write support stuff to write files to nodes for testing
#@+node:ekr.20040713062858:Design new atFile organization
@killcolor

- Based on small, official methods.

- Only one class:
    - rename methods.
#@nonl
#@-node:ekr.20040713062858:Design new atFile organization
#@+node:ekr.20040710124416:(string-based i/o & unit tests)
@nocolor

To do:

- atFile.readFromString

- atFile.writeToList

- atFile.writeToString:
@color
    def writeToString(self,p):
        aList = self.writeToList(p)
        return string.join(aList,'')
@nocolor
        
* Problem: Don't want to put @file nodes in test.leo
    - solution: unit test calls atFile.writeToString.
        - This will allow us to create unit tests much more easily.
#@nonl
#@-node:ekr.20040710124416:(string-based i/o & unit tests)
#@+node:EKR.20040507100502:about calling scanDirectives
- called by top_df.read just before calling df.readOpenFile.

- called by atFile.openWriteFile
#@nonl
#@-node:EKR.20040507100502:about calling scanDirectives
#@+node:ekr.20031218072017.2622:<< class baseAtFile methods >>
@others
#@nonl
#@+node:ekr.20031218072017.2623:atFile.__init__ & initIvars
def __init__(self,c):
    
    self.c = c
    self.fileCommands = self.c.fileCommands
    
    # Create subcommanders to handler old and new format derived files.
    self.old_df = oldDerivedFile(c)
    self.new_df = newDerivedFile(c)
    
    self.initIvars()
    
def initIvars(self):
    
    # Set by scanDefaultDirectory.
    self.default_directory = None
    self.errors = 0

    # Set by scanHeader when reading. Set by scanAllDirectives...
    self.encoding = g.app.config.default_derived_file_encoding
    self.endSentinelComment = ""
    self.startSentinelComment = ""
#@nonl
#@-node:ekr.20031218072017.2623:atFile.__init__ & initIvars
#@+node:ekr.20031218072017.2624:top_df.error
def error(self,message):

    g.es(message,color="red")
    print message
    self.errors += 1
#@nonl
#@-node:ekr.20031218072017.2624:top_df.error
#@+node:ekr.20031218072017.2625:Reading
#@+node:ekr.20031218072017.2626: top_df.readAll
def readAll(self,root,partialFlag=False):
    
    """Scan vnodes, looking for @file nodes to read."""

    at = self ; c = at.c
    c.endEditing() # Capture the current headline.
    anyRead = False
    at.initIvars()
    p = root.copy()
    if partialFlag: after = p.nodeAfterTree()
    else: after = c.nullPosition()
    while p and not p.equal(after): # Don't use iterator.
        if p.isAtIgnoreNode():
            p.moveToNodeAfterTree()
        elif p.isAtThinFileNode():
            anyRead = True
            at.read(p,thinFile=True)
            p.moveToNodeAfterTree()
        elif p.isAtFileNode() or p.isAtNorefFileNode():
            anyRead = True
            if partialFlag:
                # We are forcing the read.
                at.read(p)
            else:
                # if p is an orphan, we don't expect to see a derived file,
                # and we shall read a derived file if it exists.
                wasOrphan = p.isOrphan()
                ok = at.read(p)
                if wasOrphan and not ok:
                    # Remind the user to fix the problem.
                    p.setDirty()
                    c.setChanged(True)
            p.moveToNodeAfterTree()
        else: p.moveToThreadNext()
    # Clear all orphan bits.
    for p in c.allNodes_iter():
        p.v.clearOrphan()
        
    if partialFlag and not anyRead:
        g.es("no @file nodes in the selected tree")
#@nonl
#@-node:ekr.20031218072017.2626: top_df.readAll
#@+node:EKR.20040604155223.1:top_df.parseLeoSentinel
def parseLeoSentinel (self,s):
    
    at = self
    new_df = False ; valid = True ; n = len(s)
    encoding_tag = "-encoding="
    version_tag = "-ver="
    tag = "@+leo"
    thin_tag = "-thin"
    << set the opening comment delim >>
    << make sure we have @+leo >>
    << read optional version param >>
    << read optional thin param >>
    << read optional encoding param >>
    << set the closing comment delim >>
    return valid,new_df,start,end
#@nonl
#@+node:EKR.20040604155223:<< set the opening comment delim >>
# s contains the tag
i = j = g.skip_ws(s,0)

# The opening comment delim is the initial non-tag
while i < n and not g.match(s,i,tag) and not g.is_nl(s,i):
    i += 1

if j < i:
    start = s[j:i]
else:
    valid = False
#@nonl
#@-node:EKR.20040604155223:<< set the opening comment delim >>
#@+node:ekr.20031218072017.2635:<< make sure we have @+leo >>
@ REM hack: leading whitespace is significant before the @+leo.  We do this so that sentinelKind need not skip whitespace following self.startSentinelComment.  This is correct: we want to be as restrictive as possible about what is recognized as a sentinel.  This minimizes false matches.
@c

if 0: # Make leading whitespace significant.
    i = g.skip_ws(s,i)

if g.match(s,i,tag):
    i += len(tag)
else: valid = False
#@nonl
#@-node:ekr.20031218072017.2635:<< make sure we have @+leo >>
#@+node:ekr.20031218072017.2636:<< read optional version param >>
new_df = g.match(s,i,version_tag)

if new_df:
    # Skip to the next minus sign or end-of-line
    i += len(version_tag)
    j = i
    while i < len(s) and not g.is_nl(s,i) and s[i] != '-':
        i += 1

    if j < i:
        pass # version = s[j:i]
    else:
        valid = False
#@-node:ekr.20031218072017.2636:<< read optional version param >>
#@+node:EKR.20040503105354:<< read optional thin param >>
if g.match(s,i,thin_tag):
    i += len(tag)
#@nonl
#@-node:EKR.20040503105354:<< read optional thin param >>
#@+node:ekr.20031218072017.2637:<< read optional encoding param >>
# Set the default encoding
at.encoding = g.app.config.default_derived_file_encoding

if g.match(s,i,encoding_tag):
    # Read optional encoding param, e.g., -encoding=utf-8,
    i += len(encoding_tag)
    # Skip to the next end of the field.
    j = s.find(",.",i)
    if j > -1:
        # The encoding field was written by 4.2 or after:
        encoding = s[i:j]
        i = j + 1 # 6/8/04
    else:
        # The encoding field was written before 4.2.
        j = s.find('.',i)
        if j > -1:
            encoding = s[i:j]
            i = j + 1 # 6/8/04
        else:
            encoding = None
    # g.trace("encoding:",encoding)
    if encoding:
        if g.isValidEncoding(encoding):
            at.encoding = encoding
        else:
            print "bad encoding in derived file:",encoding
            g.es("bad encoding in derived file:",encoding)
    else:
        valid = False
#@-node:ekr.20031218072017.2637:<< read optional encoding param >>
#@+node:ekr.20031218072017.2638:<< set the closing comment delim >>
# The closing comment delim is the trailing non-whitespace.
i = j = g.skip_ws(s,i)
while i < n and not g.is_ws(s[i]) and not g.is_nl(s,i):
    i += 1
end = s[j:i]
#@nonl
#@-node:ekr.20031218072017.2638:<< set the closing comment delim >>
#@-node:EKR.20040604155223.1:top_df.parseLeoSentinel
#@+node:ekr.20031218072017.1812:top_df.read
# The caller has enclosed this code in beginUpdate/endUpdate.

def read(self,root,importFileName=None,thinFile=False):
    
    """Common read logic for any derived file."""
    
    at = self ; c = at.c
    at.errors = 0
    importing = importFileName is not None
    << set fileName from root and importFileName >>
    << open file or return False >>
    g.es("reading: " + root.headString())
    firstLines,read_new = at.scanHeader(file,fileName)
    df = g.choose(read_new,at.new_df,at.old_df)
    # g.trace(g.choose(df==at.new_df,"new","old"))
    << copy ivars to df >>
    root.clearVisitedInTree()
    try:
        # 1/28/04: Don't set comment delims when importing.
        # 1/28/04: Call scanAllDirectives here, not in readOpenFile.
        importing = importFileName is not None
        df.scanAllDirectives(root,importing=importing,reading=True)
        df.readOpenFile(root,file,firstLines)
    except:
        at.error("Unexpected exception while reading derived file")
        g.es_exception()
    file.close()
    root.clearDirty() # May be set dirty below.
    after = root.nodeAfterTree()
    << warn about non-empty unvisited nodes >>
    if df.errors == 0:
        if not df.importing:
            << copy all tempBodyStrings to tnodes >>
    << delete all tempBodyStrings >>
    return df.errors == 0
#@nonl
#@+node:ekr.20031218072017.1813:<< set fileName from root and importFileName >>
at.scanDefaultDirectory(root,importing=importing)
if at.errors: return

if importFileName:
    fileName = importFileName
elif root.isAnyAtFileNode():
    fileName = root.anyAtFileNodeName()
else:
    fileName = None

if not fileName:
    at.error("Missing file name.  Restoring @file tree from .leo file.")
    return False
#@nonl
#@-node:ekr.20031218072017.1813:<< set fileName from root and importFileName >>
#@+node:ekr.20031218072017.1814:<< open file or return false >>
fn = g.os_path_join(at.default_directory,fileName)
fn = g.os_path_normpath(fn)

try:
    # 11/4/03: open the file in binary mode to allow 0x1a in bodies & headlines.
    file = open(fn,'rb')
    if file:
        << warn on read-only file >>
    else: return False
except:
    at.error("Can not open: " + '"@file ' + fn + '"')
    root.setDirty()
    return False
#@nonl
#@+node:ekr.20031218072017.1815:<< warn on read-only file >>
try:
    read_only = not os.access(fn,os.W_OK)
    if read_only:
        g.es("read only: " + fn,color="red")
except:
    pass # os.access() may not exist on all platforms.
#@nonl
#@-node:ekr.20031218072017.1815:<< warn on read-only file >>
#@-node:ekr.20031218072017.1814:<< open file or return false >>
#@+node:ekr.20031218072017.1816:<< copy ivars to df >>
# Telling what kind of file we are reading.
df.importing = importFileName != None
df.raw = False
if importing and df == at.new_df:
    thinFile = True
df.thinFile = thinFile

# Set by scanHeader.
df.encoding = at.encoding
df.endSentinelComment = at.endSentinelComment
df.startSentinelComment = at.startSentinelComment

# Set other common ivars.
df.errors = 0
df.file = file
df.importRootSeen = False
df.indent = 0
df.targetFileName = fileName
df.root = root
df.root_seen = False
df.perfectImportRoot = None # Set only in readOpenFile.
#@nonl
#@-node:ekr.20031218072017.1816:<< copy ivars to df >>
#@+node:ekr.20031218072017.1817:<< warn about non-empty unvisited nodes >>
for p in root.self_and_subtree_iter():

    # g.trace(p)
    try: s = p.v.t.tempBodyString
    except: s = ""
    if s and not p.v.t.isVisited():
        at.error("Not in derived file:" + p.headString())
        p.v.t.setVisited() # One message is enough.
#@nonl
#@-node:ekr.20031218072017.1817:<< warn about non-empty unvisited nodes >>
#@+node:ekr.20031218072017.1818:<< copy all tempBodyStrings to tnodes >>
for p in root.self_and_subtree_iter():
    try: s = p.v.t.tempBodyString
    except: s = ""
    if s != p.bodyString():
        if 0: # For debugging.
            print ; print "changed: " + p.headString()
            print ; print "new:",s
            print ; print "old:",p.bodyString()
        if thinFile:
            p.v.setTnodeText(s)
            if p.v.isDirty():
                p.setAllAncestorAtFileNodesDirty()
        else:
            p.setBodyStringOrPane(s) # Sets v and v.c dirty.
            
        if not thinFile or (thinFile and p.v.isDirty()):
            g.es("changed: " + p.headString(),color="blue")
            p.setMarked()
#@nonl
#@-node:ekr.20031218072017.1818:<< copy all tempBodyStrings to tnodes >>
#@+node:ekr.20031218072017.1819:<< delete all tempBodyStrings >>
for p in c.allNodes_iter():
    
    if hasattr(p.v.t,"tempBodyString"):
        delattr(p.v.t,"tempBodyString")
#@nonl
#@-node:ekr.20031218072017.1819:<< delete all tempBodyStrings >>
#@-node:ekr.20031218072017.1812:top_df.read
#@+node:ekr.20031218072017.2639:top_df.readLine
def readLine (self,file):

    """Reads one line from file using the present encoding"""
    
    s = g.readlineForceUnixNewline(file)
    u = g.toUnicode(s,self.encoding)
    return u
#@nonl
#@-node:ekr.20031218072017.2639:top_df.readLine
#@+node:ekr.20031218072017.2627:top_df.scanDefaultDirectory
def scanDefaultDirectory(self,p,importing=False):
    
    """Set default_directory ivar by looking for @path directives."""

    at = self ; c = at.c
    at.default_directory = None
    << Set path from @file node >>
    if at.default_directory:
        return
        
    for p in p.self_and_parents_iter():
        s = p.v.t.bodyString
        dict = g.get_directives_dict(s)
        if dict.has_key("path"):
            << handle @path >>
            return

    << Set current directory >>
    if not at.default_directory and not importing:
        # This should never happen: c.openDirectory should be a good last resort.
        g.trace()
        at.error("No absolute directory specified anywhere.")
        at.default_directory = ""
#@nonl
#@+node:ekr.20031218072017.2628:<< Set path from @file node >>  in df.scanDeafaultDirectory in leoAtFile.py
# An absolute path in an @file node over-rides everything else.
# A relative path gets appended to the relative path by the open logic.

name = p.anyAtFileNodeName() # 4/28/04
    
dir = g.choose(name,g.os_path_dirname(name),None)

if dir and g.os_path_isabs(dir):
    if g.os_path_exists(dir):
        at.default_directory = dir
    else:
        at.default_directory = g.makeAllNonExistentDirectories(dir)
        if not at.default_directory:
            at.error("Directory \"" + dir + "\" does not exist")
#@nonl
#@-node:ekr.20031218072017.2628:<< Set path from @file node >>  in df.scanDeafaultDirectory in leoAtFile.py
#@+node:ekr.20031218072017.2629:<< handle @path >> in df.scanDeafaultDirectory in leoAtFile.py
# We set the current director to a path so future writes will go to that directory.

k = dict["path"]
<< compute relative path from s[k:] >>

if path and len(path) > 0:
    base = g.getBaseDirectory() # returns "" on error.
    path = g.os_path_join(base,path)
    
    if g.os_path_isabs(path):
        << handle absolute path >>
    else:
        at.error("ignoring bad @path: " + path)
else:
    at.error("ignoring empty @path")

#@+node:ekr.20031218072017.2630:<< compute relative path from s[k:] >>
j = i = k + len("@path")
i = g.skip_to_end_of_line(s,i)
path = string.strip(s[j:i])

# Remove leading and trailing delims if they exist.
if len(path) > 2 and (
    (path[0]=='<' and path[-1] == '>') or
    (path[0]=='"' and path[-1] == '"') ):
    path = path[1:-1]

path = path.strip()
#@nonl
#@-node:ekr.20031218072017.2630:<< compute relative path from s[k:] >>
#@+node:ekr.20031218072017.2631:<< handle absolute path >>
# path is an absolute path.

if g.os_path_exists(path):
    at.default_directory = path
else:
    at.default_directory = g.makeAllNonExistentDirectories(path)
    if not at.default_directory:
        at.error("invalid @path: " + path)
#@nonl
#@-node:ekr.20031218072017.2631:<< handle absolute path >>
#@-node:ekr.20031218072017.2629:<< handle @path >> in df.scanDeafaultDirectory in leoAtFile.py
#@+node:ekr.20031218072017.2632:<< Set current directory >>
# This code is executed if no valid absolute path was specified in the @file node or in an @path directive.

assert(not at.default_directory)

if c.frame :
    base = g.getBaseDirectory() # returns "" on error.
    for dir in (c.tangle_directory,c.frame.openDirectory,c.openDirectory):
        if dir and len(dir) > 0:
            dir = g.os_path_join(base,dir)
            if g.os_path_isabs(dir): # Errors may result in relative or invalid path.
                if g.os_path_exists(dir):
                    at.default_directory = dir ; break
                else:
                    at.default_directory = g.makeAllNonExistentDirectories(dir)
#@-node:ekr.20031218072017.2632:<< Set current directory >>
#@-node:ekr.20031218072017.2627:top_df.scanDefaultDirectory
#@+node:ekr.20031218072017.2633:top_df.scanHeader
def scanHeader(self,file,fileName):
    
    """Scan the @+leo sentinel.
    
    Sets self.encoding, and self.start/endSentinelComment.
    
    Returns (firstLines,new_df) where:
    firstLines contains all @first lines,
    new_df is True if we are reading a new-format derived file."""
    
    at = self
    firstLines = [] # The lines before @+leo.
    tag = "@+leo"
    valid = True ; new_df = False
    << skip any non @+leo lines >>
    if valid:
        valid,new_df,start,end = at.parseLeoSentinel(s)
    if valid:
        at.startSentinelComment = start
        at.endSentinelComment = end
    else:
        at.error("Bad @+leo sentinel in " + fileName)
    # g.trace("start,end",repr(at.startSentinelComment),repr(at.endSentinelComment))
    return firstLines, new_df
#@nonl
#@+node:ekr.20031218072017.2634:<< skip any non @+leo lines >>
@ Queue up the lines before the @+leo.  These will be used to add as parameters to the @first directives, if any.  Empty lines are ignored (because empty @first directives are ignored). NOTE: the function now returns a list of the lines before @+leo.

We can not call sentinelKind here because that depends on the comment delimiters we set here.  @first lines are written "verbatim", so nothing more needs to be done!
@c

s = at.readLine(file)
while len(s) > 0:
    j = s.find(tag)
    if j != -1: break
    firstLines.append(s) # Queue the line
    s = at.readLine(file)
    
n = len(s)
valid = n > 0
#@-node:ekr.20031218072017.2634:<< skip any non @+leo lines >>
#@-node:ekr.20031218072017.2633:top_df.scanHeader
#@-node:ekr.20031218072017.2625:Reading
#@+node:ekr.20031218072017.2640:Writing
#@+node:ekr.20031218072017.2015:top_df.writeAll
def writeAll(self,writeAtFileNodesFlag=False,writeDirtyAtFileNodesFlag=False,toString=False):
    
    """Write @file nodes in all or part of the outline"""

    at = self ; c = at.c
    write_new = not g.app.config.write_old_format_derived_files
    df = g.choose(write_new,at.new_df,at.old_df)
    df.initIvars()
    writtenFiles = [] # Files that might be written again.
    mustAutoSave = False

    if writeAtFileNodesFlag:
        # Write all nodes in the selected tree.
        p = c.currentPosition()
        after = p.nodeAfterTree()
    else:
        # Write dirty nodes in the entire outline.
        p =  c.rootPosition()
        after = c.nullPosition()

    << Clear all orphan bits >>
    while p and p != after:
        if p.isAnyAtFileNode() or p.isAtIgnoreNode():
            << handle v's tree >>
            p.moveToNodeAfterTree()
        else:
            p.moveToThreadNext()

    << say the command is finished >>
    return mustAutoSave
#@nonl
#@+node:ekr.20031218072017.2016:<< Clear all orphan bits >>
@ We must clear these bits because they may have been set on a previous write.
Calls to atFile::write may set the orphan bits in @file nodes.
If so, write_Leo_file will write the entire @file tree.
@c
    
for v2 in p.self_and_subtree_iter():
    v2.clearOrphan()
#@nonl
#@-node:ekr.20031218072017.2016:<< Clear all orphan bits >>
#@+node:ekr.20031218072017.2017:<< handle v's tree >>
if p.v.isDirty() or writeAtFileNodesFlag or p.v.t in writtenFiles:

    df.fileChangedFlag = False # 1/9/04
    autoSave = False
    
    # Tricky: @ignore not recognised in @silentfile nodes.
    if p.isAtAsisFileNode():
        at.asisWrite(p,toString=toString)
        writtenFiles.append(p.v.t) ; autoSave = True
    elif p.isAtIgnoreNode():
        pass
    elif p.isAtNorefFileNode():
        at.norefWrite(p,toString=toString)
        writtenFiles.append(p.v.t) ; autoSave = True
    elif p.isAtNoSentFileNode():
        at.write(p,nosentinels=True,toString=toString)
        writtenFiles.append(p.v.t) # No need for autosave
    elif p.isAtThinFileNode():
        at.write(p,thinFile=True,toString=toString)
        writtenFiles.append(p.v.t) # No need for autosave.
    elif p.isAtFileNode():
        at.write(p,toString=toString)
        writtenFiles.append(p.v.t) ; autoSave = True

    if df.fileChangedFlag and autoSave: # Set by replaceTargetFileIfDifferent.
        mustAutoSave = True
#@nonl
#@-node:ekr.20031218072017.2017:<< handle v's tree >>
#@+node:ekr.20031218072017.2018:<< say the command is finished >>
if writeAtFileNodesFlag or writeDirtyAtFileNodesFlag:
    if len(writtenFiles) > 0:
        g.es("finished")
    elif writeAtFileNodesFlag:
        g.es("no @file nodes in the selected tree")
    else:
        g.es("no dirty @file nodes")
#@nonl
#@-node:ekr.20031218072017.2018:<< say the command is finished >>
#@-node:ekr.20031218072017.2015:top_df.writeAll
#@+node:ekr.20031218072017.2641:top_df.write, norefWrite, asisWrite
def norefWrite (self,p,toString=False):
    at = self
    write_new = not g.app.config.write_old_format_derived_files
    df = g.choose(write_new,at.new_df,at.old_df)
    try:    df.norefWrite(p,toString=toString)
    except: at.writeException(p)
    
rawWrite = norefWrite # Compatibility with old scripts.
    
def asisWrite (self,p,toString=False):
    at = self
    try: at.old_df.asisWrite(p,toString=toString) # No new_df.asisWrite method.
    except: at.writeException(p)
    
selentWrite = asisWrite # Compatibility with old scripts.
    
def write (self,p,nosentinels=False,thinFile=False,toString=False,oneNodeOnly=False):
    at = self
    write_new = thinFile or not g.app.config.write_old_format_derived_files
    df = g.choose(write_new,at.new_df,at.old_df)
    try:    df.write(p,nosentinels=nosentinels,thinFile=thinFile,toString=toString,oneNodeOnly=oneNodeOnly)
    except: at.writeException(p)
#@nonl
#@-node:ekr.20031218072017.2641:top_df.write, norefWrite, asisWrite
#@+node:ekr.20031218072017.2642:top_df.writeOld/NewDerivedFiles
def writeOldDerivedFiles (self,toString=False):
    
    self.writeDerivedFiles(write_old=True,toString=toString)

def writeNewDerivedFiles (self,toString=False):

    self.writeDerivedFiles(write_old=False,toString=toString)
    
def writeDerivedFiles (self,write_old,toString=False):
    
    config = g.app.config
    old = config.write_old_format_derived_files
    config.write_old_format_derived_files = write_old
    self.writeAll(writeAtFileNodesFlag=True,toString=toString)
    config.write_old_format_derived_files = old
#@nonl
#@-node:ekr.20031218072017.2642:top_df.writeOld/NewDerivedFiles
#@+node:ekr.20031218072017.2019:top_df.writeMissing
def writeMissing(self,p,toString=False):

    at = self

    write_new = not g.app.config.write_old_format_derived_files
    df = g.choose(write_new,at.new_df,at.old_df)
    df.initIvars()
    writtenFiles = False ; changedFiles = False

    p = p.copy()
    after = p.nodeAfterTree()
    while p and p != after: # Don't use iterator.
        if p.isAtAsisFileNode() or (p.isAnyAtFileNode() and not p.isAtIgnoreNode()):
            missing = False ; valid = True
            df.targetFileName = p.anyAtFileNodeName()
            << set missing if the file does not exist >>
            if valid and missing:
                << create df.outputFile >>
                if df.outputFile:
                    << write the @file node >>
                    df.closeWriteFile()
            p.moveToNodeAfterTree()
        elif p.isAtIgnoreNode():
            p.moveToNodeAfterTree()
        else:
            p.moveToThreadNext()
    
    if writtenFiles > 0:
        g.es("finished")
    else:
        g.es("no missing @file node in the selected tree")
        
    return changedFiles # So caller knows whether to do an auto-save.
#@nonl
#@+node:ekr.20031218072017.2020:<< set missing if the file does not exist >>
# This is similar, but not the same as, the logic in openWriteFile.

valid = df.targetFileName and len(df.targetFileName) > 0

if valid:
    try:
        # Creates missing directives if option is enabled.
        df.scanAllDirectives(p)
        valid = df.errors == 0
    except:
        g.es("exception in atFile.scanAllDirectives")
        g.es_exception()
        valid = False

if valid:
    try:
        fn = df.targetFileName
        df.shortFileName = fn # name to use in status messages.
        df.targetFileName = g.os_path_join(df.default_directory,fn)
        df.targetFileName = g.os_path_normpath(df.targetFileName)

        path = df.targetFileName # Look for the full name, not just the directory.
        valid = path and len(path) > 0
        if valid:
            missing = not g.os_path_exists(path)
    except:
        g.es("exception creating path:" + fn)
        g.es_exception()
        valid = False
#@nonl
#@-node:ekr.20031218072017.2020:<< set missing if the file does not exist >>
#@+node:ekr.20031218072017.2021:<< create df.outputFile >>
if toString:
    df.outputFileName = "<string: %s>" % df.targetFileName
    df.outputFile = g.fileLikeObject()
else:
    try:
        df.outputFileName = df.targetFileName
        df.outputFile = open(df.outputFileName,'wb')
        if df.outputFile is None:
            g.es("can not open " + df.outputFileName)
    except IOError:
        g.es("Can not open " + df.outputFileName)
        g.es_exception()
        df.outputFile = None
#@nonl
#@-node:ekr.20031218072017.2021:<< create df.outputFile >>
#@+node:ekr.20031218072017.2022:<< write the @file node >>
if p.isAtAsisFileNode():
    at.asisWrite(p)
elif p.isAtNorefFileNode():
    at.norefWrite(p)
elif p.isAtNoSentFileNode():
    at.write(p,nosentinels=True)
elif p.isAtFileNode():
    at.write(p)
else: assert(0)

writtenFiles = True

if df.fileChangedFlag: # Set by replaceTargetFileIfDifferent.
    changedFiles = True
#@nonl
#@-node:ekr.20031218072017.2022:<< write the @file node >>
#@-node:ekr.20031218072017.2019:top_df.writeMissing
#@+node:EKR.20040620103353:top_df.writeException
def writeException(self,p):

    self.error("Unexpected exception while writing " + p.headString())
    g.es_exception()
#@nonl
#@-node:EKR.20040620103353:top_df.writeException
#@-node:ekr.20031218072017.2640:Writing
#@-node:ekr.20031218072017.2622:<< class baseAtFile methods >>
#@+node:ekr.20031218072017.2387:old_df.scanAllDirectives
@ Once a directive is seen, no other related directives in nodes further up the tree have any effect.  For example, if an @color directive is seen in node p, no @color or @nocolor directives are examined in any ancestor of p.

This code is similar to Commands.scanAllDirectives, but it has been modified for use by the atFile class.
@c

def scanAllDirectives(self,p,scripting=False,importing=False,reading=False):
    
    """Scan position p and p's ancestors looking for directives,
    setting corresponding atFile ivars.
    """

    c = self.c
    << Set ivars >>
    << Set path from @file node >>
    old = {}
    for p in p.self_and_parents_iter():
        s = p.v.t.bodyString
        dict = g.get_directives_dict(s)
        << Test for @path >>
        << Test for @encoding >>
        << Test for @comment and @language >>
        << Test for @header and @noheader >>
        << Test for @lineending >>
        << Test for @pagewidth >>
        << Test for @tabwidth >>
        old.update(dict)
    << Set current directory >>
    if not importing and not reading:
        # 5/19/04: don't override comment delims when reading!
        << Set comment strings from delims >>
#@nonl
#@+node:ekr.20031218072017.2388:<< Set ivars >>
self.page_width = self.c.page_width
self.tab_width  = self.c.tab_width

self.default_directory = None # 8/2: will be set later.

delim1, delim2, delim3 = g.set_delims_from_language(c.target_language)
self.language = c.target_language

self.encoding = g.app.config.default_derived_file_encoding
self.output_newline = g.getOutputNewline() # 4/24/03: initialize from config settings.
#@nonl
#@-node:ekr.20031218072017.2388:<< Set ivars >>
#@+node:ekr.20031218072017.2389:<< Set path from @file node >> in scanDirectory in leoGlobals.py
# An absolute path in an @file node over-rides everything else.
# A relative path gets appended to the relative path by the open logic.

name = p.anyAtFileNodeName() # 4/28/04

dir = g.choose(name,g.os_path_dirname(name),None)

if dir and len(dir) > 0 and g.os_path_isabs(dir):
    if g.os_path_exists(dir):
        self.default_directory = dir
    else: # 9/25/02
        self.default_directory = g.makeAllNonExistentDirectories(dir)
        if not self.default_directory:
            self.error("Directory \"" + dir + "\" does not exist")
#@nonl
#@-node:ekr.20031218072017.2389:<< Set path from @file node >> in scanDirectory in leoGlobals.py
#@+node:ekr.20031218072017.2390:<< Test for @comment and @language >>
# 10/17/02: @language and @comment may coexist in @file trees.
# For this to be effective the @comment directive should follow the @language directive.

if not old.has_key("comment") and dict.has_key("comment"):
    k = dict["comment"]
    # 11/14/02: Similar to fix below.
    delim1, delim2, delim3 = g.set_delims_from_string(s[k:])

# Reversion fix: 12/06/02: We must use elif here, not if.
elif not old.has_key("language") and dict.has_key("language"):
    k = dict["language"]
    # 11/14/02: Fix bug reported by J.M.Gilligan.
    self.language,delim1,delim2,delim3 = g.set_language(s,k)
#@nonl
#@-node:ekr.20031218072017.2390:<< Test for @comment and @language >>
#@+node:ekr.20031218072017.2391:<< Test for @encoding >>
if not old.has_key("encoding") and dict.has_key("encoding"):
    
    e = g.scanAtEncodingDirective(s,dict)
    if e:
        self.encoding = e
#@nonl
#@-node:ekr.20031218072017.2391:<< Test for @encoding >>
#@+node:ekr.20031218072017.2392:<< Test for @header and @noheader >>
# EKR: 10/10/02: perform the sames checks done by tangle.scanAllDirectives.
if dict.has_key("header") and dict.has_key("noheader"):
    g.es("conflicting @header and @noheader directives")
#@nonl
#@-node:ekr.20031218072017.2392:<< Test for @header and @noheader >>
#@+node:ekr.20031218072017.2393:<< Test for @lineending >>
if not old.has_key("lineending") and dict.has_key("lineending"):
    
    lineending = g.scanAtLineendingDirective(s,dict)
    if lineending:
        self.output_newline = lineending
#@-node:ekr.20031218072017.2393:<< Test for @lineending >>
#@+node:ekr.20031218072017.2394:<< Test for @path >>
# We set the current director to a path so future writes will go to that directory.

if not self.default_directory and not old.has_key("path") and dict.has_key("path"):

    k = dict["path"]
    << compute relative path from s[k:] >>
    if path and len(path) > 0:
        base = g.getBaseDirectory() # returns "" on error.
        path = g.os_path_join(base,path)
        if g.os_path_isabs(path):
            << handle absolute path >>
        else:
            self.error("ignoring bad @path: " + path)
    else:
        self.error("ignoring empty @path")
#@nonl
#@+node:ekr.20031218072017.2395:<< compute relative path from s[k:] >>
j = i = k + len("@path")
i = g.skip_to_end_of_line(s,i)
path = string.strip(s[j:i])

# Remove leading and trailing delims if they exist.
if len(path) > 2 and (
    (path[0]=='<' and path[-1] == '>') or
    (path[0]=='"' and path[-1] == '"') ):
    path = path[1:-1]
path = path.strip()

if 0: # 11/14/02: we want a _relative_ path, not an absolute path.
    path = g.os_path_join(g.app.loadDir,path)
#@nonl
#@-node:ekr.20031218072017.2395:<< compute relative path from s[k:] >>
#@+node:ekr.20031218072017.2396:<< handle absolute path >>
# path is an absolute path.

if g.os_path_exists(path):
    self.default_directory = path
else: # 9/25/02
    self.default_directory = g.makeAllNonExistentDirectories(path)
    if not self.default_directory:
        self.error("invalid @path: " + path)
#@-node:ekr.20031218072017.2396:<< handle absolute path >>
#@-node:ekr.20031218072017.2394:<< Test for @path >>
#@+node:ekr.20031218072017.2397:<< Test for @pagewidth >>
if dict.has_key("pagewidth") and not old.has_key("pagewidth"):
    
    w = g.scanAtPagewidthDirective(s,dict,issue_error_flag=True)
    if w and w > 0:
        self.page_width = w
#@nonl
#@-node:ekr.20031218072017.2397:<< Test for @pagewidth >>
#@+node:ekr.20031218072017.2398:<< Test for @tabwidth >>
if dict.has_key("tabwidth") and not old.has_key("tabwidth"):
    
    w = g.scanAtTabwidthDirective(s,dict,issue_error_flag=True)
    if w and w != 0:
        self.tab_width = w

#@-node:ekr.20031218072017.2398:<< Test for @tabwidth >>
#@+node:ekr.20031218072017.2399:<< Set current directory >>
# This code is executed if no valid absolute path was specified in the @file node or in an @path directive.

if c.frame and not self.default_directory:
    base = g.getBaseDirectory() # returns "" on error.
    for dir in (c.tangle_directory,c.frame.openDirectory,c.openDirectory):
        if dir and len(dir) > 0:
            dir = g.os_path_join(base,dir)
            if g.os_path_isabs(dir): # Errors may result in relative or invalid path.
                if g.os_path_exists(dir):
                    self.default_directory = dir ; break
                else: # 9/25/02
                    self.default_directory = g.makeAllNonExistentDirectories(dir)

if not self.default_directory and not scripting and not importing:
    # This should never happen: c.openDirectory should be a good last resort.
    g.trace()
    self.error("No absolute directory specified anywhere.")
    self.default_directory = ""
#@-node:ekr.20031218072017.2399:<< Set current directory >>
#@+node:ekr.20031218072017.2400:<< Set comment strings from delims >>
if scripting:
    # Force Python language.
    delim1,delim2,delim3 = g.set_delims_from_language("python")
    self.language = "python"
    
# Use single-line comments if we have a choice.
# 8/2/01: delim1,delim2,delim3 now correspond to line,start,end

if delim1:
    self.startSentinelComment = delim1
    self.endSentinelComment = "" # Must not be None.
elif delim2 and delim3:
    self.startSentinelComment = delim2
    self.endSentinelComment = delim3
else: # Emergency!
    # assert(0)
    g.es("Unknown language: using Python comment delimiters")
    g.es("c.target_language:",c.target_language)
    g.es("delim1,delim2,delim3:",delim1,delim2,delim3)
    self.startSentinelComment = "#" # This should never happen!
    self.endSentinelComment = ""
    
# g.trace(repr(self.startSentinelComment),repr(self.endSentinelComment))
#@nonl
#@-node:ekr.20031218072017.2400:<< Set comment strings from delims >>
#@-node:ekr.20031218072017.2387:old_df.scanAllDirectives
#@+node:EKR.20040507095329.2:To do
@nocolor

- Reading and writing to/from strings.
	- Use these routines in perfectImport and Execute Script command.

- User alterable reading/writing (tangling/untangling)
#@nonl
#@-node:EKR.20040507095329.2:To do
#@+node:ekr.20031218072017.2647:old_df.readOpenFile
def readOpenFile(self,root,file,firstLines):
    
    """Read an open 3.x derived file."""
    
    at = self

    # Scan the file buffer
    lastLines = at.scanText(file,root,[],endLeo)
    root.v.t.setVisited() # Disable warning about set nodes.

    # Handle first and last lines.
    try: body = root.v.t.tempBodyString
    except: body = ""
    lines = body.split('\n')
    at.completeFirstDirectives(lines,firstLines)
    at.completeLastDirectives(lines,lastLines)
    s = '\n'.join(lines).replace('\r', '')
    root.v.t.tempBodyString = s
#@nonl
#@-node:ekr.20031218072017.2647:old_df.readOpenFile
#@+node:ekr.20031218072017.2757:new_df.readOpenFile
def readOpenFile(self,root,file,firstLines,perfectImportRoot=None):
    
    """Read an open 4.x thick or thin derived file."""
    
    at = self
    
    # This is safe (just barely) because only this method calls scanText4>
    at.perfectImportRoot = perfectImportRoot

    # Scan the 4.x file.
    at.tnodeListIndex = 0
    # at.thinFile tells scanText4 whether this is a thin file or not.
    lastLines = at.scanText4(file,root)
    root.v.t.setVisited() # Disable warning about set nodes.
    
    # Handle first and last lines.
    try: body = root.v.t.tempBodyString
    except: body = ""
    lines = body.split('\n')
    at.completeFirstDirectives(lines,firstLines)
    at.completeLastDirectives(lines,lastLines)
    s = '\n'.join(lines).replace('\r', '')
    root.v.t.tempBodyString = s
#@nonl
#@-node:ekr.20031218072017.2757:new_df.readOpenFile
#@+node:EKR.20040506075328:new_df.writeOpenFile
def writeOpenFile(self,root,nosentinels=False,thinFile=False,toString=False,oneNodeOnly=False):
    
    at = self ; c = at.c
    
    << init atFile ivars for writing >>
    root.clearAllVisitedInTree() # Clear both vnode and tnode bits.
    root.clearVisitedInTree()

    << put all @first lines in root >>

    # Put the main part of the file.
    at.putOpenLeoSentinel("@+leo-ver=4")
    at.putInitialComment()
    at.putOpenNodeSentinel(root)
    at.putBody(root)
    at.putCloseNodeSentinel(root)
    at.putSentinel("@-leo")
    root.setVisited()
    
    << put all @last lines in root >>
    
    if not toString and not nosentinels:
        at.warnAboutOrphandAndIgnoredNodes()
#@nonl
#@+node:EKR.20040506075328.1:<< init atFile ivars for writing >>
# Set flags telling what kind of writing we are doing.
at.sentinels = not nosentinels
at.thinFile = thinFile
at.raw = False
assert(at.toStringFlag == toString) # Must have been set earlier.

# Init other ivars.
at.errors = 0
c.setIvarsFromPrefs()
at.root = root
at.root.v.t.tnodeList = []

c.endEditing() # Capture the current headline.
#@nonl
#@-node:EKR.20040506075328.1:<< init atFile ivars for writing >>
#@+node:ekr.20031218072017.2118:<< put all @first lines in root >> (4.x)
@ Write any @first lines.  These lines are also converted to @verbatim lines, so the read logic simply ignores lines preceding the @+leo sentinel.
@c

s = root.v.t.bodyString
tag = "@first"
i = 0
while g.match(s,i,tag):
    i += len(tag)
    i = g.skip_ws(s,i)
    j = i
    i = g.skip_to_end_of_line(s,i)
    # Write @first line, whether empty or not
    line = s[j:i]
    self.os(line) ; self.onl()
    i = g.skip_nl(s,i)
#@nonl
#@-node:ekr.20031218072017.2118:<< put all @first lines in root >> (4.x)
#@+node:ekr.20031218072017.2119:<< put all @last lines in root >> (4.x)
@ Write any @last lines.  These lines are also converted to @verbatim lines, so the read logic simply ignores lines following the @-leo sentinel.
@c

tag = "@last"

# 4/17/04 Use g.splitLines to preserve trailing newlines.
lines = g.splitLines(root.v.t.bodyString)
n = len(lines) ; j = k = n - 1

# Scan backwards for @last directives.
while j >= 0:
    line = lines[j]
    if g.match(line,0,tag): j -= 1
    elif not line.strip():
        j -= 1
    else: break
    
# Write the @last lines.
for line in lines[j+1:k+1]:
    if g.match(line,0,tag):
        i = len(tag) ; i = g.skip_ws(line,i)
        self.os(line[i:])
#@nonl
#@-node:ekr.20031218072017.2119:<< put all @last lines in root >> (4.x)
#@-node:EKR.20040506075328:new_df.writeOpenFile
#@-node:EKR.20040507095329.1:More flexible atFile classes
#@+node:ekr.20031218072017.734:Rewrite colorizer to use jEdit language descriptionfiles
#@+node:ekr.20031218072017.735:Option: case insensitive keywords
Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=2097076
By: nobody

Using Jason's code for Rebol and a visual DIFF, I was able to quickly add the
keywords for the assembler I use. I'm wondering if there is a way to tell Leo
to ignore keyword case -- at the present, each keyword is entered twice. Not
a big problem with modern editors, but it _would_ be nicer...

--Rich
#@-node:ekr.20031218072017.735:Option: case insensitive keywords
#@+node:ekr.20040831114214:Thread the colorizer?
#@-node:ekr.20040831114214:Thread the colorizer?
#@-node:ekr.20031218072017.734:Rewrite colorizer to use jEdit language descriptionfiles
#@+node:ekr.20040718050922:(Implement write_strips_blank_lines and trailing_body_newlines options)
#@+node:EKR.20040603092958.1:Add new options for handling newlines in body text
@nocolor

Alas, this really does not solve problems with Import.

- "asis":  ideal, maybe not possible?
- "zero": no trailing newlines.
- "one": exactly one trailing newline.

Special case code is required so empty nodes stay empty with "one" option.
#@nonl
#@+node:ekr.20040722132104:Write script to report underindented lines
#@-node:ekr.20040722132104:Write script to report underindented lines
#@+node:ekr.20040802063546.1:(Made sure body text changes reported properly)
# This may have to change when mass whitespace changes happen)
#@nonl
#@+node:ekr.20031218072017.1812:top_df.read
# The caller has enclosed this code in beginUpdate/endUpdate.

def read(self,root,importFileName=None,thinFile=False):
    
    """Common read logic for any derived file."""
    
    at = self ; c = at.c
    at.errors = 0
    importing = importFileName is not None
    << set fileName from root and importFileName >>
    << open file or return False >>
    g.es("reading: " + root.headString())
    firstLines,read_new = at.scanHeader(file,fileName)
    df = g.choose(read_new,at.new_df,at.old_df)
    # g.trace(g.choose(df==at.new_df,"new","old"))
    << copy ivars to df >>
    root.clearVisitedInTree()
    try:
        # 1/28/04: Don't set comment delims when importing.
        # 1/28/04: Call scanAllDirectives here, not in readOpenFile.
        importing = importFileName is not None
        df.scanAllDirectives(root,importing=importing,reading=True)
        df.readOpenFile(root,file,firstLines)
    except:
        at.error("Unexpected exception while reading derived file")
        g.es_exception()
    file.close()
    root.clearDirty() # May be set dirty below.
    after = root.nodeAfterTree()
    << warn about non-empty unvisited nodes >>
    if df.errors == 0:
        if not df.importing:
            << copy all tempBodyStrings to tnodes >>
    << delete all tempBodyStrings >>
    return df.errors == 0
#@nonl
#@+node:ekr.20031218072017.1813:<< set fileName from root and importFileName >>
at.scanDefaultDirectory(root,importing=importing)
if at.errors: return

if importFileName:
    fileName = importFileName
elif root.isAnyAtFileNode():
    fileName = root.anyAtFileNodeName()
else:
    fileName = None

if not fileName:
    at.error("Missing file name.  Restoring @file tree from .leo file.")
    return False
#@nonl
#@-node:ekr.20031218072017.1813:<< set fileName from root and importFileName >>
#@+node:ekr.20031218072017.1814:<< open file or return false >>
fn = g.os_path_join(at.default_directory,fileName)
fn = g.os_path_normpath(fn)

try:
    # 11/4/03: open the file in binary mode to allow 0x1a in bodies & headlines.
    file = open(fn,'rb')
    if file:
        << warn on read-only file >>
    else: return False
except:
    at.error("Can not open: " + '"@file ' + fn + '"')
    root.setDirty()
    return False
#@nonl
#@+node:ekr.20031218072017.1815:<< warn on read-only file >>
try:
    read_only = not os.access(fn,os.W_OK)
    if read_only:
        g.es("read only: " + fn,color="red")
except:
    pass # os.access() may not exist on all platforms.
#@nonl
#@-node:ekr.20031218072017.1815:<< warn on read-only file >>
#@-node:ekr.20031218072017.1814:<< open file or return false >>
#@+node:ekr.20031218072017.1816:<< copy ivars to df >>
# Telling what kind of file we are reading.
df.importing = importFileName != None
df.raw = False
if importing and df == at.new_df:
    thinFile = True
df.thinFile = thinFile

# Set by scanHeader.
df.encoding = at.encoding
df.endSentinelComment = at.endSentinelComment
df.startSentinelComment = at.startSentinelComment

# Set other common ivars.
df.errors = 0
df.file = file
df.importRootSeen = False
df.indent = 0
df.targetFileName = fileName
df.root = root
df.root_seen = False
df.perfectImportRoot = None # Set only in readOpenFile.
#@nonl
#@-node:ekr.20031218072017.1816:<< copy ivars to df >>
#@+node:ekr.20031218072017.1817:<< warn about non-empty unvisited nodes >>
for p in root.self_and_subtree_iter():

    # g.trace(p)
    try: s = p.v.t.tempBodyString
    except: s = ""
    if s and not p.v.t.isVisited():
        at.error("Not in derived file:" + p.headString())
        p.v.t.setVisited() # One message is enough.
#@nonl
#@-node:ekr.20031218072017.1817:<< warn about non-empty unvisited nodes >>
#@+node:ekr.20031218072017.1818:<< copy all tempBodyStrings to tnodes >>
for p in root.self_and_subtree_iter():
    try: s = p.v.t.tempBodyString
    except: s = ""
    if s != p.bodyString():
        if 0: # For debugging.
            print ; print "changed: " + p.headString()
            print ; print "new:",s
            print ; print "old:",p.bodyString()
        if thinFile:
            p.v.setTnodeText(s)
            if p.v.isDirty():
                p.setAllAncestorAtFileNodesDirty()
        else:
            p.setBodyStringOrPane(s) # Sets v and v.c dirty.
            
        if not thinFile or (thinFile and p.v.isDirty()):
            g.es("changed: " + p.headString(),color="blue")
            p.setMarked()
#@nonl
#@-node:ekr.20031218072017.1818:<< copy all tempBodyStrings to tnodes >>
#@+node:ekr.20031218072017.1819:<< delete all tempBodyStrings >>
for p in c.allNodes_iter():
    
    if hasattr(p.v.t,"tempBodyString"):
        delattr(p.v.t,"tempBodyString")
#@nonl
#@-node:ekr.20031218072017.1819:<< delete all tempBodyStrings >>
#@-node:ekr.20031218072017.1812:top_df.read
#@-node:ekr.20040802063546.1:(Made sure body text changes reported properly)
#@-node:EKR.20040603092958.1:Add new options for handling newlines in body text
#@+node:ekr.20040721094203:Mulder comments re whitespace
@killcolor

https://sourceforge.net/forum/message.php?msg_id=2674828
By: bwmulder

If I understand correctly, there are two different problems:

- Trailing whitespaces
- Left indented comments

1. Before commenting on these, a question:

If I write something like
....a=b
....if a:
........@others

[four spaces indentation]

I sometimes see tab characters before the lines inserted for '@others'.

It seems that Leo remembers 'two levels of indentation', and then
fabricates this indentation using tab characters.

I think it might be more robust if Leo remembered the actual
characters in front of @others and would use them for indentation.

I suspect that many indentation errors I had with Leo could have been
avoided had not Leo had its own ideas on how to do indentation.

Maybe Leo could use the exact sequence of characters in front of
@others for all inserted lines?

2. I do not care much about trailing whitespaces. Most of the time
they are invisible, but sometimes they are very misleading (trailing in python or C).

As far as I am concerned, Leo need never produce trailing whitespaces.

Maybe we need special machinerie if sources have trailing whitespaces
and the sources are not under our control. 

Not sure if everybody would be happy if we essentially ban all trailing
whitespaces.

3. Unindented commentlines could be dealt with by using indentation to
indicate structure.

Of course, this might break the correspondance Leo structure / Python
structure, which is very ugly.

4. One possible alternative might be a new directive which indicates outindent:

@outindent <number>.

This would apply to the next comment block.

To me, 4. looks a little bit less ugly than 3.

5. As indicated in a different post, I suspect that Leo has a problem
if almost every node in an outline is changed. Profiling should point
to the culprit.

> 2. Have Leo strip whitespace from otherwise blank lines, and hope
that that solves all problems in Perfect Import. This is solving the
problem in the atFile code. The problem is that if we apply settings
in general we will get massive changes to derived files. In most
programming settings these changes might be welcome, but the problem
is actually making these changes. My first few attempts met with
abject failure.

Are you saying that you could not remove trailing blanks? What kind of
problems did you run into?

Summary:
- Use actual characters in front of @others or <<>> for indentation.
- Don't write trailing whitespaces
- Introduce directive for left-indendet comments.
- Profile to find out what slows Leo down if many nodes change.
#@nonl
#@-node:ekr.20040721094203:Mulder comments re whitespace
#@+node:ekr.20040720065022: Notes
@nocolor

Leo can't represent some files using nodes!

I call this the "unindented blank line" problem.

Example:

@color

class aClass:
    def spam(): pass
# comment line
    def eggs(): pass
    
@nocolor

Leo's import code can't handle this:
    
- If the import code putsthe comment line in a node, the line won't be output with the proper indentation!!

- Having the comment line stop the scanning of aClass is even worse.

- This usually shows up with an unindented blank line instead of the comment line.
#@nonl
#@-node:ekr.20040720065022: Notes
#@+node:ekr.20031218072017.1416:app.__init__
def __init__(self):

    # These ivars are the global vars of this program.
    self.afterHandler = None
    self.batchMode = False # True: run in batch mode.
    self.commandName = None # The name of the command being executed.
    self.config = None # The leoConfig instance.
    self.count = 0 # General purpose debugging count.
    self.debug = False # True: enable extra debugging tests (not used at present).
        # WARNING: this could greatly slow things down.
    self.disableSave = False
    self.globalWindows = []
    self.gui = None # The gui class.
    self.hasOpenWithMenu = False # True: open with plugin has been loaded.
    self.hookError = False # True: suppress further calls to hooks.
    self.hookFunction = None # Application wide hook function.
    self.idle_imported = False # True: we have done an import idle
    self.idleTimeDelay = 100 # Delay in msec between calls to "idle time" hook.
    self.idleTimeHook = False # True: the global idleTimeHookHandler will reshedule itself.
    self.initing = True # True: we are initiing the app.
    self.killed = False # True: we are about to destroy the root window.
    self.leoID = None # The id part of gnx's.
    self.loadDir = None # The directory from which Leo was loaded.
    self.loadedPlugins = [] # List of loaded plugins that have signed on.
    self.log = None # The LeoFrame containing the present log.
    self.logIsLocked = False # True: no changes to log are allowed.
    self.logWaiting = [] # List of messages waiting to go to a log.
    self.menuWarningsGiven = False # True: supress warnings in menu code.
    self.nodeIndices = None # Singleton node indices instance.
    self.numberOfWindows = 0 # Number of opened windows.
    self.openWithFiles = [] # List of data used by Open With command.
    self.openWithFileNum = 0 # Used to generate temp file names for Open With command.
    self.openWithTable = None # The table passed to createOpenWithMenuFromTable.
    self.positions = 0 # Count of the number of positions generated.
    self.quitting = False # True if quitting.  Locks out some events.
    self.realMenuNameDict = {} # Contains translations of menu names and menu item names.
    self.root = None # The hidden main window. Set later.
    self.searchDict = {} # For communication between find/change scripts.
    self.scriptDict = {} # For communication between Execute Script command and scripts.
    self.trace = False # True: enable debugging traces.
    self.tracePositions = False
    self.trace_list = [] # "Sherlock" argument list for tracing().
    self.tkEncoding = "utf-8"
    self.unicodeErrorGiven = True # True: suppres unicode tracebacks.
    self.unitTestDict = {} # For communication between unit tests and code.
    self.unitTesting = False # True if unit testing.
    self.use_gnx = True # True: generate gnx's instead of tnode indices.
    self.windowList = [] # Global list of all frames.  Does not include hidden root window.

    # Global panels.  Destroyed when Leo ends.
    self.findFrame = None
    self.pythonFrame = None
    
    << Define global constants >>
    << Define global data structures >>
#@nonl
#@+node:ekr.20031218072017.1417:<< define global constants >>
self.prolog_string = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"

# New in leo.py 3.0
self.prolog_prefix_string = "<?xml version=\"1.0\" encoding="
self.prolog_postfix_string = "?>"

# leo.py 3.11
self.use_unicode = True # True: use new unicode logic.
#@-node:ekr.20031218072017.1417:<< define global constants >>
#@+node:ekr.20031218072017.368:<< define global data structures >> app
# Internally, lower case is used for all language names.
self.language_delims_dict = {
    "actionscript" : "// /* */", #jason 2003-07-03
    "c" : "// /* */", # C, C++ or objective C.
    "csharp" : "// /* */",	# C#
    "css" : "/* */", # 4/1/04
    "cweb" : "@q@ @>", # Use the "cweb hack"
    "elisp" : ";",
    "forth" : "_\\_ _(_ _)_", # Use the "REM hack"
    "fortran" : "C",
    "fortran90" : "!",
    "html" : "<!-- -->",
    "java" : "// /* */",
    "latex" : "%",
    "pascal" : "// { }",
    "perl" : "#",
    "perlpod" : "# __=pod__ __=cut__", # 9/25/02: The perlpod hack.
    "php" : "//",
    "plain" : "#", # We must pick something.
    "python" : "#",
    "rapidq" : "'", # fil 2004-march-11
    "rebol" : ";",  # jason 2003-07-03
    "shell" : "#",  # shell scripts
    "tcltk" : "#",
    "unknown" : "#" } # Set when @comment is seen.

self.language_extension_dict = {
    "actionscript" : "as", #jason 2003-07-03
    "c" : "c",
    "css" : "css", # 4/1/04
    "cweb" : "w",
    "elisp" : "el",
    "forth" : "forth",
    "fortran" : "f",
    "fortran90" : "f",
    "html" : "html",
    "java" : "java",
    "latex" : "tex", # 1/8/04
    "noweb" : "nw",
    "pascal" : "p",
    "perl" : "perl",
    "perlpod" : "perl",
    "php" : "php",
    "plain" : "txt",
    "python" : "py",
    "rapidq" : "bas", # fil 2004-march-11
    "rebol" : "r",    # jason 2003-07-03
    "shell" : "sh",   # DS 4/1/04
    "tex" : "tex",
    "tcltk" : "tcl",
    "unknown" : "txt" } # Set when @comment is seen.
    
self.extension_dict = {
    "as"    : "actionscript",
    "bas"   : "rapidq",
    "c"     : "c",
    "css"   : "css",
    "el"    : "elisp",
    "forth" : "forth",
    "f"     : "fortran90", # or fortran ?
    "html"  : "html",
    "java"  : "java",
    "noweb" : "nw",
    "p"     : "pascal",
    "perl"  : "perl",
    "php"   : "php",
    "py"    : "python",
    "r"     : "rebol",
    "sh"    : "shell",
    "tex"   : "tex",
    "txt"   : "plain",
    "tcl"   : "tcltk",
    "w"     : "cweb" }
#@-node:ekr.20031218072017.368:<< define global data structures >> app
#@-node:ekr.20031218072017.1416:app.__init__
#@+node:ekr.20031218072017.2404:<< define defaultsDict >>
@ This contains only the "interesting" defaults.
Ints and bools default to 0, floats to 0.0 and strings to "".
@c

defaultBodyFontSize = g.choose(sys.platform=="win32",9,12)
defaultLogFontSize  = g.choose(sys.platform=="win32",8,12)
defaultTreeFontSize = g.choose(sys.platform=="win32",9,12)

# Defaults for ivars are specified in the ctor, _not_ here.

defaultsDict = {
    # compare options...
    "ignore_blank_lines" : 1,
    "limit_count" : 9,
    "print_mismatching_lines" : 1,
    "print_trailing_lines" : 1,
    # find/change options...
    "search_body" : 1,
    "whole_word" : 1,
    # Prefs panel.
    "default_target_language" : "Python",
    "tab_width" : -4,
    "page_width" : 132,
    "output_doc_chunks" : 1,
    "tangle_outputs_header" : 1,
    # Syntax coloring options...
    # Defaults for colors are handled by leoColor.py.
    "color_directives_in_plain_text" : 1,
    "underline_undefined_section_names" : 1,
    # Window options...
    "allow_clone_drags" : 1,
    "body_pane_wraps" : 1,
    "body_text_font_family" : "Courier",
    "body_text_font_size" : defaultBodyFontSize,
    "body_text_font_slant" : "roman",
    "body_text_font_weight" : "normal",
    "enable_drag_messages" : 1,
    "headline_text_font_size" : defaultTreeFontSize,
    "headline_text_font_slant" : "roman",
    "headline_text_font_weight" : "normal",
    "log_text_font_size" : defaultLogFontSize,
    "log_text_font_slant" : "roman",
    "log_text_font_weight" : "normal",
    "initial_window_height" : 600, # 7/24/03: In pixels.
    "initial_window_width" :  800, # 7/24/03: In pixels.
    "initial_window_left" : 10,
    "initial_window_top" : 10,
    "initial_splitter_orientation" : "vertical",
    "initial_vertical_ratio" : 0.5,
    "initial_horizontal_ratio" : 0.3,
    "initial_horizontal_secondary_ratio" : 0.5,
    "initial_vertical_secondary_ratio" : 0.7,
    "outline_pane_scrolls_horizontally" : 0,
    "split_bar_color" : "LightSteelBlue2",
    "split_bar_relief" : "groove",
    "split_bar_width" : 7 }
#@nonl
#@-node:ekr.20031218072017.2404:<< define defaultsDict >>
#@+node:ekr.20031218072017.3004:<< initialize ivars that may be set by config options >>
# Defaults for these ivaars are specified here, _not_ in defaultsDict.

self.at_root_bodies_start_in_doc_mode = True # For compatibility with previous versions.
self.config = None # The current instance of ConfigParser
self.config_encoding = "utf-8" # Encoding used for leoConfig.txt.
self.create_nonexistent_directories = False
self.default_derived_file_encoding = "utf-8"
self.new_leo_file_encoding = "UTF-8" # Upper case for compatibility with previous versions.
self.output_initial_comment = "" # "" or None for compatibility with previous versions.
self.output_newline = "nl"
self.read_only = True # Make sure we don't alter an illegal leoConfig.txt file!
self.redirect_execute_script_output_to_log_pane = False
self.relative_path_base_directory = "!"
self.remove_sentinels_extension = ".txt"
self.save_clears_undo_buffer = False
self.stylesheet = None
self.tkEncoding = None # Defaults to None so it doesn't override better defaults.
self.trailing_body_newlines = "asis"
self.use_plugins = False # Should never be True here!
self.use_psyco = False
self.undo_granularity = "word" # "char","word","line","node"
self.write_strips_blank_lines = False

# TO BE REMOVED:
self.write_old_format_derived_files = False # Use new format if leoConfig.txt does not exist.
#@nonl
#@-node:ekr.20031218072017.3004:<< initialize ivars that may be set by config options >>
#@+node:ekr.20031218072017.1421:<< get config options >>
@ Rewritten 10/11/02 as follows:

1. We call initConfigParam and initBooleanConfigParam to get the values.

The general purpose code will enter all these values into configDict.  This allows update() to write the configuration section without special case code.  configDict is not accessible by the user.  Rather, for greater speed the user access these values via the ivars of this class.

2. We pass the ivars themselves as params so that default initialization is done in the ctor, as would normally be expected.
@c

self.at_root_bodies_start_in_doc_mode = self.initBooleanConfigParam(
    "at_root_bodies_start_in_doc_mode",self.at_root_bodies_start_in_doc_mode)
    
encoding = self.initConfigParam(
    "config_encoding",self.config_encoding)
    
if g.isValidEncoding(encoding):
    self.config_encoding = encoding
else:
    g.es("bad config_encoding: " + encoding)
    
self.create_nonexistent_directories = self.initBooleanConfigParam(
    "create_nonexistent_directories",self.create_nonexistent_directories)
    
encoding = self.initConfigParam(
    "default_derived_file_encoding",self.default_derived_file_encoding)

if g.isValidEncoding(encoding):
    self.default_derived_file_encoding = encoding
else:
    g.es("bad default_derived_file_encoding: " + encoding)
    
encoding = self.initConfigParam(
    "new_leo_file_encoding",
    self.new_leo_file_encoding)

if g.isValidEncoding(encoding):
    self.new_leo_file_encoding = encoding
else:
    g.es("bad new_leo_file_encoding: " + encoding)

self.output_initial_comment = self.initConfigParam(
    "output_initial_comment",self.output_initial_comment)

self.output_newline = self.initConfigParam(
    "output_newline",self.output_newline)

self.read_only = self.initBooleanConfigParam(
    "read_only",self.read_only)

self.relative_path_base_directory = self.initConfigParam(
    "relative_path_base_directory",self.relative_path_base_directory)
    
self.redirect_execute_script_output_to_log_pane = self.initBooleanConfigParam(
    "redirect_execute_script_output_to_log_pane",
    self.redirect_execute_script_output_to_log_pane)
    
self.remove_sentinels_extension = self.initConfigParam(
    "remove_sentinels_extension",self.remove_sentinels_extension)

self.save_clears_undo_buffer = self.initBooleanConfigParam(
    "save_clears_undo_buffer",self.save_clears_undo_buffer)
    
self.stylesheet = self.initConfigParam(
    "stylesheet",self.stylesheet)
    
encoding = self.initConfigParam(
    "tk_encoding",self.tkEncoding)
    
if encoding and len(encoding) > 0: # May be None.
    if g.isValidEncoding(encoding):
        self.tkEncoding = encoding
    else:
        g.es("bad tk_encoding: " + encoding)
        
# New in 4.2
self.trailing_body_newlines = self.initConfigParam(
    "trailing_body_newlines",self.trailing_body_newlines)

# TO BE REMOVED
g.app.use_gnx = self.initBooleanConfigParam(
    "use_gnx",g.app.use_gnx)
    
self.use_plugins = self.initBooleanConfigParam(
    "use_plugins",self.use_plugins)

self.use_psyco = self.initBooleanConfigParam(
    "use_psyco",self.use_psyco)
    
self.undo_granularity = self.initConfigParam(
    "undo_granularity",self.undo_granularity)
    
# TO BE REMOVED
self.write_old_format_derived_files = self.initBooleanConfigParam(
    "write_old_format_derived_files",self.write_old_format_derived_files)

# New in 4.2
self.write_strips_blank_lines = self.initBooleanConfigParam(
    "write_strips_blank_lines",self.write_strips_blank_lines)
    
#g.trace("write_strips_blank_lines",self.write_strips_blank_lines)
#g.trace("trailing_body_newlines",self.trailing_body_newlines)
#@nonl
#@-node:ekr.20031218072017.1421:<< get config options >>
#@+node:ekr.20040720075446.1:(Handle write_strips_blank_lines option)
#@+node:ekr.20031218072017.2134:putCodeLine
def putCodeLine (self,s,i):
    
    """Put a normal code line."""
    
    at = self
    
    # Put @verbatim sentinel if required.
    k = g.skip_ws(s,i)
    if g.match(s,k,self.startSentinelComment + '@'):
        self.putSentinel("@verbatim")

    j = g.skip_line(s,i)
    line = s[i:j]

    # g.app.config.write_strips_blank_lines
    if 0: # 7/22/04: Don't put any whitespace in otherwise blank lines.
        if line.strip(): # The line has non-empty content.
            if not at.raw:
                at.putIndent(at.indent)
        
            if line[-1:]=="\n":
                at.os(line[:-1])
                at.onl()
            else:
                at.os(line)
        elif line and line[-1] == '\n':
            at.onl()
        else:
            g.trace("Can't happen: completely empty line")
    else:
        # 1/29/04: Don't put leading indent if the line is empty!
        if line and not at.raw:
            at.putIndent(at.indent)
    
        if line[-1:]=="\n":
            at.os(line[:-1])
            at.onl()
        else:
            at.os(line)
#@nonl
#@-node:ekr.20031218072017.2134:putCodeLine
#@-node:ekr.20040720075446.1:(Handle write_strips_blank_lines option)
#@+node:ekr.20040720103642.1:Script to do trailing_body_newlines = "one" VERY DANGEROUS
import leoGlobals as g

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

count = 0

for p in c.all_positions_iter():
    
    s = p.bodyString().rstrip()
    if s:
        s = p.bodyString().rstrip() + '\n'
        if s != p.bodyString():
            # Do NOT set anything dirty here!
            ## p.v.t.setTnodeText(s)
            count += 1

print "%d nodes converted" % count
print "You MUST do Write All Nodes AND Save this file to complete conversion"
#@nonl
#@-node:ekr.20040720103642.1:Script to do trailing_body_newlines = "one" VERY DANGEROUS
#@+node:ekr.20040720075446:(Handle trailing_body_newlines option)
#@+node:ekr.20040720105309:Notes
@killcolor

My first idea was to do this in the body text setters.  However, this converts everything when reading any .leo file, and that is _very_ slow.  It also appears to be very dangerous.
#@nonl
#@-node:ekr.20040720105309:Notes
#@+node:ekr.20031218072017.2772:readEndNode (4.x)
def readEndNode (self,s,i,middle=False):
    
    """Handle end-of-node processing for @-others and @-ref sentinels."""

    at = self ; c = self.c
    
    # End raw mode.
    at.raw = False
    
    # Set the temporary body text.
    s = ''.join(at.out)
    s = g.toUnicode(s,g.app.tkEncoding) # 9/28/03

    if at.importing:
        at.t.bodyString = s
    elif middle: 
        pass # Middle sentinels never alter text.
    else:
        if hasattr(at.t,"tempBodyString") and s != at.t.tempBodyString:
            old = at.t.tempBodyString
        elif at.t.hasBody() and s != at.t.getBody():
            old = at.t.getBody()
        else:
            old = None
        # 9/4/04: Suppress this warning for the root: @first complicates matters.
        if old and not g.app.unitTesting and at.t != at.root.t:
            << indicate that the node has been changed >>
        at.t.tempBodyString = s

    # Indicate that the tnode has been set in the derived file.
    at.t.setVisited()

    # End the previous node sentinel.
    at.indent = at.indentStack.pop()
    at.out = at.outStack.pop()
    at.t = at.tStack.pop()
    if at.thinFile and not at.importing:
        at.lastThinNode = at.thinNodeStack.pop()

    at.popSentinelStack(endNode)
#@nonl
#@+node:ekr.20040904081433:<< indicate that the node has been changed >>
if at.perfectImportRoot:
    << bump at.correctedLines and tell about the correction >>
    # p.setMarked()
    at.t.bodyString = s # Just etting at.t.tempBodyString won't work here.
    at.t.setDirty() # Mark the node dirty.  Ancestors will be marked dirty later.
    at.c.setChanged(True)
else:
    if not at.updateWarningGiven:
        at.updateWarningGiven = True
        # print "***",at.t,at.root.t
        g.es("Warning: updating changed text in %s" %
            (at.root.headString()),color="blue")
    # g.es("old...\n%s\n" % old)
    # g.es("new...\n%s\n" % s)
    # Just set the dirty bit. Ancestors will be marked dirty later.
    at.t.setDirty()
    if 1: # We must avoid the full setChanged logic here!
        c.changed = True
    else: # Far too slow for mass changes.
        at.c.setChanged(True)
#@nonl
#@+node:ekr.20040717133944:<< bump at.correctedLines and tell about the correction >>
# Report the number of corrected nodes.
at.correctedLines += 1

found = False
for p in at.perfectImportRoot.self_and_subtree_iter():
    if p.v.t == at.t:
        found = True ; break

if found:
    if 0: # Not needed: we mark all corrected nodes.
        g.es("Correcting %s" % p.headString(),color="blue")
    if 0: # For debugging.
        print ; print '-' * 40
        print "old",len(old)
        for line in g.splitLines(old):
            #line = line.replace(' ','< >').replace('\t','<TAB>')
            print repr(str(line))
        print ; print '-' * 40
        print "new",len(s)
        for line in g.splitLines(s):
            #line = line.replace(' ','< >').replace('\t','<TAB>')
            print repr(str(line))
        print ; print '-' * 40
else:
    # This should never happen.
    g.es("Correcting hidden node: t=%s" % repr(at.t),color="red")
#@nonl
#@-node:ekr.20040717133944:<< bump at.correctedLines and tell about the correction >>
#@-node:ekr.20040904081433:<< indicate that the node has been changed >>
#@-node:ekr.20031218072017.2772:readEndNode (4.x)
#@+node:ekr.20031218072017.1485:setTnodeText
# This sets the text in the tnode from the given string.

def setTnodeText (self,s,encoding="utf-8"):
    
    """Set the body text of a tnode to the given string."""
    
    s = g.toUnicode(s,encoding,reportErrors=True)
    
    if 0: # DANGEROUS:  This automatically converts everything when reading files.

        option = g.app.config.trailing_body_newlines
        
        if option == "one":
            s = s.rstrip() + '\n'
        elif option == "zero":
            s = s.rstrip()
    
    self.bodyString = s
#@nonl
#@-node:ekr.20031218072017.1485:setTnodeText
#@+node:ekr.20031218072017.1319:(Key handlers)
@ These routines are involved in many projects.  Cloning them over and over can slow down Leo a lot.
#@+node:ekr.20031218072017.1320:body key handlers
@ The <Key> event generates the event before the body text is changed(!), so we register an idle-event handler to do the work later.

1/17/02: Rather than trying to figure out whether the control or alt keys are down, we always schedule the idle_handler.  The idle_handler sees if any change has, in fact, been made to the body text, and sets the changed and dirty bits only if so.  This is the clean and safe way.

2/19/02: We must distinguish between commands like "Find, Then Change", that call onBodyChanged, and commands like "Cut" and "Paste" that call onBodyWillChange.  The former commands have already changed the body text, and that change must be captured immediately.  The latter commands have not changed the body text, and that change may only be captured at idle time.
@c

@others
#@nonl
#@+node:ekr.20031218072017.1321:idle_body_key
def idle_body_key (self,p,oldSel,undoType,ch=None,oldYview=None,newSel=None,oldText=None):
    
    """Update the body pane at idle time."""

    # g.trace(ch,ord(ch))
    c = self.c
    if not c: return "break"
    if not p: return "break"
    if not c.isCurrentPosition(p): return "break"

    if g.doHook("bodykey1",c=c,v=p,ch=ch,oldSel=oldSel,undoType=undoType):
        return "break" # The hook claims to have handled the event.
    body = p.bodyString()
    if not newSel:
        newSel = c.frame.body.getTextSelection()
    if oldText != None:
        s = oldText
    else:
        s = c.frame.body.getAllText()
    << return if nothing has changed >>
    << set removeTrailing >>
    if ch in ('\t','\n','\r',chr(8)):
        d = g.scanDirectives(c,p) # Support @tab_width directive properly.
        tab_width = d.get("tabwidth",c.tab_width) # ; g.trace(tab_width)
        if ch in ('\n','\r'):
            << Do auto indent >>
        elif ch == '\t' and tab_width < 0:
            << convert tab to blanks >>
        elif ch in (chr(8)) and tab_width < 0:
            << handle backspace with negative tab_width >>
    << set s to widget text, removing trailing newlines if necessary >>
    if undoType: # 11/6/03: set oldText properly when oldText param exists.
        if not oldText: oldText = body
        newText = s
        c.undoer.setUndoTypingParams(p,undoType,oldText,newText,oldSel,newSel,oldYview=oldYview)
    p.v.setTnodeText(s)
    p.v.t.insertSpot = c.frame.body.getInsertionPoint()
    << recolor the body >>
    if not c.changed:
        c.setChanged(True)
    << redraw the screen if necessary >>
    g.doHook("bodykey2",c=c,v=p,ch=ch,oldSel=oldSel,undoType=undoType)
    return "break"
#@+node:ekr.20031218072017.1322:<< return if nothing has changed >>
# 6/22/03: Make sure we handle delete key properly.
if ch not in ('\n','\r',chr(8)):

    if s == body:
        return "break"

    # Do nothing for control characters.
    if (ch == None or len(ch) == 0) and body == s[:-1]:
        return "break"
#@nonl
#@-node:ekr.20031218072017.1322:<< return if nothing has changed >>
#@+node:ekr.20031218072017.1323:<< set removeTrailing >>
@ Tk will add a newline only if:
1. A real change has been made to the Tk.Text widget, and
2. the change did _not_ result in the widget already containing a newline.

It's not possible to tell, given the information available, what Tk has actually done. We need only make a reasonable guess here.   setUndoTypingParams stores the number of trailing newlines in each undo bead, so whatever we do here can be faithfully undone and redone.
@c
new = s ; old = body

if len(new) == 0 or new[-1] != '\n':
    # There is no newline to remove.  Probably will never happen.
    removeTrailing = False
elif len(old) == 0:
    # Ambigous case.  Formerly always returned False.
    if new == "\n\n":
        removeTrailing = True # Handle a very strange special case.
    else:
        removeTrailing = ch not in ('\r','\n')
elif old == new[:-1]:
    # A single trailing character has been added.
    removeTrailing = ch not in ('\r','\n') # 6/12/04: Was false.
else:
    # The text didn't have a newline, and now it does.
    # Moveover, some other change has been made to the text,
    # So at worst we have misrepresented the user's intentions slightly.
    removeTrailing = True

if 0:
    print removeTrailing
    print repr(ch)
    print repr(oldText)
    print repr(old)
    print repr(new)
#@nonl
#@-node:ekr.20031218072017.1323:<< set removeTrailing >>
#@+node:ekr.20031218072017.1324:<< Do auto indent >> (David McNab)
# Do nothing if we are in @nocolor mode or if we are executing a Change command.
if self.frame.body.colorizer.useSyntaxColoring(p) and undoType != "Change":
    # Get the previous line.
    s=c.frame.bodyCtrl.get("insert linestart - 1 lines","insert linestart -1c")
    # Add the leading whitespace to the present line.
    junk,width = g.skip_leading_ws_with_indent(s,0,tab_width)
    if s and len(s) > 0 and s[-1]==':':
        # For Python: increase auto-indent after colons.
        if self.colorizer.scanColorDirectives(p) == "python":
            width += abs(tab_width)
    if g.app.config.getBoolWindowPref("smart_auto_indent"):
        # Added Nov 18 by David McNab, david@rebirthing.co.nz
        # Determine if prev line has unclosed parens/brackets/braces
        brackets = [width]
        tabex = 0
        for i in range(0, len(s)):
            if s[i] == '\t':
                tabex += tab_width - 1
            if s[i] in '([{':
                brackets.append(i+tabex + 1)
            elif s[i] in '}])' and len(brackets) > 1:
                brackets.pop()
        width = brackets.pop()
        # end patch by David McNab
    ws = g.computeLeadingWhitespace (width,tab_width)
    if ws and len(ws) > 0:
        c.frame.bodyCtrl.insert("insert", ws)
        removeTrailing = False # bug fix: 11/18
#@nonl
#@-node:ekr.20031218072017.1324:<< Do auto indent >> (David McNab)
#@+node:ekr.20031218072017.1325:<< convert tab to blanks >>
# Do nothing if we are executing a Change command.
if undoType != "Change":
    
    # Get the characters preceeding the tab.
    prev=c.frame.bodyCtrl.get("insert linestart","insert -1c")
    
    if 1: # 6/26/03: Convert tab no matter where it is.

        w = g.computeWidth(prev,tab_width)
        w2 = (abs(tab_width) - (w % abs(tab_width)))
        # g.trace("prev w:",w,"prev chars:",prev)
        c.frame.bodyCtrl.delete("insert -1c")
        c.frame.bodyCtrl.insert("insert",' ' * w2)
    
    else: # Convert only leading tabs.
    
        # Get the characters preceeding the tab.
        prev=c.frame.bodyCtrl.get("insert linestart","insert -1c")

        # Do nothing if there are non-whitespace in prev:
        all_ws = True
        for ch in prev:
            if ch != ' ' and ch != '\t':
                all_ws = False
        if all_ws:
            w = g.computeWidth(prev,tab_width)
            w2 = (abs(tab_width) - (w % abs(tab_width)))
            # g.trace("prev w:",w,"prev chars:",prev)
            c.frame.bodyCtrl.delete("insert -1c")
            c.frame.bodyCtrl.insert("insert",' ' * w2)
#@nonl
#@-node:ekr.20031218072017.1325:<< convert tab to blanks >>
#@+node:EKR.20040604090913:<< handle backspace with negative tab_width >>
# Get the preceeding characters.
prev   =c.frame.bodyCtrl.get("insert linestart","insert")
allPrev=c.frame.bodyCtrl.get("1.0","insert")
n = len(allPrev)
try:
    oldAllPrev = body[:n]
    assert(allPrev==oldAllPrev)
    deletedChar = body[n:n+1]
except (IndexError,AssertionError):
    deletedChar = None

if deletedChar in (u' ',' '):
    n = len(prev) ; w = abs(tab_width)
    n2 = n % w # Delete up to n2 - 1 spaces.
    if n2 == w - 1: # Delete spaces only if they could have come from a tab.
        count = 0
        while n2 > 0:
            n2 -= 1
            ch = prev[n-count-1]
            # g.trace(count,repr(ch))
            if ch in (u' ',' '): count += 1
            else: break
        # g.trace(count,(n%w))
        if count > 0:
            c.frame.bodyCtrl.delete("insert -%dc" % count,"insert")
#@nonl
#@-node:EKR.20040604090913:<< handle backspace with negative tab_width >>
#@+node:ekr.20031218072017.1326:<< set s to widget text, removing trailing newlines if necessary >>
s = c.frame.body.getAllText()
if len(s) > 0 and s[-1] == '\n' and removeTrailing:
    s = s[:-1]
    
# Major change: 6/12/04
if s == body:
    # print "no real change"
    return "break"
#@nonl
#@-node:ekr.20031218072017.1326:<< set s to widget text, removing trailing newlines if necessary >>
#@+node:ekr.20031218072017.1327:<< recolor the body >>
self.frame.scanForTabWidth(p)

incremental = undoType not in ("Cut","Paste") and not self.forceFullRecolorFlag
self.frame.body.recolor_now(p,incremental=incremental)

self.forceFullRecolorFlag = False
#@nonl
#@-node:ekr.20031218072017.1327:<< recolor the body >>
#@+node:ekr.20031218072017.1328:<< redraw the screen if necessary >>
redraw_flag = False

c.beginUpdate()

# Update dirty bits.
if not p.isDirty() and p.setDirty(): # Sets all cloned and @file dirty bits
    redraw_flag = True
    
# Update icons.
val = p.computeIcon()

# 7/8/04: During unit tests the node may not have been drawn,
# So p.v.iconVal may not exist yet.
if not hasattr(p.v,"iconVal") or val != p.v.iconVal:
    p.v.iconVal = val
    redraw_flag = True

c.endUpdate(redraw_flag) # redraw only if necessary
#@nonl
#@-node:ekr.20031218072017.1328:<< redraw the screen if necessary >>
#@-node:ekr.20031218072017.1321:idle_body_key
#@+node:ekr.20031218072017.1329:onBodyChanged (called from core)
# Called by command handlers that have already changed the text.

def onBodyChanged (self,p,undoType,oldSel=None,oldYview=None,newSel=None,oldText=None):
    
    """Handle a change to the body pane."""
    
    c = self.c
    if not p:
        p = c.currentPosition()

    if not oldSel:
        oldSel = c.frame.body.getTextSelection()

    self.idle_body_key(p,oldSel,undoType,oldYview=oldYview,newSel=newSel,oldText=oldText)
#@nonl
#@-node:ekr.20031218072017.1329:onBodyChanged (called from core)
#@+node:ekr.20031218072017.1330:onBodyKey
def onBodyKey (self,event):
    
    """Handle any key press event in the body pane."""

    c = self.c ; ch = event.char
    
    # This translation is needed on MacOS.
    if ch == '':
        d = {'Return':'\r', 'Tab':'\t', 'BackSpace':chr(8)}
        ch = d.get(event.keysym,'')

    oldSel = c.frame.body.getTextSelection()
    
    p = c.currentPosition()

    if 0: # won't work when menu keys are bound.
        self.handleStatusLineKey(event)
        
    # We must execute this even if len(ch) > 0 to delete spurious trailing newlines.
    self.c.frame.bodyCtrl.after_idle(self.idle_body_key,p,oldSel,"Typing",ch)
#@nonl
#@+node:ekr.20040105223536:handleStatusLineKey
def handleStatusLineKey (self,event):
    
    c = self.c ; frame = c.frame
    ch = event.char ; keysym = event.keysym
    keycode = event.keycode ; state = event.state

    if 1: # ch and len(ch)>0:
        << trace the key event >>

    try:
        status = self.keyStatus
    except:
        status = [] ; frame.clearStatusLine()
    
    for sym,name in (
        ("Alt_L","Alt"),("Alt_R","Alt"),
        ("Control_L","Control"),("Control_R","Control"),
        ("Escape","Esc"),
        ("Shift_L","Shift"), ("Shift_R","Shift")):
        if keysym == sym:
            if name not in status:
                status.append(name)
                frame.putStatusLine(name + ' ')
            break
    else:
        status = [] ; frame.clearStatusLine()

    self.keyStatus = status
#@nonl
#@+node:ekr.20040105223536.1:<< trace the key event >>
try:    self.keyCount += 1
except: self.keyCount  = 1

printable = g.choose(ch == keysym and state < 4,"printable","")

print "%4d %s %d %s %x %s" % (
    self.keyCount,repr(ch),keycode,keysym,state,printable)
#@nonl
#@-node:ekr.20040105223536.1:<< trace the key event >>
#@-node:ekr.20040105223536:handleStatusLineKey
#@-node:ekr.20031218072017.1330:onBodyKey
#@+node:ekr.20031218072017.1331:onBodyWillChange
# Called by command handlers that change the text just before idle time.

def onBodyWillChange (self,p,undoType,oldSel=None,oldYview=None):
    
    """Queue the body changed idle handler."""
    
    c = self.c

    if not oldSel:
        oldSel = c.frame.body.getTextSelection()

    if not p:
        p = c.currentPosition()

    self.c.frame.bodyCtrl.after_idle(self.idle_body_key,p,oldSel,undoType,oldYview)
#@nonl
#@-node:ekr.20031218072017.1331:onBodyWillChange
#@-node:ekr.20031218072017.1320:body key handlers
#@+node:ekr.20040803072955.91:idle_head_key
def idle_head_key (self,p,ch=None):
    
    """Update headline text at idle time."""
    
    c = self.c

    if not p or not p.isCurrentPosition():
        return "break"
        
    edit_text = self.edit_text(p)
    index = edit_text.index("insert")

    if g.doHook("headkey1",c=c,p=p,ch=ch):
        return "break" # The hook claims to have handled the event.
        
    << set head to vnode text >>
    done = ch in ('\r','\n')
    if done:
        << set the widget text to head >>
    << set s to the widget text >>
    changed = s != head
    if changed:
        c.undoer.setUndoParams("Change Headline",p,newText=s,oldText=head)
        << update p >>
    if done or changed:
        << reconfigure p and all nodes joined to p >>
        << update the screen >>

    g.doHook("headkey2",c=c,p=p,ch=ch)
    return "break"
#@nonl
#@+node:ekr.20040803072955.92:<< set head to vnode text >>
head = p.headString()
if head == None:
    head = u""
head = g.toUnicode(head,"utf-8")
#@nonl
#@-node:ekr.20040803072955.92:<< set head to vnode text >>
#@+node:ekr.20040803072955.93:<< set the widget text to head >>
self.setText(edit_text,head,tag="idle_head_key")
edit_text.mark_set("insert",index)
#@nonl
#@-node:ekr.20040803072955.93:<< set the widget text to head >>
#@+node:ekr.20040803072955.94:<< set s to the widget text >>
s = edit_text.get("1.0","end")

# Don't truncate if the user is hitting return.
# That should just end editing.
if 1:
    # Truncate headline text to workaround Tk problems...
    # Another kludge: remove one or two trailing newlines before warning of truncation.
    if s and s[-1] == '\n': s = s[:-1]
    if s and s[-1] == '\n': s = s[:-1]
    i = s.find('\n')
    if i > -1:
        # g.trace(i,len(s),repr(s))
        g.es("Truncating headline to one line",color="blue")
        s = s[:i]
    limit = 1000
    if len(s) > limit:
        g.es("Truncating headline to %d characters" % (limit),color="blue")
        s = s[:limit]

s = g.toUnicode(s,g.app.tkEncoding)

if not s:
    s = u""
    
if 0: # 6/10/04: No longer needed.  This was stressing Tk needlessly.
    s = s.replace('\n','')
    s = s.replace('\r','')
#@nonl
#@-node:ekr.20040803072955.94:<< set s to the widget text >>
#@+node:ekr.20040803072955.95:<< update p >>
c.beginUpdate()
if 1: # update...
    # Update changed bit.
    if not c.changed:
        c.setChanged(True)
    # Update all dirty bits.
    # Bug fix 8/2/04: must call p.setDirty even if p is dirty!
    p.setDirty()
    # Update p.
    p.initHeadString(s)
    self.setText(edit_text,s,tag="idle_head_key2")
    edit_text.mark_set("insert",index)
c.endUpdate(False) # do not redraw now.
#@nonl
#@-node:ekr.20040803072955.95:<< update p >>
#@+node:ekr.20040803072955.96:<< reconfigure p and all nodes joined to p >>
# Reconfigure p's headline.
if done:
    self.setDisabledLabelState(p)

edit_text.configure(width=self.headWidth(p))
#@nonl
#@-node:ekr.20040803072955.96:<< reconfigure p and all nodes joined to p >>
#@+node:ekr.20040803072955.97:<< update the screen >>
if done:
    # g.trace("done")
    c.beginUpdate()
    self.endEditLabel()
    c.endUpdate()

elif changed:
    # g.trace("changed")
    # Update p immediately.  Joined nodes are redrawn later by endEditLabel.
    # Redrawing the whole screen now messes up the cursor in the headline.
    self.drawIcon(p) # just redraw the icon.
#@nonl
#@-node:ekr.20040803072955.97:<< update the screen >>
#@-node:ekr.20040803072955.91:idle_head_key
#@-node:ekr.20031218072017.1319:(Key handlers)
#@-node:ekr.20040720075446:(Handle trailing_body_newlines option)
#@-node:ekr.20040718050922:(Implement write_strips_blank_lines and trailing_body_newlines options)
#@+node:ekr.20040919101930.1:Emacs emulation
#@+node:EKR.20040424151321:(Status line experiments) TO DO: create status line class
#@+node:ekr.20031218072017.3941: frame.Birth & Death
#@+node:ekr.20031218072017.1801:f.__init__
def __init__(self,title):

    # Init the base class.
    leoFrame.leoFrame.__init__(self)

    self.title = title
    leoTkinterFrame.instances += 1
    self.c = None # Set in finishCreate.

    << set the leoTkinterFrame ivars >>
#@+node:ekr.20031218072017.1802:<< set the leoTkinterFrame ivars >>
# Created in createLeoFrame and its allies.
self.top = None
self.tree = None
self.f1 = self.f2 = None
self.log = None  ; self.logBar = None
self.body = None ; self.bodyCtrl = None ; self.bodyBar = None ; self.bodyXBar = None
self.canvas = None ; self.treeBar = None
self.splitter1 = self.splitter2 = None
self.icon = None
self.outerFrame = None # 5/20/02
self.iconFrame = None # 5/20/02
self.statusFrame = None # 5/20/02
self.statusText = None # 5/20/02
self.statusLabel = None # 5/20/02

# Used by event handlers...
self.redrawCount = 0
self.draggedItem = None
self.controlKeyIsDown = False # For control-drags
self.revertHeadline = None # Previous headline text for abortEditLabel.
#@nonl
#@-node:ekr.20031218072017.1802:<< set the leoTkinterFrame ivars >>
#@-node:ekr.20031218072017.1801:f.__init__
#@+node:ekr.20031218072017.3942:f.__repr__
def __repr__ (self):

    return "<leoTkinterFrame: %s>" % self.title
#@-node:ekr.20031218072017.3942:f.__repr__
#@+node:ekr.20031218072017.3943:Creating the frame
#@+node:ekr.20031218072017.3944:f.createCanvas
def createCanvas (self,parentFrame):
    
    frame = self ; config = g.app.config
    
    scrolls = config.getBoolWindowPref('outline_pane_scrolls_horizontally')
    scrolls = g.choose(scrolls,1,0)

    canvas = Tk.Canvas(parentFrame,name="canvas",
        bd=0,bg="white",relief="flat")

    frame.treeBar = treeBar = Tk.Scrollbar(parentFrame,name="treeBar")
    
    # Bind mouse wheel event to canvas
    if sys.platform != "win32": # Works on 98, crashes on XP.
        canvas.bind("<MouseWheel>", self.OnMouseWheel)
        
    canvas['yscrollcommand'] = self.setCallback
    treeBar['command']     = self.yviewCallback
    
    treeBar.pack(side="right", fill="y")
    if scrolls: 
        treeXBar = Tk.Scrollbar( 
            parentFrame,name='treeXBar',orient="horizontal") 
        canvas['xscrollcommand'] = treeXBar.set 
        treeXBar['command'] = canvas.xview 
        treeXBar.pack(side="bottom", fill="x")
    
    canvas.pack(expand=1,fill="both")

    canvas.bind("<Button-1>", frame.OnActivateTree)

    # Handle mouse wheel in the outline pane.
    if sys.platform == "linux2": # This crashes tcl83.dll
        canvas.bind("<MouseWheel>", frame.OnMouseWheel)
    if 1:
        << do scrolling by hand in a separate thread >>
    
    # g.print_bindings("canvas",canvas)
    return canvas
#@nonl
#@+node:ekr.20040709081208:<< do scrolling by hand in a separate thread >>
import threading
import time

way = 'Down' # global.
ev = threading.Event()

def run(ev = ev):
    global way
    while 1:
        ev.wait()
        if way=='Down': canvas.yview("scroll", 1,"units")
        else:           canvas.yview("scroll",-1,"units")
        time.sleep(.1)

t = threading.Thread(target = run)
t.setDaemon(True)
t.start()
    
def exe(event,ev=ev,theWay='Down',canvas=canvas):
    global way
    if event.widget!=canvas: return
    if canvas.find_overlapping(event.x,event.y,event.x,event.y): return
    ev.set()
    way = theWay
        
def off(event,ev=ev,canvas=canvas):
    if event.widget!=canvas: return
    ev.clear()

if 1: # Use shift-click
    canvas.bind_all('<Shift Button-3>',exe)
    canvas.bind_all('<Shift Button-1>',lambda event,way='Up': exe(event,theWay=way))
    canvas.bind_all('<Shift ButtonRelease-1>', off)
    canvas.bind_all('<Shift ButtonRelease-3>', off)
else: # Use plain click.
    canvas.bind_all( '<Button-3>', exe)
    canvas.bind_all( '<Button-1>', lambda event,way='Up': exe(event,theWay=way))
    canvas.bind_all( '<ButtonRelease-1>', off)
    canvas.bind_all( '<ButtonRelease-3>', off)
#@nonl
#@-node:ekr.20040709081208:<< do scrolling by hand in a separate thread >>
#@-node:ekr.20031218072017.3944:f.createCanvas
#@+node:ekr.20031218072017.2176:f.finishCreate
def finishCreate (self,c):
    
    frame = self ; frame.c = c ; gui = g.app.gui

    << create the toplevel frame >>
    << create all the subframes >>
    << create the first tree node >>

    self.menu = leoTkinterMenu.leoTkinterMenu(frame)

    v = c.currentVnode()

    if not g.doHook("menu1",c=c,v=v):
        frame.menu.createMenuBar(self)

    g.app.setLog(frame.log,"tkinterFrame.__init__") # the leoTkinterFrame containing the log

    g.app.windowList.append(frame)
    
    c.initVersion()
    c.signOnWithVersion()
    
    self.body.createBindings(frame)
#@nonl
#@+node:ekr.20031218072017.2177:<< create the toplevel frame >>
frame.top = top = Tk.Toplevel()
gui.attachLeoIcon(top)
top.title(frame.title)
top.minsize(30,10) # In grid units.

frame.top.protocol("WM_DELETE_WINDOW", frame.OnCloseLeoEvent)
frame.top.bind("<Button-1>", frame.OnActivateLeoEvent)

frame.top.bind("<Activate>", frame.OnActivateLeoEvent) # Doesn't work on windows.
frame.top.bind("<Deactivate>", frame.OnDeactivateLeoEvent) # Doesn't work on windows.

frame.top.bind("<Control-KeyPress>",frame.OnControlKeyDown)
frame.top.bind("<Control-KeyRelease>",frame.OnControlKeyUp)
#@nonl
#@-node:ekr.20031218072017.2177:<< create the toplevel frame >>
#@+node:ekr.20031218072017.2178:<< create all the subframes >>
# Create the outer frame.
self.outerFrame = outerFrame = Tk.Frame(top)
self.outerFrame.pack(expand=1,fill="both")

self.createIconBar()
<< create both splitters >>

# Create the canvas, tree, log and body.
frame.canvas   = self.createCanvas(self.split2Pane1)
frame.tree     = leoTkinterTree.leoTkinterTree(c,frame,frame.canvas)
frame.log      = leoTkinterLog(frame,self.split2Pane2)
frame.body     = leoTkinterBody(frame,self.split1Pane2)

# Yes, this an "official" ivar: this is a kludge.
frame.bodyCtrl = frame.body.bodyCtrl

# Configure.  N.B. There may be Tk bugs here that make the order significant!
frame.setTabWidth(c.tab_width)
frame.tree.setTreeColorsFromConfig()
self.reconfigurePanes()
self.body.setFontFromConfig()

if 0: # No longer done automatically.

    # Create the status line.
    self.createStatusLine()
    self.putStatusLine("Welcome to Leo")
#@nonl
#@+node:ekr.20031218072017.2179:<< create both splitters >>
# Splitter 1 is the main splitter containing splitter2 and the body pane.
f1,bar1,split1Pane1,split1Pane2 = self.createLeoSplitter(outerFrame, self.splitVerticalFlag)
self.f1,self.bar1 = f1,bar1
self.split1Pane1,self.split1Pane2 = split1Pane1,split1Pane2

# Splitter 2 is the secondary splitter containing the tree and log panes.
f2,bar2,split2Pane1,split2Pane2 = self.createLeoSplitter(split1Pane1, not self.splitVerticalFlag)
self.f2,self.bar2 = f2,bar2
self.split2Pane1,self.split2Pane2 = split2Pane1,split2Pane2
#@nonl
#@-node:ekr.20031218072017.2179:<< create both splitters >>
#@-node:ekr.20031218072017.2178:<< create all the subframes >>
#@+node:ekr.20031218072017.2180:<< create the first tree node >>
t = leoNodes.tnode()
v = leoNodes.vnode(c,t)
p = leoNodes.position(v,[])
v.initHeadString("NewHeadline")

p.moveToRoot()
c.beginUpdate()
c.selectVnode(p)
c.redraw()
c.frame.getFocus()
c.editPosition(p)
c.endUpdate(False)
#@-node:ekr.20031218072017.2180:<< create the first tree node >>
#@-node:ekr.20031218072017.2176:f.finishCreate
#@+node:ekr.20031218072017.3945:Creating the splitter
@ The key invariants used throughout this code:

1. self.splitVerticalFlag tells the alignment of the main splitter and
2. not self.splitVerticalFlag tells the alignment of the secondary splitter.

Only the general-purpose divideAnySplitter routine doesn't know about these invariants.  So most of this code is specialized for Leo's window.  OTOH, creating a single splitter window would be much easier than this code.
#@+node:ekr.20031218072017.3946:resizePanesToRatio
def resizePanesToRatio(self,ratio,secondary_ratio):

    self.divideLeoSplitter(self.splitVerticalFlag, ratio)
    self.divideLeoSplitter(not self.splitVerticalFlag, secondary_ratio)
    # g.trace(ratio)
#@-node:ekr.20031218072017.3946:resizePanesToRatio
#@+node:ekr.20031218072017.3947:bindBar
def bindBar (self, bar, verticalFlag):
    
    if verticalFlag == self.splitVerticalFlag:
        bar.bind("<B1-Motion>", self.onDragMainSplitBar)

    else:
        bar.bind("<B1-Motion>", self.onDragSecondarySplitBar)
#@-node:ekr.20031218072017.3947:bindBar
#@+node:ekr.20031218072017.3948:createLeoSplitter
# 5/20/03: Removed the ancient kludge for forcing the height & width of f.
# The code in leoFileCommands.getGlobals now works!

def createLeoSplitter (self, parent, verticalFlag):
    
    """Create a splitter window and panes into which the caller packs widgets.
    
    Returns (f, bar, pane1, pane2) """
    
    # Create the frames.
    f = Tk.Frame(parent,bd=0,relief="flat")
    f.pack(expand=1,fill="both",pady=1)
    pane1 = Tk.Frame(f)
    pane2 = Tk.Frame(f)
    bar =   Tk.Frame(f,bd=2,relief="raised",bg="LightSteelBlue2")

    # Configure and place the frames.
    self.configureBar(bar,verticalFlag)
    self.bindBar(bar,verticalFlag)
    self.placeSplitter(bar,pane1,pane2,verticalFlag)

    return f, bar, pane1, pane2
#@nonl
#@-node:ekr.20031218072017.3948:createLeoSplitter
#@+node:ekr.20031218072017.3949:divideAnySplitter
# This is the general-purpose placer for splitters.
# It is the only general-purpose splitter code in Leo.

def divideAnySplitter (self, frac, verticalFlag, bar, pane1, pane2):

    if verticalFlag:
        # Panes arranged vertically; horizontal splitter bar
        bar.place(rely=frac)
        pane1.place(relheight=frac)
        pane2.place(relheight=1-frac)
    else:
        # Panes arranged horizontally; vertical splitter bar
        bar.place(relx=frac)
        pane1.place(relwidth=frac)
        pane2.place(relwidth=1-frac)
#@nonl
#@-node:ekr.20031218072017.3949:divideAnySplitter
#@+node:ekr.20031218072017.3950:divideLeoSplitter
# Divides the main or secondary splitter, using the key invariant.
def divideLeoSplitter (self, verticalFlag, frac):
    if self.splitVerticalFlag == verticalFlag:
        self.divideLeoSplitter1(frac,verticalFlag)
        self.ratio = frac # Ratio of body pane to tree pane.
    else:
        self.divideLeoSplitter2(frac,verticalFlag)
        self.secondary_ratio = frac # Ratio of tree pane to log pane.

# Divides the main splitter.
def divideLeoSplitter1 (self, frac, verticalFlag): 
    self.divideAnySplitter(frac, verticalFlag,
        self.bar1, self.split1Pane1, self.split1Pane2)

# Divides the secondary splitter.
def divideLeoSplitter2 (self, frac, verticalFlag): 
    self.divideAnySplitter (frac, verticalFlag,
        self.bar2, self.split2Pane1, self.split2Pane2)
#@nonl
#@-node:ekr.20031218072017.3950:divideLeoSplitter
#@+node:ekr.20031218072017.3951:onDrag...
def onDragMainSplitBar (self, event):
    self.onDragSplitterBar(event,self.splitVerticalFlag)

def onDragSecondarySplitBar (self, event):
    self.onDragSplitterBar(event,not self.splitVerticalFlag)

def onDragSplitterBar (self, event, verticalFlag):

    # x and y are the coordinates of the cursor relative to the bar, not the main window.
    bar = event.widget
    x = event.x
    y = event.y
    top = bar.winfo_toplevel()

    if verticalFlag:
        # Panes arranged vertically; horizontal splitter bar
        wRoot	= top.winfo_rooty()
        barRoot = bar.winfo_rooty()
        wMax	= top.winfo_height()
        offset = float(barRoot) + y - wRoot
    else:
        # Panes arranged horizontally; vertical splitter bar
        wRoot	= top.winfo_rootx()
        barRoot = bar.winfo_rootx()
        wMax	= top.winfo_width()
        offset = float(barRoot) + x - wRoot

    # Adjust the pixels, not the frac.
    if offset < 3: offset = 3
    if offset > wMax - 2: offset = wMax - 2
    # Redraw the splitter as the drag is occuring.
    frac = float(offset) / wMax
    # g.trace(frac)
    self.divideLeoSplitter(verticalFlag, frac)
#@nonl
#@-node:ekr.20031218072017.3951:onDrag...
#@+node:ekr.20031218072017.3952:placeSplitter
def placeSplitter (self,bar,pane1,pane2,verticalFlag):

    if verticalFlag:
        # Panes arranged vertically; horizontal splitter bar
        pane1.place(relx=0.5, rely =   0, anchor="n", relwidth=1.0, relheight=0.5)
        pane2.place(relx=0.5, rely = 1.0, anchor="s", relwidth=1.0, relheight=0.5)
        bar.place  (relx=0.5, rely = 0.5, anchor="c", relwidth=1.0)
    else:
        # Panes arranged horizontally; vertical splitter bar
        # adj gives tree pane more room when tiling vertically.
        adj = g.choose(verticalFlag != self.splitVerticalFlag,0.65,0.5)
        pane1.place(rely=0.5, relx =   0, anchor="w", relheight=1.0, relwidth=adj)
        pane2.place(rely=0.5, relx = 1.0, anchor="e", relheight=1.0, relwidth=1.0-adj)
        bar.place  (rely=0.5, relx = adj, anchor="c", relheight=1.0)
#@nonl
#@-node:ekr.20031218072017.3952:placeSplitter
#@-node:ekr.20031218072017.3945:Creating the splitter
#@+node:ekr.20031218072017.3953:Creating the icon area
#@+node:ekr.20031218072017.3954:createIconBar
def createIconBar (self):
    
    """Create an empty icon bar in the packer's present position"""

    if not self.iconFrame:
        self.iconFrame = Tk.Frame(self.outerFrame,height="5m",bd=2,relief="groove")
        self.iconFrame.pack(fill="x",pady=2)
#@nonl
#@-node:ekr.20031218072017.3954:createIconBar
#@+node:ekr.20031218072017.3955:hideIconBar
def hideIconBar (self):
    
    """Hide the icon bar by unpacking it.
    
    A later call to showIconBar will repack it in a new location."""
    
    if self.iconFrame:
        self.iconFrame.pack_forget()
#@-node:ekr.20031218072017.3955:hideIconBar
#@+node:ekr.20031218072017.3956:clearIconBar
def clearIconBar(self):
    
    """Destroy all the widgets in the icon bar"""
    
    f = self.iconFrame
    if not f: return
    
    for slave in f.pack_slaves():
        slave.destroy()

    f.configure(height="5m") # The default height.
    g.app.iconWidgetCount = 0
    g.app.iconImageRefs = []
#@-node:ekr.20031218072017.3956:clearIconBar
#@+node:ekr.20031218072017.3957:showIconBar
def showIconBar(self):
    
    """Show the icon bar by repacking it"""

    self.iconFrame.pack(fill="x",pady=2)
#@nonl
#@-node:ekr.20031218072017.3957:showIconBar
#@+node:ekr.20031218072017.3958:addIconButton
def addIconButton(self,text=None,imagefile=None,image=None,command=None,bg=None):
    
    """Add a button containing text or a picture to the icon bar.
    
    Pictures take precedence over text"""
    
    f = self.iconFrame
    if not imagefile and not image and not text: return

    # First define n.	
    try:
        g.app.iconWidgetCount += 1
        n = g.app.iconWidgetCount
    except:
        n = g.app.iconWidgetCount = 1

    if not command:
        def command(n=n):
            print "command for widget %s" % (n)

    if imagefile or image:
        << create a picture >>
    elif text:
        w = min(6,len(text))
        b = Tk.Button(f,text=text,width=w,relief="groove",bd=2,command=command)
        b.pack(side="left", fill="y")
        return b
        
    return None
#@nonl
#@+node:ekr.20031218072017.3959:<< create a picture >>
try:
    if imagefile:
        # Create the image.  Throws an exception if file not found
        imagefile = g.os_path_join(g.app.loadDir,imagefile)
        imagefile = g.os_path_normpath(imagefile)
        image = Tk.PhotoImage(master=g.app.root,file=imagefile)
        
        # Must keep a reference to the image!
        try:
            refs = g.app.iconImageRefs
        except:
            refs = g.app.iconImageRefs = []
    
        refs.append((imagefile,image),)
    
    if not bg:
        bg = f.cget("bg")

    b = Tk.Button(f,image=image,relief="flat",bd=0,command=command,bg=bg)
    b.pack(side="left",fill="y")
    return b
    
except:
    g.es_exception()
    return None
#@nonl
#@-node:ekr.20031218072017.3959:<< create a picture >>
#@-node:ekr.20031218072017.3958:addIconButton
#@-node:ekr.20031218072017.3953:Creating the icon area
#@+node:ekr.20031218072017.3960:Creating the status area


#@+node:ekr.20031218072017.3961:createStatusLine
def createStatusLine (self):
    
    if self.statusFrame and self.statusLabel:
        return
    
    self.statusFrame = statusFrame = Tk.Frame(self.outerFrame,bd=2)
    statusFrame.pack(fill="x",pady=1)
    
    text = "line 0, col 0"
    width = len(text) + 4
    self.statusLabel = Tk.Label(statusFrame,text=text,width=width,anchor="w")
    self.statusLabel.pack(side="left",padx=1)
    
    bg = statusFrame.cget("background")
    self.statusText = Tk.Text(statusFrame,height=1,state="disabled",bg=bg,relief="groove")
    self.statusText.pack(side="left",expand=1,fill="x")

    # Register an idle-time handler to update the row and column indicators.
    self.statusFrame.after_idle(self.updateStatusRowCol)
#@nonl
#@-node:ekr.20031218072017.3961:createStatusLine
#@+node:ekr.20031218072017.3962:clearStatusLine
def clearStatusLine (self):
    
    t = self.statusText
    if not t: return
    
    t.configure(state="normal")
    t.delete("1.0","end")
    t.configure(state="disabled")
#@nonl
#@-node:ekr.20031218072017.3962:clearStatusLine
#@+node:EKR.20040424153344:enable/disableStatusLine & isEnabled
def disableStatusLine (self):
    
    t = self.statusText
    if t:
        t.configure(state="disabled",background="gray")
    
def enableStatusLine (self):
    
    t = self.statusText
    if t:
        t.configure(state="normal",background="pink")
        t.focus_set()
        
def statusLineIsEnabled(self):
    t = self.statusText
    if t:
        state = t.cget("state")
        return state == "normal"
    else:
        return False
#@nonl
#@-node:EKR.20040424153344:enable/disableStatusLine & isEnabled
#@+node:ekr.20031218072017.3963:putStatusLine
def putStatusLine (self,s,color=None):
    
    t = self.statusText ; tags = self.statusColorTags
    if not t: return

    t.configure(state="normal")
    
    if "black" not in self.log.colorTags:
        tags.append("black")
        
    if color and color not in tags:
        tags.append(color)
        t.tag_config(color,foreground=color)

    if color:
        t.insert("end",s)
        t.tag_add(color,"end-%dc" % (len(s)+1),"end-1c")
        t.tag_config("black",foreground="black")
        t.tag_add("black","end")
    else:
        t.insert("end",s)
    
    t.configure(state="disabled")
#@nonl
#@-node:ekr.20031218072017.3963:putStatusLine
#@+node:EKR.20040424154804:setFocusStatusLine
def setFocusStatusLine (self):
    
    t = self.statusText
    if t:
        t.focus_set()
#@nonl
#@-node:EKR.20040424154804:setFocusStatusLine
#@+node:ekr.20031218072017.1733:updateStatusRowCol
def updateStatusRowCol (self):
    
    c = self.c ; body = self.bodyCtrl ; lab = self.statusLabel
    gui = g.app.gui
    if not lab: return
    
    # New for Python 2.3: may be called during shutdown.
    if g.app.killed:
        return

    if 0: # New code
        index = c.frame.body.getInsertionPoint()
        row,col = c.frame.body.indexToRowColumn(index)
        index1 = c.frame.body.rowColumnToIndex(row,0)
    else:
        index = body.index("insert")
        row,col = gui.getindex(body,index)
    
    if col > 0:
        if 0: # new code
            s = c.frame.body.getRange(index1,index2)
        else:
            s = body.get("%d.0" % (row),index)
        s = g.toUnicode(s,g.app.tkEncoding) # 9/28/03
        col = g.computeWidth (s,self.tab_width)

    if row != self.lastStatusRow or col != self.lastStatusCol:
        s = "line %d, col %d " % (row,col)
        lab.configure(text=s)
        self.lastStatusRow = row
        self.lastStatusCol = col
        
    # Reschedule this routine 100 ms. later.
    # Don't use after_idle: it hangs Leo.
    self.statusFrame.after(100,self.updateStatusRowCol)
#@nonl
#@-node:ekr.20031218072017.1733:updateStatusRowCol
#@-node:ekr.20031218072017.3960:Creating the status area
#@-node:ekr.20031218072017.3943:Creating the frame
#@+node:ekr.20031218072017.3964:Destroying the frame
#@+node:ekr.20031218072017.1975:destroyAllObjects
def destroyAllObjects (self):

    """Clear all links to objects in a Leo window."""

    frame = self ; c = self.c ; tree = frame.tree ; body = self.body

    # Do this first.
    << clear all vnodes and tnodes in the tree >>

    # Destroy all ivars in subclasses.
    g.clearAllIvars(c.atFileCommands)
    g.clearAllIvars(c.fileCommands)
    g.clearAllIvars(c.importCommands)
    g.clearAllIvars(c.tangleCommands)
    g.clearAllIvars(c.undoer)
    g.clearAllIvars(c)
    g.clearAllIvars(body.colorizer)
    g.clearAllIvars(body)
    g.clearAllIvars(tree)

    # This must be done last.
    frame.destroyAllPanels()
    g.clearAllIvars(frame)
#@nonl
#@+node:ekr.20031218072017.1976:<< clear all vnodes and tnodes in the tree>>
# Using a dict here is essential for adequate speed.
vList = [] ; tDict = {}

for p in c.allNodes_iter():
    vList.append(p.v)
    if p.v.t:
        key = id(p.v.t)
        if not tDict.has_key(key):
            tDict[key] = p.v.t

for key in tDict.keys():
    g.clearAllIvars(tDict[key])

for v in vList:
    g.clearAllIvars(v)

vList = [] ; tDict = {} # Remove these references immediately.
#@nonl
#@-node:ekr.20031218072017.1976:<< clear all vnodes and tnodes in the tree>>
#@-node:ekr.20031218072017.1975:destroyAllObjects
#@+node:ekr.20031218072017.3965:destroyAllPanels
def destroyAllPanels (self):
    
    """Destroy all panels attached to this frame."""
    
    panels = (self.comparePanel, self.colorPanel, self.fontPanel, self.prefsPanel)

    for panel in panels:
        if panel:
            panel.top.destroy()
            
    self.comparePanel = None
    self.colorPanel = None
    self.fontPanel = None
    self.prefsPanel = None
#@nonl
#@-node:ekr.20031218072017.3965:destroyAllPanels
#@+node:ekr.20031218072017.1974:destroySelf
def destroySelf (self):
    
    top = self.top # Remember this: we are about to destroy all of our ivars!

    if g.app.windowList:
        self.destroyAllObjects()

    top.destroy()
#@nonl
#@-node:ekr.20031218072017.1974:destroySelf
#@-node:ekr.20031218072017.3964:Destroying the frame
#@-node:ekr.20031218072017.3941: frame.Birth & Death
#@+node:ekr.20031218072017.3960:Creating the status area


#@+node:ekr.20031218072017.3961:createStatusLine
def createStatusLine (self):
    
    if self.statusFrame and self.statusLabel:
        return
    
    self.statusFrame = statusFrame = Tk.Frame(self.outerFrame,bd=2)
    statusFrame.pack(fill="x",pady=1)
    
    text = "line 0, col 0"
    width = len(text) + 4
    self.statusLabel = Tk.Label(statusFrame,text=text,width=width,anchor="w")
    self.statusLabel.pack(side="left",padx=1)
    
    bg = statusFrame.cget("background")
    self.statusText = Tk.Text(statusFrame,height=1,state="disabled",bg=bg,relief="groove")
    self.statusText.pack(side="left",expand=1,fill="x")

    # Register an idle-time handler to update the row and column indicators.
    self.statusFrame.after_idle(self.updateStatusRowCol)
#@nonl
#@-node:ekr.20031218072017.3961:createStatusLine
#@+node:ekr.20031218072017.3962:clearStatusLine
def clearStatusLine (self):
    
    t = self.statusText
    if not t: return
    
    t.configure(state="normal")
    t.delete("1.0","end")
    t.configure(state="disabled")
#@nonl
#@-node:ekr.20031218072017.3962:clearStatusLine
#@+node:EKR.20040424153344:enable/disableStatusLine & isEnabled
def disableStatusLine (self):
    
    t = self.statusText
    if t:
        t.configure(state="disabled",background="gray")
    
def enableStatusLine (self):
    
    t = self.statusText
    if t:
        t.configure(state="normal",background="pink")
        t.focus_set()
        
def statusLineIsEnabled(self):
    t = self.statusText
    if t:
        state = t.cget("state")
        return state == "normal"
    else:
        return False
#@nonl
#@-node:EKR.20040424153344:enable/disableStatusLine & isEnabled
#@+node:ekr.20031218072017.3963:putStatusLine
def putStatusLine (self,s,color=None):
    
    t = self.statusText ; tags = self.statusColorTags
    if not t: return

    t.configure(state="normal")
    
    if "black" not in self.log.colorTags:
        tags.append("black")
        
    if color and color not in tags:
        tags.append(color)
        t.tag_config(color,foreground=color)

    if color:
        t.insert("end",s)
        t.tag_add(color,"end-%dc" % (len(s)+1),"end-1c")
        t.tag_config("black",foreground="black")
        t.tag_add("black","end")
    else:
        t.insert("end",s)
    
    t.configure(state="disabled")
#@nonl
#@-node:ekr.20031218072017.3963:putStatusLine
#@+node:EKR.20040424154804:setFocusStatusLine
def setFocusStatusLine (self):
    
    t = self.statusText
    if t:
        t.focus_set()
#@nonl
#@-node:EKR.20040424154804:setFocusStatusLine
#@+node:ekr.20031218072017.1733:updateStatusRowCol
def updateStatusRowCol (self):
    
    c = self.c ; body = self.bodyCtrl ; lab = self.statusLabel
    gui = g.app.gui
    if not lab: return
    
    # New for Python 2.3: may be called during shutdown.
    if g.app.killed:
        return

    if 0: # New code
        index = c.frame.body.getInsertionPoint()
        row,col = c.frame.body.indexToRowColumn(index)
        index1 = c.frame.body.rowColumnToIndex(row,0)
    else:
        index = body.index("insert")
        row,col = gui.getindex(body,index)
    
    if col > 0:
        if 0: # new code
            s = c.frame.body.getRange(index1,index2)
        else:
            s = body.get("%d.0" % (row),index)
        s = g.toUnicode(s,g.app.tkEncoding) # 9/28/03
        col = g.computeWidth (s,self.tab_width)

    if row != self.lastStatusRow or col != self.lastStatusCol:
        s = "line %d, col %d " % (row,col)
        lab.configure(text=s)
        self.lastStatusRow = row
        self.lastStatusCol = col
        
    # Reschedule this routine 100 ms. later.
    # Don't use after_idle: it hangs Leo.
    self.statusFrame.after(100,self.updateStatusRowCol)
#@nonl
#@-node:ekr.20031218072017.1733:updateStatusRowCol
#@-node:ekr.20031218072017.3960:Creating the status area
#@-node:EKR.20040424151321:(Status line experiments) TO DO: create status line class
#@+node:ekr.20040217153407.3:Emacs-style keystroke handling
#@+node:ekr.20031218072017.806:Auto-completion (probably won't be done)
Jonathan M. Gilligan

Autocompletion of some sort (like IDLE's edit/expand-word command). It would be
nice also to have an autocompletion for noweb node names, possibly also with
autocompletion of abbreviations (as many text editors do) and/or
language-specific keywords taken from a dictionary selected by @language.
#@-node:ekr.20031218072017.806:Auto-completion (probably won't be done)
#@+node:ekr.20031218072017.789:Delay body display so alt+arrow keys aren't slowed
@nocolor

need a delay on body display so alt+arrow keys arent slowed down while navigating.

EKR: Alt-Up and Alt-Down are bound to Go To Next/Prev visible.  The idea is that if we don't display the body text these keys will work faster.  
#@nonl
#@-node:ekr.20031218072017.789:Delay body display so alt+arrow keys aren't slowed
#@+node:ekr.20031218072017.749:Meta key
#@+node:ekr.20031218072017.750:Comments
@nocolor

As recent experiments have shown, it is very difficult to make changes in the body key handlers.

Furthermore, emacs essentially takes over the handling of _all_ keystrokes.  I like this approach, and it is way too much to do in 3.11 or 4.0.
#@nonl
#@-node:ekr.20031218072017.750:Comments
#@+node:ekr.20031218072017.751:Request
@nocolor

How about a Emacs style meta-key(Esc?) followed by up or down? or F1/F2 ? anything to escape having to hold down three keys at the same time -- which seems to be common to other shortcut combinations too. so if you could put this in as an option in the preferences -- to turn on or off meta-key binding, that would let people choose what they prefer...

EKR:  This would require Leo to remember state in the event handlers.  It could be done, and having user-configurable shortcuts should reduce the need for this considerably.
#@-node:ekr.20031218072017.751:Request
#@+node:ekr.20031218072017.752:From Brian Theado
Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1864564
By: btheado

WAS:RE: Leo 3.10 comments
edream wrote:
>This is due to apparent glitches in the Tk event dispatching. The problem is
that pressing a control or alt or shift key _all by themselves_ will generate
keypress events that are passed on to Leo's key handlers

This should be easy to make simpler--just bind an empty script to <Alt-KeyPress>,
<Shift-KeyPress>, etc.  Tk chooses the most specific event it can find, so the
more general <KeyPress> handler will not fire.

On a broader note, when programming the text widget in Tcl/Tk, watching key
events is not the easiest way to detect changes in the text.  The only way the
text in a text widget can change is if either the delete or the insert subcommands
(methods) are called.  Any keypresses that end up changing text will have called
one of these subcommands.

So the simplest way to detect changes is to just intercept the calls to insert
and delete.  In Tcl/Tk intercepting these calls is pretty straightforward. 
I don't know if the same is true in Tkinter.

Also note the text widget in Tk8.4 (http://www.tcl.tk/man/tcl8.4/TkCmd/text.htm#M72)
has a built-in way of seeing if the text has changed

All this is just food for thought.  I don't know the details of Leo's code,
so it may not be very helpful.

Brian Theado

-----

Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1864584
By: btheado

If you don't already know about Tk's bindtags command, then check it out.  It
should make things like this easy.  It basically allows you to dynamically add
and remove a whole group of bindings all at once without affecting any other
bindings.

i.e. if you have a window called .text:

# Enter escape mode
bind .text <Esc> {
    # add EscapeMode to .text's list of bindings by
    # using the bindtags command (not shown)
}
bind EscapeMode <Up> {
    # Add special escape mode up handling code here
    break
}
bind EscapeMode <Down> {
    # Add special escape mode down handling code
    break
}
etc.

# Leave escape mode
bind EscapeMode <Esc> {
    # remove EscapeMode from .text's list of bindings
    # by using the bindtags command (not shown)
}

See (http://wiki.tcl.tk/bindtags) for a page with more information about bindtags
and a link to the bindtags man page.

I just wanted you to be aware of this functionality if and when you do tackle
this.  I have found Tk's event handling, bindtags functionality very powerful,
easy to use, and quite elegant for the coding I've done.

Brian Theado
#@nonl
#@-node:ekr.20031218072017.752:From Brian Theado
#@-node:ekr.20031218072017.749:Meta key
#@+node:ekr.20040401121456:emacs roadmap
Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=2499058
By: sgbotsford

Actually, I don't particularly care if VIM or gvim, or vi or whatever actually
works with leo.  What I would like is for the vi command keys mostly work in
leo.  It doesn't have to be
complete vi, although the closer it is, the easier for people to adapt. 

-------

Thanks for these remarks.  It's good to know what is most important for a (typical?) Emacs user.

> A first level approximation to this would be just a different shortcut keymap.

That's already possible: just edit leoConfig.txt.

> Second level would be in implement the movement commands that aren't in leo.

That would be pretty easy in a plugin.  This is probably the most important thing that Leo should have in its core to support "driving" the editor as in Emacs.  The other thing is to support the emacs style of handling keystrokes (writing keystroke info to a status line).  Better feedback on the status line will happen asap.

> Third level would be to put the hooks in for running external commands.

That already exists in various forms.  However, if what already exists isn't exactly what you want it would be very easy to write a plugin to do it.

Edward
#@nonl
#@-node:ekr.20040401121456:emacs roadmap
#@-node:ekr.20040217153407.3:Emacs-style keystroke handling
#@-node:ekr.20040919101930.1:Emacs emulation
#@+node:ekr.20040919182750.1:Make sure Leo works with CVS
#@+node:ekr.20040916122326:Check Import command
#@-node:ekr.20040916122326:Check Import command
#@+node:ekr.20040919101930.2:Resolve CVS Conflicts command
#@-node:ekr.20040919101930.2:Resolve CVS Conflicts command
#@-node:ekr.20040919182750.1:Make sure Leo works with CVS
#@+node:EKR.20040602153716:(Finish drawUserIcons)
#@+node:ekr.20040317050439:Right-click to add user-specified attributes and icons
Also add corresponding commands.
#@nonl
#@-node:ekr.20040317050439:Right-click to add user-specified attributes and icons
#@+node:ekr.20040317184631:Design
@nocolor
http://sourceforge.net/forum/message.php?msg_id=2477913
By: edream

A better design for user icons

Using tuples in t.unknownAttributes["icons"] is too constricted.  Much better
to have t.unknownAttributes["icons"] be a list of Python _dictionaries_ rather
than a list of tuples.  Each of these "subsidiary" dictionaries could have the
following keys:

"type"

"file", "icon" or "url".  At present my prototype code uses "file" to specify
the location of an icon.

"where":

"beforeBox", "beforeIcon", "beforeHeadLine", "afterHeadline".  This specifies
where to put the icon.  The default would be "beforeHeadline".  "beforeBox"
means before the plus/minus box.  "beforeIcon" means before the standard icon.

"height":

the height of the icon to use when calculating the line height.  Default: get
from the icon itself, if possible.

"width":

the width of the icon to use when calculating where to put the following element.
Default: get from the icon, if possible.

"xoffset"

Leo draws the icon at x + xoffset, where x is determined by the where param
above.  Default 0.

"xpad"

The amount of extra space following the icon.  Default 0.

"yoffset"

Leo draws the icon at y + yoffset.  Default 0.

"ypad"

The amount of extra vertical space to add to the line height.  Default 0.

These offset and pad values are integer pixel values and may be positive or
negative.

"lineHeight"  NOT USED.

Overrides any calculated line height.  Default:  lineHeight = yoffset + height + ypad.

"onClick", "onRightClick", "onDoubleClick"

commands to call when the specified event happens.  By default, the "onRightClick"
icon will be bound to a popup menu that offers the user a chance to delete the
icon,  and maybe other options such as "nudging" the icon up or down, left or
right.

"popUpMenu"

a list of items to put in a popup menu.  This would be a convenience to avoid
having to use "onRightClick".

I think you get the idea.  We want the drawing code to support lots of common
things that plugins would like to do.  I'll probably think of other goodies,
but this already is a big step forward.  Note that each of these keys will have
a default, so plugins only need to specify keys that have non-default values.

Edward

P.S.  Other gui's might not be able to support all these options.  That's ok.
The drawing code in gui plugins should degrade gracefully.

EKR
#@nonl
#@-node:ekr.20040317184631:Design
#@+node:ekr.20040317184631.2:Changes
@nocolor

"lineHeight"

This key won't be used.

t.unknownAttributes["lineYOffset"] 

An integer y offset for the entire line (except user icons, which are flexible enough).  You can use this to center the following vertically: the plus/minus box, the horizontal line to the plus/minus box, the standard headline box, and the Tk.Text widget for the headline.
#@nonl
#@-node:ekr.20040317184631.2:Changes
#@+node:ekr.20040317184631.1:To do
@nocolor

"onClick", "onRightClick", "onDoubleClick"

commands to call when the specified event happens.  By default, the "onRightClick"
icon will be bound to a popup menu that offers the user a chance to delete the
icon,  and maybe other options such as "nudging" the icon up or down, left or
right.

"popUpMenu"

a list of items to put in a popup menu.  This would be a convenience to avoid
having to use "onRightClick".

I think you get the idea.  We want the drawing code to support lots of common
things that plugins would like to do.  I'll probably think of other goodies,
but this already is a big step forward.  Note that each of these keys will have
a default, so plugins only need to specify keys that have non-default values.
#@nonl
#@-node:ekr.20040317184631.1:To do
#@+node:EKR.20040526202501:putUnknownAttributes
def putUnknownAttributes (self,torv,toString=False):
    
    """Put pickleable values for all keys in torv.unknownAttributes dictionary."""
    
    result = []
    attrDict = torv.unknownAttributes
    if type(attrDict) != type({}):
        g.es("ignoring non-dictionary unknownAttributes for",torv,color="blue")
        return

    for key in attrDict.keys():
        try:
            val = attrDict[key]
            s = pickle.dumps(val,bin=True)
            attr = ' %s="%s"' % (key,binascii.hexlify(s))
            self.put(attr)

        except pickle.PicklingError:
            # New in 4.2 beta 1: keep going after error.
            g.es("ignoring non-pickleable attribute %s in %s" % (
                key,torv),color="blue")
#@nonl
#@-node:EKR.20040526202501:putUnknownAttributes
#@-node:EKR.20040602153716:(Finish drawUserIcons)
#@-node:ekr.20040919101930:Most important
#@+node:ekr.20040919101930.3:Other
#@+node:ekr.20040130073846:Use Python indices instead of Tk indices
This would really help when writing gui plugins.

The first place it would be useful would be in eliminating calls to the Tk search routine.
#@nonl
#@+node:ekr.20040105120208.1:Use string.find or re.find instead of tk.find
#@-node:ekr.20040105120208.1:Use string.find or re.find instead of tk.find
#@+node:ekr.20040203075059:use pos linestart + 1 lines to go to next line?
This might be the best way to reliably have Tk move to the next line!
#@-node:ekr.20040203075059:use pos linestart + 1 lines to go to next line?
#@-node:ekr.20040130073846:Use Python indices instead of Tk indices
#@+node:EKR.20040628085831:@import, etc.
#@+node:ekr.20040718033104:@shadow
#@-node:ekr.20040718033104:@shadow
#@+node:ekr.20040718032616:@import
#@-node:ekr.20040718032616:@import
#@+node:EKR.20040503160843:@directory-import (Generalization of @import)
#@-node:EKR.20040503160843:@directory-import (Generalization of @import)
#@-node:EKR.20040628085831:@import, etc.
#@+node:ekr.20040217153407:User customizeable tangling and untangling
#@+node:ekr.20031218072017.805:Allow other section delims besides << and >>
Maybe the section operator could be customizable, 
I personally prefer the wiki way [[name of section]]. 

@setlink-tag [[ ]] 
#@-node:ekr.20031218072017.805:Allow other section delims besides << and >>
#@+node:ekr.20031218072017.803:@template directive?
#@-node:ekr.20031218072017.803:@template directive?
#@+node:ekr.20031218072017.795:Metatags
@nocolor

By: nobody ( Nobody/Anonymous ) 
 RE: 3.11 todo list & schedule   
2003-02-11 03:25  

Here are some features I'd like to see: 
 

 
3. Metatags. @sectionname or @savedate are expanded to the appropriate text when saved.

-marshall-  
#@-node:ekr.20031218072017.795:Metatags
#@-node:ekr.20040217153407:User customizeable tangling and untangling
#@+node:ekr.20040918165519:Mark Task & Clone To Task commands
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2415033
By: nobody

Ive been scanning Speed Reams Slash post and the Faq and saw an interesting
usage pattern: Creating a node called a Task and adding cloned nodes to it that
represent that task(I hope that explains it).  I can see myself using this idiom
in the future.  Now my question is does the Task/clone idiom warrant special
commands in Leo?  My basis:

1. Do enough Leo users work with this idiom that making it easier to utilize
would be desirable?  I guess a good rule of thumb might be, if the sequence
of actions occur more frequently than the primitive commands like opening a
file it should be considered.

Maybe a way of doing it:

1. A special mark node as Task command.
2. A Add to lask marked task command, that clones a node and moves that node
into the Task node.
#@nonl
#@-node:ekr.20040918165519:Mark Task & Clone To Task commands
#@+node:ekr.20031218072017.736:Use xml parser to read .leo files
http://www.reportlab.com

import xml.sax
#@-node:ekr.20031218072017.736:Use xml parser to read .leo files
#@+node:ekr.20040831092540:Allow uA's to be stored as plain strings
I would like to store unknown
attributes as a simple string. The reason, is because I am using an unknown
attribute to carry additional markup information, in this case, to carry color
information for each node. (I used Joe Orr's XSL stylesheet as a startpoint for
HTML generation).

In order to achieve my nefarious aim, I have had to edit leoFileCommands.py but
I would prefer not to have to edit the main source code.

Martin Clifford
Senior Software Engineer
Snell & Wilcox
#@-node:ekr.20040831092540:Allow uA's to be stored as plain strings
#@-node:ekr.20040919101930.3:Other
#@-node:ekr.20040916121944:Can be done in plugins
#@-node:EKR.20040609091327:To do: 4.3: configuration, plugins & translation
#@+node:ekr.20040329182535.7:1-2 hour projects
#@+node:EKR.20040611081747.1:Look at windows scripting
------cut openleo.pys
@path c:\bIn\pys\
@lineending crnl
@
w04609p09:57:19 a pys to open leo and do something.
you need the win32 extensions and you have to enable 
the script engine. details how on google or on the win32 page.

the sendkeys method can insert any test or control codes
msdn or microsoft.com/scripting

@c
pypath = r'C:\c\py\Python233'
py =  pypath + r'\python.exe -itOO '
leosrc = r'c:\c\leo\leo4CVS233\src'
name = "blank.leo"

WshShell = WScript.CreateObject("WScript.Shell")

#look like it comes up untitled 
# if it cant find an existing leo of that name?

WshShell.Run(py + leosrc + r'\Leo.py ../' + name)
WScript.Sleep(4900) 

wname = "untitled"

WshShell.AppActivate(wname);  WScript.Sleep(2100)
WshShell.SendKeys("%Fe");   WScript.Sleep(1300);  #^Q

---end openleo.pys
#@nonl
#@-node:EKR.20040611081747.1:Look at windows scripting
#@+node:EKR.20040422132037.7:Problems with run script command on Mac x11
Jon Schull <jschull@softlock.com>  
Date:  2003/12/30 Tue PM 05:50:51 EST 
To:  edreamleo@charter.net 
Subject:  Leo, Mac OS X 10.3, and VPython 
             
I've been evaluating leo or vpython programming on  Mac OS X 10.3, and 
have some observations and a suggestion.

Observations.
		Leo runs under X11 as well as under OS X.
		My X11 python configuration was created using the recipe at XXX 
(which enables vpython).
		The OS X configuration is vanilla MacPython from MacPython.org, along 
with AquaTclTk batteries included XXX.
		In both environments I can run leo under python leo.py and under idle.
		Under OS X we get font smoothing, but we can't run visual python 
programs (python crashes;  this is a known incompatibility with 
MacPython.)
	
		Under X11 we can run visual python programs like this one
			#box.py
			from visual import *
			box()
			
    And we can even run them under leo (under X11). HOWEVER, when the 
visual python program is terminated, leo vanishes (leo and the vp 
program apparently run in the same space)
	
	Under x11, we can keep leo alive by putting the vp program in its own 
space:
	
		os.popen3('/sw/bin/python /Users/jis/box.py')
	
	However,  this doesn't let us see the output of stderr and stdout.  
Those text streams are available...
		
		def do(cmd='ls'):
			from os import popen3
			pIn,pOut,pErr=0,1,2
			popenResults=popen3(cmd)
			print popenResults[pOut].read()
			print popenResults[pErr].read()
		
		import os	
		do('/sw/bin/python /Users/jis/box.py')
		
	...but only when the vpython program terminates.
	
	Here's the good news:  if we execute our vp program with 
/sw/bin/idle.py rather than with python, we get to see the program 
output in real time (under idle, under X11).
	
		import os	
		os.chdir('/sw/lib/python2.3/idlelib')
		os.popen3('/sw/bin/python idle.py -r /Users/jis/box.py')
		
		#this runs as an executed script in leo, and produces a live idle 
with real time ongoing output.
	
	Now, while idle is running, leo sits in suspended animation.  But when 
the vpython program terminates, we are left in idle, and when idle is 
terminated, leo becomes active again.
	
	It would be even better if leo were not suspended (using os.spawn, 
perhaps) but the real point is that I would really really like leo's 
"Execute script" command to execute code this way and spare me having 
to  hard-write the path to box.py.  It ought to be possible to 
eliminate os.chdir as well.

------------------
Jon Schull, Ph.D.
Associate Professor
Information Technology
Rochester Institute of Technology
schull@digitalgoods.com 585-738-6696
#@-node:EKR.20040422132037.7:Problems with run script command on Mac x11
#@+node:EKR.20040604082214:Use virtual events for Copy, Paste etc.
@nocolor

- This might fix some binding problems on some platforms.

- Might want to define <<localUndo>> for undo in Find Panel.

- Probably should define <<selectAll>> for Find Panel and similar stuff.
#@nonl
#@-node:EKR.20040604082214:Use virtual events for Copy, Paste etc.
#@+node:ekr.20040414093758:Suggestions
@nocolor
 RE: Leo's reorganized to-do list   
2004-03-31 05:48  

 in order of most likely to least possible,

+1 on defining __main__ and allowing parameters to scripts.

add python -t to test suite to catch mixed tab & space in derived files.
leoFind.py: inconsistent use of tabs and spaces 

Edit-> Delete while in headline deletes body text.

unregisterhook()

we can script the find, can we script the compare?

insert/overstrike mode in body

@path and other directives accept python expressions

add a Stop button for find/change 
and other possibly long running scripts.
possibly a plugin can already do this.

Tabbed log/Find/config window.

 
#@-node:ekr.20040414093758:Suggestions
#@+node:ekr.20040329190314:Most important
#@+node:ekr.20031218072017.833:Use @file extension by default if no @language
@nocolor

Open discussion
By: jasonic ( Jason Cunliffe ) 
 use of @language   
2003-07-16 03:40  

I am wondering why Leo does not default to just use the file suffix in @file nodes, instead of obliging @language line in in the body pane 

For example any @file ending with a suffix as defined in the language extensions could just default to use those. 

".py" for python 
".r" for rebol 
".as" for actionscript etc.. 

Should anyone need to over-ride those, they could use @language.
#@nonl
#@-node:ekr.20031218072017.833:Use @file extension by default if no @language
#@+node:ekr.20031218072017.746:Minimizing/maximizing windows
@nocolor

Open Discussion
https://sourceforge.net/forum/message.php?msg_id=2090601
By: jasonic

I'll be happy to run some test and report to you. But  I suspect from your post
a slight misunderstanding about Alt+Tab.

Alt+Tab just toggles the active window.
It works fine with Leo on Windows wherver I have tried it.

Resizing wondows via hotkeys is another matter.
The special "Windows" Key + m will minimize all windows. 

But unfortunately I've not yet discovered any opposite shortcut to  maximize
all windows :-(
I've tried various 3rd party hotkey utililties, but somehow always end up dropping
them.

<rant>
I really hate wasting any time dragging windows around and resizing them or
clicking to bring them into top focus. As I work mainly with graphics, and mult-task
intesenlty bwetn apps,  it is especially important to reduce visual clutter
on screen.

For many years the way to do this has been combining maximized [or minimized]
windows with  Alt+Tab. Alt+Tab  lets one jump rapidly betwen open apps and windows
without ever need to repostion or resize them.

As a consultant, its one of the first things I ever teach clients. Often it
transforms their perception of using Windows.

It is so painful to watch people dragging and rescaling  windows all over the
place. Ironically, I've witnessed some very smart programmers fly through vi
or emacs but then slow to tortoises when navigation mice and windows. I beleive
as long as we have the curertn wnidows UI paradigm,  use of Alt+Tab is an essential
part of  GUI 'litteracy' - an essentail skill all shoudl have. Alas its still
little known or discussed.

There is also a nice toggle feature so one can jump back to the last app/window
as its first "stop". Typically one is working between two apps for some specific
task, even though many are open.

Keeping one's finger on the 'Alt' key then tapping 'Tab' lets one hop along
the list.

Adding 'shift' to the mix helps one to go in reverse. 

Above all one can keep one hand on the mouse, the other on the keyboard [ALt+Tab]
and ones mind/eye on teh screen. For me it means left hand "plays" the bass
pattern using Alt+Tab along with the clipboard shortcuts [CTRL+ z,x,c,v] ,while
right hand on the mouse mouse  provides the melody.

I've been using variations on the above since 1987 when I bought an Amiga2000.
I seem to recall SGI had one also. And after learning about Alt+Tab on windows,
I  found a utility to work the same way on Macintosh.  I am very happy to discover
that MacOSX now has built-in ALt+Tab :-)
</rant>

Meanwhile, Iam  still wondering how to set Leo [3.12 beta 1, Python 2.2.2, Tk
8.3.2] default to a maxmimized or minimized window size by itself.

And also to remember the last size/position it had. I vaguely remember that
it used to do that, but perhaps I am mistaking it for some other software ?

thanks
Jason
#@-node:ekr.20031218072017.746:Minimizing/maximizing windows
#@+node:ekr.20031218072017.742:Show diffs of changed node in top_df.read
- Show changes:
	When a derived file is modified by an editor it would be very nice if Leo would:
	- flag the file as modified and,
	- flag (underline, color, diff, whatever) the parts that were modified.
#@nonl
#@-node:ekr.20031218072017.742:Show diffs of changed node in top_df.read
#@+node:ekr.20040220110030:Change cursor when caps lock is down
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2431552
By: nobody

From: Rich

 I just got nipped twice by the following effect: the Caps-Lock key is ON, but
because the LED is on the Caps-Lock key, it is hidden behind my hand. I hit
Ctrl-x, expecting to cut my selection, but the entire node is cut.

   I know there's a problem with tk and the shift key status, so I'm wondering
if it would be possible to change the shape of the cursor when the Caps-Lock
is ON (preferrably a big red flashing blot 8-), or otherwise show that Caps-Lock
is active ( "CAPS" on a status line, for instance).

  Another way: I don't know if this goes against an "anti-modalism rule," but
only allowing Ctrl-Shift-x|c|v in the outline pane would also be acceptable
to me.
#@nonl
#@-node:ekr.20040220110030:Change cursor when caps lock is down
#@+node:ekr.20040315060557:declone command
By: nobody ( Nobody/Anonymous ) 
 having a declone() method for vnodes?   
2004-03-15 04:36  

 hi,

Ive had a use for a declone() method in vnodes recently. Have you ever thought about adding a method that declones a clone? This would entail:

1. Making a clone node a normal node.

I can see this happening when cutting a node and pasting a node that is a clone. But there doesn't seem to be a dedicated function to do the operation. :)  
#@nonl
#@-node:ekr.20040315060557:declone command
#@+node:ekr.20031218072017.792:Double Click Node command
#@-node:ekr.20031218072017.792:Double Click Node command
#@+node:ekr.20031218072017.852:Scroll improvement
@nocolor

By: jwashin ( James Washington ) 
 Scrolling and the Tree Frame in Linux   
2003-10-20 12:13  

I'm using tk-8.4.3, python-2.2.3 on linux. The MouseWheel events do not work for me in the Tree Frame.

I added <Button-4> and <Button-5> bindings similar to the existing <MouseWheel> binding in LeoFrame.finishCreate(), associating them with two methods, LeoFrame.OnMouseScrollDown and LeoFrame.OnMouseScrollUp that took the appropriate parts of OnMouseWheel()

Now it works, sort-of. It scrolls properly unless the cursor is an I-Bar, e.g., over text. But at least this removes in-part the most glaring UI problem for me with Leo in tk. I suppose I am spoiled with the mouse wheel and want it to work everywhere.

Thanks for the great program.

-- Jim Washington  
#@-node:ekr.20031218072017.852:Scroll improvement
#@-node:ekr.20040329190314:Most important
#@+node:ekr.20040329190314.2:Others
#@+node:ekr.20040213090121:Insert at end
@nocolor

By: tibi ( Thiébaut Champenier ) 
 inserting a new node   
2004-02-13 13:23  

 Hi,

When doing ctrl+i I on the node that has children I would prefer Leo to insert the new node as the last children instead of the first. It just makes more sense to me, for exemple when you take a big file and cut it in subnodes you typically start at the top and create new nodes while paging down the file.
What do you think ?
To try it just make this little change:
RCS file: /cvsroot/leo/leo/src/leoCommands.py,v
retrieving revision 1.76
diff -r1.76 leoCommands.py
2516c2516
< v = current.insertAsNthChild(0)
---
> v = current.insertAsLastChild()  
#@nonl
#@-node:ekr.20040213090121:Insert at end
#@+node:ekr.20031218072017.740:Disallow writes outside a "top-level" folder
1. Warn when creating _any_ new file.

2. Warn when rewriting any file that was not read properly.

This prevents "hijacking" an already existing file.
#@nonl
#@-node:ekr.20031218072017.740:Disallow writes outside a "top-level" folder
#@+node:ekr.20031218072017.745:@@first <n>
@nocolor

Hate to break into the grand design discussions, but here's a hopefully small thing. If you need to place a good sized copyright statement at the top of your files, LEO doesn't handle this case very cleanly. As I'm sure you're aware, you wind up with a matching number of @@first lines for each leading line in your source. 

As an example: 
# 1 
# 2 
# 3 
# 4 
# 5 
#@verbatim
#@+leo 
#@verbatim
#@+node:0::@file /tmp/firstcheck.py 
#@verbatim
#@+body 
#@verbatim
#@@first 
#@verbatim
#@@first 
#@verbatim
#@@first 
#@verbatim
#@@first 
#@verbatim
#@@first 
#@verbatim
#@+doc 
# 
# How many firsts do I get? 

#@verbatim
#@-doc 
#@verbatim
#@@c 
Start code. 
#@verbatim
#@-body 
#@verbatim
#@-node:0::@file /tmp/firstcheck.py 
#@verbatim
#@-leo 

My fellow co-workers who don't use LEO, aren't exactly loving me here. 

Might we introduce an: 

@@first <num> 

Type tag instead? So one '@@first 5' could represent all 5 of the above @@first lines? It makes for a smaller, cleaner LEO footprint and will tick off non-LEO users much less. 

Thanks. 

- ordinarius 
#@nonl
#@-node:ekr.20031218072017.745:@@first <n>
#@+node:ekr.20031218072017.807:Put up file dialog on empty @url, etc.
@nocolor

Open Discussion
https://sourceforge.net/forum/message.php?msg_id=2003457
By: dsalomoni

Proposal: modify the code for @url so that if you type for example just "@url"
(no file specified) in a headline, a window pops up allowing you to browse the
local file system and select the file (similar to what browsers do when you
want to open a file).

This would be more convenient than manually writing @url
file://a/long/path/to/the/file. @read-only nodes already allow this, it would
perhaps be nice if all these types of plugins (@folder might be another one
for example) and directives (@file etc) had the same behavior (and this should
probably be specified in some guidelines for writing new plugins -see e.g. the
jedit plugin guidelines).

Davide
#@-node:ekr.20031218072017.807:Put up file dialog on empty @url, etc.
#@+node:ekr.20040226094105:Put the scrollWheel workaround in the FAQ (or in the code??)
@nocolor
#@nonl
#@+node:ekr.20040226094105.1:halw

https://sourceforge.net/forum/message.php?msg_id=2437859
By: halw

David,

I agree that your solution is best -- the problem is it doesn't work. 

If you look at the original post you'll note that the mouse wheel works fine
in all other Leo panes (which are native text panes, not a custom canvas).

In fact, a stock RH9 system does set up X as those pages indicate. The problem
is elsewhere, perhaps Tk or Tkinter.

If Edward doesn't want to ship a workaround, that's fine. It should, IMO, make
it into the FAQ, though.

--Hal

#@-node:ekr.20040226094105.1:halw
#@+node:ekr.20040226094105.2:djsg
https://sourceforge.net/forum/message.php?msg_id=2435622
By: djsg

Tk is complying with the behavior of XFree86, and not hiding that behavior. 

Documentation for XFree86 4.2.0 -- documentation for a 4.4 development version
matches it, so 4.3.0 ought to match it also:

http://www.xfree86.org/4.2.0/mouse7.html

An example of configuration for XFree86 3.3.2.

First, the URL: 

http://www.genius-europe.com/service/faq/tuxmouse.htm

o save their bandwidth, the example follows: 


All mices with scroll wheel or stick

XFree86 >= 3.3.2 (or other XServer with wheel to mouse button support)
The mouse must be setup in XF86Config to send the mouse buttons 4 and 5 for
wheel actions.
Edit /etc/XF86Config with your favorite editor
Add the following line to the "Pointer" section.

ZAxisMapping 4 5

Make sure your Protocol is set to either "IMPS/2" for a PS/2 mouse
or for serial mice set it to "IntelliMouse" or "Auto".
Example for PS/2 wheel mouse:

Section "Pointer"
Protocol "IMPS/2"
Device "/dev/psaux"
SampleRate 60
BaudRate 1200
ZAxisMapping 4 5
Buttons 3
EndSection

After XWindows is started run :
imwheel -k


#@-node:ekr.20040226094105.2:djsg
#@+node:ekr.20040226094105.3:The actual workaround

https://sourceforge.net/forum/message.php?msg_id=2433628
By: halw

Okay, I have a workaround that works for me with my existing Tk version. My
Tkinter skills are weak, but I don't think this will hurt anything else :)

Turns out this Tk version (or X or Tkinter or 	) doesn't generate MouseWheel
events, but maps to buttons 4 (up) & 5 (down).

Add to if clause 
# Handle mouse wheel in the outline pane.
if sys.platform == "linux2": # This crashes tcl83.dll
..canvas.bind("<MouseWheel>", frame.OnMouseWheel)
..def mapWheel(e):
	if e.num == 4:
	..e.delta = 120
	..return frame.OnMouseWheel(e)
	elif e.num == 5:
	..e.delta = -120
	..return frame.OnMouseWheel(e)
..canvas.bind("<ButtonPress>", mapWheel, add=1)
#@nonl
#@-node:ekr.20040226094105.3:The actual workaround
#@-node:ekr.20040226094105:Put the scrollWheel workaround in the FAQ (or in the code??)
#@+node:ekr.20040217090833:Change how Open with works?
By: rogererens ( Roger Erens ) 
 RE: Documentation for 4.1rc3   
2004-02-04 10:04  

@nocolor

It is stated that you can edit the temporary file with the editor as named in the "Open with..." submenu.
However, the temporary files are edited with the application that is associated with their extension. In my case I always fire up vim with .txt-files.

Hence the menu item would be better called something like "Open as..." with submenu items like ".doc file" or ".txt file"

Or, use something else instead of "os.startfile" to really use the intended editor.
#@nonl
#@-node:ekr.20040217090833:Change how Open with works?
#@+node:ekr.20040311022923:Make sentinel name in @-node optional
#@-node:ekr.20040311022923:Make sentinel name in @-node optional
#@+node:ekr.20040217154134:XML as a @language
#@-node:ekr.20040217154134:XML as a @language
#@+node:ekr.20040908104644:Leo splash screen
To create a splash screen:
    
- Draw the screen.
- Erase the screen with self.after(5000, self.destroy)
#@+node:ekr.20040908221501:@url http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/120687
#@-node:ekr.20040908221501:@url http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/120687
#@-node:ekr.20040908104644:Leo splash screen
#@-node:ekr.20040329190314.2:Others
#@-node:ekr.20040329182535.7:1-2 hour projects
#@+node:ekr.20040329182535.5:1 day projects
#@+node:EKR.20040607073134:Download and study wingide
#@+node:EKR.20040607073134.1:@url http://wingide.com/
#@-node:EKR.20040607073134.1:@url http://wingide.com/
#@-node:EKR.20040607073134:Download and study wingide
#@+node:EKR.20040517090625.1:@url http://komodo.nique.net/~grayrest/leoUIproposal.txt
#@-node:EKR.20040517090625.1:@url http://komodo.nique.net/~grayrest/leoUIproposal.txt
#@+node:ekr.20031218072017.801:Zipped .leo files
By: samcollett ( Sam Collett ) 
 Compressed LEO files   
2003-01-07 16:57


Would it be possible to have a new file format that was basically a compressed
version of LEO files? Maybe using the gzip compression method. You could then
save a lot of space when you do large files. Not being a professional
programmer myself (I mainly dabble in web design - HTML and Active Server
Pages) how difficult would this be to implement? You would just output using a
different file extension so users of the older versions can still use files
with the LEO extension.
#@+node:ekr.20040226092546:Saving .leo files with file compression
@nocolor
http://sourceforge.net/forum/message.php?msg_id=2442772
By: ejoy

I made an experiment last night. I replaced the call to open()in leofilecommand.py
with a call to gzip.open().
The LeoPy.leo file saved this way is only 600K! And there is no significant
speed down in saving.

I think it is a good idea to add compression support for load/save .leo files.
When reading or writing file with name ending with ".leo.gz", leo can call gzip
module to uncompress/compress the file, saving a lot of disk space.

What do you think of this?
#@-node:ekr.20040226092546:Saving .leo files with file compression
#@-node:ekr.20031218072017.801:Zipped .leo files
#@+node:ekr.20031218072017.733:Execute scripts like IDE's do
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2321235
By: paulpaterson

I think I understand what Samir is getting at. 

In PythonWin, or other IDE's for other languages, you are able to execute the
"current program" in a way that it will behave identically to if it had been
run from the command line.

For Python this requires,

1. That the script run with __name__ == "__main__", so that the standard "if
__name__ ..." section will be executed properly.

2. You are able to pass command line parameters to the script. In some IDE's
you are able to set default command line parameters and these are "injected"
into the sys.argv (or equivalent) so that, as far as the program is concerned,
it looks like you ran it from the command line with some aruments.


The underlying assumption is that you want to run an entire program from Leo,
not just a script. I would find this very useful also as I could run unit tests
or even my entire apps by just hitting, for exampe, F5.

Paul
#@nonl
#@-node:ekr.20031218072017.733:Execute scripts like IDE's do
#@+node:ekr.20031218072017.748:Import/Export to yaml
Need a good yaml parser first: I don't want to write another parser by hand.
#@nonl
#@-node:ekr.20031218072017.748:Import/Export to yaml
#@+node:ekr.20031218072017.790:Import dialog improvements
@nocolor

Other options I though would be really handy:

1. Use an existing node as a source also

2. Use an node from another Leo file.. I am not sure what the syntax for that
would be exactly

3. From a URL.. this would be really cool. People could post outlines not only
as existing Leo xml files, but as text files or even dynamic scripts. The code
to handle these would presumably need to deal with http:// intelligently. But
that's easy in Python. Rebol is great at that too.

4. Other XML file with valid filepaths in them.
That's probably a much bigger project like Leo 3.10  

Jason
#@-node:ekr.20031218072017.790:Import dialog improvements
#@+node:ekr.20031218072017.793:Keep right panes constant when tiling horizontally
This is done automatically now!  I may have to use configure events.

> When I have the 'split mode' set to display tree and log on left, and viewpane
on right, I sometimes need to increase the width of the window.

When I do the resize, the tree/log panes grow in proportion. I don't know about
others, but I'd much prefer if the tree/log panes stayed at the same width,
and only the view pane grew.
#@-node:ekr.20031218072017.793:Keep right panes constant when tiling horizontally
#@+node:ekr.20031218072017.800:Enhancements to extract section
Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1858824
By: gilshwartz
Open discusstion

Currently Extract Section is only available if the first line in a selection
is a section name <<x>>. I would like to propose a few enhancements I think
should be useful, while I believe most of the code is already implemented in
Leo.

1. If the first line in a selection is not <<x>>, than Extract Section WILL
make a section name from the first line (or a version of it, see below), leave
the section name in the body, create a new node with that section name, and
will copy the selection including the first line to the new node.

Rational: this is useful when selecting a function or a class. Thus the section
name becomes the function or the class definition. The section name can either
be the full first line, or, knowing the language, Leo can make a nice section
name like it does in import, e.g. "function foo", or "class bar", without the
parameters list.

2. Even better, when Extract Section is called WITHOUT a selection it will look
for the first function/class definition before the cursor's position and will
either use it as a selection and do 1 above, or just mark it as selection, which
will enable 1 above upon a second Extract Section.

Rational: Leo does it beautifully in import and when a node's code starts to
build it is most convenient. Also, I think a variation on this was recently
asked by another user.

3. Add an option Merge Section, which when called from a named section will
merge it back to all the sections containing it.

Rational: make it easy (together with 2) to create/delete sections until the
sections picture of a new code becomes clear.

Gil

--

Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1859516
By: nobody

Simpler & more intutive:
Mark text, select from menu - 'extract section', this presents a dialog box
in which you fill in the section name. It is too much work to type <<name>>
then select the whole thing...

As an enhancement, the dialog can show the first line of the selection as the
default section name, which obviously can be changed.

- Rajiv Bhagwat
#@-node:ekr.20031218072017.800:Enhancements to extract section
#@+node:ekr.20040329094003:Apply patch command
#@-node:ekr.20040329094003:Apply patch command
#@-node:ekr.20040329182535.5:1 day projects
#@+node:ekr.20040329182535.3:2 day projects
#@+node:ekr.20040217152936.1:Incremental drawing of outline pane: see projects
#@-node:ekr.20040217152936.1:Incremental drawing of outline pane: see projects
#@+node:ekr.20031218072017.743:Note windows
Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=2205285
By: nobody

From: Rich

Some of the things I'd like to see in Leo:

A note window for each node. This is similar to (1), but a little fancier
looking. I envision a short window at the bottom of the edit window that could
hold notes and comments about the code, such as "Test this harder" or "Find
a better way of phrasing this". This is currently available in uSoft Office
and the Eclipse IDE (http://www.eclipse.org). Perhaps a numeric reference, such
as "<<1>>" could be used.

(2) Color ''and'' italic/bold characters with @markup. One thing I'd like to
''not'' see are the markup characters in @file-nosent files.  "~~red:NOTE:~~"
does nothing for readability in plain text.
#@nonl
#@-node:ekr.20031218072017.743:Note windows
#@+node:ekr.20031218072017.754:Open text files in text window
#@-node:ekr.20031218072017.754:Open text files in text window
#@+node:ekr.20040329182535.4:Leo + zodb
#@+node:ekr.20040117111232:svn links
https://sourceforge.net/forum/message.php?msg_id=2379777
By: nobody

Here is my results of a short surfing:

- The SubVersion book (explain concepts, usage, etc..)
http://svnbook.red-bean.com/html-chunk/

-SubVersion applications can be programmed in Python
http://svnbook.red-bean.com/html-chunk/ch08s02.html
http://pysvn.tigris.org/

 The main problem of using SubVersion will be the binary dependencies (no more
pure python code).

- Zope Version Control
Similar Plone access to SVN (Plone is a Zope application Plone->Zope->Python),
could give some ideas
http://plone.org/events/conferences/1/archive/PloneSVN2003.pdf

Got nothing very clear...
I think we should start by creating a requirements document.
What exactly we want to create ?

rodrigob
#@-node:ekr.20040117111232:svn links
#@+node:ekr.20040117092727.2:using zodb versions
Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=2379351
By: korakot

If leo use database storage (ZODB or otherwise)
it could scale much better. This will bring us some
possiblity to store all versions of changes to a node
or a tree.

Cloning will then point to a specific version. If there
is no conflict, it can update to a later version when
the primary clone change. If there is a conflict
we can deal with it wisely using 'diff information'
because we have 'all versions' stored.

#@-node:ekr.20040117092727.2:using zodb versions
#@+node:ekr.20040226114127:Zope test
import leoGlobals

from ZODB import FileStorage, DB

if 1:
	print "-"*20
	storage = FileStorage.FileStorage(r"c:\prog\zopeTemp\leo.fs") 
	db = DB(storage)
	print storage ; print db
	db.close()
#@nonl
#@-node:ekr.20040226114127:Zope test
#@+node:ekr.20031218072017.737:Leo & data bases
From: "Scott Chapman" <scott_list@mischko.com>
To: <edreamleo@charter.net>
Sent: Monday, November 10, 2003 11:05 AM
Subject: Enhancement request: Make Leo deal with a database

It would be Way Cool if Leo could deal with psycopg or other DataBase 
API compliant implementations and fetch and store database records.

Details of what I'd like to see:

I'm working on Python source code. I want Leo to pull a source code file 
from a database record built something like this:

filename: text
updated: datetimestamp
code: text

Leo would insert a new record, not update an existing one, each time a 
Save was done, with a more current time stamp.  

When a File Open was requested (i.e. a node on a existing Leo outline 
was being pulled from a database), Leo would fetch the most recent 
time-stamped version only.

I use this same technique for my wiki pages.  It's very easy to 
implement in SQL and works great.

Leo would also need the ability to save the same file to a regular 
python source code file on disk at a specific location, so you'd have 
to be able to put more than one save/load directive in a given node.

This would provide a nice version history and could lead to other Useful 
Things in Leo.

Scott
#@nonl
#@-node:ekr.20031218072017.737:Leo & data bases
#@-node:ekr.20040329182535.4:Leo + zodb
#@+node:ekr.20031218072017.797:Allow @file http & @file ftp
I'd like to see leo's @file can be extended to cover more protocols, like REBOL's "read" does. 

in short, it would be very sweet if the following work: 

@file http://www.somedomain.org/python/foo.py 

@file pass@ftp.sd.org/python/foo.py" target="_blank" target="_new">ftp://user:pass@ftp.sd.org/python/foo.py> 

while we are at it, what about xmlrpc/soap? 

should there be new directive, like @source ?
#@nonl
#@+node:ekr.20031218072017.810:Remote access Scott Powell
I will wait. Here's clarification, when you're ready for it:

All of my projects are stored on remote computers, and accessed via FTP. 
What I want is basically the ability to open up these projects directly 
through leo, instead of transferring the files manually between my computer 
and the computers that hold my projects, preferably through FTP.

My solution: A new menu item called 'FTP' or 'Remote'. Click on this, and an 
FTP dialog opens up, with an empty list of FTP sites, and the ability to add 
more. You select a site, and it brings up a list of files. You select a 
file, and it is added to your project. When you hit 'save', it automatically 
does an FTP send.

Python makes this a lot easier with the builtin module 'ftplib'. I'm sure 
there are similar things for C++. I hope you take this idea into 
consideration.

Scott Powell
CEO, Dev Designs
#@-node:ekr.20031218072017.810:Remote access Scott Powell
#@-node:ekr.20031218072017.797:Allow @file http & @file ftp
#@-node:ekr.20040329182535.3:2 day projects
#@+node:ekr.20040329182535.1:1-2 week projects
#@+node:ekr.20040124074218.3:pyGtk plugin
#@-node:ekr.20040124074218.3:pyGtk plugin
#@+node:ekr.20040217153407.1:** Unify @root and @file
#@-node:ekr.20040217153407.1:** Unify @root and @file
#@+node:ekr.20031218072017.658:Resolve Conflicts command (may not be needed with @thin)
@nocolor

There have been a couple different suggestions on how to obtain a .leo file
that presents the conflicts for resolution.  My initial thought was to have
some sort of external file containing only the structure information of interest.
Edward has suggested that we could process the CVS created .leo file with all
its conflict indicators.

I'd like to offer another alternative: based on the ad-hoc procedure that Edward
is currently using, i.e.,

* Save your work to foo.leo file.
* copy your foo.leo file to fooCvsTmp.leo
* cvs ci
* if there are no conflicts (hurray)
*    remove fooCvsTmp.leo; finished
* otherwise
*    remove foo.leo
*    cvs up foo.leo
*    read foo.leo into an internal directed acyclic graph (DAG)
*    generate the conflict resolution DAG from the proposed new leo internal
DAG (which Leo already has as a matter of course) and the CVS derived DAG
*    present for editting
*    when you are satisfied and want to try to check in again, repeat.

From the user's point of view, a CVS check in either succeeds or requires that
the conflicts be resolved, after which another check in may be attempted.  If
we're somehow interrupted in the middle of the process, the fooCvsTmp.leo file
preserves the users work.
#@+node:ekr.20031218072017.659:Jonathon 1
https://sourceforge.net/forum/message.php?msg_id=1803722
By: jmgilligan

What is the intended behavior when foo.leo has a cloned node that appears several
times in a derived (or multiple derived files) and the user edits this file
or files to change two or more instances of the cloned node in different ways.

Example:

@file foo.py
****begin tnode
@others
****end tnode
...definition of procedure a
***begin tnode
def a:
...print "a:", <<bar>>
***end tnode
...<<bar>>
****begin tnode
"bar"
****end tnode

...definition of procedure b
***begin tnode
def b:
...print "b:", <<bar>>
***end tnode
...<<bar>>
****begin tnode
"bar"
****end tnode

...definition of procedure c
***begin tnode
def c:
...print "a:", <<bar>>
***end tnode
...<<bar>>
****begin tnode
"bar"
****end tnode

In the .leo, if I edit <<bar>>, it will change simultaneously in each place,
always in synch. However, suppose I edit foo.py in a text editor and change
foo.py to read:

#@verbatim
#@+leo
#@verbatim
#@+node:0::@file foo.py
#@verbatim
#@+body
#@verbatim
#@+others
#@verbatim
#@+node:1::definition of a
#@verbatim
#@+body
print "a:", 
#@verbatim
#@<<bar>>
#@verbatim
#@+node:1::<<bar>>
#@verbatim
#@+body
"bar"
#@verbatim
#@-body
#@verbatim
#@-node:1::<<bar>>
#@verbatim
#@-body
#@verbatim
#@-node:1::definition of a
#@verbatim
#@+node:2::definition of b
#@verbatim
#@+body
print "b:", 
#@verbatim
#@<<bar>>
#@verbatim
#@+node:1::<<bar>>
#@verbatim
#@+body
"variation b"
#@verbatim
#@-body
#@verbatim
#@-node:1::<<bar>>
#@verbatim
#@-body
#@verbatim
#@-node:2::definition of b
#@verbatim
#@+node:3::definition of c
#@verbatim
#@+body
print "c:", 
#@verbatim
#@<<bar>>
#@verbatim
#@+node:1::<<bar>>
#@verbatim
#@+body
"variation c"
#@verbatim
#@-body
#@verbatim
#@-node:1::<<bar>>
#@verbatim
#@-body
#@verbatim
#@-node:3::definition of c
#@verbatim
#@-others
#@verbatim
#@-body
#@verbatim
#@-node:0::@file foo.py
#@verbatim
#@-leo

Now what is supposed to happen when leo tries to read foo.py back in? It seems
that there are three possible behaviors:

1) leo reports a conflict that the user needs to resolve.
2) leo breaks the clone: the three nodes become separate vnodes, not clones
of the same one.
3) leo arbitrarily takes one of the tnodes to be the new tnode for all of the
clones. This is currently what happens. It creates something like a race condition,
where the last node in the derived file determines what the final result will
be. In this case, all three tnodes get text "variation c".

This general question of how Leo should deal with conflicts in clone nodes seems
to me that it needs to be addressed, particularly with respect to trying to
avoid cvs conflicts via thick/thin modes.

Note that this can become quite a subtle question because the same node can
be cloned across several different derived files, in which case a serious race
condition may pertain.

At the least, I would like to see leo perform consistency checking on cloned
nodes when it reads a derived file back in and warn the user if cloned nodes
are inconsistent.

Note that the issue also occurs with OpenWith: I can open each instance of a
cloned node as a separate file in the external text editor. Which version is
read back into leo depends on the order in which I save them from the external
editor.
#@-node:ekr.20031218072017.659:Jonathon 1
#@+node:ekr.20031218072017.660:Jonathan 2
By: jmgilligan ( Jonathan M. Gilligan ) 
 Possible solution   
2003-01-13 20:55  
One possible solution for the conflicting clones problem is to have Leo, when it detects a conflicting clone, generate a text file (perhaps named LeoConflict_NNNN.leo, where NNNN is the gid of the node in question), which contains all the different versions of the node in question, output in leo XML format. 

The vnode would then be marked with a "conflict" flag that would prevent the user from opening it in Leo until the conflict is resolved. See below for how the user resolves the conflict. 

What I have in mind is something similar to the CVS conflict file, where a conflict between two versions is marked 

<<<<<<< 
blah blah blah? 
======= 
blah blah blah! 
>>>>>>> 

Except that we would output this file in Leo XML format. The reason for XML format would be to avoid problems of how to generically delimit the different sections (different versions of the cloned node). Here leo's XML tags can unambiguously delimit the sections. 

The user would then edit the conflict file and delete all but the desired version. Then he would tell leo to resolve the conflict by reading the corrected file in and replacing the contents of the conflicting node with the contents of the LeoConflict_NNNN.leo file. 

On the down side, this may well be too baroque a fix for a problem that most users may never encounter. If so, perhaps it's best left alone until more pressing problems are solved. I know what I am doing with Leo and am always careful NOT to generate conflicting clones when I edit in an external text editor, so I don't absolutely need a resolution to this problem. I raised it because it's good for a program to have well-defined behavior when presented with anomalous input. 

In this sense, perhaps the best thing is to allow users to generate code from clones (what Allan Holub referred to as "enough rope to shoot yourself in the foot"), but to tell them that this practice is frowned upon.  

 
#@-node:ekr.20031218072017.660:Jonathan 2
#@+node:ekr.20031218072017.661:Gil 1
Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1836117
By: gilshwartz

Edward, now that conflicting clones may not be the result of bad style, I would
like to propose yet another solution that I have been thinking of for a while.

My basic approach is that cloning is not just a convenience tool, it may also
reflect some of the properties of the code/code set. Therefore my goal is for
clone links to remain even if they are conflicting, and let the user resolve
them at any convenient time. I also think that Leo's user interface is the best
tool to resolve such conflicts.

Thus here is my view of clone management and resolution inside Leo. Anytime
content is loaded into Leo, if a clone set agrees (i.e. have the same content)
all clone copies are marked "green". When one green clone is edited, all green
clones are changed. This is Leo as it is now.

If at some point conflicting clones are loaded, Leo decides on some representing
content (may be based on policies like most occurring content, or latest timestamp,
etc.) and provides visual clues for the conflict. The visual clue is give by
a double node box, e.g.

+---------------+
+ clone org +
+---------------+
+ resolution +
+---------------+

such that it is a single node in the tree, but has two content node, the original
text, and the possibly arbitrary resolution.

The resolution pseudo node is marked "blue", while the original text is either
"green", if it is identical to the blue node, or "red", if it is not. To emphasis,
the red/green nodes contain the specific (possibly) unique code associated with
the derived file, while the pseudo blue node contains the shared clone content.
During save to derived files, only the red/green content is saved, so effectively
the file is not changed and the conflict is not resolved until the user chooses
to do so. However, the clone relationship (via the gti) remains.

During editing, changes to red/green clones are local and do not propagate to
other clone copies (actually any change to a green node would turn it red).
Changes to the blue nodes do propagate since it is a single view of the clone.
A node pair may be converted to a regular node, effectively getting a new gti
and eliminating the blue copy. Or, it may be converted to the shared copy,
effectively forgetting its original content (leaving only the blue node). Once
there are no more red nodes in a clone set, all its nodes become green again
and the conflict is resolved.

Some additional clone actions I think are useful are:

1. Go to next/prev clone.
2. Go to next/prev green clone (useful when there are red ones).
3. Convert all green copies to a new clone group (useful when some clone copies
needs to remain clones, but break from the original clone set, thus getting
a new clone gti)

Action 3 enables the user to partition its clone set to several clone groups
by copying a clone's original content to its blue copy and finding matching
(green) clones.

What do we gain by all this?

1. We can have conflicting clones without catastrophes.
2. We get tools to handle conflicts and resolve them.
3. We keep on working is Leo's environment, which is the most supportive one
we can expect.

Gil

(By the way, I have a feeling that it would be useful to include along with
the gti a hash of its node content, which could tell Leo is a node was changed
outside of it. Also, including a timestamp in the opening sentinel, indicating
when Leo last saved it. These may help having intelligent decisions by Leo in
cases like conflicting clones.)
#@-node:ekr.20031218072017.661:Gil 1
#@+node:ekr.20031218072017.662:Gil 2
Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1804169
By: gilshwartz

Another thing to think about is what should happen when close heading is changing.
Right now, if you are inside Leo, all headings will be changed, and if you try
to change by hand it in the derived file, clone links are removed (after some
error reporting). This is quite reasonable in the current scheme of things,
where one is not really expected to mess up with Leo sentinels.

However, if/when @include is implemented, one would probably edit some files
with clones that may extend to other files and changing the clone node name
(via Leo) is quite feasible. Note that the gti for the cloned node would probably
not change. So

1. Do Leo resync the clone content based on the gti?
2. Does it break cloning and allocates a new gti for one (arbitrary?) clone
set?
3. Let the user resolve manually, offer undoable auto-resolution with reporting,
other?

More things to think about.

Gil
#@-node:ekr.20031218072017.662:Gil 2
#@-node:ekr.20031218072017.658:Resolve Conflicts command (may not be needed with @thin)
#@+node:ekr.20031218072017.811:Split panes (could be done in gui plugins)
Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1759009
By: davidmcnab

Hi,

I know I've put this one before, but as I use Leo more and more, the lack of
this is really pressing.

I tried your previous suggestion - opening another editor to have a view of
the file, but this is really painful, especially when I'm wanting to work on
two or more files simultaneously.

I've tried having files open in Emacs and writing them out, then doing a 'read
@file nodes', but this doesn't always work - the changes made in Emacs aren't
getting incorporated into the Leo tree (unless I quit/restart Leo).

(BTW - I'm using latest CVS of Leo).

What would make for Total Hacking Heaven is the ability to split a body pane
vertically or horizontally, and to be able to split the sub-panes
vertically/horizontally.

What that, hotkeys would move the cursor between panes (ie make different panes
'active'). Also, clicking on a node would display that node in the *currently
active* body pane.

This in place, as well as an accurage 'goto line number' feature would make
Leo even more of a killer app.

Cheers
David

#@-node:ekr.20031218072017.811:Split panes (could be done in gui plugins)
#@+node:ekr.20040914095432:Use TreeCC to create generalized import
@killcolor

http://sourceforge.net/forum/message.php?msg_id=2754954
RE: euphoria or general language plugins  
2004-09-11 20:37
>>Leo doesn't really understand languages otherwise.
import is another area Leo is too hardwired
wrt language. if a plugin for each language
could preprocess the file or code string and insert some minimal sentinals to create nodes and headlines,
then pass the string to Leo to do the dirty work, adding new languages for import could be easier.
I'm at this point in a new html file import,
creating the nodes while parsing is more complicated than one would like.
I think this has been discussed before, so
maybe will have to search the forums before I say more.

e

---------

http://sourceforge.net/forum/message.php?msg_id=2758616
By: differance

Check out treecc:
http://www.southern-storm.com.au/treecc.html
http://www.southern-storm.com.au/treecc_essay.html

It was developed for DotGNU, but I think you should be able to use it.

Seth

#@-node:ekr.20040914095432:Use TreeCC to create generalized import
#@-node:ekr.20040329182535.1:1-2 week projects
#@+node:ekr.20040123102724:Can't or wont
#@+node:ekr.20031218072017.835:(Use pywin extensions to improve cut/paste between apps)
@color

@ This is not going well :-(  Probably what is happening is that Tk is competing with the win32 extensions in setting the clipboard.  It might not be so easy to

a) completely disable Tk's clipboard handling, in _both_ headlines and body text.
b) do the cut/paste operations "by hand".

This might require new body routlines to replace the selected text in the body.  And then there are issues relating to whether the headline or body text should be changed.  All in all, this is much harder than it looks.

Also, if one is not careful one can take fatal Python errors related to missing threads.  Pretty much a nightmare.
#@nonl
#@+node:ekr.20031218072017.836:updateEditMenu
def updateEditMenu (self):

    c = self.c ; frame = c.frame ; gui = g.app.gui
    if not c: return
    try:
        # Top level Edit menu...
        enable = frame.menu.enableMenu
        menu = frame.menu.getMenu("Edit")
        c.undoer.enableMenuItems()
        << enable cut/paste >>
        if 0: # Always on for now.
            menu = frame.menu.getMenu("Find...")
            enable(menu,"Find Next",c.canFind())
            flag = c.canReplace()
            enable(menu,"Replace",flag)
            enable(menu,"Replace, Then Find",flag)
        # Edit Body submenu...
        menu = frame.menu.getMenu("Edit Body...")
        enable(menu,"Extract Section",c.canExtractSection())
        enable(menu,"Extract Names",c.canExtractSectionNames())
        enable(menu,"Extract",c.canExtract())
        enable(menu,"Match Brackets",c.canFindMatchingBracket())
    except:
        g.es("exception updating Edit menu")
        g.es_exception()
#@nonl
#@+node:ekr.20040130164211:<< enable cut/paste >>
if frame.body.hasFocus():
    data = frame.body.getSelectedText()
    canCut = data and len(data) > 0
else:
    # This isn't strictly correct, but we can't get the Tk headline selection.
    canCut = True

enable(menu,"Cut",canCut)
enable(menu,"Copy",canCut)

data = gui.getTextFromClipboard()
canPaste = data and len(data) > 0
enable(menu,"Paste",canPaste)
#@nonl
#@-node:ekr.20040130164211:<< enable cut/paste >>
#@-node:ekr.20031218072017.836:updateEditMenu
#@+node:ekr.20031218072017.837: tkinterGui.__init__
def __init__ (self):

    # Initialize the base class.
    leoGui.leoGui.__init__(self,"tkinter")

    self.bitmap_name = None
    self.bitmap = None
    self.win32clipboard = None
    
    if 0: # This seems both dangerous and non-functional.
        if sys.platform == "win32":
            try:
                import win32clipboard
                self.win32clipboard = win32clipboard
            except:
                g.es_exception()
#@nonl
#@-node:ekr.20031218072017.837: tkinterGui.__init__
#@+node:ekr.20031218072017.838:tkBody.createBindings
def createBindings (self,frame):
    
    t = self.bodyCtrl
    
    # Event handlers...
    t.bind("<Button-1>", frame.OnBodyClick)
    t.bind("<Button-3>", frame.OnBodyRClick)
    t.bind("<Double-Button-1>", frame.OnBodyDoubleClick)
    t.bind("<Key>", frame.body.onBodyKey)

    # Gui-dependent commands...
    t.bind(g.virtual_event_name("Cut"), frame.OnCut)
    t.bind(g.virtual_event_name("Copy"), frame.OnCopy)
    t.bind(g.virtual_event_name("Paste"), frame.OnPaste)
#@nonl
#@-node:ekr.20031218072017.838:tkBody.createBindings
#@+node:ekr.20031218072017.839:<< define editMenuTopTable >>
self.editMenuTopTable = (
    ("Can't Undo","Ctrl+Z",c.undoer.undo), # &U reserved for Undo
    ("Can't Redo","Shift+Ctrl+Z",c.undoer.redo), # &R reserved for Redo
    ("-",None,None),
    ("Cu&t","Ctrl+X",f.OnCutFromMenu), 
    ("Cop&y","Ctrl+C",f.OnCopyFromMenu),
    ("&Paste","Ctrl+V",f.OnPasteFromMenu),
    ("&Delete",None,c.delete),
    ("Select &All","Ctrl+A",f.body.selectAllText),
    ("-",None,None))
#@nonl
#@-node:ekr.20031218072017.839:<< define editMenuTopTable >>
#@+node:ekr.20031218072017.840:Cut/Copy/Paste body text
#@+node:ekr.20031218072017.841:frame.OnCut, OnCutFrom Menu
def OnCut (self,event=None):
    
    """The handler for the virtual Cut event."""

    frame = self ; c = frame.c ; v = c.currentVnode()
    
    # This is probably being subverted by Tk.
    if g.app.gui.win32clipboard:
        data = frame.body.getSelectedText()
        if data:
            g.app.gui.replaceClipboardWith(data)

    # Activate the body key handler by hand.
    frame.body.forceFullRecolor()
    frame.body.onBodyWillChange(v,"Cut")

def OnCutFromMenu (self):
    
    w = self.getFocus()
    w.event_generate(g.virtual_event_name("Cut"))
    
    frame = self ; c = frame.c ; v = c.currentVnode()

    if not frame.body.hasFocus(): # 1/30/04: Make sure the event sticks.
        frame.tree.onHeadChanged(v)




#@-node:ekr.20031218072017.841:frame.OnCut, OnCutFrom Menu
#@+node:ekr.20031218072017.842:frame.OnCopy, OnCopyFromMenu
def OnCopy (self,event=None):
    
    frame = self

    if g.app.gui.win32clipboard:
        data = frame.body.getSelectedText()
        if data:
            g.app.gui.replaceClipboardWith(data)
        
    # Copy never changes dirty bits or syntax coloring.
    
def OnCopyFromMenu (self):

    frame = self
    w = frame.getFocus()
    w.event_generate(g.virtual_event_name("Copy"))

#@-node:ekr.20031218072017.842:frame.OnCopy, OnCopyFromMenu
#@+node:ekr.20031218072017.843:frame.OnPaste & OnPasteFromMenu
def OnPaste (self,event=None):
    
    frame = self ; c = frame.c ; v = c.currentVnode()

    # Activate the body key handler by hand.
    frame.body.forceFullRecolor()
    frame.body.onBodyWillChange(v,"Paste")
    
def OnPasteFromMenu (self):
    
    frame = self ; c = frame.c ; v = c.currentVnode()

    w = self.getFocus()
    w.event_generate(g.virtual_event_name("Paste"))
    
    if not frame.body.hasFocus(): # 1/30/04: Make sure the event sticks.
        frame.tree.onHeadChanged(v)
#@-node:ekr.20031218072017.843:frame.OnPaste & OnPasteFromMenu
#@-node:ekr.20031218072017.840:Cut/Copy/Paste body text
#@+node:ekr.20031218072017.844:Clipboard (tkGui)
#@+node:ekr.20031218072017.845:replaceClipboardWith
def replaceClipboardWith (self,s):
    
    wcb = g.app.gui.win32clipboard

    if wcb:
        try:
            wcb.OpenClipboard(0)
            wcb.EmptyClipboard()
            wcb.SetClipboardText(s)
            wcb.CloseClipboard()
        except:
            g.es_exception()
    else:
        self.root.clipboard_clear()
        self.root.clipboard_append(s)
#@nonl
#@-node:ekr.20031218072017.845:replaceClipboardWith
#@+node:ekr.20031218072017.846:getTextFromClipboard
def getTextFromClipboard (self):
    
    wcb = g.app.gui.win32clipboard
    
    if wcb:
        try:
            wcb.OpenClipboard(0)
            data = wcb.GetClipboardData()
            wcb.CloseClipboard()
            # g.trace(data)
            return data
        except TypeError:
            # g.trace(None)
            return None
        except:
            g.es_exception()
            return None
    else:
        try:
            return self.root.selection_get(selection="CLIPBOARD")
        except:
            return None
#@-node:ekr.20031218072017.846:getTextFromClipboard
#@-node:ekr.20031218072017.844:Clipboard (tkGui)
#@-node:ekr.20031218072017.835:(Use pywin extensions to improve cut/paste between apps)
#@+node:ekr.20031218072017.753:Emacs comint-mode:  The improved Execute Script command does most of this
@nocolor

Michael Manti
mmanti@mac.com

P.S. I think a feature that could make Leo *the* IDE for developing in 
interpreted languages is something like the (X)Emacs comint-mode.el for 
interacting with the shell and interpreters.

comint-mode.el serves as the basis for interactive modes for a number of
languages--OCaml, Haskell, SML, among them. It allows for editing expressions in
one buffer and triggering their evaluation in another buffer that has an
interpreter running in it, along with entering commands in the interpreter
buffer and moving back and forth through the history of their evaluation.

Imagine being able to highlight a node in Leo, and have all the code in it and
its children evaluated in an interpreter running in a separate window or pane,
much as Leo can open a Python shell now. Users of those languages could build
plug-ins specific to their language atop that layer, and the @language directive
could activate that. I think that would be very cool.
#@-node:ekr.20031218072017.753:Emacs comint-mode:  The improved Execute Script command does most of this
#@+node:ekr.20031218072017.729:HTML rendering in Leo's body pane
#@+node:ekr.20031218072017.731:HTML plugin: opml
@nocolor
http://sourceforge.net/forum/message.php?msg_id=2283466
By: billp9619

FYI
I played around with opml a while back and it seemed very versatile.

It basically consists of an xml file of nested outline tags similiar to v-nodes
in leo xml. This then works with an xsl stylesheet that displays the outline
in a browser with scripted outline manipulation. (Uses div tags for this
display.)

What I discovered is that any html can make up the outline nodes , even forms,
etc. which collapse with the outline interaction. Just that the angle brackets
in the html must be escaped as is done within leo t nodes in .leo xml.

Actually, it would be interesting to see an addin that just passes leo nodes
to opml and then pops into the default browser. Also keep in mind that javascript
has an eval() statement that can be passed any script as a string. The leo text
box could be a form textarea box except that then there is no way to emulate
syntax coloring. Alternatively, this could be a floating window wrappiing node
text in html/body. (if nothing else, just destroy/close the window and reinitialize).
Maybe the images used in the opml could have javascript events like onclick()
to trigger refreshing leo text box from the t-nodes stored in an array or in
hidden form boxes.

Of course the effect of the stylesheet could be done via python script if no
xslt in the receiving browser. The minimal html and script might be just boilerplate
output.

regards,
bill p
#@nonl
#@-node:ekr.20031218072017.731:HTML plugin: opml
#@-node:ekr.20031218072017.729:HTML rendering in Leo's body pane
#@+node:ekr.20040216054459:@h @f @endh and @endf directives
@nocolor

http://sourceforge.net/forum/message.php?msg_id=2424151
By: ksejlod ( Peter Barrel ) 
 I Have a (maybe) great idea!   
2004-02-15 04:29

I've been using LEO for a while and finding surprinsingly powerfull new uses now and then, (hey, not a week passes that i dont think to myself : "why did'nt anyone thought of that kind of tool that is LEO. It's so stupid to program such a tool, yet no one thought of doing such a thing ! ")

I was wondering if there was a leo keyword (beginning with "@") that would do a feature I thought would be great: something such as :
@h
@endh
and of course, similarily...
@f
@endf

Standing for "Header", "End Header", "Footer" and "End Footer". Let me please explain ...

When creating files with @file (or nosentinels) I use the keyword "@others" in the starting node body of the file and place in the file, as it's decendants (children, grand-children & so on) some clones of other stuff somewhere else outside of this file (usualy, clones of parts of program regrouped as children of a "components" node up in the leo outline. Typical Example:

-Introduction
-+components
-a
-b
-c
-+@file program.BAS
-b
-c
-a

a, b, and c are clones and the @file node contains @others.

As you see, I proceed that way because in older programming languages or in lower level languages, the order of components such as procs, declarations, etc as an importance. It also has the implication that << and >> brackets are irrelevant in my way of using leo.

Now, my feature that I looked for in the doc but could not find (so i suggest it here in case no one had any need of this before) is that when used in the BODY of a node part of an "@file" the @h and @endh would define a chunk of text in the body, you've guessed it, to be added before _each_ children node and ONLY children no grandchildren or any deeper. But It could also be used INSIDE the body of a children to define headers or footers for IT'S OWN direct children.

so, eehh, do you see the relevance of such a feature? Have i explained it clearly? maybe this would help:
CONST baba=2 AS INTEGER
CONST bebe=7 AS INTEGER
CONST zaza=5 AS INTEGER
CONST bobo=1 AS INTEGER
... the beginning and end of each of those "parts-of-a-program" is the same for a potential lot of lines... 

To Be Precise :
It's just really for adding something at end or beginning of a direct children of a node part of an @file in the tangling process. 

Is this feature already implemented but i have not found it? I'm pretty sure it easy to implement... what do you people think of this?
Thanks 
--
k

p.s. I'm the guy who proposed that in the untangling process, a clone would not be updated by it's _Last-Instance-Found_ in the @file beeing untangled, but instead updated by the _Last-Modified-One-Found_ in the @file... :)

(ooouuuuhh that would be slick...)  

By: ksejlod ( Peter Barrel ) 
 RE: I Have a (maybe) great idea!   
2004-02-15 04:35  

 The tree i tried to draw in ascii did not came out the way i did it, sourceforge "eated" leading spaces sorry a, b and c are children of their "+" node just above them		.
--
k  
#@nonl
#@-node:ekr.20040216054459:@h @f @endh and @endf directives
#@+node:ekr.20040123102724.2:Templates
@nocolor
#@nonl
#@+node:ekr.20040123102724.3:daliuslt
https://sourceforge.net/forum/message.php?msg_id=2385991
By: nobody

I often found myself using the same template for many projects but something differs in them. E.g. name of files. So my offer is add new directive variable:

@variable ProjectName MyFunProject

Later in parent node, child nodes or headers I could use this as:

$(ProjectNode).py or smth. similar.
This line when tangling could be replaces with
MyFunProject.py

Of course, there comes some complexity with untangling, but it is just an offer :)

Dalius
#@-node:ekr.20040123102724.3:daliuslt
#@+node:ekr.20040123102724.4:Rich: @constant
https://sourceforge.net/forum/message.php?msg_id=2386199
By: nobody

Dalius-
FWIW, I like it! I also can think of a LOAD of variations (i.e., multi-line
variables, etc.), but I'll limit myself to one thing: 'variable' by definition
should be changeable. May I suggest you call the directive "@constant"?

--Rich
#@nonl
#@-node:ekr.20040123102724.4:Rich: @constant
#@+node:ekr.20040123102724.5:paulpaterson
https://sourceforge.net/forum/message.php?msg_id=2386359
By: paulpaterson

I like this idea because I think templating is an important "design pattern"
that Leo could usefully support. Currently you can only do this in a limited
way by cloning nodes across derived files, which is something Edward doesn't
like to do because it makes reading derived files ambiguous (same information
in more than one place). I still do this because I rarely read files into Leo,
I mostly explicitely tangle files out of Leo.

Back to your idea ;) ISTM that your idea works well for directives (you can
do the variable substitution) but for general text (where I think there is a
bigger gain) there are two issues,

1. The template itself is now present in multiple derived files (see above)

2. The file derived from the template has now lost the variable name because
it was substituted out for the variable value

The end result is that templated files would almost certainly have to be a one
way street for Leo - they are derived from the Leo file but never read back
in. If you don't mind having this restriction, which basically means you have
to use @root nodes, then I don't see a problem.

Presumably most of this could all be done with a tangle_done script - the only
problem is how to change directives.
#@nonl
#@-node:ekr.20040123102724.5:paulpaterson
#@+node:ekr.20040123102724.6:daliuslt
http://sourceforge.net/forum/message.php?msg_id=2389750
By: daliuslt

Just for your interests where is one way to create constants in leo (even multiline).
Let's way we have outline:

+-[ ] @file-nosent objects.mak
|   +-[clone] << my_const >>
+-[ ] @file-nosent makefile
    +-[close] << my_const >>

objects.mak is:
--
<< my_const >>.o:
gcc -g -c << my_const >>.c -o << my_const >>.o

<< my_const >>cmd.o:
gcc -g -c << my_const >>cmd.c -o << my_const >>cmd.o
--

makefile is
--
<< my_const >>:
gcc << my_const >>.o << my_const >>cmd.o -o << my_const >>
--

<< my_const >> can be:
test

While it works pretty good it still have some drawbacks:
1. You are forced to use @file-nosent. You can edit your files outside of Leo.
2. You can't use << my_const >> in header lines, while it would be really nice
if you could write
@file-nosent obj_<< my_const >>.mak
(Leo lacks introspection?)
3. Result is not 100% what you expect.

---

(constant = variable = define) ???

Constants and clones in fact are the same. Usually clones are used like symbolic
links (or shortcuts) not like clones or constants. I think this could be
separated.

Constants could be read back into leo. E.g.: you have constant value in leo
file and while reading back files to outline you change all values with constant
name.

I hope I am clear :)
Dalius
#@nonl
#@-node:ekr.20040123102724.6:daliuslt
#@-node:ekr.20040123102724.2:Templates
#@+node:ekr.20040329185649:Bugs: can't be fixed or can wait
#@+node:ekr.20031218072017.663:Bug: can't be fixed
#@+node:ekr.20031218072017.664:Cut/paste bug on X windows (waiting for help)
@nocolor

Under X Window system, when text is selected, it is automatically entered into a buffer and can be pasted with the middle button of the mouse.

In Leo, when this is done, the text is rendered in right place, but it doesn't stick unless some key is pressed after pasting. That is, if I leave the node in question without pressing any key after pressing the middle button, the pasted text is gone when I come back to that node.

Doing copy and paste works normally when done through the edit menu.

@color
#@nonl
#@+node:ekr.20031218072017.665:(Cut & Paste ) (Middle-button bug reported by Timo)
#@+node:ekr.20031218072017.666: Paste bug report
@nocolor

By: riotnrrrd ( Timo Honkasalo ) 
 Pasted text doesn't stick   
2002-11-01 13:38  
System: Linux 

Under X Window system, when text is selected, it is automatically entered into a buffer and can be pasted with the middle button of the mouse. 

In Leo, when this is done, the text is rendered in right place, but it doesn't stick unless some key is pressed after pasting. That is, if I leave the node in question without pressing any key after pressing the middle button, the pasted text is gone when I come back to that node. 

Doing copy and paste works normally when done through the edit menu. 

-------------------

I also found out that if you do an extra "click" on the control key, it will
stick from then on.

If your text should have color in it, you can see that right before you "click",
the text has no color and the color back on right after you click the control.

It maybe a clue to someone, but seems strange to me. 
#@-node:ekr.20031218072017.666: Paste bug report
#@+node:ekr.20031218072017.667: Test
abc bbb bbbxyz bbb
#@nonl
#@-node:ekr.20031218072017.667: Test
#@-node:ekr.20031218072017.665:(Cut & Paste ) (Middle-button bug reported by Timo)
#@+node:ekr.20031218072017.668:Automatic select & Paste bug (Linux?)
@nocolor

Bumping the thread because the bug still persists. 

I've also noticed that the automatic select'n'paste doesn't work between nodes. That is, I can select text and paste a copy of it in the same node with middle button, but if I change click to another node, the paste buffer is erased. The automatic pasting works between Leo and other applications, however, and I can paste between nodes if I copy the selection to buffer by CTR-C. 

Maybe this is related to the non-sticking bug?

----

This may be a Linux-only bug related to the control-v workaround.
#@nonl
#@-node:ekr.20031218072017.668:Automatic select & Paste bug (Linux?)
#@-node:ekr.20031218072017.664:Cut/paste bug on X windows (waiting for help)
#@+node:ekr.20031218072017.669:Linux-only Bugs
These may indicate problems with Tk on Linux.  I can not reproduce them on XP.
#@nonl
#@+node:ekr.20031218072017.670:Possible webbrowser bug
(In Linux) The home page and online tutorial options in the menu only work properly if Mozilla window is already open. If not, a Mozilla window opens, but with empty page and url field. 
#@nonl
#@-node:ekr.20031218072017.670:Possible webbrowser bug
#@+node:ekr.20031218072017.671:Fix horiz scrollbar bug when tiling horizontally
When in 'vertical split' mode (with viewpane on right, and tree pane over log pane on left), the horixontal scrollbar at bottom of screen is at full width, despite the fact that not all of the tree pane area is displayed. 

Another way of saying this - I narrow the tree and log panes, to the extent that the display of tree node headings is truncated. But the horizontal scrollbar at the bottom doesn't contract, and doesn't allow me to horizontally scroll the tree pane to expose the rest of the node headings. 
#@-node:ekr.20031218072017.671:Fix horiz scrollbar bug when tiling horizontally
#@+node:ekr.20031218072017.672:Control-V doesn't work on Linux
This has been and continues to be a known issue with Tk. Has been logged as a bug; no response from the Tk folks. 

Here is a link to the Tk bug report: 

http://sourceforge.net/tracker/?func=detail&aid=605277&group_id=12997&atid=112997 

Note the work-around/patch in the followup post at the bottom of that page. Commenting out some statements in text.tcl removes the problem. 
#@-node:ekr.20031218072017.672:Control-V doesn't work on Linux
#@-node:ekr.20031218072017.669:Linux-only Bugs
#@+node:ekr.20031218072017.673:Tk bugs
The following bugs can not be fixed because they are Tk bugs.
#@nonl
#@+node:EKR.20040523192553:(Crash when pasting large text into headlines)
#@+node:EKR.20040606104355:Report
@nocolor

From: <eltronic@juno.com>
To: <edreamleo@charter.net>
Sent: Sunday, May 23, 2004 9:36 AM
Subject: fatal bug in Leo headline handling


> found a fatal bug in Leo headline handling.
> not sure if anyone reported before,
> an oversize string can crash python 2.3.3
> 
> 
> the text was about 4500 bytes. nothing but text.
> opened the  leo again, copy a large page of text,
> insert headline, paste, fatal error in python.
> 
> I have by mistake pasted whatever node xml was in 
> the copy buffer into a headline w/o problem.
> but that was just dumb luck. just verified,
> had the node been large enough it crashes.
> 
> Leo 4.1 final, py2.3.3 win98
> PYTHON caused an invalid page fault in
> module TK84.DLL at 0167:1022b74f.
> 
> Leo 4.1 final, py2.2 win98
> paste a 15k node copy into headline. no problem.
> 
> this is the first repeatable hard crash I've stumbled on
> and thought it best to report it privately.
> I can think of no advantage to allowing a headline 
> of this size anyway. think of the tooltip that would create!
> 
> there are latent bugs in the selectall and delete from 
> the edit menu related to headline as well on the todo list.
> reported many times. 
> covert destruction of the selected body text.
> use of virtual events, with out proper focus to headline.
> 
> without myself being able to supply a patch, I'll guess,
> the virtual event paste called can as well point 
> to a function that checks the size before pasting.
> or simply sets the headline directly with 
> g.app.gui.getTextFromClipboard()[:1024]
> 
> 
> e
#@-node:EKR.20040606104355:Report
#@-node:EKR.20040523192553:(Crash when pasting large text into headlines)
#@+node:ekr.20031218072017.674:Caps lock affects keyboard shortcuts on Windows
Using leo under Windows, the keyboard shortcuts seem to use the "Caps Lock" state in determining the shift state when executing a shortcut.   For example, if the caps-lock key is on, then Ctrl-X is interpreted as Shift-Ctrl-X and cuts a node rather than selected text, and Shift-Ctrl-X is interpreted as Ctrl-X and cuts text.
#@-node:ekr.20031218072017.674:Caps lock affects keyboard shortcuts on Windows
#@+node:ekr.20031218072017.675:Tree problems
1. The border of the tree control is gray, and it is overwritten with large headlines.  This may be a Tk or Tkinter bug.

2. Adding trailing whitespace to a line in body text does not set the file-dirty mark.  This can never cause a derived file to become "out-of-synch" because the read code does not compare body text.

Apparently there is no way to fix this glitch because of holes in Tk's event mechanism.  Specifically, tree.idle_body_key has no way to tell directly what keystroke caused it to be entered.
#@nonl
#@-node:ekr.20031218072017.675:Tree problems
#@+node:ekr.20031218072017.676:Control-T can't be overridden in canvas text.
#@-node:ekr.20031218072017.676:Control-T can't be overridden in canvas text.
#@+node:ekr.20031218072017.677:(Alt-ctrl = Alt)
@nocolor

Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1765069
By: dalcolmo

I use the bindings that come with Leo:

[keyboard shortcuts]
pastenode = Shift+Ctrl+V
gonextvisible = Alt+DnArrow
importtofile = Shift+Ctrl+F
writefilenodes = Shift+Ctrl+W
editheadline = Ctrl+H
markchangeditems = Alt+C
replace = Ctrl+=
goprevvisible = Alt+UpArrow
gotonextmarked = Alt+M
readoutlineonly = Shift+Ctrl+R
extractnames = Shift+Ctrl+N
gonext = Alt+Shift+DnArrow
findpanel = Ctrl+F
close = Ctrl+W
demote = Ctrl+}
tangle = Shift+Ctrl+T
extract = Shift+Ctrl+D
openpythonwindow = Alt+P
marksubheads = Alt+S
saveas = Shift+Ctrl+S
cut = Ctrl+X
preferences = Ctrl+Y
equalsizedpanes = Ctrl+E
cantundo = Ctrl+Z
open = Ctrl+O
promote = Ctrl+{
sortsiblings = Alt-A
unmarkall = Alt+U
mark = Ctrl+M
showinvisibles = Alt+V
exit = Ctrl-Q
insertnode = Ctrl+I
findprevious = F4
converttabs = Shift+Ctrl+J
save = Ctrl+S
tanglemarked = Shift+Ctrl+M
moveup = Ctrl+U
copynode = Shift+Ctrl+C
contractparent = Alt+0
selectall = Ctrl+A
setfont = Alt+Shift+T
aborteditheadline = Shift+Esc
goback = Alt+Shift+UpArrow
toggleactivepane = Ctrl+T
findnext = F3
tangleall = Shift+Ctrl+A
endeditheadline = Esc
deletenode = Shift+Ctrl+BkSp
cantredo = Shift+Ctrl+Z
new = Ctrl+N
contractall = Alt+1
moveleft = Ctrl+L
copy = Ctrl+C
paste = Ctrl+V
convertblanks = Shift+Ctrl+B
expandall = Alt+9
markchangedroots = Alt+R
cutnode = Shift+Ctrl+X
indent = Ctrl+]
gotonextchanged = Alt+D
expandnextlevel = Alt+=
setcolors = Alt+Shift+S
matchbrackets = Ctrl+K
movedown = Ctrl+D
clonenode = Ctrl+`
untangle = Shift+Ctrl+U
expandtolevel7 = Alt+7
expandtolevel6 = Alt+6
expandtolevel5 = Alt+5
expandtolevel4 = Alt+4
expandtolevel3 = Alt+3
expandtolevel2 = Alt+2
moveright = Ctrl+R
unindent = Ctrl+[
replacethenfind = Ctrl+-
extractsection = Shift+Ctrl+E
expandtolevel8 = Alt+8


However, I use a utility called AllChars (Free as in beer :-(  ) to be able
to type all kinds of chars on my US keyboard, and "Handything" to place the
windows on the screen (Win2000). Perhaps this makes a difference, although disabling
them did not seem to make it go away. Still, on pressing alt+ctrl+uparrow I
end up at the next upper node etc...

- Josef

#@-node:ekr.20031218072017.677:(Alt-ctrl = Alt)
#@+node:ekr.20031218072017.678:Report Tk bugs
Create a Tk demo for each bug.
#@nonl
#@+node:ekr.20031218072017.679:Tk code
@color
#@nonl
#@+node:ekr.20031218072017.680:@file c:/prog/test/leoSplitter.tcl
# This file creates tk test code for prototyping.
@language tcltk
@others

go
#@nonl
#@+node:ekr.20031218072017.681:go
proc go {} {
	# createLeo 1
	# createLeo 2
	# createFindPanel
	# createPrefsPanel
	# createColorPanel
	# toplevel .font
	# createFontPanel .font
	# createComparePanel
	# createWindowWithIcon
	# createWindowWithCursor
}
#@-node:ekr.20031218072017.681:go
#@+node:ekr.20031218072017.682:createLeo
proc createLeo { n } {

	toplevel .leo$n

	# Create two splitters
	createSplitter .leo$n.s 1
	createSplitter .leo$n.s.pane1.s 0 ;# contains tree and log
	
	text      .leo$n.s.pane2.body -bd 2 -yscrollcommand ".leo$n.s.pane2.scroll set" -setgrid 1
	scrollbar .leo$n.s.pane2.scroll -command ".leo$n.s.pane2.body yview"

	pack .leo$n.s.pane2.scroll -side right -fill y
	pack .leo$n.s.pane2.body -expand yes -fill both

	text .leo$n.s.pane1.s.pane1.tree -bd 2 -yscrollcommand ".leo$n.s.pane1.s.pane1.scroll set" -setgrid 1
	scrollbar .leo$n.s.pane1.s.pane1.scroll -command ".leo$n.s.pane1.s.pane1.tree yview"
	
	pack .leo$n.s.pane1.s.pane1.scroll -side right -fill y
	pack .leo$n.s.pane1.s.pane1.tree   -expand yes -fill both

	# -padx is needed to handle overlap of splitter bar
	text      .leo$n.s.pane1.s.pane2.log    -yscrollcommand ".leo$n.s.pane1.s.pane2.scroll set" -setgrid 1 -padx 4
	scrollbar .leo$n.s.pane1.s.pane2.scroll -command ".leo$n.s.pane1.s.pane2.log yview"
	pack .leo$n.s.pane1.s.pane2.scroll -side right -fill y
	pack .leo$n.s.pane1.s.pane2.log -expand yes -fill both
	
	createMenus $n
}
#@nonl
#@-node:ekr.20031218072017.682:createLeo
#@+node:ekr.20031218072017.683:createMenus
proc createMenus { n } {

	# Create the menu bar
	menu .leo$n.menu -tearoff 0

    set m .leo$n.menu.file
    menu $m -tearoff 0

    .leo$n.menu add cascade -label "File" -menu $m -underline 0
	# to do: fill in the commands...
    $m add command -label "Open..." -command {""}
    $m add command -label "New" -command {""}
    $m add command -label "Save" -command {""}
    $m add command -label "Save As..." -command {""}
    $m add separator
    $m add command -label "Print Setup..." -command {""}
    $m add command -label "Print..." -command {""}
    $m add separator
    $m add command -label "Quit" -command "destroy ."
	
	.leo$n configure -menu .leo$n.menu
}
#@nonl
#@-node:ekr.20031218072017.683:createMenus
#@+node:ekr.20031218072017.684:createSplitter
#@+node:ekr.20031218072017.685:createSplitter
# Create a splitter window into which the caller packs widgets.

proc createSplitter {w verticalFlag} {
	# verticalFlag, height, width could be params
	frame $w -width 4i -height 3i
	frame $w.pane1
	frame $w.pane2
	if { $verticalFlag } {
		# Panes arranged vertically; horizontal splitter bar
		frame $w.bar -height 7
		place $w.pane1 -relx 0.5 -rely   0 -anchor n -relwidth 1.0 -relheight 0.5
		place $w.pane2 -relx 0.5 -rely 1.0 -anchor s -relwidth 1.0 -relheight 0.5
		place $w.bar   -relx 0.5 -rely 0.5 -anchor c -relwidth 1.0
		bind $w.bar <ButtonPress-1>		"onGrabSplitterBar $w 1"
		bind $w.bar <B1-Motion>			"onDragSplitterBar $w 1 %y"
		bind $w.bar <ButtonRelease-1>	"onDropSplitterBar $w 1 %y"
	} else {
		# Panes arranged horizontally; vertical splitter bar
		frame $w.bar -width 7
		place $w.pane1 -rely 0.5 -relx   0 -anchor w -relheight 1.0 -relwidth 0.5
		place $w.pane2 -rely 0.5 -relx 1.0 -anchor e -relheight 1.0 -relwidth 0.5
		place $w.bar   -rely 0.5 -relx 0.5 -anchor c -relheight 1.0
		bind $w.bar <ButtonPress-1>		"onGrabSplitterBar $w 0"
		bind $w.bar <B1-Motion>			"onDragSplitterBar $w 0 %x"
		bind $w.bar <ButtonRelease-1>	"onDropSplitterBar $w 0 %x"
	}
	# Borderwidth required
	$w.bar configure -borderwidth 2 -relief raised -background LightSteelBlue2 
	pack $w -expand yes -fill both
}
#@-node:ekr.20031218072017.685:createSplitter
#@+node:ekr.20031218072017.686:onGrabSplitterBar
proc onGrabSplitterBar {w verticalFlag} {

	# We should change the cursor here.
	
	# Changing the relief is pointless.
		# $w.bar configure -relief sunken
	
	# Changing colors is really bad
		# $w.bar configure -relief sunken -background LightSteelBlue4
		# $w.bar configure -background LightSteelBlue4
}
#@nonl
#@-node:ekr.20031218072017.686:onGrabSplitterBar
#@+node:ekr.20031218072017.687:onDropSplitterBar
proc onDropSplitterBar {w verticalFlag xy} {

	# We should reset the cursor here.
	$w.bar configure -relief raised -background LightSteelBlue2
}
#@-node:ekr.20031218072017.687:onDropSplitterBar
#@+node:ekr.20031218072017.688:onDragSplitterBar
# xy is the coordinate of the cursor relative to the bar, not the main window.

proc onDragSplitterBar {w verticalFlag xy} {

	if { $verticalFlag } {
		# Panes arranged vertically; horizontal splitter bar
		set wRoot	[winfo rooty $w]
		set barRoot [winfo rooty $w.bar]
		set wMax	[winfo height $w]
	} else {
		# Panes arranged horizontally; vertical splitter bar
		set wRoot	[winfo rootx $w]
		set barRoot [winfo rootx $w.bar]
		set wMax	[winfo width $w]
	}
	set offset [expr double($barRoot) + $xy - $wRoot ]
	# Adjust the pixels, not the frac.
	if { $offset < 3 } { set offset 3 }
	if { $offset > [expr $wMax - 2] } { set offset [expr $wMax - 2] }
	set frac [ expr double($offset) / $wMax ]
	# This redraws the splitter as the drag is occuring.
	# We could also redraw in onDropSplitterBar for non-dynamic updates.
	divideSplitter $w $verticalFlag $frac
	return $frac ;# No longer used
}
#@nonl
#@-node:ekr.20031218072017.688:onDragSplitterBar
#@+node:ekr.20031218072017.689:divideSplitter
proc divideSplitter {w verticalFlag frac} {

	if { $verticalFlag } {
		# Panes arranged vertically; horizontal splitter bar
		place $w.bar -rely $frac
		place $w.pane1 -relheight $frac
		place $w.pane2 -relheight [expr 1 - $frac]
	} else {
		# Panes arranged horizontally; vertical splitter bar
		place $w.bar -relx $frac
		place $w.pane1 -relwidth $frac
		place $w.pane2 -relwidth [expr 1 - $frac]
	}
}
#@nonl
#@-node:ekr.20031218072017.689:divideSplitter
#@-node:ekr.20031218072017.684:createSplitter
#@+node:ekr.20031218072017.690:createFindPanel
proc createFindPanel {} {

	toplevel .find
	<< Create the Find and Change panes >>
	<< Create two columns of checkboxes >>
	<< Create two rows of buttons >>
}
#@+node:ekr.20031218072017.691:<< Create the Find and Change panes >>
frame .find.fc -bd 1m
pack  .find.fc -anchor n -expand yes -fill x

frame .find.fc.fpane -bd 1 -height 0.95i -width 1.5i
frame .find.fc.cpane -bd 1 -height 0.95i -width 1.5i

pack  .find.fc.fpane -anchor n -expand yes -fill x
pack  .find.fc.cpane -anchor s -expand yes -fill x

# Create the labels and text fields.
label .find.fc.fpane.lab -width 8 -text "Find:"
label .find.fc.cpane.lab -width 8 -text "Change:"
text  .find.fc.fpane.t -height 2 -width 20 ; # 2 lines, 20 characters
text  .find.fc.cpane.t -height 2 -width 20

pack  .find.fc.fpane.lab -side left 
pack  .find.fc.cpane.lab -side left
pack  .find.fc.cpane.t -side right -expand yes -fill both
pack  .find.fc.fpane.t -side right -expand yes -fill both
#@-node:ekr.20031218072017.691:<< Create the Find and Change panes >>
#@+node:ekr.20031218072017.692:<< Create two columns of checkboxes >>
frame .find.boxes -bd 1m
pack  .find.boxes -anchor n -expand yes -fill x

frame .find.boxes.lt -bd 1
frame .find.boxes.rt -bd 1
pack  .find.boxes.lt -side left  -padx 5m
pack  .find.boxes.rt -side right -ipadx 2m

# Create the left column
checkbutton .find.boxes.lt.batch -anchor w -text "Batch"
checkbutton .find.boxes.lt.wrap  -anchor w -text "Wrap around"
checkbutton .find.boxes.lt.word  -anchor w -text "Whole word"
checkbutton .find.boxes.lt.case  -anchor w -text "Ignore case"
checkbutton .find.boxes.lt.pat   -anchor w -text "Pattern match"
pack .find.boxes.lt.batch -fill x
pack .find.boxes.lt.wrap  -fill x
pack .find.boxes.lt.word  -fill x
pack .find.boxes.lt.case  -fill x
pack .find.boxes.lt.pat    -fill x

# Create the right column
checkbutton .find.boxes.rt.headline -anchor w -text "Search Headline Text"
checkbutton .find.boxes.rt.body     -anchor w -text "Search Body Text"
checkbutton .find.boxes.rt.outline  -anchor w -text "Suboutline Only"
checkbutton .find.boxes.rt.mark     -anchor w -text "Mark Changes"
checkbutton .find.boxes.rt.markch   -anchor w -text "Mark Finds"
pack .find.boxes.rt.headline -side top -fill x
pack .find.boxes.rt.body     -side top -fill x
pack .find.boxes.rt.outline  -side top -fill x
pack .find.boxes.rt.mark     -side top -fill x
pack .find.boxes.rt.markch   -side top -fill x
#@-node:ekr.20031218072017.692:<< Create two columns of checkboxes >>
#@+node:ekr.20031218072017.693:<< Create two rows of buttons >>
# Create the button panes
frame .find.buttons -bd 1
pack  .find.buttons -anchor n -expand yes -fill x

frame .find.buttons2 -bd 1
pack  .find.buttons2 -anchor n -expand yes -fill x

# Create the first row of buttons
button      .find.buttons.find    -width 8 -text "Find"
checkbutton .find.buttons.reverse -width 8 -text "Reverse"
button      .find.buttons.findAll -width 8 -text "Find All"

pack .find.buttons.find    -pady 1m -padx 1m -side left
pack .find.buttons.reverse -pady 1m          -side left -expand 1
pack .find.buttons.findAll -pady 1m -padx 1m -side right

# Create the second row of buttons
button .find.buttons2.change     -width 8 -text "Change"
button .find.buttons2.changeFind           -text "Change, Then Find"
button .find.buttons2.changeAll  -width 8 -text "Change All"

pack .find.buttons2.change       -pady 1m -padx 1m -side left
pack .find.buttons2.changeFind   -pady 1m          -side left -expand 1
pack .find.buttons2.changeAll    -pady 1m -padx 1m -side right
#@-node:ekr.20031218072017.693:<< Create two rows of buttons >>
#@-node:ekr.20031218072017.690:createFindPanel
#@+node:ekr.20031218072017.694:createPrefsPanel
proc createPrefsPanel {} {

	toplevel .prefs
	<< Create the Global Options frame >>
	<< Create the Tangle Options frame >>
	<< Create the Target Language frame >>
}
#@nonl
#@+node:ekr.20031218072017.695:<< Create the Global Options frame >>
frame .prefs.glob -bd 1m -relief groove
pack  .prefs.glob -anchor n -pady 1m -ipadx 1m -expand 1 -fill x

label .prefs.glob.title -text "Global Options..."
pack  .prefs.glob.title -pady 1m

# Page width & page width
frame .prefs.glob.f
pack  .prefs.glob.f -anchor w -pady 1m -expand 1 -fill x

label .prefs.glob.f.lab -anchor w -padx 1m -text "Page width:"
text  .prefs.glob.f.txt -height 1 -width 4 ;# lines, characters
pack  .prefs.glob.f.lab .prefs.glob.f.txt -side left

label .prefs.glob.f.lab2 -padx 1m -text "Tab width:"
text  .prefs.glob.f.txt2 -height 1 -width 4 ;# lines, characters
pack  .prefs.glob.f.lab2 .prefs.glob.f.txt2 -side left

# Checkbuttons
checkbutton .prefs.glob.done -anchor w -text "Execute Leo_done.bat after Tangle"
checkbutton .prefs.glob.un   -anchor w -text "Execute Leo_un.bat after Tangle"
pack .prefs.glob.done .prefs.glob.un -fill x
#@-node:ekr.20031218072017.695:<< Create the Global Options frame >>
#@+node:ekr.20031218072017.696:<< Create the Tangle Options frame >>
# Frame and title
frame .prefs.tangle -bd 1m -relief groove
pack  .prefs.tangle -anchor n -ipadx 1m -expand 1 -fill x

label .prefs.tangle.title -text "Default Options..."
pack  .prefs.tangle.title -pady 1m

# Label and text
label .prefs.tangle.lab -anchor w -text "Default tangle directory"
text  .prefs.tangle.txt -height 1 -width 30 ;# width significant

pack  .prefs.tangle.lab           -padx 1m -pady 1m -fill x
pack  .prefs.tangle.txt -anchor w -padx 1m -pady 1m -fill x

# Checkbuttons
checkbutton .prefs.tangle.header -anchor w -text "Tangle outputs header line"
checkbutton .prefs.tangle.doc    -anchor w -text "Tangle outputs document chunks"
pack .prefs.tangle.header .prefs.tangle.doc -fill x
#@-node:ekr.20031218072017.696:<< Create the Tangle Options frame >>
#@+node:ekr.20031218072017.697:<< Create the Target Language frame >>
# Frame and title
frame .prefs.target -bd 1m -relief groove
pack  .prefs.target -anchor n -pady 1m -ipadx 1m -expand 1 -fill x

label .prefs.target.title -text "Default Target Language..."
pack  .prefs.target.title -pady 1m

# Frames for two columns of radio buttons
frame .prefs.target.lt 
frame .prefs.target.rt
pack  .prefs.target.lt -side left
pack  .prefs.target.rt -side right

## To do: make radio buttons functional.

# Left column of radio buttons
radiobutton .prefs.target.lt.c      -anchor w -text "C/C++"
radiobutton .prefs.target.lt.cweb   -anchor w -text "CWEB"
radiobutton .prefs.target.lt.html   -anchor w -text "HTML"
radiobutton .prefs.target.lt.java   -anchor w -text "Java"
radiobutton .prefs.target.lt.pascal -anchor w -text "Pascal"

pack .prefs.target.lt.c      -fill x
pack .prefs.target.lt.cweb   -fill x
pack .prefs.target.lt.html   -fill x
pack .prefs.target.lt.java   -fill x
pack .prefs.target.lt.pascal -fill x

# Right column of radio buttons
radiobutton .prefs.target.rt.perl   -width 12 -anchor w -text "Perl"
radiobutton .prefs.target.rt.pod              -anchor w -text "Perl + POD"
radiobutton .prefs.target.rt.plain            -anchor w -text "Plain Text"
radiobutton .prefs.target.rt.python           -anchor w -text "Python"

pack .prefs.target.rt.perl   -fill x
pack .prefs.target.rt.pod    -fill x
pack .prefs.target.rt.plain  -fill x
pack .prefs.target.rt.python -fill x
#@-node:ekr.20031218072017.697:<< Create the Target Language frame >>
#@-node:ekr.20031218072017.694:createPrefsPanel
#@+node:ekr.20031218072017.698:createColorPanel
proc showColorPicker {} {
	tk_chooseColor
}

proc createColorPanel {} {

	toplevel .color
	wm title .color "Syntax Coloring"

	frame .color.f -bd 2 -relief groove
	pack  .color.f -anchor n -pady 2 -ipady 1 -ipadx 0 -expand 1 -fill x
	
	# label .color.f.lab -text "Choose syntax colors..."
	# pack  .color.f.lab -side top -fill x
	
	frame .color.f.docF -bd 2
	pack  .color.f.docF
	label .color.f.docF.lab -text "Doc parts:" -width 14 -anchor e
	button .color.f.docF.show -text "" -bg "red" -width 4
	button .color.f.docF.set -text "Set..." -command showColorPicker
	pack  .color.f.docF.lab .color.f.docF.show .color.f.docF.set -side left -padx 3
	
	frame .color.f.cF -bd 2
	pack  .color.f.cF
	label .color.f.cF.lab -text "Comments:" -width 14 -anchor e
	button .color.f.cF.show -text "" -bg "red" -width 4
	button .color.f.cF.set -text "Set..." -command showColorPicker
	pack  .color.f.cF.lab .color.f.cF.show .color.f.cF.set -side left -padx 3

	frame .color.f.sF -bd 2
	pack  .color.f.sF
	label .color.f.sF.lab  -text "Strings:" -width 14 -anchor e
	button .color.f.sF.show -bg "green" -width 4
	button .color.f.sF.set -text "Set..." -command showColorPicker
	pack  .color.f.sF.lab .color.f.sF.show .color.f.sF.set -side left -padx 3
	
	frame .color.f.kF -bd 2
	pack  .color.f.kF
	label .color.f.kF.lab  -text "Keywords:" -width 14 -anchor e
	button .color.f.kF.show -bg "blue" -width 4
	button .color.f.kF.set -text "Set..." -command showColorPicker
	pack  .color.f.kF.lab .color.f.kF.show .color.f.kF.set -side left -padx 3
	
	frame .color.f.dF -bd 2
	pack  .color.f.dF
	label .color.f.dF.lab  -text "Directives:" -width 14 -anchor e
	button .color.f.dF.show -bg "blue" -width 4
	button .color.f.dF.set -text "Set..." -command showColorPicker
	pack  .color.f.dF.lab .color.f.dF.show .color.f.dF.set -side left -padx 3

	frame .color.f.snF -bd 2
	pack  .color.f.snF
	label .color.f.snF.lab  -text "Section names:" -width 14 -anchor e
	button .color.f.snF.show -bg "red" -width 4
	button .color.f.snF.set -text "Set..." -command showColorPicker
	pack  .color.f.snF.lab .color.f.snF.show .color.f.snF.set -side left -padx 3

	frame .color.f.unF -bd 2
	pack  .color.f.unF
	label .color.f.unF.lab  -text "Undefined names:" -width 14 -anchor e
	button .color.f.unF.show -bg "orange" -width 4 
	button .color.f.unF.set -text "Set..." -command showColorPicker
	pack  .color.f.unF.lab .color.f.unF.show .color.f.unF.set -side left -padx 3
}
#@nonl
#@-node:ekr.20031218072017.698:createColorPanel
#@+node:ekr.20031218072017.699:createFontPanel
proc createFontPanel {w} {

	frame $w.top
	frame $w.size -bd 2 -relief "ridge"
	frame $w.sample -bd 2 -relief "ridge"
	pack $w.top $w.size $w.sample -pady 2 -ipadx 5 -anchor w -fill both
	
	<< create family pane >>
	<< create style pane >>
	<< create buttons >>
	<< create size pane >>
	<< create sample pane >>
}
#@nonl
#@+node:ekr.20031218072017.700:<< create family pane >>
frame $w.top.family -bd 2 -relief "ridge"
label $w.top.family.lab -text "family"
listbox $w.top.family.box
pack $w.top.family.lab $w.top.family.box -anchor w

frame $w.top.style -bd 2 -relief "ridge"
frame $w.top.right -bd 2 -relief "flat"
pack $w.top.family $w.top.style $w.top.right -side left -fill y -padx 2 -pady 2 ; # -ipadx 5 -ipady 5
#@nonl
#@-node:ekr.20031218072017.700:<< create family pane >>
#@+node:ekr.20031218072017.701:<< create style pane >>
label $w.top.style.lab -text "Style"
pack  $w.top.style.lab -anchor w
foreach {name text} {
	b Bold
	i Italic
	u Underline
	o Overstrike
} {
	checkbutton $w.top.style.$name -text $text
	pack $w.top.style.$name -side top -anchor w -expand 1
}
#@nonl
#@-node:ekr.20031218072017.701:<< create style pane >>
#@+node:ekr.20031218072017.702:<< create buttons >>
foreach {name text} {
	ok OK
	cancel Cancel
	apply Apply
} {
	button $w.top.right.$name -text $text -width 6
	pack   $w.top.right.$name -side top -anchor w -pady 2m ; # -expand 1
}
#@nonl
#@-node:ekr.20031218072017.702:<< create buttons >>
#@+node:ekr.20031218072017.703:<< create size pane >>
frame $w.size.left
label $w.size.lab -text "Size"

text  $w.size.text -height 1 -width 4
pack  $w.size.lab -anchor w
pack  $w.size.left -side left
pack  $w.size.text -side left -fill x -expand 1

frame $w.size.left.row1
frame $w.size.left.row2
pack $w.size.left.row1 $w.size.left.row2 -side top

foreach {size} {
	8 12 18
} {
	radiobutton $w.size.left.row1.radio$size -text $size -variable size
	pack $w.size.left.row1.radio$size -side left
}
foreach {size} {
	10 14 24
} {
	radiobutton $w.size.left.row2.radio$size -text $size -variable size
	pack $w.size.left.row2.radio$size -side left
}
#@nonl
#@-node:ekr.20031218072017.703:<< create size pane >>
#@+node:ekr.20031218072017.704:<< create sample pane >>
label $w.sample.lab -text "Sample"
pack  $w.sample.lab -side top -anchor w

label $w.sample.text -text "ABCabcXYZxyz123(may be changed)"

# entry $w.sample.text -background [$w.sample cget -background]
# $w.sample.text insert 0 "ABCabcXYZxyz123(may be changed)"

pack $w.sample.text -side top -anchor c -expand 1 -fill none

# $w.sample.text insert 0 "ABCabcXYZxyz123(may be changed)"
# $w.sample.text configure -state disabled
#@nonl
#@-node:ekr.20031218072017.704:<< create sample pane >>
#@-node:ekr.20031218072017.699:createFontPanel
#@+node:ekr.20031218072017.705:dkffontCreateFontPanel
@ Build the font panel (except for the apply button, which is handled by the 'configure_apply procedure...
@c

proc origCreateFontPanel {w} {
	# Framed regions.  Do this with grid and labels, as that seems
	# to be the most effective technique in practise!
	frame $w.border1 -class DKFChooseFontFrame
	frame $w.border2 -class DKFChooseFontFrame
	frame $w.border3 -class DKFChooseFontFrame
	frame $w.border4 -class DKFChooseFontFrame
	set gap [get_gap $w]
	grid $w.border1 -row 0 -column 0 -rowspan 4 -columnspan 4 \
		-padx $gap -pady $gap -sticky nsew
	grid $w.border2 -row 0 -column 4 -rowspan 4 -columnspan 3 \
		-padx $gap -pady $gap -sticky nsew
	grid $w.border3 -row 4 -column 0 -rowspan 3 -columnspan 9 \
		-padx $gap -pady $gap -sticky nsew
	grid $w.border4 -row 7 -column 0 -rowspan 3 -columnspan 9 \
		-padx $gap -pady $gap -sticky nsew
	incr gap $gap
	foreach col {0 3 4 6 8} {
		grid columnconfigure $w $col -minsize $gap
	}
	foreach row {0 3 4 6 7 9} {
		grid rowconfigure    $w $row -minsize $gap
	}
	grid columnconfigure $w 1 -weight 1
	grid rowconfigure    $w 1 -weight 1
	grid rowconfigure    $w 8 -weight 1

	# Labels for the framed boxes & focus accelerators for their contents
	foreach {subname row col focusWin} {
		Family 0 1 .family     
		Style  0 5 .style.sBold
		Size   4 1 .size.b8    
		Sample 7 1 .sample.text
	} {
		set l [label $w.lbl$subname]
		grid $l -row $row -column $col -sticky w
		## set accel [get_accel $l]
		## if {[string length $accel]} {
			## bind $w <$accel> [list focus $w$focusWin]
		##}
	}

	# Font families
	frame $w.familyBox
	listbox $w.family -exportsel 0 -selectmode browse \
		-xscrollcommand [list $w.familyX set] \
		-yscrollcommand [list $w.familyY set]
	scrollbar $w.familyX -command [list $w.family xview]
	scrollbar $w.familyY -command [list $w.family yview]
	##foreach family [list_families] {
	##	$w.family insert end ['map 'capitalise $family]
	##}
	grid $w.familyBox -row 1 -column 1 -rowspan 1 -columnspan 2 -sticky nsew
	grid columnconfigure $w.familyBox 0 -weight 1
	grid rowconfigure    $w.familyBox 0 -weight 1
	grid $w.family  $w.familyY -sticky nsew -in $w.familyBox
	grid $w.familyX            -sticky nsew -in $w.familyBox
	## bind $w.family <1> [namespace code {'change_family %W [%W nearest %y]}]
	## bindtags $w.family [concat [bindtags $w.family] key$w.family]
	## bind key$w.family <Key> [namespace code {'change_family %W active %A}]

	# Font styles.
	frame $w.style
	grid $w.style -row 1 -column 5 -sticky news
	grid columnconfigure $w.style 0 -weight 1
	foreach {fontstyle lcstyle row next prev} {
		Bold      bold       0 Italic    {}
		Italic    italic     1 Underline Bold
		Underline underline  2 Strikeout Italic
		Strikeout overstrike 3 {}        Underline
	} {
		set b $w.style.s$fontstyle
		checkbutton $b -variable [namespace current]::Style($lcstyle) \
			-command [namespace code 'set_font]
		grid $b -sticky nsew -row $row
		grid rowconfigure $w.style $row -weight 1
		if {[string length $next]} {
			## bind $b <Down> [list focus $w.style.s$next]
		}
		if {[string length $prev]} {
			## bind $b <Up> [list focus $w.style.s$prev]
		}
		## bind $b <Tab>       "[list focus $w.size.b8];break"
		## bind $b <Shift-Tab> "[list focus $w.family ];break"
		## set accel ['get_accel $b]
		## if {[string length $accel]} {
			## bind $w <$accel> "focus $b; $b invoke"
		## }
		## bind $b <Return> "$b invoke; break"
	}
	
	# Size adjustment.  Common sizes with radio buttons, and an
	# entry for everything else.
	frame $w.size
	grid $w.size -row 5 -column 1 -rowspan 1 -columnspan 7 -sticky nsew
	foreach {size row col u d l r} {
		8  0 0  {} 10 {} 12
		10 1 0   8 {} {} 14
		12 0 1  {} 14  8 18
		14 1 1  12 {} 10 24
		18 0 2  {} 24 12 {}
		24 1 2  18 {} 14 {}
	} {
		set b $w.size.b$size
		radiobutton $b -variable [namespace current]::Size -value $size \
			-command [namespace code 'set_font]
		grid $b -row $row -column $col -sticky ew
		#grid columnconfigure $w.size $col -weight 1
		## bif {[string length $u]} {bind $b <Up>    [list focus $w.size.b$u]}
		## bif {[string length $d]} {bind $b <Down>  [list focus $w.size.b$d]}
		## bif {[string length $l]} {bind $b <Left>  [list focus $w.size.b$l]}
		## bif {[string length $r]} {bind $b <Right> [list focus $w.size.b$r]}
		## bind $b <Tab>       "[list focus $w.size.entry ];break"
		## bind $b <Shift-Tab> "[list focus $w.style.sBold];break"
		## set accel ['get_accel $b]
		## if {[string length $accel]} {
			## bind $w <$accel> "focus $b; $b invoke"
		## }
		## bind $b <Return> "$b invoke; break"
	}
	entry $w.size.entry -textvariable [namespace current]::Size
	grid $w.size.entry -row 0 -column 3 -rowspan 2 -sticky ew
	grid columnconfigure $w.size 3 -weight 1
	## bind $w.size.entry <Return> [namespace code {'set_font;break}]
	
	# Sample text.  Note that this is editable
	frame $w.sample
	grid $w.sample -row 8 -column 1 -columnspan 7 -sticky nsew
	grid propagate $w.sample 0
	entry $w.sample.text -background [$w.sample cget -background]
	$w.sample.text insert 0 [option get $w.sample.text text Text]
	grid $w.sample.text
	
	# OK, Cancel and (partially) Apply.  See also 'configure_apply
	frame $w.butnframe
	grid $w.butnframe -row 0 -column 7 -rowspan 4 -columnspan 2 -sticky nsew -pady $gap
	foreach {but code} {
		ok  0
		can 1
	} {
		button $w.butnframe.$but -command [namespace code [list set Done $code]]
		pack   $w.butnframe.$but -side top -fill x -padx [expr {$gap/2}] -pady [expr {$gap/2}]
	}
	button $w.butnframe.apl
	## bind $w.butnframe.ok <Down> [list focus $w.butnframe.can]
	## bind $w.butnframe.can <Up> [list focus $w.butnframe.ok]
}
#@nonl
#@+node:ekr.20031218072017.706:get_accel
# Convenience proc to get the accelerator for a particular window
# if the user has given one.  Makes it simpler to get this right
# everywhere it is needed...

proc get_accel {w} {
	option get $w accelerator Accelerator
}
#@nonl
#@-node:ekr.20031218072017.706:get_accel
#@+node:ekr.20031218072017.707:get_gap
# Get the gap spacing for the frameboxes.  Use a user-specified
# default if there is one (that is a valid integer) and fall back
# to measuring/guessing otherwise.
proc get_gap {w} {
	set gap [option get $w lineGap LineGap]
	if {[catch {incr gap 0}]} {
		# Some cunning font measuring!
		label $w._testing
		set font [$w._testing cget -font]
		set gap [expr {[font metrics $font -linespace]/2+1}]
		destroy $w._testing
	}
	return $gap
}
#@nonl
#@-node:ekr.20031218072017.707:get_gap
#@+node:ekr.20031218072017.708:list_families
# Get a sorted lower-case list of all the font families defined on
# the system.  A canonicalisation of [font families]
proc list_families {} {
	lsort [string tolower [font families]]
}
#@-node:ekr.20031218072017.708:list_families
#@-node:ekr.20031218072017.705:dkffontCreateFontPanel
#@+node:ekr.20031218072017.709:createComparePanel
# Path 1: text box
# Path 2: text box

# check: File extension: text box

# Radio buttons:
# 	* print all lines
# 	* print mismatches
# 	* print matches
	
# check: stop after first mismatch
# check: send result to file: text box
# check: generate diffs
# check: ignore whitespace
# check: ignore blank lines

proc createComparePanel {} {

	toplevel .comparePanel
}
#@nonl
#@-node:ekr.20031218072017.709:createComparePanel
#@+node:ekr.20031218072017.710:createWindowWithCursor
# apparantly this is a Tk bug on XP.
proc createWindowWithCursor {} {

	toplevel .panel
	text     .panel.text
	.panel.text configure -cursor {gumby red green}
	pack .panel.text
}
#@nonl
#@-node:ekr.20031218072017.710:createWindowWithCursor
#@+node:ekr.20031218072017.711:createWindowWithIcon
proc createWindowWithIcon {} {

	global tk_library
	global tcl_patchLevel

	toplevel .panel
	
	# ***** Bitmaps apparently must be only 2 colors.  Photos are everything else!

	# wm iconbitmap .panel [image create photo -file c:/prog/leoCVS/leo/Icons/box00.GIF]
	#wm iconbitmap .panel [image create bitmap -file c:/prog/leoCVS/leo/Icons/box00.bmp]
	
	# no errors, no icon
	#wm iconbitmap .panel [image create bitmap @[file join c:/ Tcl lib tk8.3 demos images face.bmp]]
	# wm iconbitmap .panel @[file join c:/ Tcl lib tk8.3 demos images face.bmp]
	
	wm iconbitmap .panel info
	
	# Bitmap image1 not defined
	#wm iconbitmap .panel [image create photo -file [file join c:/ prog leoCVS leo Icons Leoapp.GIF]]
	
	# works
	#label .panel.bitmap -borderwidth 2 -relief sunken -bitmap @[file join $tk_library demos images face.bmp]
	#label .panel.lab -text $tk_library
	# label .panel.lab -borderwidth 2 -relief sunken -bitmap @[file join c:/ Tcl lib tk8.3 demos images face.bmp]
	# label .panel.lab -borderwidth 2 -relief sunken -bitmap @[file join c:/ prog leoCVS leo Icons face.bmp]
	
	# works!! (Only GIF allowed?)
	label .panel.lab -borderwidth 2 -relief sunken -image \
		[image create photo -file [file join c:/ prog leoCVS leo Icons Leoapp.GIF]]
	pack  .panel.lab

	label .panel.lab2 -text $tcl_patchLevel
	pack  .panel.lab2
}
#@nonl
#@-node:ekr.20031218072017.711:createWindowWithIcon
#@+node:ekr.20031218072017.712:myFrame
# w is the frame to be created.
# This configures w.f to be the content.
# Typically, the user will pack more content into w.f.

proc myFrame {w args} {

	# Args must come in pairs.
    if {([llength $args] % 2) != 0} {
        error {wrong # args: should be "myFrame pathName ?options?"}
    }
	# Initialize the local vars.
    set allopts {} # options that apply everywhere.
    set fopts {} # border options.
    set lopts {} # label options (only for labels that are Tk label widgets)
    set labelanchor nw # The usual default.
    set padx 0
    set pady 0
    set bd 2
    set relief groove
    set labelwindow "" # Can be any Tk widget!
    set text "" # The user usually just sets this.
	# Set vars based on args.
    foreach {opt val} $args {
        switch -- $opt {
            -bd - -borderwidth {
                set bd $val
            }
            -relief {
                set relief $val
            }
            -text {
                lappend lopts $opt $val
                set text $val
            }
            -font - -fg - -foreground {
                lappend lopts $opt $val
            }
            -labelanchor {
                set labelanchor $val
            }
            -labelwindow {
                set labelwindow $val
            }
            -padx {
                set padx $val
            }
            -pady {
                set pady $val
            }
            -bg - -background - -cursor {
                lappend allopts $opt $val
            }
            default {
                error "Unknown or unsupported option: $opt"
            }
        }
    }
	# relief and border options are frame options.
    lappend fopts -relief $relief -bd $bd 
	# Create the frame and its border, w.bd.
    eval frame $w $allopts
    eval frame $w.bd $fopts $allopts
	# Create lw depending on args.
    if {$labelwindow != ""} {
		# Used the window the user passed in.
        set lw $labelwindow
        raise $labelwindow $w
    } elseif {$text != ""} {
        set lw $w.l
		# This is typical.
        eval label $lw $lopts $allopts -highlightthickness 0 -bd 0
    } else {
        set lw ""
    }
	# Create the frame's grid.
    eval frame $w.f $allopts
	# Configure w as a grid with 5 rows and columns:
	# 1 border, 2 pad, 3 w.f, 4 pad, 5 border.
    grid columnconfigure $w {2 4} -minsize $padx
    grid rowconfigure    $w {2 4} -minsize $pady
    grid columnconfigure $w 3     -weight 1
    grid rowconfigure    $w 3     -weight 1
    grid columnconfigure $w {1 5} -minsize $bd
    grid rowconfigure    $w {1 5} -minsize $bd
	# Create the border, w.bd.
    grid $w.bd -row 1 -col 1 -rowspan 5 -columnspan 5 -sticky news
	# Create the content, w.f in the center.
    grid $w.f -row 3 -col 3 -sticky news
	# Optional: configure lw, the label window in the frame.
    if {$lw != ""} {
		# n*, s*, w* and e* are patterns to -glob.
        switch -glob $labelanchor {
            n* {
                grid $lw -in $w -row 0 -col 2 -rowspan 2 -columnspan 3 -padx 4
            }
            s* {
                grid $lw -in $w -row 5 -col 2 -rowspan 2 -columnspan 3 -padx 4
            }
            w* {
                grid $lw -in $w -row 2 -col 0 -rowspan 3 -columnspan 2 -pady 4
            }
            e* {
                grid $lw -in $w -row 2 -col 5 -rowspan 3 -columnspan 2 -pady 4
            }
        }
		# set the sticky param to the first character of the labelanchor, i.e., n,s,e or w.
        grid $lw -sticky [string index $labelanchor 1]
    }
}
#@nonl
#@-node:ekr.20031218072017.712:myFrame
#@+node:ekr.20031218072017.713:tk labeled frames
proc makeLeoFrame {} {

	# Create the outer labeld frame (doesn't actually have a label).
	myFrame .f -relief ridge -padx 5 -pady 5
	pack .f -fill both -expand 1 -padx 5 -pady 5
	
	# Initialize the grid positioin.
	set row 0
	set col 0
	# lp is the labelanchor value: 1 or two characters, the first character indicates side.
	foreach lp {nw n ne en e es se s sw ws w wn} {
		set w .f.f.f$lp
		# Create a myFrame: everything after %w are args.
		myFrame $w -text "Hej" -padx 2 -pady 2 -labelanchor $lp
		# Place the frame in the grid.
		grid $w -row $row -col $col -sticky news -padx 5 -pady 5
		# Create two buttons and pack them in the labeled frame.
		button $w.f.b1 -text Hoppsan
		button $w.f.b2 -text Quit -command exit
		pack $w.f.b1 $w.f.b2 -side top -fill x -padx 2 -pady 2
		# Bump the grid position.
		incr col
		if {$col == 3} {
			incr row
			set col 0
		}
	}
	
	# Create the last row.
	foreach wl {l1 l2 l3} {
		# Create the "special" label w.
		switch $wl {
			l1 {
				label .$wl -text Hej -bd 2 -relief groove
			} 
			l2 {
				checkbutton .$wl -text Hej
			} 
			l3 {
				radiobutton .$wl -text Hej
			}
		} 
		set w .f.f.f$wl
	
		# Create the labled frame using the -labelwindow option.
		myFrame $w -labelwindow .$wl -padx 2 -pady 2 -labelanchor nw
		# Place the labeled frame in the grid
		grid $w -row $row -col $col -sticky news -padx 5 -pady 5
		# Create two buttons and pack them in the labeled frame.
		button $w.f.b1 -text Hoppsan
		button $w.f.b2 -text Quit -command exit
		pack $w.f.b1 $w.f.b2 -side top -fill x -padx 2 -pady 2
		# Bump the grid position.
		incr col
		if {$col == 3} {
			incr row
			set col 0
		}
	}
}
#@nonl
#@-node:ekr.20031218072017.713:tk labeled frames
#@-node:ekr.20031218072017.680:@file c:/prog/test/leoSplitter.tcl
#@+node:ekr.20031218072017.714:tkBugs.tcl
#@+node:ekr.20031218072017.715:@file c:/prog/test/tkBugs.tcl
# This file creates tk test code for prototyping.
@language tcltk
@others

bindBug
#@+node:ekr.20031218072017.716:canvasBug
proc canvasBug {} {
	toplevel .test -bg "blue"
	canvas .test.c -bd 20 -bg "white" -relief "raised"
	pack .test.c -expand 1 -fill "both"
	text .test.c.t -height 1 -background "red"
	pack .test.c.t
	menu .test 
}
#@-node:ekr.20031218072017.716:canvasBug
#@+node:ekr.20031218072017.717:bindBug
proc bindBug {} {

	toplevel .top
	
	text .top.t
	pack .top.t
	
	menu .top.m   -tearoff 0
	menu .top.m.f -tearoff 0
	menu .top.m.e -tearoff 0
	
	.top.m.f add separator
	.top.m.f add command -label "A" -command {""}
	.top.m.f add command -label "B" -command {""}

	.top.m add cascade -label "File" -menu top.m.f -underline 0
	.top.m add cascade -label "Edit" -menu top.m.e -underline 0
	
	.top configure -menu .top.m
}
#@-node:ekr.20031218072017.717:bindBug
#@-node:ekr.20031218072017.715:@file c:/prog/test/tkBugs.tcl
#@-node:ekr.20031218072017.714:tkBugs.tcl
#@-node:ekr.20031218072017.679:Tk code
#@-node:ekr.20031218072017.678:Report Tk bugs
#@+node:ekr.20031218072017.718:(tab bug)
#@+node:ekr.20040117092727:This is definitely a Tk bug
By: dthein ( Dave Hein ) 
 RE: BUG: Non-leading tabs not working properl   
2004-01-17 14:40  

 This seems to be a TK bug. I've reproduced the problem directly in Tk.

It's been around for a long time :-(

More details on this page, along with a patch for an earlier version.

http://www.qs.co.nz/Tcl/TkTabs.html

The Tk folks fixed a bug I reported with Ctrl-V behavior, but it took about a year for them to get to it. I don't have high expectations with this problem either, but I'll probably put together a patch for some of the recent version of Tk and submit the patches and bug report.  
#@-node:ekr.20040117092727:This is definitely a Tk bug
#@+node:ekr.20040118090055:Patch and bug report
https://sourceforge.net/forum/message.php?msg_id=2380238
By: dthein

I've submitted a patch and bug report to the Tk project.

The patch, #879073, for those that want to fix this problem on their systems,
is at:

http://sourceforge.net/tracker/?func=detail&aid=879073&group_id=12997&atid=31299
7

And the bug report, #879077, is at:

http://sourceforge.net/tracker/?func=detail&aid=879077&group_id=12997&atid=11299
7

The patch is for 8.4.2.  If you have a different version, you can probably figure
out the changes needed by looking at the patch file.  If not, let me know your
version and I may be able to produce a patch for it.

Note: If you use tabs for anything other than leading whitespace, you will find
this patch really helpful.  I make lots of little tables when I'm documenting
or note-taking ... this fix really helped my sanity when making those tables
inside Leo.

Dave Hein
#@nonl
#@-node:ekr.20040118090055:Patch and bug report
#@+node:ekr.20031218072017.719:Report
@nocolor

Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1906790
By: dspeed
Open Discussion

-- Tabs are not expanded correctly in .c files, when language in preferences is set to c, and when the tabs occur in the middle of a line. The tabs are expanded as spaces until the next tab location is reached, then the tabs are expanded correctly. 
#@-node:ekr.20031218072017.719:Report
#@+node:ekr.20040105070023.5:Report 2
Leo 4.1 rc3, build 1.62 , December 19, 2003
Python 2.3.0, Tk 8.4.2
Linux 2.4.22-21mdkenterprise

1. Any tab typed before the first tab stop behaves correctly (the cursor is moved to the tab stop). Good.

2. Any tab typed after a non-tab character (even a space) _and_ after the first tab stop position doesn't behave like a tab and doesn't move the cursor to the next tab stop. Bad.

3. Any tab typed after a tab character will behave properly no matter what position on the line. Okay.

To reproduce this, set your global tab prefernence to 4. Show invisibles. And then create a node containing:

[BEGIN BODY TEXT]
@language plain
@tabwidth 8
[END BODY TEXT]

Create a child node to that one, containing:

[BEGIN BODY TEXT]
@root-code somefilename
\t\tThis works
bbb\tAnd This works
So\tdoes this

But, this \tdoes not.
Here is the two-tab \t\t behavior.
[END BODY TEXT]

I hope this is a Leo bug and not a Tk bug. 

Dave Hein 
#@nonl
#@-node:ekr.20040105070023.5:Report 2
#@+node:ekr.20031218072017.720:Minimal test
This is a test line.
#@nonl
#@-node:ekr.20031218072017.720:Minimal test
#@+node:ekr.20031218072017.721:Test File for Non Expanding Tabs
This is a test line.
put the text insertion point in the space between 'a' and 'test' above. Enter 3 tabs in a row and watch it not work.

If your expansion works correctly, then maybe something with leoconfig?  But wait, Im using the leoconfig from the beta download.

The contents of my Log Windows when opening this file:

Leo Log Window...
Pyton 2.2.2, Tk 8.3.2
reading d:\test.leo


#@-node:ekr.20031218072017.721:Test File for Non Expanding Tabs
#@+node:ekr.20031218072017.722:setTabWidth
def setTabWidth (self, w):
    
    try: # This can fail when called from scripts
        # Use the present font for computations.
        font = self.bodyCtrl.cget("font")
        root = g.app.root # 4/3/03: must specify root so idle window will work properly.
        font = tkFont.Font(root=root,font=font)
        tabw = font.measure(" " * abs(w)) # 7/2/02
        self.bodyCtrl.configure(tabs=tabw)
        self.tab_width = w
        # g.trace(w,tabw)
    except:
        g.es_exception()
        pass
#@-node:ekr.20031218072017.722:setTabWidth
#@-node:ekr.20031218072017.718:(tab bug)
#@-node:ekr.20031218072017.673:Tk bugs
#@+node:ekr.20031218072017.723:Bugs: can't recreate
#@+node:ekr.20031218072017.724:(Uppercase bug) (Can't recreate)
#@+node:ekr.20031218072017.725:Report (I can't recreate this)
@nocolor

Read and respond to this message at: 
https://sourceforge.net/forum/message.php?msg_id=1819473
By: nobody

When I create a @file node for a file with an uppercase name like TEST.CPP, by
picking up its name, an empty node is created. On saving the leo file, it
empties the TEST.CPP file!! BAD.

On the other hand, if I key in the filename in the dialog box as 'test.cpp'
(all lowercase) the node is created properly.

Rajiv Bhagwat
#@-node:ekr.20031218072017.725:Report (I can't recreate this)
#@-node:ekr.20031218072017.724:(Uppercase bug) (Can't recreate)
#@-node:ekr.20031218072017.723:Bugs: can't recreate
#@+node:ekr.20031218072017.726:Put the setup stuff in the dist folder where they belong
#@-node:ekr.20031218072017.726:Put the setup stuff in the dist folder where they belong
#@+node:ekr.20040129133809.8:top node not saved
When opening a .leo file Leo selects the correct node but it is no longer the top most node in the window.

(no) Probably related to Leo now saving of the body pane size.
#@nonl
#@+node:ekr.20040130174232:What I did
@nocolor

- Eliminated entries like a="":  This happened because Leo no longer writes clone bits.

- Made sure Leo writes a="T" entries.  However, Leo really can't use this easily.

Another possibility would be to save the scrolling state, but that is very gui-dependent.
#@nonl
#@-node:ekr.20040130174232:What I did
#@+node:ekr.20031218072017.1579:putVnodes
def putVnodes (self):

    """Puts all <v> elements in the order in which they appear in the outline."""

    c = self.c
    c.clearAllVisited()

    self.put("<vnodes>") ; self.put_nl()

    # Make only one copy for all calls.
    self.currentPosition = c.currentPosition() 
    self.topPosition     = c.topPosition()

    if self.usingClipboard:
        self.putVnode(self.currentPosition,ignored=False) # Write only current tree.
    else:
        for p in c.rootPosition().self_and_siblings_iter():
            self.putVnode(p,ignored=False) # Write the next top-level node.

    self.put("</vnodes>") ; self.put_nl()
#@nonl
#@-node:ekr.20031218072017.1579:putVnodes
#@+node:ekr.20031218072017.1566:getVnode changed for 4.2)
def getVnode (self,parent,back,skip,appendToCurrentStack,appendToTopStack):

    c = self.c ; v = None
    setCurrent = setExpanded = setMarked = setOrphan = setTop = False
    tref = -1 ; headline = "" ; tnodeList = None ; attrDict = {} 
    # we have already matched <v.
    while 1:
        if self.matchTag("a=\""):
            << Handle vnode attribute bits >>
        elif self.matchTag("t="):
            # New for 4.1.  Read either "Tnnn" or "gnx".
            tref = self.getDqString()
        elif self.matchTag("vtag=\"V"):
            self.getIndex() ; self.getDquote() # ignored
        elif self.matchTag("tnodeList="):
            s = self.getDqString()
            tnodeList = self.getTnodeList(s) # New for 4.0
        elif self.matchTag("descendentTnodeUnknownAttributes="):
            # New for 4.2
            s = self.getDqString()
            dict = self.getDescendentUnknownAttributes(s)
            if dict:
                self.descendentUnknownAttributesDictList.append(dict)
        elif self.matchTag("expanded="): # New in 4.2
            s = self.getDqString()
            self.descendentExpandedList.extend(self.getDescendentAttributes(s,tag="expanded"))
        elif self.matchTag("marks="): # New in 4.2
            s = self.getDqString()
            self.descendentMarksList.extend(self.getDescendentAttributes(s,tag="marks"))
        elif self.matchTag(">"):
            break
        else: # New for 4.0: allow unknown attributes.
            # New in 4.2: allow pickle'd and hexlify'ed values.
            attr,val = self.getUnknownAttribute("vnode")
            if attr: attrDict[attr] = val
    # Headlines are optional.
    if self.matchTag("<vh>"):
        headline = self.getEscapedString() ; self.getTag("</vh>")
    
    # g.trace("skip:",skip,"parent:",parent,"back:",back,"headline:",headline)
    if skip:
        v = self.getExistingVnode(tref,headline)
    if v is None:
        v,skip2 = self.createVnode(parent,back,tref,headline,attrDict)
        skip = skip or skip2
        if tnodeList:
            v.t.tnodeList = tnodeList # New for 4.0, 4.2: now in tnode.

    << Set the remembered status bits >>

    # Recursively create all nested nodes.
    parent = v ; back = None
    while self.matchTag("<v"):
        append1 = appendToCurrentStack and len(self.currentVnodeStack) == 0
        append2 = appendToTopStack and len(self.topVnodeStack) == 0
        back = self.getVnode(parent,back,skip,
            appendToCurrentStack=append1,appendToTopStack=append2)
            
    << Append to current or top stack >>

    # End this vnode.
    self.getTag("</v>")
    return v
#@nonl
#@+node:ekr.20031218072017.1567:<< Handle vnode attribute bits  >>
# The a=" has already been seen.
while 1:
    if   self.matchChar('C'): pass # Not used: clone bits are recomputed later.
    elif self.matchChar('D'): pass # Not used.
    elif self.matchChar('E'): setExpanded = True
    elif self.matchChar('M'): setMarked = True
    elif self.matchChar('O'): setOrphan = True
    elif self.matchChar('T'): setTop = True
    elif self.matchChar('V'): setCurrent = True
    else: break

self.getDquote()
#@nonl
#@-node:ekr.20031218072017.1567:<< Handle vnode attribute bits  >>
#@+node:ekr.20031218072017.1568:<< Set the remembered status bits >>
if setCurrent:
    self.currentVnodeStack = [v]

if setTop:
    self.topVnodeStack = [v]
    
if setExpanded:
    v.initExpandedBit()
    
if setMarked:
    v.initMarkedBit() # 3/25/03: Do not call setMarkedBit here!

if setOrphan:
    v.setOrphan()
#@nonl
#@-node:ekr.20031218072017.1568:<< Set the remembered status bits >>
#@+node:ekr.20040326055828:<< Append to current or top stack >>
if not setCurrent and len(self.currentVnodeStack) > 0 and appendToCurrentStack:
    #g.trace("append current",v)
    self.currentVnodeStack.append(v)
    
if not setTop and len(self.topVnodeStack) > 0 and appendToTopStack:
    #g.trace("append top",v)
    self.topVnodeStack.append(v)
#@nonl
#@-node:ekr.20040326055828:<< Append to current or top stack >>
#@-node:ekr.20031218072017.1566:getVnode changed for 4.2)
#@+node:ekr.20031218072017.1863:putVnode (3.x and 4.x)
def putVnode (self,p,ignored):

    """Write a <v> element corresponding to a vnode."""

    fc = self ; c = fc.c ; v = p.v
    isThin = p.isAtThinFileNode()
    isIgnore = False
    if 1: # New in 4.2 b3: use ignored argument to compute this without leaking positions.
        ignored = ignored or p.isAtIgnoreNode()
    else:
        for p2 in p.self_and_parents_iter():
            if p2.isAtIgnoreNode():
                isIgnore = True ; break
    isOrphan = p.isOrphan()
    forceWrite = isIgnore or not isThin or (isThin and isOrphan)

    fc.put("<v")
    << Put tnode index >>
    << Put attribute bits >>
    << Put tnodeList and unKnownAttributes >>
    fc.put(">")
    << Write the head text >>

    if not self.usingClipboard:
        << issue informational messages >>

   # New in 4.2: don't write child nodes of @file-thin trees (except when writing to clipboard)
    if p.hasChildren():
        if forceWrite or self.usingClipboard:
            fc.put_nl()
            # This optimization eliminates all "recursive" copies.
            p.moveToFirstChild()
            while 1:
                fc.putVnode(p,ignored)
                if p.hasNext(): p.moveToNext()
                else:           break
            p.moveToParent()

    fc.put("</v>") ; fc.put_nl()
#@nonl
#@+node:ekr.20031218072017.1864:<< Put tnode index >>
if v.t.fileIndex:
    if g.app.use_gnx:
        gnx = g.app.nodeIndices.toString(v.t.fileIndex)
        fc.put(" t=") ; fc.put_in_dquotes(gnx)
    else:
        fc.put(" t=") ; fc.put_in_dquotes("T" + str(v.t.fileIndex))
        
    # g.trace(v.t)
    if forceWrite or self.usingClipboard:
        v.t.setWriteBit() # 4.2: Indicate we wrote the body text.
else:
    g.trace(v.t.fileIndex,v)
    g.es("error writing file(bad v.t.fileIndex)!")
    g.es("try using the Save To command")
#@nonl
#@-node:ekr.20031218072017.1864:<< Put tnode index >>
#@+node:ekr.20031218072017.1865:<< Put attribute bits >>
attr = ""
if p.v.isExpanded(): attr += "E"
if p.v.isMarked():   attr += "M"
if p.v.isOrphan():   attr += "O"

if 1: # No longer a bottleneck now that we use p.equal rather than p.__cmp__
    # Almost 30% of the entire writing time came from here!!!
    if p.equal(self.topPosition):   attr += "T" # was a bottleneck
    if c.isCurrentPosition(p):      attr += "V" # was a bottleneck

if attr: fc.put(' a="%s"' % attr)
#@nonl
#@-node:ekr.20031218072017.1865:<< Put attribute bits >>
#@+node:ekr.20040324082713:<< Put tnodeList and unKnownAttributes >>
# Write the tnodeList only for @file nodes.
# New in 4.2: tnode list is in tnode.

if 0: # Debugging.
    if v.isAnyAtFileNode():
        if hasattr(v.t,"tnodeList"):
            g.trace(v.headString(),len(v.t.tnodeList))
        else:
            g.trace(v.headString(),"no tnodeList")

if hasattr(v.t,"tnodeList") and len(v.t.tnodeList) > 0 and v.isAnyAtFileNode():
    if isThin:
        if g.app.unitTesting:
            g.app.unitTestDict["warning"] = True
        g.es("deleting tnode list for %s" % p.headString(),color="blue")
        # This is safe: cloning can't change the type of this node!
        delattr(v.t,"tnodeList")
    else:
        fc.putTnodeList(v) # New in 4.0

if hasattr(v,"unknownAttributes"): # New in 4.0
    self.putUnknownAttributes(v)
    
if p.hasChildren() and not forceWrite and not self.usingClipboard:
    # We put the entire tree when using the clipboard, so no need for this.
    self.putDescendentUnknownAttributes(p)
    self.putDescendentAttributes(p)
#@nonl
#@-node:ekr.20040324082713:<< Put tnodeList and unKnownAttributes >>
#@+node:ekr.20040702085529:<< issue informational messages >>
if p.isAtThinFileNode and p.isOrphan():
    g.es("Writing erroneous: %s" % p.headString(),color="blue")
    p.clearOrphan()

if 0: # For testing.
    if p.isAtIgnoreNode():
         for p2 in p.self_and_subtree_iter():
                if p2.isAtThinFileNode():
                    g.es("Writing @ignore'd: %s" % p2.headString(),color="blue")
#@nonl
#@-node:ekr.20040702085529:<< issue informational messages >>
#@+node:ekr.20031218072017.1866:<< Write the head text >>
headString = p.v.headString()

if headString:
    fc.put("<vh>")
    fc.putEscapedString(headString)
    fc.put("</vh>")
#@nonl
#@-node:ekr.20031218072017.1866:<< Write the head text >>
#@-node:ekr.20031218072017.1863:putVnode (3.x and 4.x)
#@+node:ekr.20031218072017.2297:fileCommands.open
def open(self,file,fileName):

    c = self.c ; frame = c.frame
    # Read the entire file into the buffer
    self.fileBuffer = file.read() ; file.close()
    self.fileIndex = 0
    << Set the default directory >>
    self.topPosition = None
    c.beginUpdate()
    ok, ratio = self.getLeoFile(fileName,atFileNodesFlag=True)
    frame.resizePanesToRatio(ratio,frame.secondary_ratio) # 12/2/03
    if 0: # 1/30/04: this is useless.
        if self.topPosition: 
            c.setTopVnode(self.topPosition)
    c.endUpdate()
    # delete the file buffer
    self.fileBuffer = ""
    return ok
#@nonl
#@+node:ekr.20031218072017.2298:<< Set the default directory >> in fileCommands.readOutlineOnly
@ The most natural default directory is the directory containing the .leo file that we are about to open.  If the user has specified the "Default Directory" preference that will over-ride what we are about to set.
@c

dir = g.os_path_dirname(fileName)

if len(dir) > 0:
    c.openDirectory = dir
#@nonl
#@-node:ekr.20031218072017.2298:<< Set the default directory >> in fileCommands.readOutlineOnly
#@-node:ekr.20031218072017.2297:fileCommands.open
#@+node:ekr.20031218072017.3030:fileCommands.readOutlineOnly
def readOutlineOnly (self,file,fileName):

    c = self.c
    # Read the entire file into the buffer
    self.fileBuffer = file.read() ; file.close()
    self.fileIndex = 0
    << Set the default directory >>
    c.beginUpdate()
    ok, ratio = self.getLeoFile(fileName,atFileNodesFlag=False)
    c.endUpdate()
    c.frame.deiconify()
    vflag,junk,secondary_ratio = self.frame.initialRatios()
    c.frame.resizePanesToRatio(ratio,secondary_ratio)
    if 0: # 1/30/04: this is useless.
        # This should be done after the pane size has been set.
        if self.topPosition:
            c.frame.tree.setTopPosition(self.topPosition)
            c.redraw()
    # delete the file buffer
    self.fileBuffer = ""
    return ok
#@nonl
#@+node:ekr.20031218072017.2298:<< Set the default directory >> in fileCommands.readOutlineOnly
@ The most natural default directory is the directory containing the .leo file that we are about to open.  If the user has specified the "Default Directory" preference that will over-ride what we are about to set.
@c

dir = g.os_path_dirname(fileName)

if len(dir) > 0:
    c.openDirectory = dir
#@nonl
#@-node:ekr.20031218072017.2298:<< Set the default directory >> in fileCommands.readOutlineOnly
#@-node:ekr.20031218072017.3030:fileCommands.readOutlineOnly
#@-node:ekr.20040129133809.8:top node not saved
#@-node:ekr.20031218072017.663:Bug: can't be fixed
#@+node:ekr.20040105064959:Bugs: can wait
@nocolor
#@nonl
#@+node:ekr.20040115165036:bug in xml doc parts (hard to fix?)
@language html
@ignore
@color
#@nonl
#@+node:ekr.20040115165036.1:Demo XML comment bug
@ 
This document demonstrates what appears to be a bug in Leo 4.1 rc3, build 1.62 of December 19, 2003.

It has manifested when Leo is executed under Python 2.3.3, Tk 8.4.3 under Windows 2000.

In brief, derived XML files are not well-formed with respect to comments under some conditions.  Comments can wind up nested, which looks okay to humans but not to XML parsers.
@c
#@nonl
#@-node:ekr.20040115165036.1:Demo XML comment bug
#@+node:ekr.20040115165036.3:@file xmlcommentbug.xml
@first
@language HTML
<HiMom>
@ This will produce, in the derived file, an XML comment with another XML comment embedded.  Or, if you prefer, it will produce an unclosed XML comment followed by a well-formed one, followed by a string of text containing a comment-close marker.

This text is sitting in the inner comment, according to the first view.
@c


@
This comment is well-formed, seemingly because its content does not begin on the same line as the at-sign.
@c
</HiMom>
#@nonl
#@-node:ekr.20040115165036.3:@file xmlcommentbug.xml
#@+node:ekr.20040115165036.4:xmlcommentbug.xml
<?xml version='1.0'?>
<!--@+leo-ver=4-->
<!--@+node:@file xmlcommentbug.xml-->
<!--@@first-->
<!--@@language HTML-->
<HiMom>
<!--@+at -->
<!--
<!--@nonl-->
This will produce, in the derived file, an XML comment with another XML 
comment embedded.  Or, if you prefer, it will produce an unclosed XML comment 
followed by a well-formed one, followed by a string of text containing a 
comment-close marker.

This text is sitting in the inner comment, according to the first view.
-->
<!--@-at-->
<!--@@c-->


<!--@+at-->
<!--
This comment is well-formed, seemingly because its content does not begin on 
the same line as the at-sign.
-->
<!--@-at-->
<!--@@c-->
</HiMom>
<!--@nonl-->
<!--@-node:@file xmlcommentbug.xml-->
<!--@-leo-->
#@nonl
#@-node:ekr.20040115165036.4:xmlcommentbug.xml
#@-node:ekr.20040115165036:bug in xml doc parts (hard to fix?)
#@+node:ekr.20040125114453:Import bug?control-alt-f of python code misalloctes code (waiting for answer)
@nocolor
http://sourceforge.net/forum/message.php?msg_id=2391076
By: thyrsus

There is a lot of correct intepretation going on, but there are some errors.
As an example, the anaconda code, in text.py, contains the following lines.
I'll use periods for leading whitespace, the two characters ^I for leading tabs,
and a $ to indicate a newline:

class WaitWindow:
def pop(self):
	self.screen.popWindow()
	self.screen.refresh()

def __init__(self, screen, title, text):
	self.screen = screen
	width = 40
	if (len(text) < width): width = len(text)

	t = TextboxReflowed(width, text)

	g = GridForm(self.screen, title, 1, 1)
	g.add(t, 0, 0)
	g.draw()
	self.screen.refresh()


After importing file text.py, I get three associated nodes like so:

[class WaitWindow]
.|
.+-[pop]
.|
.+-[__init__]

However, the contents of the nodes are off.  In node [class WaitWindow] the
text is

class WaitWindow:
@others
	self.screen = screen
	width = 40
	if (len(text) < width): width = len(text)

	t = TextboxReflowed(width, text)
	g = GridForm(self.screen, title, 1, 1)
	g.add(t, 0, 0)
	g.draw()
	self.screen.refresh()

Node [pop] contains the text

def pop(self):

Node [__init__] contains the text

self.screen.popWindow()
self.screen.refresh()

def __init__(self, screen, title, text):

This anaconda code is being correctly interpreted by the python 1.5 interpreter.
I'm too green with python to pronounce on whether the formatting is conventional.
I don't consider this a bug a major problem, but it should probably be addressed
before we start touting Leo for large collections of existing code.

This is my first experience importing python; in the past I've imported perl
code, and Leo gave me just one big @file node, and I was on my own to better
structure it.  Given the perversity of perl syntax ("Nothing but perl can parse
Perl." - Tom Christiansen), that's probably the right thing to do.  It's a judgement
call for whomever wants to take responsibility for the python importer as to
whether that may be the right thing to do for python.
#@nonl
#@-node:ekr.20040125114453:Import bug?control-alt-f of python code misalloctes code (waiting for answer)
#@+node:ekr.20040129133809.5:Expand/contract may not work after drag (works for me)
sometimes after a drag of a node, 
then the expand/contract doesnt work.
click or menu has no effect.
in an open leo
maybe it is ok after you save the file
other times only fix is to exit & restart.
#@nonl
#@-node:ekr.20040129133809.5:Expand/contract may not work after drag (works for me)
#@-node:ekr.20040105064959:Bugs: can wait
#@-node:ekr.20040329185649:Bugs: can't be fixed or can wait
#@-node:ekr.20040123102724:Can't or wont
#@-all
#@nonl
#@-node:ekr.20040117181936:@thin ../doc/leoToDo.txt
#@-leo
