#@+leo-ver=5-thin
#@+node:ekr.20170302123956.1: * @file ../doc/leoAttic.txt
# This is Leo's final resting place for dead code.
# Much easier to access than a git repo.

#@@language python
#@@killbeautify
#@+all
#@+node:ekr.20190412154439.1: ** Abandoned projects
#@+node:ekr.20170203080350.1: *3* Abandoned #396: Show images in Leo's body pane
https://github.com/leo-editor/leo-editor/issues/396

#@+node:ekr.20170302151109.1: *4* ** Notes
@language rest
@wrap

Unicode 'object replacement character': u+FFFC

QTextDocument may be helpful: http://doc.qt.io/qt-5/qtextdocument.html
See QTextDocument.MetaInformation: http://doc.qt.io/qt-5/qtextdocument.html#MetaInformation-enum

http://stackoverflow.com/questions/3254652/
several-ways-of-placing-an-image-in-a-qtextedit

http://doc.qt.io/qt-5/qtextdocument.html#resource

QVariant QTextDocument::resource(int type, const QUrl &name) const

Returns data of the specified type from the resource with the given name.

This function is called by the rich text engine to request data that isn't directly stored by QTextDocument, but still associated with it. For example, images are referenced indirectly by the name attribute of a QTextImageFormat object.

Resources are cached internally in the document. If a resource can not be found in the cache, loadResource is called to try to load the resource. loadResource should then use addResource to add the resource to the cache.
#@+node:ekr.20170203105538.1: *4* Test inserting picture (new)
# https://github.com/leo-editor/leo-editor/issues/396
g.cls()
images = [i for i in range(len(p.b)) if ord(p.b[i]) > 128]
if images:
    print('images at', images)
else:
    table = (
        'application-x-leo-outline.png',
        'LeoDoc.ico',
    )    
    for image in table:
        path = g.os_path_finalize_join(g.app.loadDir, '..', 'Icons', image)
        assert g.os_path_exists(path), repr(path)
        c.frame.body.wrapper.setInsertPoint(len(p.b))
        if 0:
            format = QtGui.QTextImageFormat()
            format.setName(path)
            cursor = cursor = body.widget.textCursor()
            cursor.insertImage(format)
        c.frame.body.widget.insertHtml('<img src="%s">' % path)
            # style="width:40px;height:80px;"
    for i, ch in enumerate(p.b):
        if ord(ch) > 128: print('new', i, ord(ch))
#
#￼￼
#@+node:ekr.20170204110006.1: *4* test3 insert an image
g.cls()
from leo.core.leoQt import QtGui
body = c.frame.body
table = ('box01.bmp','box02.bmp','box03.bmp',)
d = g.app.permanentScriptDict
images = d.get('images', [])
for image in table:
    path = g.os_path_finalize_join(g.app.loadDir, '..', 'Icons', image)
    assert g.os_path_exists(path), repr(path)
    image = QtGui.QImage(path)
    images.append(image)
    body.wrapper.setInsertPoint(len(p.b))
    cursor = body.widget.textCursor()
    cursor.insertImage(image)
for i, ch in enumerate(p.b):
    if ord(ch) > 128: print('new', i, ord(ch))
d ['images'] = images
#
#
#@+node:ekr.20170204140521.1: *4* clear g.app.permanentScriptDict
g.printDict(g.app.permanentScriptDict)
g.app.permanentScriptDict = {}
#@+node:ekr.20170204135338.1: *4* @@button show-images
from leo.core.leoQt import QtCore
d = g.app.permanentScriptDict
name_index = d.get('name_index', 0)
names = ['leo_image%s' % (i) for i in range(name_index)]
# print('image names', names)
widget = c.frame.body.widget
doc = widget.document()
for i, name in enumerate(names):
    image = doc.resource(doc.ImageResource, QtCore.QUrl(name))
    print(name, image)
#@+node:ekr.20170204105958.1: *4* test2 insert an image
g.cls()
from leo.core.leoQt import QtGui
body = c.frame.body
table = (
    'application-x-leo-outline.png',
    'LeoDoc.ico',
)
g.app.permanentScriptDict = {}
d = g.app.permanentScriptDict
images = d.get('images', [])
cursors = d.get('cursors', [])
name_index = d.get('name_index', 0)
for image in table:
    path = g.os_path_finalize_join(g.app.loadDir, '..', 'Icons', image)
    assert g.os_path_exists(path), repr(path)
    image = QtGui.QImage(path)
    print(image.format().name())
    images.append(image)
    body.wrapper.setInsertPoint(len(p.b))
    cursor = body.widget.textCursor()
    #name = 'leo_image%s' % name_index
    #name_index += 1
    cursor.insertImage(image)
    cursors.append(cursor)
for i, ch in enumerate(p.b):
    if ord(ch) > 128: print('new', i, ord(ch))
d ['images'] = images
d ['cursors'] = cursors
d ['name_index'] = name_index
g.app.permanentScriptDict = d
g.printDict(d)
#
#￼￼￼
#￼
#@+node:ekr.20190910020834.1: *3* black
#@+node:ekr.20190910023018.1: *4* imports
try:
    # pylint: disable=import-error
        # We can't assume the user has this.
    import black
except Exception:
    black = None
#@+node:ekr.20150531042746.1: *4* munging leo directives
#@+node:ekr.20150529084212.1: *5* comment_leo_lines (leoBeautifier.py)
def comment_leo_lines(p):
    '''Replace lines with Leonine syntax with special comments.'''
    # Choose the comment string so it appears nowhere in s.
    s0 = p.b
    n = 5
    while s0.find('#' + ('!' * n)) > -1:
        n += 1
    comment = '#' + ('!' * n)
    # Create a dict of directives.
    d = {}
    for z in g.globalDirectiveList:
        d[z] = True
    # Convert all Leonine lines to special comments.
    i, lines, result = 0, g.splitLines(s0), []
    while i < len(lines):
        progress = i
        s = lines[i]
        # Comment out any containing a section reference.
        j = s.find('<<')
        k = s.find('>>') if j > -1 else -1
        if -1 < j < k:
            result.append(comment + s)
            # Generate a properly-indented pass line.
            j2 = g.skip_ws(s, 0)
            result.append('%spass\n' % (' ' * j2))
        elif s.lstrip().startswith('@'):
            # Comment out all other Leonine constructs.
            if starts_doc_part(s):
                # Comment the entire doc part, until @c or @code.
                result.append(comment + s)
                i += 1
                while i < len(lines):
                    s = lines[i]
                    result.append(comment + s)
                    i += 1
                    if ends_doc_part(s):
                        break
            else:
                j = g.skip_ws(s, 0)
                assert s[j] == '@'
                j += 1
                k = g.skip_id(s, j, chars='-')
                if k > j:
                    word = s[j: k]
                    if word == 'others':
                        # Remember the original @others line.
                        result.append(comment + s)
                        # Generate a properly-indented pass line.
                        result.append('%spass\n' % (' ' * (j - 1)))
                    else:
                        # Comment only Leo directives, not decorators.
                        result.append(comment + s if word in d else s)
                else:
                    result.append(s)
        else:
            # A plain line.
            result.append(s)
        if i == progress:
            i += 1
    return comment, ''.join(result)
#@+node:ekr.20150531042830.1: *5* starts_doc_part & ends_doc_part
def starts_doc_part(s):
    '''Return True if s word matches @ or @doc.'''
    for delim in ('@\n', '@doc\n', '@ ', '@doc '):
        if s.startswith(delim):
            return True
    return False

def ends_doc_part(s):
    '''Return True if s word matches @c or @code.'''
    for delim in ('@c\n', '@code\n', '@c ', '@code '):
        if s.startswith(delim):
            return True
    return False
#@+node:ekr.20150529095117.1: *5* uncomment_leo_lines
def uncomment_leo_lines(comment, p, s0):
    '''Reverse the effect of comment_leo_lines.'''
    lines = g.splitLines(s0)
    i, result = 0, []
    while i < len(lines):
        progress = i
        s = lines[i]
        i += 1
        if s.find(comment) == -1:
            # A regular line.
            result.append(s)
        else:
            # One or more special lines.
            i = uncomment_special_lines(comment, i, lines, p, result, s)
        assert progress < i
    return ''.join(result).rstrip() + '\n'
#@+node:ekr.20150531041720.1: *5* uncomment_special_line & helpers
def uncomment_special_lines(comment, i, lines, p, result, s):
    '''
    s is a line containing the comment delim.
    i points at the *next* line.
    Handle one or more lines, appending stripped lines to result.
    '''
    s = s.lstrip().lstrip(comment)
    if starts_doc_part(s):
        result.append(s)
        while i < len(lines):
            s = lines[i].lstrip().lstrip(comment)
            i += 1
            result.append(s)
            if ends_doc_part(s):
                break
        return i
    j = s.find('<<')
    k = s.find('>>') if j > -1 else -1
    if -1 < j < k or s.find('@others') > -1:
        # A section reference line or an @others line.
        # Such lines are followed by a pass line.
        # The beautifier may insert blank lines before the pass line.
        kind = 'section ref' if -1 < j < k else '@others'
        # Restore the original line, including leading whitespace.
        result.append(s)
        # Skip blank lines.
        while i < len(lines) and not lines[i].strip():
            i += 1
        # Skip the pass line.
        if i < len(lines) and lines[i].lstrip().startswith('pass'):
            i += 1
        else:
            g.trace('*** no pass after %s: %s' % (kind, p.h))
    else:
        # A directive line.
        result.append(s)
    return i
#@+node:ekr.20180328065332.1: *3* Check conventions stuff
#@+node:ekr.20171208042251.1: *4* @@button check-conventions (no longer used)
g.cls()
if c.changed: c.save()

import imp
import leo.core.leoCheck as leoCheck
imp.reload(leoCheck)

do_all = True
do_string = True

fails = []
    # All of Leo's core files pass!
fn = g.os_path_finalize_join(g.app.loadDir, '..', 'core', 'leoTest.py')
<< define s >>
<< old tests >>
if do_all:
    utils = leoCheck.ProjectUtils()
    aList = utils.project_files('leo', force_all=False)
    # g.printList(aList)
    for fn in aList:
        sfn = g.shortFileName(fn)
        if sfn in fails:
            print('===== skipping', sfn)
        else:
            print('==== fn', sfn)
            leoCheck.ConventionChecker(c).check(fn=fn)
elif do_string: # Test string s.
    leoCheck.ConventionChecker(c).check(s=s)
else: # Test an actual file.
    leoCheck.ConventionChecker(c).check(fn=fn)
#@+node:ekr.20171208105236.1: *5* << define s >>
s = '''\
class T:
    
    def __init__(self, tempNode):
        self.tempNode = tempNode.copy()
    
    def setUp(self):
        tempNode = self.tempNode
        while tempNode.firstChild():
            tempNode.firstChild().doDelete()
'''

s_ok2 = '''
class Context(object):
    def __init__ (self, parent_context):
        self.parent_context = parent_context
        if parent_context:
            parent_context.inner_contexts_list.append(self)
'''

s_ok= '''
class TC:
    def __init__(self, c):
        c.tc = self
    def add_tag(self, p):
        print(p.v) # AttributeError if p is a vnode.

class Test:
    def __init__(self,c):
        self.c = c
        self.tc = self.c.tc
    def add_tag(self):
        p = self.c.p
        self.tc.add_tag(p.v) # WRONG: arg should be p.
'''

#@+node:ekr.20171210062719.1: *5* << old tests >>
s_passes_1 = '''\
class C1:
        
    def f1(self, p):
        print(p.v)
        
    def f2(self, p):
        self.f1(p.v) # WRONG

'''


s_1 = '''\
class C1:

    def __init__(self, c):
        self.c = c
        c.theTagController = self
        
    def add_tag(self, p):
        pass

class C2:

    def oops(self, p):
        c.tagController.add_tag(p.v,tag)
            # WRONG: should be p.

'''


s_2 = '''\
class TagController:

    def __init__(self, c):
        self.c = c
        c.theTagController = self

    def add_tag(self, p, tag):
        # Will fail if p is a vnode
        tags = set(p.v.u.get('__node_tags', set([])))

class LeoTagWidget(QtWidgets.QWidget):

    def __init__(self,c,parent=None):
        self.c = c
        self.tc = self.c.theTagController

    def add_tag(self, event=None):
        p = self.c.p
        self.tc.add_tag(p.v,tag) # WRONG: should be p.

'''
#@+node:ekr.20160109150703.1: *4* class Stats (old & stupid, from leoCheck.py)
class Stats(object):
    '''A class containing global statistics & other data'''
    @others
#@+node:ekr.20160109150703.2: *5*  sd.ctor
def __init__ (self):

    # Files...
    # self.completed_files = [] # Files handled by do_files.
    # self.failed_files = [] # Files that could not be opened.
    # self.files_list = [] # Files given by user or by import statements.
    # self.module_names = [] # Module names corresponding to file names.

    # Contexts.
    # self.context_list = {}
        # Keys are fully qualified context names; values are contexts.
    # self.modules_dict = {}
        # Keys are full file names; values are ModuleContext's.

    # Statistics...
    # self.n_chains = 0
    self.n_contexts = 0
    # self.n_errors = 0
    self.n_lambdas = 0
    self.n_modules = 0
    # self.n_relinked_pointers = 0
    # self.n_resolvable_names = 0
    # self.n_resolved_contexts = 0
    # self.n_relinked_names = 0

    # Names...
    self.n_attributes = 0
    self.n_expressions = 0
    self.n_ivars = 0
    self.n_names = 0        # Number of symbol table entries.
    self.n_del_names = 0
    self.n_load_names = 0
    self.n_param_names = 0
    self.n_param_refs = 0
    self.n_store_names = 0

    # Statements...
    self.n_assignments = 0
    self.n_calls = 0
    self.n_classes = 0
    self.n_defs = 0
    self.n_fors = 0
    self.n_globals = 0
    self.n_imports = 0
    self.n_lambdas = 0
    self.n_list_comps = 0
    self.n_returns = 0
    self.n_withs = 0

    # Times...
    self.parse_time = 0.0
    self.pass1_time = 0.0
    self.pass2_time = 0.0
    self.total_time = 0.0
#@+node:ekr.20160109150703.6: *5* sd.print_times
def print_times (self):

    sd = self
    times = (
        'parse_time',
        'pass1_time',
        # 'pass2_time', # the resolve_names pass is no longer used.
        'total_time',
    )
    max_n = 5
    for s in times:
        max_n = max(max_n,len(s))
    print('\nScan times...\n')
    for s in times:
        pad = ' ' * (max_n - len(s))
        print('%s%s: %2.2f' % (pad,s,getattr(sd,s)))
    print('')
#@+node:ekr.20160109150703.7: *5* sd.print_stats
def print_stats (self):

    sd = self
    table = (
        '*', 'errors',

        '*Contexts',
        'classes','contexts','defs','modules',

        '*Statements',
        'assignments','calls','fors','globals','imports',
        'lambdas','list_comps','returns','withs',

        '*Names',
        'attributes','del_names','load_names','names',
        'param_names','param_refs','store_names',
        #'resolvable_names','relinked_names','relinked_pointers',
        # 'ivars',
        # 'resolved_contexts',
    )
    max_n = 5
    for s in table:
        max_n = max(max_n,len(s))
    print('\nStatistics...\n')
    for s in table:
        var = 'n_%s' % s
        pad = ' ' * (max_n - len(s))
        if s.startswith('*'):
            if s[1:].strip():
                print('\n%s\n' % s[1:])
            else:
                pass # print('')
        else:
            pad = ' ' * (max_n - len(s))
            print('%s%s: %s' % (pad,s,getattr(sd,var)))
    print('')
#@+node:ekr.20171211054600.1: *4* OLD checkConventions (leoCheck.py)
def checkConventions(c):
    '''
    A stand-alone version of the @button node that tested the
    ConventionChecker class.
    
    The check-conventions command in checkerCommands.py saves c and reloads
    the leoCheck module before calling this function.
    '''
    g.cls()
    kind = 'all'
    project_name = 'leo'  # 'coverage', 'leo', 'lib2to3', 'pylint', 'rope'
    assert kind in ('all', 'file', 'production', 'string'), repr(kind)
    fn = g.os_path_finalize_join(g.app.loadDir, '..', 'plugins', 'qt_tree.py')
    report_stats = True # and kind != 'production'
    trace_fn = True
    trace_skipped = False
    fails_dict = {
        'coverage': ['cmdline.py',],
        'lib2to3': ['fixer_util.py', 'fix_dict.py', 'patcomp.py', 'refactor.py'],
        'leo': [], # All of Leo's core files pass.
        'pylint': [
            'base.py', 'classes.py', 'format.py',
            'logging.py', 'python3.py', 'stdlib.py', 
            'docparams.py', 'lint.py',
        ],
        'rope': ['objectinfo.py', 'objectdb.py', 'runmod.py',],
    }
    fails = fails_dict.get(project_name, [])
    << define s >>
    s = g.adjustTripleString(s, c.tab_width)
    << old tests >>
    stats = Stats()
    if kind == 'production':
        for p in g.findRootsWithPredicate(c, c.p, predicate=None):
            x = ConventionChecker(c, stats)
            x.check(fn=g.fullPath(c, p), trace_fn=trace_fn)
    elif kind == 'all':
        utils = ProjectUtils()
        aList = utils.project_files(project_name, force_all=False)
        if aList:
            t1 = time.clock()
            for fn in aList:
                sfn = g.shortFileName(fn)
                if sfn in fails or fn in fails:
                    if trace_skipped: print('===== skipping', sfn)
                else:
                    ConventionChecker(c, stats).check(fn=fn, trace_fn=trace_fn)
            t2 = time.clock()
            print('%s files in %4.2f sec.' % (len(aList), (t2-t1)))
        else:
            print('no files for project: %s' % (project_name))
    elif kind == 'string':
        ConventionChecker(c, stats).check(s=s)
    else:
        assert kind == 'file', repr(kind)
        ConventionChecker(c, stats).check(fn=fn)
    if report_stats:
        stats.report()
#@+node:ekr.20171211054736.2: *5* << define s >>
s = '''\
class T:
    
    def __init__(self, tempNode):
        self.tempNode = tempNode.copy()
    
    def setUp(self):
        tempNode = self.tempNode
        while tempNode.firstChild():
            tempNode.firstChild().doDelete()
'''

s_ok2 = '''
class Context(object):
    def __init__ (self, parent_context):
        self.parent_context = parent_context
        if parent_context:
            parent_context.inner_contexts_list.append(self)
'''
assert s_ok2

s_ok= '''
class TC:
    def __init__(self, c):
        c.tc = self
    def add_tag(self, p):
        print(p.v) # AttributeError if p is a vnode.

class Test:
    def __init__(self,c):
        self.c = c
        self.tc = self.c.tc
    def add_tag(self):
        p = self.c.p
        self.tc.add_tag(p.v) # WRONG: arg should be p.
'''
assert s_ok

#@+node:ekr.20171211054736.3: *5* << old tests >>
s_passes_1 = '''\
class C1:
        
    def f1(self, p):
        print(p.v)
        
    def f2(self, p):
        self.f1(p.v) # WRONG

'''
assert s_passes_1

s_1 = '''\
class C1:

    def __init__(self, c):
        self.c = c
        c.theTagController = self
        
    def add_tag(self, p):
        pass

class C2:

    def oops(self, p):
        c.tagController.add_tag(p.v,tag)
            # WRONG: should be p.

'''
assert s_1


s_2 = '''\
class TagController:

    def __init__(self, c):
        self.c = c
        c.theTagController = self

    def add_tag(self, p, tag):
        # Will fail if p is a vnode
        tags = set(p.v.u.get('__node_tags', set([])))

class LeoTagWidget(QtWidgets.QWidget):

    def __init__(self,c,parent=None):
        self.c = c
        self.tc = self.c.theTagController

    def add_tag(self, event=None):
        p = self.c.p
        self.tc.add_tag(p.v,tag) # WRONG: should be p.

'''
assert s_2
#@+node:ekr.20180813063846.1: *3* Fast-draw branches
#@+node:ekr.20180808075509.1: *4* qtree.partialDraw & helpers (never used)
def partialDraw(self, p):

    trace = True and not g.unitTesting
    c = self.c
    if 1:
        self.drawVisible()
    else:
        first_p = c.hoistStack[-1].p if c.hoistStack else c.rootPosition()
        aList1 = self.countVisible(first_p=first_p, target_p=p)
        aList2 = self.countVisible(first_p=p, target_p=None)
        if trace:
            n1, n2 = len(aList1), len(aList2)
            g.trace('%s + %s = %s' % (n1, n2, n1+n2))
        if 1:
            # Draw everything.
            self.drawList(aList1 + aList2)
        else:
            aList = self.computeVisiblePositions(aList1, aList2, p)
            self.drawList(aList)
#@+node:ekr.20180809110957.1: *5* qtree.computeVisiblePositions
def computeVisiblePositions(self, aList1, aList2, p):
    '''
    Compute the list of *visible* positions to be drawn.
    
    This is tricky.  We don't want to scroll the screen unnecessarily.
    '''
    # Show everything if possible.
    if len(aList1) + len(aList2) <= self.size:
        return aList1 + aList2
    c = self.c
    while p.hasParent():
        p.moveToParent()
    aList = []
    for i in range(self.size):
        aList.append(p.copy())
        p.moveToVisNext(c)
        if not p:
            break
    return aList
#@+node:ekr.20180809123937.1: *5* qtree.countVisible
def countVisible(self, first_p, target_p):
    """
    Return the number of visible positions from first_p to target_p.
    """
    c = self.c
    aList, p = [first_p.copy()], first_p.copy()
    while p:
        if p == target_p:
            return aList[:-1]
        v = p.v
        # if v.isExpanded() and v.hasChildren():
        if (v.statusBits & v.expandedBit) != 0 and v.children:
            # p.moveToFirstChild()
            p.stack.append((v, p._childIndex),)
            p.v = v.children[0]
            p._childIndex = 0
            aList.append(p.copy())
            continue
        # if p.hasNext():
        parent_v = p.stack[-1][0] if p.stack else c.hiddenRootNode
        if p._childIndex + 1 < len(parent_v.children):
            # p.moveToNext()
            p._childIndex += 1
            p.v = parent_v.children[p._childIndex]
            aList.append(p.copy())
            continue
        #
        # A fast version of p.moveToThreadNext().
        # We look for a parent with a following sibling.
        while p.stack:
            # p.moveToParent()
            p.v, p._childIndex = p.stack.pop()
            # if p.hasNext():
            parent_v = p.stack[-1][0] if p.stack else c.hiddenRootNode
            if p._childIndex + 1 < len(parent_v.children):
                # p.moveToNext()
                p._childIndex += 1
                p.v = parent_v.children[p._childIndex]
                break # Found: moveToThreadNext()
        else:
            break # Not found.
        # Found moveToThreadNext()
        aList.append(p.copy())
        continue
    if target_p:
        g.trace('NOT FOUND:', target_p.h)
    return aList
#@+node:ekr.20180809115019.1: *5* qtree.drawList
def drawList(self, aList):
    
    trace = False
    c = self.c
    parents = []
    # Clear the widget.
    w = self.treeWidget
    w.clear()
    # Clear the dicts.
    self.initData()
    for p in aList:
        level = p.level()
        parent_item = w if level == 0 else parents[level-1]
        item = QtWidgets.QTreeWidgetItem(parent_item)
        item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
        item.setChildIndicatorPolicy(
            item.ShowIndicator if p.hasChildren()
            else item.DontShowIndicator)
        item.setExpanded(bool(p.hasChildren() and p.isExpanded()))
        self.items.append(item)
        if trace:
            print('')
            g.trace('===== level', level, p.h)
            g.trace('parent', id(parent_item), parent_item.__class__.__name__)
            g.trace('item  ', id(item), item.__class__.__name__)
            self.print_parents(parents, 1)
        # Update parents.
        if level == 0:
            parents = []
        else:
            parents = parents[:level]
        parents.append(item)
        if trace:
            self.print_parents(parents, 2)
        # Update the dicts.  Like rememberItem.
        itemHash = self.itemHash(item)
        self.item2positionDict[itemHash] = p.copy()
        self.item2vnodeDict[itemHash] = p.v
        self.position2itemDict[p.key()] = item
        d = self.vnode2itemsDict
        v = p.v
        aList = d.get(v, [])
        aList.append(item)
        d[v] = aList
        # Enter the headline.
        item.setText(0, p.h)
        # Set current item.
        if p == c.p:
            w.setCurrentItem(item)
#@+node:ekr.20180809111725.1: *5* qtree.printParents
def print_parents(self, parents, tag):
    print(tag)
    g.printObj([
        '%10s %s' % (id(z), z.__class__.__name__)
            for z in parents])
#@+node:ekr.20180810060655.1: *4* test vieldVisible
g.cls()
import time
tree = c.frame.tree
if 1: # works
    for p in c.all_positions():
        p.expand()
elif 1:
    for p in c.all_positions():
        p.v.expandedPositions = []
    for p in c.all_positions():
        p.v.expand()
        p.v.expandedPositions.append(p.copy())
else: # Doesn't work
    for v in c.all_nodes():
        v.expand()
    c.redraw() # This would be wrong.
if 1:
    t1 = time.clock()
    for i in range(1):
        aList1 = [z.copy() for z in tree.slowYieldVisible(c.rootPosition())]
    t2 = time.clock()
    print('slow: %6.3f' % (t2-t1))
if 1:
    t1 = time.clock()
    for i in range(1):
        aList2 = [z.copy() for z in tree.yieldVisible(c.rootPosition())]
    t2 = time.clock()
    print('fast: %6.3f' % (t2-t1))
v1 = [z.v for z in aList1]
v2 = [z.v for z in aList2]
try:
    i = 0
    while i < min(len(aList1), len(aList2)) and aList1[i] == aList2[i]:
        # print(i, aList1[i].h)
        i += 1
    if i == len(aList1) == len(aList2):
        print('OK')
    else:
        print('FAIL', i, len(aList1), len(aList2))
    assert aList1 == aList2, (len(aList1), len(aList2))
    assert v1 == v2, (len(v1), len(v2))
finally:
    for v in c.all_nodes():
        v.contract()
    c.redraw()
#@+node:ekr.20180810111515.1: *4* benchmark
import time
t1 = time.clock()
c.expandAllHeadlines()
t2 = time.clock()
print('expand: %5.2f sec' % (t2-t1))
t3 = time.clock()
tree = c.frame.tree
w = tree.treeWidget
n = 0
for p in tree.yieldVisible(c.rootPosition()):
    n += 1
    # c.selectPosition(p)
    item = tree.position2itemDict.get(c.p.key())
    if item:
        w.setCurrentItem(item)
t4 = time.clock()
print('%s nodes in %5.2f sec' % (n, t4-t3))
#@+node:ekr.20110605121601.17879: *4* qtree.rememberItem
def rememberItem(self, p, item):

    v = p.v
    # Update position dicts.
    itemHash = self.itemHash(item)
    self.position2itemDict[p.key()] = item
    self.item2positionDict[itemHash] = p.copy() # was item
    # Update item2vnodeDict.
    self.item2vnodeDict[itemHash] = v # was item
    # Update vnode2itemsDict.
    d = self.vnode2itemsDict
    aList = d.get(v, [])
    if item in aList:
        g.trace('*** ERROR *** item already in list: %s, %s' % (item, aList))
    else:
        aList.append(item)
    d[v] = aList
#@+node:ekr.20120219154958.10488: *4* LM.initFocusAndDraw (not used)
def initFocusAndDraw(self, c, fileName):

    def init_focus_handler(timer, c=c, p=c.p):
        '''Idle-time handler for initFocusAndDraw'''
        c.initialFocusHelper()
        c.outerUpdate()
        timer.stop()

    # This must happen after the code in getLeoFile.
    timer = g.IdleTime(init_focus_handler, delay=0.1, tag='getLeoFile')
    if timer:
        timer.start()
    else:
        # Default code.
        c.selectPosition(c.p)
        c.initialFocusHelper()
        c.k.showStateAndMode()
        c.outerUpdate()
#@+node:ekr.20150312225028.29: *3* leoViews project
This was a major project, now abandoned.
#@+node:ekr.20150312225028.31: *4* class OrganizerData
class OrganizerData:
    '''A class containing all data for a particular organizer node.'''
    def __init__ (self,h,unl,unls):
        self.anchor = None # The anchor position of this od node.
        self.children = [] # The direct child od nodes of this od node.
        self.closed = False # True: this od node no longer accepts new child od nodes.
        self.drop = True # Drop the unl for this od node when associating positions with unls.
        self.descendants = None # The descendant od nodes of this od node.
        self.exists = False # True: this od was created by @existing-organizer:
        self.h = h # The headline of this od node.
        self.moved = False # True: the od node has been moved to a global move list.
        self.opened = False # True: the od node has been opened.
        self.organized_nodes = [] # The list of positions organized by this od node.
        self.parent_od = None # The parent od node of this od node. (None is valid.)
        self.p = None # The position of this od node.
        self.parent = None # The original parent position of all nodes organized by this od node.
            # If parent_od is None, this will be the parent position of the od node.
        self.source_unl = None # The unl of self.parent.
        self.unl = unl # The unl of this od node.
        self.unls = unls # The unls contained in this od node.
        self.visited = False # True: demote_helper has already handled this od node.
    def __repr__(self):
        return 'OrganizerData: %s' % (self.h or '<no headline>')
    __str__ = __repr__
#@+node:ekr.20150312225028.32: *4* class ViewController
class ViewController:
    << docstring >>
    @others
#@+node:ekr.20150312225028.33: *5*  << docstring >> (class ViewController)
'''
A class to handle @views trees and related operations.
Such trees have the following structure:

- @views
  - @auto-view <unl of @auto node>
    - @organizers
      - @organizer <headline>
    - @clones
    
The body text of @organizer and @clones consists of unl's, one per line.
'''
#@+node:ekr.20150312225028.34: *5*  vc.ctor & vc.init
def __init__ (self,c):
    '''Ctor for ViewController class.'''
    self.c = c
    self.headline_ivar = '_imported_headline'
    self.init()
    
def init(self):
    '''
    Init all ivars of this class.
    Unit tests may call this method to ensure that this class is re-inited properly.
    '''
    self.all_ods = []
        # List of all od nodes.
    self.anchors_d = {}
        # Keys are anchoring positions, values are sorted lists of ods.
    self.anchor_offset_d = {}
        # Keys are anchoring positions, values are ints.
    self.existing_ods = []
        # List of od instances corresponding to @existing-organizer: nodes.
    self.global_bare_organizer_node_list = []
        # List of organizers that have no parent organizer node.
        # This list excludes existing organizer nodes.
    self.headlines_dict = {}
        # Keys are vnodes; values are list of child headlines.
    self.imported_organizers_list = []
        # The list of nodes that have children on entry, such as class nodes.
    self.n_nodes_scanned = 0
        # Number of nodes scanned by demote.
    self.organizer_ods = []
        # List of od instances corresponding to @organizer: nodes.
    self.organizer_unls = []
        # The list of od.unl for all od instances in self.organizer_ods.
    self.root = None
        # The position of the @auto node.
    self.pending = []
        # The list of nodes pending to be added to an organizer.
    self.stack = []
        # The stack containing real and virtual parent nodes during the main loop.
    self.temp_node = None
        # The parent position of all holding cells.
    self.trail_write_1 = None
        # The trial write on entry.
    self.views_node = None
        # The position of the @views node.
    self.work_list = []
        # A gloal list of (parent,child) tuples for all nodes that are
        # to be moved to **non-existing** organizer nodes.
        # **Important**: Nodes are moved in the order they appear in this list:
        # the tuples contain no childIndex component!
        # This list is the "backbone" of this class:
        # - The front end (demote and its helpers) adds items to this list.
        # - The back end (move_nodes and its helpers) moves nodes using this list.
#@+node:ekr.20150312225028.35: *5* vc.Entry points
#@+node:ekr.20150312225028.36: *6* vc.convert_at_file_to_at_auto
def convert_at_file_to_at_auto(self,root):
    # Define class ConvertController.
    @others
    vc = self
    c = vc.c
    if root.isAtFileNode():
        ConvertController(c,root).run()
    else:
        g.es_print('not an @file node:',root.h)
#@+node:ekr.20150312225028.37: *7* class ConvertController
class ConvertController:
    def __init__ (self,c,p):
        self.c = c
        # self.ic = c.importCommands
        self.vc = c.viewController
        self.root = p.copy()
    @others
#@+node:ekr.20150312225028.38: *8* cc.delete_at_auto_view_nodes
def delete_at_auto_view_nodes(self,root):
    '''Delete all @auto-view nodes pertaining to root.'''
    cc = self
    vc = cc.vc
    while True:
        p = vc.has_at_auto_view_node(root)
        if not p: break
        p.doDelete()
#@+node:ekr.20150312225028.39: *8* cc.import_from_string
def import_from_string(self,s):
    '''Import from s into a temp outline.'''
    cc = self # (ConvertController)
    c = cc.c
    ic = c.importCommands
    root = cc.root
    language = g.scanForAtLanguage(c,root) 
    ext = '.'+g.app.language_extension_dict.get(language)
    scanner = ic.scanner_for_ext(ext)
    # g.trace(language,ext,scanner.__name__)
    p = root.insertAfter()
    ok = scanner(atAuto=True,parent=p,s=s)
    p.h = root.h.replace('@file','@auto' if ok else '@@auto')
    return ok,p
#@+node:ekr.20150312225028.40: *8* cc.run
def run(self):
    '''Convert an @file tree to @auto tree.'''
    trace = True and not g.unitTesting
    trace_s = False
    cc = self
    c = cc.c
    root,vc = cc.root,c.viewController
    # set the headline_ivar for all vnodes.
    t1 = time.clock()
    cc.set_expected_imported_headlines(root)
    t2 = time.clock()
    # Delete all previous @auto-view nodes for this tree.
    cc.delete_at_auto_view_nodes(root)
    t3 = time.clock()
    # Ensure that all nodes of the tree are regularized.
    ok = vc.prepass(root)
    t4 = time.clock()
    if not ok:
        g.es_print('Can not convert',root.h,color='red')
        if trace: g.trace(
            '\n  set_expected_imported_headlines: %4.2f sec' % (t2-t1),
            # '\n  delete_at_auto_view_nodes:     %4.2f sec' % (t3-t2),
            '\n  prepass:                         %4.2f sec' % (t4-t3),
            '\n  total:                           %4.2f sec' % (t4-t1))
        return
    # Create the appropriate @auto-view node.
    at_auto_view = vc.update_before_write_at_auto_file(root)
    t5 = time.clock()
    # Write the @file node as if it were an @auto node.
    s = cc.strip_sentinels()
    t6 = time.clock()
    if trace and trace_s:
        g.trace('source file...\n',s)
    # Import the @auto string.
    ok,p = cc.import_from_string(s)
    t7 = time.clock()
    if ok:
        # Change at_auto_view.b so it matches p.gnx.
        at_auto_view.b = vc.at_auto_view_body(p)
        # Recreate the organizer nodes, headlines, etc.
        ok = vc.update_after_read_at_auto_file(p)
        t8 = time.clock()
        if not ok:
            p.h = '@@' + p.h
            g.trace('restoring original @auto file')
            ok,p = cc.import_from_string(s)
            if ok:
                p.h = '@@' + p.h + ' (restored)'
                if p.next():
                    p.moveAfter(p.next())
        t9 = time.clock()
    else:
        t8 = t9 = time.clock()
    if trace: g.trace(
        '\n  set_expected_imported_headlines: %4.2f sec' % (t2-t1),
        # '\n  delete_at_auto_view_nodes:     %4.2f sec' % (t3-t2),
        '\n  prepass:                         %4.2f sec' % (t4-t3),
        '\n  update_before_write_at_auto_file:%4.2f sec' % (t5-t4),
        '\n  strip_sentinels:                 %4.2f sec' % (t6-t5),
        '\n  import_from_string:              %4.2f sec' % (t7-t6),
        '\n  update_after_read_at_auto_file   %4.2f sec' % (t8-t7),
        '\n  import_from_string (restore)     %4.2f sec' % (t9-t8),
        '\n  total:                           %4.2f sec' % (t9-t1))
    if p:
        c.selectPosition(p)
    c.redraw()
#@+node:ekr.20150312225028.41: *8* cc.set_expected_imported_headlines
def set_expected_imported_headlines(self,root):
    '''Set the headline_ivar for all vnodes.'''
    trace = False and not g.unitTesting
    cc = self
    c = cc.c
    ic = cc.c.importCommands
    language = g.scanForAtLanguage(c,root) 
    ext = '.'+g.app.language_extension_dict.get(language)
    aClass = ic.classDispatchDict.get(ext)
    scanner = aClass(importCommands=ic,atAuto=True)
    # Duplicate the fn logic from ic.createOutline.
    theDir = g.setDefaultDirectory(c,root,importing=True)
    fn = c.os_path_finalize_join(theDir,root.h)
    fn = root.h.replace('\\','/')
    junk,fn = g.os_path_split(fn)
    fn,junk = g.os_path_splitext(fn)
    if aClass and hasattr(scanner,'headlineForNode'):
        ivar = cc.vc.headline_ivar
        for p in root.subtree():
            if not hasattr(p.v,ivar):
                h = scanner.headlineForNode(fn,p)
                setattr(p.v,ivar,h)
                if trace and h != p.h:
                    g.trace('==>',h) # p.h,'==>',h
#@+node:ekr.20150312225028.42: *8* cc.strip_sentinels
def strip_sentinels(self):
    '''Write the file to a string without headlines or sentinels.'''
    trace = False and not g.unitTesting
    cc = self
    at = cc.c.atFileCommands
    # ok = at.writeOneAtAutoNode(cc.root,
        # toString=True,force=True,trialWrite=True)
    at.errors = 0
    at.write(cc.root,
        kind = '@file',
        nosentinels = True,
        perfectImportFlag = False,
        scriptWrite = False,
        thinFile = True,
        toString = True)
    ok = at.errors == 0
    s = at.stringOutput
    if trace: g.trace('ok:',ok,'s:...\n'+s)
    return s
#@+node:ekr.20150312225028.43: *6* vc.pack & helper
def pack(self):
    '''
    Undoably convert c.p to a packed @view node, replacing all cloned
    children of c.p by unl lines in c.p.b.
    '''
    vc = self
    c,u = vc.c,vc.c.undoer
    vc.init()
    changed = False
    root = c.p
    # Create an undo group to handle changes to root and @views nodes.
    # Important: creating the @views node does *not* invalidate any positions.'''
    u.beforeChangeGroup(root,'view-pack')
    if not vc.has_at_views_node():
        changed = True
        bunch = u.beforeInsertNode(c.rootPosition())
        views = vc.find_at_views_node()
            # Creates the @views node as the *last* top-level node
            # so that no positions become invalid as a result.
        u.afterInsertNode(views,'create-views-node',bunch)
    # Prepend @view if need.
    if not root.h.strip().startswith('@'):
        changed = True
        bunch = u.beforeChangeNodeContents(root)
        root.h = '@view ' + root.h.strip()
        u.afterChangeNodeContents(root,'view-pack-update-headline',bunch)
    # Create an @view node as a clone of the @views node.
    bunch = u.beforeInsertNode(c.rootPosition())
    new_clone = vc.create_view_node(root)
    if new_clone:
        changed = True
        u.afterInsertNode(new_clone,'create-view-node',bunch)
    # Create a list of clones that have a representative node
    # outside of the root's tree.
    reps = [vc.find_representative_node(root,p)
        for p in root.children()
            if vc.is_cloned_outside_parent_tree(p)]
    reps = [z for z in reps if z is not None]
    if reps:
        changed = True
        bunch = u.beforeChangeTree(root)
        c.setChanged(True)
        # Prepend a unl: line for each cloned child.
        unls = ['unl: %s\n' % (vc.unl(p)) for p in reps]
        root.b = ''.join(unls) + root.b
        # Delete all child clones in the reps list.
        v_reps = set([p.v for p in reps])
        while True:
            for child in root.children():
                if child.v in v_reps:
                    child.doDelete()
                    break
            else: break
        u.afterChangeTree(root,'view-pack-tree',bunch)
    if changed:
        u.afterChangeGroup(root,'view-pack')
        c.selectPosition(root)
        c.redraw()
#@+node:ekr.20150312225028.44: *7* vc.create_view_node
def create_view_node(self,root):
    '''
    Create a clone of root as a child of the @views node.
    Return the *newly* cloned node, or None if it already exists.
    '''
    vc = self
    c = vc.c
    # Create a cloned child of the @views node if it doesn't exist.
    views = vc.find_at_views_node()
    for p in views.children():
        if p.v == c.p.v:
            return None
    p = root.clone()
    p.moveToLastChildOf(views)
    return p
#@+node:ekr.20150312225028.45: *6* vc.unpack
def unpack(self):
    '''
    Undoably unpack nodes corresponding to leading unl lines in c.p to child clones.
    Return True if the outline has, in fact, been changed.
    '''
    vc = self
    c,root,u = vc.c,vc.c.p,vc.c.undoer
    vc.init()
    # Find the leading unl: lines.
    i,lines,tag = 0,g.splitLines(root.b),'unl:'
    for s in lines:
        if s.startswith(tag): i += 1
        else: break
    changed = i > 0
    if changed:
        bunch = u.beforeChangeTree(root)
        # Restore the body
        root.b = ''.join(lines[i:])
        # Create clones for each unique unl.
        unls = list(set([s[len(tag):].strip() for s in lines[:i]]))
        for unl in unls:
            p = vc.find_absolute_unl_node(unl)
            if p: p.clone().moveToLastChildOf(root)
            else: g.trace('not found: %s' % (unl))
        c.setChanged(True)
        c.undoer.afterChangeTree(root,'view-unpack',bunch)
        c.redraw()
    return changed
#@+node:ekr.20150312225028.46: *6* vc.update_before_write_at_auto_file
def update_before_write_at_auto_file(self,root):
    '''
    Update the @auto-view node for root, an @auto node. Create @organizer,
    @existing-organizer, @clones and @headlines nodes as needed.
    This *must not* be called for trial writes.
    '''
    trace = False and not g.unitTesting
    vc = self
    c = vc.c
    changed = False
    t1 = time.clock()
    # Create lists of cloned and organizer nodes.
    clones,existing_organizers,organizers = \
        vc.find_special_nodes(root)
    # Delete all children of the @auto-view node for this @auto node.
    at_auto_view = vc.find_at_auto_view_node(root)
    if at_auto_view.hasChildren():
        changed = True
        at_auto_view.deleteAllChildren()
    # Create the single @clones node.
    if clones:
        at_clones = vc.find_at_clones_node(root)
        at_clones.b = ''.join(
            ['gnx: %s\nunl: %s\n' % (z[0],z[1]) for z in clones])
    # Create the single @organizers node.
    if organizers or existing_organizers:
        at_organizers = vc.find_at_organizers_node(root)
    # Create one @organizers: node for each organizer node.
    for p in organizers:
        # g.trace('organizer',p.h)
        at_organizer = at_organizers.insertAsLastChild()
        at_organizer.h = '@organizer: %s' % p.h
        # The organizer node's unl is implicit in each child's unl.
        at_organizer.b = '\n'.join([
            'unl: '+vc.relative_unl(z,root) for z in p.children()])
    # Create one @existing-organizer node for each existing organizer.
    ivar = vc.headline_ivar
    for p in existing_organizers:
        at_organizer = at_organizers.insertAsLastChild()
        h = getattr(p.v,ivar,p.h)
        if trace and h != p.h: g.trace('==>',h) # p.h,'==>',h
        at_organizer.h = '@existing-organizer: %s' % h
        # The organizer node's unl is implicit in each child's unl.
        at_organizer.b = '\n'.join([
            'unl: '+vc.relative_unl(z,root) for z in p.children()])
    # Create the single @headlines node.
    vc.create_at_headlines(root)
    if changed and not g.unitTesting:
        g.es_print('updated @views node in %4.2f sec.' % (
            time.clock()-t1))
    if changed:
        c.redraw()
    return at_auto_view # For at-file-to-at-auto command.
#@+node:ekr.20150312225028.47: *7* vc.create_at_headlines
def create_at_headlines(self,root):
    '''Create the @headlines node for root, an @auto file.'''
    vc = self
    c = vc.c
    result = []
    ivar = vc.headline_ivar
    for p in root.subtree():
        h = getattr(p.v,ivar,None)
        if h is not None and p.h != h:
            # g.trace('custom:',p.h,'imported:',h)
            unl = vc.relative_unl(p,root)
            aList = unl.split('-->')
            aList[-1] = h
            unl = '-->'.join(aList)
            result.append('imported unl: %s\nhead: %s\n' % (
                unl,p.h))
            delattr(p.v,ivar)
    if result:
        p = vc.find_at_headlines_node(root)
        p.b = ''.join(result)
#@+node:ekr.20150312225028.48: *7* vc.find_special_nodes
def find_special_nodes(self,root):
    '''
    Scan root's tree, looking for organizer and cloned nodes.
    Exclude organizers on imported organizers list.
    '''
    trace = False and not g.unitTesting
    verbose = False
    vc = self
    clones,existing_organizers,organizers = [],[],[]
    if trace: g.trace('imported existing',
        [v.h for v in vc.imported_organizers_list])
    for p in root.subtree():
        if p.isCloned():
            rep = vc.find_representative_node(root,p)
            if rep:
                unl = vc.relative_unl(p,root)
                gnx = rep.v.gnx
                clones.append((gnx,unl),)
        if p.v in vc.imported_organizers_list:
            # The node had children created by the importer.
            if trace and verbose: g.trace('ignore imported existing',p.h)
        elif vc.is_organizer_node(p,root):
            # p.hasChildren and p.b is empty, except for comments.
            if trace and verbose: g.trace('organizer',p.h)
            organizers.append(p.copy())
        elif p.hasChildren():
            if trace and verbose: g.trace('existing',p.h)
            existing_organizers.append(p.copy())
    return clones,existing_organizers,organizers
#@+node:ekr.20150312225028.49: *6* vc.update_after_read_at_auto_file & helpers
def update_after_read_at_auto_file(self,root):
    '''
    Recreate all organizer nodes and clones for a single @auto node
    using the corresponding @organizer: and @clones nodes.
    '''
    trace = True and not g.unitTesting
    vc = self
    c = vc.c
    if not vc.is_at_auto_node(root):
        return # Not an error: it might be and @auto-rst node.
    old_changed = c.isChanged()
    try:
        vc.init()
        vc.root = root.copy()
        t1 = time.clock()
        vc.trial_write_1 = vc.trial_write(root)
        t2 = time.clock()
        at_organizers = vc.has_at_organizers_node(root)
        t3 = time.clock()
        if at_organizers:
            vc.create_organizer_nodes(at_organizers,root)
        t4 = time.clock()
        at_clones = vc.has_at_clones_node(root)
        if at_clones:
            vc.create_clone_links(at_clones,root)
        t5 = time.clock()
        n = len(vc.work_list)
        ok = vc.check(root)
        t6 = time.clock()
        if ok:
            vc.update_headlines_after_read(root)
        t7 = time.clock()
        c.setChanged(old_changed if ok else False)
            # To do: revert if not ok.
    except Exception:
        g.es_exception()
        n = 0
        ok = False
    if trace:
        if t7-t1 > 0.5: g.trace(
            '\n  trial_write:                 %4.2f sec' % (t2-t1),
            # '\n  has_at_organizers_node:    %4.2f sec' % (t3-t2),
            '\n  create_organizer_nodes:      %4.2f sec' % (t4-t3),
            '\n  create_clone_links:          %4.2f sec' % (t5-t4),
            '\n  check:                       %4.2f sec' % (t6-t5),
            '\n  update_headlines_after_read: %4.2f sec' % (t7-t6),
            '\n  total:                       %4.2f sec' % (t7-t1))
            # '\n  file:',root.h)
        # else: g.trace('total: %4.2f sec' % (t7-t1),root.h)
    if ok and n > 0:
        g.es('rearragned: %s' % (root.h),color='blue')
        g.es('moved %s nodes in %4.2f sec.' % (n,t7-t1))
        g.trace('@auto-view moved %s nodes in %4.2f sec. for' % (
            n,t2),root.h,noname=True)
    c.selectPosition(root)
    c.redraw()
    return ok
#@+node:ekr.20150312225028.50: *7* vc.check
def check (self,root):
    '''
    Compare a trial write or root with the vc.trail_write_1.
    Unlike the perfect-import checks done by the importer,
    we expecct an *exact* match, regardless of language.
    '''
    trace = True # and not g.unitTesting
    vc = self
    trial1 = vc.trial_write_1
    trial2 = vc.trial_write(root)
    if trial1 != trial2:
        g.pr('') # Don't use print: it does not appear with the traces.
        g.es_print('perfect import check failed for:',color='red')
        g.es_print(root.h,color='red')
        if trace:
            vc.compare_trial_writes(trial1,trial2)
            g.pr('')
    return trial1 == trial2
#@+node:ekr.20150312225028.51: *7* vc.create_clone_link
def create_clone_link(self,gnx,root,unl):
    '''
    Replace the node in the @auto tree with the given unl by a
    clone of the node outside the @auto tree with the given gnx.
    '''
    trace = False and not g.unitTesting
    vc = self
    p1 = vc.find_position_for_relative_unl(root,unl)
    p2 = vc.find_gnx_node(gnx)
    if p1 and p2:
        if trace: g.trace('relink',gnx,p2.h,'->',p1.h)
        if p1.b == p2.b:
            p2._relinkAsCloneOf(p1)
            return True
        else:
            g.es('body text mismatch in relinked node',p1.h)
            return False
    else:
        if trace: g.trace('relink failed',gnx,root.h,unl)
        return False
#@+node:ekr.20150312225028.52: *7* vc.create_clone_links
def create_clone_links(self,at_clones,root):
    '''
    Recreate clone links from an @clones node.
    @clones nodes contain pairs of lines (gnx,unl)
    '''
    vc = self
    lines = g.splitLines(at_clones.b)
    gnxs = [s[4:].strip() for s in lines if s.startswith('gnx:')]
    unls = [s[4:].strip() for s in lines if s.startswith('unl:')]
    # g.trace('at_clones.b',at_clones.b)
    if len(gnxs) == len(unls):
        vc.headlines_dict = {} # May be out of date.
        ok = True
        for gnx,unl in zip(gnxs,unls):
            ok = ok and vc.create_clone_link(gnx,root,unl)
        return ok
    else:
        g.trace('bad @clones contents',gnxs,unls)
        return False
#@+node:ekr.20150312225028.53: *7* vc.create_organizer_nodes & helpers
def create_organizer_nodes(self,at_organizers,root):
    '''
    root is an @auto node. Create an organizer node in root's tree for each
    child @organizer: node of the given @organizers node.
    '''
    vc = self
    c = vc.c
    trace = False and not g.unitTesting
    t1 = time.clock()
    vc.pre_move_comments(root)
        # Merge comment nodes with the next node.
    t2 = time.clock()
    vc.precompute_all_data(at_organizers,root)
        # Init all data required for reading.
    t3 = time.clock()
    vc.demote(root)
        # Traverse root's tree, adding nodes to vc.work_list.
    t4 = time.clock()
    vc.move_nodes()
        # Move nodes on vc.work_list to their final locations.
    t5 = time.clock()
    vc.post_move_comments(root)
        # Move merged comments to parent organizer nodes.
    t6 = time.clock()
    if trace: g.trace(
        '\n  pre_move_comments:   %4.2f sec' % (t2-t1),
        '\n  precompute_all_data: %4.2f sec' % (t3-t2),
        '\n  demote:              %4.2f sec' % (t4-t3),
        '\n  move_nodes:          %4.2f sec' % (t5-t4),
        '\n  post_move_comments:  %4.2f sec' % (t6-t5))
#@+node:ekr.20150312225028.54: *7* vc.update_headlines_after_read
def update_headlines_after_read(self,root):
    '''Handle custom headlines for all imported nodes.'''
    trace = False and not g.unitTesting
    vc = self
    # Remember the original imported headlines.
    ivar = vc.headline_ivar
    for p in root.subtree():
        if not hasattr(p.v,ivar):
            setattr(p.v,ivar,p.h)
    # Update headlines from @headlines nodes.
    at_headlines = vc.has_at_headlines_node(root)
    tag1,tag2 = 'imported unl: ','head: '
    n1,n2 = len(tag1),len(tag2)
    if at_headlines:
        lines = g.splitLines(at_headlines.b)
        unls  = [s[n1:].strip() for s in lines if s.startswith(tag1)]
        heads = [s[n2:].strip() for s in lines if s.startswith(tag2)]
    else:
        unls,heads = [],[]
    if len(unls) == len(heads):
        vc.headlines_dict = {} # May be out of date.
        for unl,head in zip(unls,heads):
            p = vc.find_position_for_relative_unl(root,unl)
            if p:
                if trace: g.trace('unl:',unl,p.h,'==>',head)
                p.h = head
    else:
        g.trace('bad @headlines body',at_headlines.b)
#@+node:ekr.20150312225028.55: *5* vc.Main Lines
#@+node:ekr.20150312225028.56: *6* vc.precompute_all_data & helpers
def precompute_all_data(self,at_organizers,root):
    '''Precompute all data needed to reorganize nodes.'''
    trace = False and not g.unitTesting
    vc = self
    t1 = time.clock() 
    vc.find_imported_organizer_nodes(root)
        # Put all nodes with children on vc.imported_organizer_node_list
    t2 = time.clock()
    vc.create_organizer_data(at_organizers,root)
        # Create OrganizerData objects for all @organizer:
        # and @existing-organizer: nodes.
    t3 = time.clock()
    vc.create_actual_organizer_nodes()
        # Create the organizer nodes in holding cells so positions remain valid.
    t4 = time.clock()
    vc.create_tree_structure(root)
        # Set od.parent_od, od.children & od.descendants for all ods.
    t5 = time.clock()
    vc.compute_all_organized_positions(root)
        # Compute the positions organized by each organizer.
        # ** Most of the time is spent here **.
    t6 = time.clock()
    vc.create_anchors_d()
        # Create the dictionary that associates positions with ods.
    t7 = time.clock()
    if trace: g.trace(
        '\n  find_imported_organizer_nodes:   %4.2f sec' % (t2-t1),
        '\n  create_organizer_data:           %4.2f sec' % (t3-t2),
        '\n  create_actual_organizer_nodes:   %4.2f sec' % (t4-t3),
        '\n  create_tree_structure:           %4.2f sec' % (t5-t4),
        '\n  compute_all_organized_positions: %4.2f sec' % (t6-t5),
        '\n  create_anchors_d:                %4.2f sec' % (t7-t6))
#@+node:ekr.20150312225028.57: *7* 1: vc.find_imported_organizer_nodes
def find_imported_organizer_nodes(self,root):
    '''
    Put the VNode of all imported nodes with children on
    vc.imported_organizers_list.
    '''
    trace = False # and not g.unitTesting
    vc = self
    aList = []
    for p in root.subtree():
        if p.hasChildren():
            aList.append(p.v)
    vc.imported_organizers_list = list(set(aList))
    if trace: g.trace([z.h for z in vc.imported_organizers_list])
#@+node:ekr.20150312225028.58: *7* 2: vc.create_organizer_data (od.p & od.parent)
def create_organizer_data(self,at_organizers,root):
    '''
    Create OrganizerData nodes for all @organizer: and @existing-organizer:
    nodes in the given @organizers node.
    '''
    vc = self
    vc.create_ods(at_organizers)
    vc.finish_create_organizers(root)
    vc.finish_create_existing_organizers(root)
    for od in vc.all_ods:
        assert od.parent,(od.exists,od.h)
#@+node:ekr.20150312225028.59: *8* vc.create_ods
def create_ods(self,at_organizers):
    '''Create all organizer nodes and the associated lists.'''
    # Important: we must completely reinit all data here.
    vc = self
    tag1 = '@organizer:'
    tag2 = '@existing-organizer:'
    vc.all_ods,vc.existing_ods,vc.organizer_ods = [],[],[]
    for at_organizer in at_organizers.children():
        h = at_organizer.h
        for tag in (tag1,tag2):
            if h.startswith(tag):
                unls = vc.get_at_organizer_unls(at_organizer)
                if unls:
                    organizer_unl = vc.drop_unl_tail(unls[0])
                    h = h[len(tag):].strip()
                    od = OrganizerData(h,organizer_unl,unls)
                    vc.all_ods.append(od)
                    if tag == tag1:
                        vc.organizer_ods.append(od)
                        vc.organizer_unls.append(organizer_unl)
                    else:
                        vc.existing_ods.append(od)
                        # Do *not* append organizer_unl to the unl list.
                else:
                    g.trace('===== no unls:',at_organizer.h)
#@+node:ekr.20150312225028.60: *8* vc.finish_create_organizers
def finish_create_organizers(self,root):
    '''Finish creating all organizers.'''
    trace = False # and not g.unitTesting
    vc = self
    # Careful: we may delete items from this list.
    for od in vc.organizer_ods[:]: 
        od.source_unl = vc.source_unl(vc.organizer_unls,od.unl)
        od.parent = vc.find_position_for_relative_unl(root,od.source_unl)
        if od.parent:
            od.anchor = od.parent
            if trace: g.trace(od.h,
                # '\n  exists:',od.exists,
                # '\n  unl:',od.unl,
                # '\n  source (unl):',od.source_unl or repr(''),
                # '\n  anchor (pos):',od.anchor.h,
                # '\n  parent (pos):',od.parent.h,
            )
        else:
            # This is, most likely, a true error.
            g.trace('===== removing od:',od.h)
            vc.organizer_ods.remove(od)
            vc.all_ods.remove(od)
            assert od not in vc.existing_ods
            assert od not in vc.all_ods
#@+node:ekr.20150312225028.61: *8* vc.finish_create_existing_organizers
def finish_create_existing_organizers(self,root):
    '''Finish creating existing organizer nodes.'''
    trace = False # and not g.unitTesting
    vc = self
    # Careful: we may delete items from this list.
    for od in vc.existing_ods[:]:
        od.exists = True
        assert od.unl not in vc.organizer_unls
        od.source_unl = vc.source_unl(vc.organizer_unls,od.unl)
        od.p = vc.find_position_for_relative_unl(root,od.source_unl)
        if od.p:
            od.anchor = od.p
            assert od.p.h == od.h,(od.p.h,od.h)  
            od.parent = od.p # Here, od.parent represents the "source" p.
            if trace: g.trace(od.h,
                # '\n  exists:',od.exists,
                # '\n  unl:',od.unl,
                # '\n  source (unl):',od.source_unl or repr(''),
                # '\n  anchor (pos):',od.anchor.h,
                # '\n  parent (pos):',od.parent.h,
            )
        else:
            # This arises when the imported node name doesn't match.
            g.trace('===== removing existing organizer:',od.h)
            vc.existing_ods.remove(od)
            vc.all_ods.remove(od)
            assert od not in vc.existing_ods
            assert od not in vc.all_ods

#@+node:ekr.20150312225028.62: *7* 3: vc.create_actual_organizer_nodes
def create_actual_organizer_nodes(self):
    '''
    Create all organizer nodes as children of holding cells. These holding
    cells ensure that moving an organizer node leaves all other positions
    unchanged.
    '''
    vc = self
    c = vc.c
    last = c.lastTopLevel()
    temp = vc.temp_node = last.insertAfter()
    temp.h = 'ViewController.temp_node'
    for od in vc.organizer_ods:
        holding_cell = temp.insertAsLastChild()
        holding_cell.h = 'holding cell for ' + od.h
        od.p = holding_cell.insertAsLastChild()
        od.p.h = od.h
#@+node:ekr.20150312225028.63: *7* 4: vc.create_tree_structure & helper
def create_tree_structure(self,root):
    '''Set od.parent_od, od.children & od.descendants for all ods.'''
    trace = False and not g.unitTesting
    vc = self
    # if trace: g.trace([z.h for z in data_list],g.callers())
    organizer_unls = [z.unl for z in vc.all_ods]
    for od in vc.all_ods:
        for unl in od.unls:
            if unl in organizer_unls:
                i = organizer_unls.index(unl)
                d2 = vc.all_ods[i]
                # if trace: g.trace('found organizer unl:',od.h,'==>',d2.h)
                od.children.append(d2)
                d2.parent_od = od
    # create_organizer_data now ensures od.parent is set.
    for od in vc.all_ods:
        assert od.parent,od.h
    # Extend the descendant lists.
    for od in vc.all_ods:
        vc.compute_descendants(od)
        assert od.descendants is not None
    if trace:
        def tail(head,unl):
            return str(unl[len(head):]) if unl.startswith(head) else str(unl)
        for od in vc.all_ods:
            g.trace(
                '\n  od:',od.h,
                '\n  unl:',od.unl,
                '\n  unls:', [tail(od.unl,z) for z in od.unls],
                '\n  source (unl):',od.source_unl or repr(''),
                '\n  parent (pos):', od.parent.h,
                '\n  children:',[z.h for z in od.children],
                '\n  descendants:',[str(z.h) for z in od.descendants])
#@+node:ekr.20150312225028.64: *8* vc.compute_descendants
def compute_descendants(self,od,level=0,result=None):
    '''Compute the descendant od nodes of od.'''
    trace = False # and not g.unitTesting
    vc = self
    if level == 0:
        result = []
    if od.descendants is None:
        for child in od.children:
            result.append(child)
            result.extend(vc.compute_descendants(child,level+1,result))
            result = list(set(result))
        if level == 0:
            od.descendants = result
            if trace: g.trace(od.h,[z.h for z in result])
        return result
    else:
        if trace: g.trace('cached',od.h,[z.h for z in od.descendants])
        return od.descendants
#@+node:ekr.20150312225028.65: *7* 5: vc.compute_all_organized_positions
def compute_all_organized_positions(self,root):
    '''Compute the list of positions organized by every od.'''
    trace = False and not g.unitTesting
    vc = self
    for od in vc.all_ods:
        if od.unls:
            # Do a full search only for the first unl.
            # parent = vc.find_position_for_relative_unl(root,od.unls[0])
            if True: # parent:
                for unl in od.unls:
                    p = vc.find_position_for_relative_unl(root,unl)
                    # p = vc.find_position_for_relative_unl(parent,vc.unl_tail(unl))
                    if p:
                        od.organized_nodes.append(p.copy())
                    if trace: g.trace('exists:',od.exists,
                        'od:',od.h,'unl:',unl,
                        'p:',p and p.h or '===== None')
            else:
                g.trace('fail',od.unls[0])
#@+node:ekr.20150312225028.66: *7* 6: vc.create_anchors_d
def create_anchors_d (self):
    '''
    Create vc.anchors_d.
    Keys are positions, values are lists of ods having that anchor.
    '''
    trace = False # and not g.unitTesting
    vc = self
    d = {}
    if trace: g.trace('all_ods',[z.h for z in vc.all_ods])
    for od in vc.all_ods:
        # Compute the anchor if it does not yet exists.
        # Valid now that p.__hash__ exists.
        key = od.anchor
        # key = '.'.join([str(z) for z in od.anchor.sort_key(od.anchor)])
        # key = '%s (%s)' % (key,od.anchor.h)
        aList = d.get(key,[])
        # g.trace(od.h,od.anchor.h,key,aList)
        aList.append(od)
        d[key] = aList
    if trace:
        for key in sorted(d.keys()):
            g.trace('od.anchor: %s ods: [%s]' % (key.h,','.join(z.h for z in d.get(key))))
    vc.anchors_d = d
#@+node:ekr.20150312225028.67: *6* vc.demote & helpers
def demote(self,root):
    '''
    The main line of the @auto-view algorithm. Traverse root's entire tree,
    placing items on the global work list.
    '''
    trace = False # and not g.unitTesting
    trace_loop = True
    vc = self
    active = None # The active od.
    vc.pending = [] # Lists of pending demotions.
    d = vc.anchor_offset_d # For traces.
    for p in root.subtree():
        parent = p.parent()
        if trace and trace_loop:
            if 1:
                g.trace('-----',p.childIndex(),p.h)
            else:
                g.trace(
                    '=====\np:',p.h,
                    'childIndex',p.childIndex(),
                    '\nparent:',parent.h,
                    'parent:offset',d.get(parent,0))
        vc.n_nodes_scanned += 1
        vc.terminate_organizers(active,parent)
        found = vc.find_organizer(parent,p)
        if found:
            pass # vc.enter_organizers(found,p)
        else:
            pass # vc.terminate_all_open_organizers()
        if trace and trace_loop:
            g.trace(
                'active:',active and active.h or 'None',
                'found:',found and found.h or 'None')
        # The main case statement...
        if found is None and active:
            vc.add_to_pending(active,p)
        elif found is None and not active:
            # Pending nodes will *not* be organized.
            vc.clear_pending(None,p)
        elif found and found == active:
            # Pending nodes *will* be organized.
            for z in vc.pending:
                active2,child2 = z
                vc.add(active2,child2,'found==active:pending')
            vc.pending = []
            vc.add(active,p,'found==active')
        elif found and found != active:
            # Pending nodes will *not* be organized.
            vc.clear_pending(found,p)
            active = found
            vc.enter_organizers(found,p)
            vc.add(active,p,'found!=active')
        else: assert False,'can not happen'
#@+node:ekr.20150312225028.68: *7* vc.add
def add(self,active,p,tag):
    '''
    Add p, an existing (imported) node to the global work list.
    Subtract 1 from the vc.anchor_offset_d entry for p.parent().
    
    Exception: do *nothing* if p is a child of an existing organizer node.
    '''
    trace = False # and not g.unitTesting
    verbose = False
    vc = self
    # g.trace(active,g.callers())
    if active.p == p.parent() and active.exists:
        if trace and verbose: g.trace('===== do nothing',active.h,p.h)
    else:
        data = active.p,p.copy()
        vc.add_to_work_list(data,tag)
        vc.anchor_decr(anchor=p.parent(),p=p)
        
#@+node:ekr.20150312225028.69: *7* vc.add_organizer_node
def add_organizer_node (self,od,p):
    '''
    Add od to the appropriate move list.
    p is the existing node that caused od to be added.
    '''
    trace = True # and not g.unitTesting
    verbose = False
    vc = self
    # g.trace(od.h,'parent',od.parent_od and od.parent_od.h or 'None')
    if od.parent_od:
        # Not a bare organizer: a child of another organizer node.
        # If this is an existing organizer, it's *position* may have
        # been moved without active.moved being set.
        data = od.parent_od.p,od.p
        if data in vc.work_list:
            if trace and verbose: g.trace(
                '**** duplicate 1: setting moved bit.',od.h)
            od.moved = True
        elif od.parent_od.exists:    
            anchor = od.parent_od.p
            n = vc.anchor_incr(anchor,p) + p.childIndex()
            data = anchor,od.p,n
            # g.trace('anchor:',anchor.h,'p:',p.h,'childIndex',p.childIndex())
            vc.add_to_bare_list(data,'non-bare existing')
        else:
            vc.add_to_work_list(data,'non-bare')
    elif od.p == od.anchor:
        if trace and verbose: g.trace(
            '***** existing organizer: do not move:',od.h)
    else:
        # This can be pre-computed?
        bare_list = [p for parent,p,n in vc.global_bare_organizer_node_list]
        if od.p in bare_list:
            if trace and verbose: g.trace(
                '**** duplicate 2: setting moved bit.',od.h)
            od.moved = True
        else:
            # A bare organizer node: a child of an *ordinary* node.
            anchor = p.parent()
            n = vc.anchor_incr(anchor,p) + p.childIndex()
            data = anchor,od.p,n
            vc.add_to_bare_list(data,'bare')
#@+node:ekr.20150312225028.70: *7* vc.add_to_bare_list
def add_to_bare_list(self,data,tag):
    '''Add data to the bare organizer list, with tracing.'''
    trace = False # and not g.unitTesting
    vc = self
    # Prevent duplicagtes.
    anchor,p,n = data
    for data2 in vc.global_bare_organizer_node_list:
        a2,p2,n2 = data2
        if p == p2:
            if trace: g.trace('ignore duplicate',
                'n:',n,anchor.h,'==>',p.h)
            return
    vc.global_bare_organizer_node_list.append(data)
    if trace:
        anchor,p,n = data
        g.trace('=====',tag,'n:',n,anchor.h,'==>',p.h)
            # '\n  anchor:',anchor.h,
            # '\n  p:',p.h)
#@+node:ekr.20150312225028.71: *7* vc.add_to_pending
def add_to_pending(self,active,p):
    trace = False # and not g.unitTesting
    vc = self
    if trace: g.trace(active.p.h,'==>',p.h)
    vc.pending.append((active,p.copy()),)
#@+node:ekr.20150312225028.72: *7* vc.add_to_work_list
def add_to_work_list(self,data,tag):
    '''Append the data to the work list, with tracing.'''
    trace = False # and not g.unitTesting
    vc = self
    vc.work_list.append(data)
    if trace:
        active,p = data
        g.trace('=====',tag,active.h,'==>',p.h)
#@+node:ekr.20150312225028.73: *7* vc.anchor_decr
def anchor_decr(self,anchor,p): # p is only for traces.
    '''
    Decrement the anchor dict for the given anchor node.
    Return the *previous* value.
    '''
    trace = False # and not g.unitTesting
    vc = self
    d = vc.anchor_offset_d
    n = d.get(anchor,0)
    d[anchor] = n - 1
    if trace: g.trace(n-1,anchor.h,'==>',p.h)
    return n
#@+node:ekr.20150312225028.74: *7* vc.anchor_incr
def anchor_incr(self,anchor,p): # p is only for traces.
    '''
    Increment the anchor dict for the given anchor node.
    Return the *previous* value.
    '''
    trace = False # and not g.unitTesting
    vc = self
    d = vc.anchor_offset_d
    n = d.get(anchor,0)
    d[anchor] = n + 1
    if trace: g.trace(n+1,anchor.h,'==>',p.h)
    return n
#@+node:ekr.20150312225028.75: *7* vc.clear_pending
def clear_pending(self,active,p):
    '''Clear the appropriate entries from the pending list.'''
    trace = False # and not g.unitTesting
    vc = self
    if trace: g.trace('===== clear pending',len(vc.pending))
    if False: # active and active.parent_od:
        for data in vc.pending:
            data = active.parent_od.p,data[1]
            vc.add_to_work_list(data,'clear-pending-to-active')
    vc.pending = []
#@+node:ekr.20150312225028.76: *7* vc.enter_organizers
def enter_organizers(self,od,p):
    '''Enter all organizers whose anchors are p.'''
    vc = self
    ods = []
    while od:
        ods.append(od)
        od = od.parent_od
    if ods:
        for od in reversed(ods):
            vc.add_organizer_node(od,p)
#@+node:ekr.20150312225028.77: *7* vc.find_organizer
def find_organizer(self,parent,p):
    '''Return the organizer that organizers p, if any.'''
    trace = False # and not g.unitTesting
    vc = self
    anchor = parent
    ods = vc.anchors_d.get(anchor,[])
    for od in ods:
        if p in od.organized_nodes:
            if trace: g.trace('found:',od.h,'for',p.h)
            return od
    return None
#@+node:ekr.20150312225028.78: *7* vc.terminate_organizers
def terminate_organizers(self,active,p):
    '''Terminate all organizers whose anchors are not ancestors of p.'''
    trace = False # and not g.unitTesting
    od = active
    while od and od.anchor != p and od.anchor.isAncestorOf(p):
        if not od.closed:
            if trace: g.trace('===== closing',od.h)
            od.closed = True
        od = od.parent_od
#@+node:ekr.20150312225028.79: *7* vc.terminate_all_open_organizers
def terminate_all_open_organizers(self):
    '''Terminate all open organizers.'''
    trace = True # and not g.unitTesting
    if 0:
        g.trace()
        for od in self.all_ods:
            if od.opened and not od.closed:
                if trace: g.trace('===== closing',od.h)
                od.closed = True
#@+node:ekr.20150312225028.80: *6* vc.move_nodes & helpers
def move_nodes(self):
    '''Move nodes to their final location and delete the temp node.'''
    trace = False # and not g.unitTesting
    vc = self
    vc.move_nodes_to_organizers(trace)
    vc.move_bare_organizers(trace)
    vc.temp_node.doDelete()
#@+node:ekr.20150312225028.81: *7* vc.move_nodes_to_organizers
def move_nodes_to_organizers(self,trace):
    '''Move all nodes in the work_list.'''
    trace = False # and not g.unitTesting
    trace_dict = False
    trace_moves = False
    trace_deletes = False
    vc = self
    if trace: # A highly useful trace!
        g.trace('\n\nunsorted_list...\n%s' % (
            '\n'.join(['%40s ==> %s' % (parent.h,p.h)
                for parent,p in vc.work_list])))
    # Create a dictionary of each organizers children.
    d = {}
    for parent,p in vc.work_list:
        # This key must remain stable if parent moves.
        key = parent
        aList = d.get(key,[])
        aList.append(p)
        # g.trace(key,[z.h for z in aList])
        d[key] = aList
    if trace and trace_dict:
        # g.trace('d...',sorted([z.h for z in d.keys()]))
        g.trace('d{}...')
        for key in sorted(d.keys()):
            aList = [z.h for z in d.get(key)]
            g.trace('%s %-20s %s' % (id(key),key.h,vc.dump_list(aList,indent=29)))
    # Move *copies* of non-organizer nodes to each organizer.
    organizers = list(d.keys())
    existing_organizers = [z.p.copy() for z in vc.existing_ods]
    moved_existing_organizers = {} # Keys are vnodes, values are positions.
    for parent in organizers:
        aList = d.get(parent,[])
        if trace and trace_moves:
            g.trace('===== moving/copying:',parent.h,
                'with %s children:' % (len(aList)),
                '\n  '+'\n  '.join([z.h for z in aList]))
        for p in aList:
            if p in existing_organizers:
                if trace and trace_moves:
                    g.trace('copying existing organizer:',p.h)
                    g.trace('children:',
                    '\n  '+'\n  '.join([z.h for z in p.children()]))
                copy = vc.copy_tree_to_last_child_of(p,parent)
                old = moved_existing_organizers.get(p.v)
                if old and trace_moves:
                    g.trace('*********** overwrite',p.h)
                moved_existing_organizers[p.v] = copy
            elif p in organizers:
                if trace and trace_moves:
                    g.trace('moving organizer:',p.h)
                aList = d.get(p)
                if aList:
                    if trace and trace_moves: g.trace('**** relocating',
                        p.h,'children:',
                        '\n  '+'\n  '.join([z.h for z in p.children()]))
                    del d[p]
                p.moveToLastChildOf(parent)
                if aList:
                    d[p] = aList
            else:
                parent2 = moved_existing_organizers.get(parent.v)
                if parent2:
                    if trace and trace_moves:
                        g.trace('***** copying to relocated parent:',p.h)
                    vc.copy_tree_to_last_child_of(p,parent2)
                else:
                    if trace and trace_moves: g.trace('copying:',p.h)
                    vc.copy_tree_to_last_child_of(p,parent)
    # Finally, delete all the non-organizer nodes, in reverse outline order.
    def sort_key(od):
        parent,p = od
        return p.sort_key(p)
    sorted_list = sorted(vc.work_list,key=sort_key)
    if trace and trace_deletes:
        g.trace('===== deleting nodes in reverse outline order...')
    for parent,p in reversed(sorted_list):
        if p.v in moved_existing_organizers:
            if trace and trace_deletes:
                g.trace('deleting moved existing organizer:',p.h)
            p.doDelete()
        elif p not in organizers:
            if trace and trace_deletes:
                g.trace('deleting non-organizer:',p.h)
            p.doDelete()
#@+node:ekr.20150312225028.82: *7* vc.move_bare_organizers
def move_bare_organizers(self,trace):
    '''Move all nodes in global_bare_organizer_node_list.'''
    trace = False # and not g.unitTesting
    trace_data = True
    trace_move = True
    vc = self
    # For each parent, sort nodes on n.
    d = {} # Keys are vnodes, values are lists of tuples (n,parent,p)
    existing_organizers = [od.p for od in vc.existing_ods]
    if trace: g.trace('ignoring existing organizers:',
        [p.h for p in existing_organizers])
    for parent,p,n in vc.global_bare_organizer_node_list:
        if p not in existing_organizers:
            key = parent.v
            aList = d.get(key,[])
            if (parent,p,n) not in aList:
                aList.append((parent,p,n),)
                d[key] = aList
    # For each parent, add nodes in childIndex order.
    def key_func(obj):
        return obj[0]
    for key in d.keys():
        aList = d.get(key)
        for data in sorted(aList,key=key_func):
            parent,p,n = data
            n2 = parent.numberOfChildren()
            if trace and trace_data:
                g.trace(n,parent.h,'==>',p.h)
            if trace and trace_move: g.trace(
                'move: %-20s:' % (p.h),
                'to child: %2s' % (n),
                'of: %-20s' % (parent.h),
                'with:',n2,'children')
            p.moveToNthChildOf(parent,n)
#@+node:ekr.20150312225028.83: *7* vc.copy_tree_to_last_child_of
def copy_tree_to_last_child_of(self,p,parent):
    '''Copy p's tree to the last child of parent.'''
    vc = self
    assert p != parent,p
        # A failed assert leads to unbounded recursion.
    # print('copy_tree_to_last_child_of',p.h,parent.h)
    root = parent.insertAsLastChild()
    root.b,root.h = p.b,p.h
    root.v.u = copy.deepcopy(p.v.u)
    for child in p.children():
        vc.copy_tree_to_last_child_of(child,root)
    return root
#@+node:ekr.20150312225028.84: *5* vc.Helpers
#@+node:ekr.20150312225028.85: *6* vc.at_auto_view_body and match_at_auto_body
def at_auto_view_body(self,p):
    '''Return the body text for the @auto-view node for p.'''
    # Note: the unl of p relative to p is simply p.h,
    # so it is pointless to add that to the @auto-view node.
    return 'gnx: %s\n' % p.v.gnx

def match_at_auto_body(self,p,auto_view):
    '''Return True if any line of auto_view.b matches the expected gnx line.'''
    if 0: g.trace(p.b == 'gnx: %s\n' % auto_view.v.gnx,
        g.shortFileName(p.h),auto_view.v.gnx,p.b.strip())
    return p.b == 'gnx: %s\n' % auto_view.v.gnx
#@+node:ekr.20150312225028.86: *6* vc.clean_nodes (not used)
def clean_nodes(self):
    '''Delete @auto-view nodes with no corresponding @auto nodes.'''
    vc = self
    c = vc.c
    views = vc.has_at_views_node()
    if not views:
        return
    # Remember the gnx of all @auto nodes.
    d = {}
    for p in c.all_unique_positions():
        if vc.is_at_auto_node(p):
            d[p.v.gnx] = True
    # Remember all unused @auto-view nodes.
    delete = []
    for child in views.children():
        s = child.b and g.splitlines(child.b)
        gnx = s[len('gnx'):].strip()
        if gnx not in d:
            g.trace(child.h,gnx)
            delete.append(child.copy())
    for p in reversed(delete):
        p.doDelete()
    c.selectPosition(views)
#@+node:ekr.20150312225028.87: *6* vc.comments...
#@+node:ekr.20150312225028.88: *7* vc.comment_delims
def comment_delims(self,p):
    '''Return the comment delimiter in effect at p, an @auto node.'''
    vc = self
    c = vc.c
    d = g.get_directives_dict(p)
    s = d.get('language') or c.target_language
    language,single,start,end = g.set_language(s,0)
    return single,start,end
#@+node:ekr.20150312225028.89: *7* vc.delete_leading_comments
def delete_leading_comments(self,delims,p):
    '''
    Scan for leading comments from p and return them.
    At present, this only works for single-line comments.
    '''
    single,start,end = delims
    if single:
        lines = g.splitLines(p.b)
        result = []
        for s in lines:
            if s.strip().startswith(single):
                result.append(s)
            else: break
        if result:
            p.b = ''.join(lines[len(result):])
            # g.trace('len(result)',len(result),p.h)
            return ''.join(result)
    return None
#@+node:ekr.20150312225028.90: *7* vc.is_comment_node
def is_comment_node(self,p,root,delims=None):
    '''Return True if p.b contains nothing but comments or blank lines.'''
    vc = self
    if not delims:
        delims = vc.comment_delims(root)
    # pylint: disable=unpacking-non-sequence
    single,start,end = delims
    assert single or start and end,'bad delims: %r %r %r' % (single,start,end)
    if single:
        for s in g.splitLines(p.b):
            s = s.strip()
            if s and not s.startswith(single) and not g.isDirective(s):
                return False
        return True
    else:
        def check_comment(s):
            done,in_comment = False,True
            i = s.find(end)
            if i > -1:
                tail = s[i+len(end):].strip()
                if tail: done = True
                else: in_comment = False
            return done,in_comment
        
        done,in_comment = False,False
        for s in g.splitLines(p.b):
            s = s.strip()
            if not s:
                pass
            elif in_comment:
                done,in_comment = check_comment(s)
            elif g.isDirective(s):
                pass
            elif s.startswith(start):
                done,in_comment = check_comment(s[len(start):])
            else:
                # g.trace('fail 1: %r %r %r...\n%s' % (single,start,end,s)
                return False
            if done:
                return False
        # All lines pass.
        return True
#@+node:ekr.20150312225028.91: *7* vc.is_comment_organizer_node
# def is_comment_organizer_node(self,p,root):
    # '''
    # Return True if p is an organizer node in the given @auto tree.
    # '''
    # return p.hasChildren() and vc.is_comment_node(p,root)
#@+node:ekr.20150312225028.92: *7* vc.post_move_comments
def post_move_comments(self,root):
    '''Move comments from the start of nodes to their parent organizer node.'''
    vc = self
    c = vc.c
    delims = vc.comment_delims(root)
    for p in root.subtree():
        if p.hasChildren() and not p.b:
            s = vc.delete_leading_comments(delims,p.firstChild())
            if s:
                p.b = s
                # g.trace(p.h)
#@+node:ekr.20150312225028.93: *7* vc.pre_move_comments
def pre_move_comments(self,root):
    '''
    Move comments from comment nodes to the next node.
    This must be done before any other processing.
    '''
    vc = self
    c = vc.c
    delims = vc.comment_delims(root)
    aList = []
    for p in root.subtree():
        if p.hasNext() and vc.is_comment_node(p,root,delims=delims):
            aList.append(p.copy())
            next = p.next()
            if p.b: next.b = p.b + next.b
    # g.trace([z.h for z in aList])
    c.deletePositionsInList(aList)
        # This sets c.changed.
#@+node:ekr.20150312225028.94: *6* vc.find...
# The find commands create the node if not found.
#@+node:ekr.20150312225028.95: *7* vc.find_absolute_unl_node
def find_absolute_unl_node(self,unl,priority_header=False):
    '''Return a node matching the given absolute unl.
    If priority_header == True and the node is not found, it will return the longest matching UNL starting from the tail
    '''
    import re
    pos_pattern = re.compile(r':(\d+),?(\d+)?$')
    vc = self
    aList = unl.split('-->')
    if aList:
        first,rest = aList[0],'-->'.join(aList[1:])
        count = 0
        pos = re.findall(pos_pattern,first)
        nth_sib,pos = pos[0] if pos else (0,0)
        pos = int(pos) if pos else 0
        nth_sib = int(nth_sib)
        first = re.sub(pos_pattern,"",first).replace('--%3E','-->')
        for parent in vc.c.rootPosition().self_and_siblings():
            if parent.h.strip() == first.strip():
                if pos == count:
                    if rest:
                        return vc.find_position_for_relative_unl(parent,rest,priority_header=priority_header)
                    else:
                        return parent
                count = count+1
        #Here we could find and return the nth_sib if an exact header match was not found
    return None
#@+node:ekr.20150312225028.96: *7* vc.find_at_auto_view_node & helper
def find_at_auto_view_node (self,root):
    '''
    Return the @auto-view node for root, an @auto node.
    Create the node if it does not exist.
    '''
    vc = self
    views = vc.find_at_views_node()
    p = vc.has_at_auto_view_node(root)
    if not p:
        p = views.insertAsLastChild()
        p.h = '@auto-view:' + root.h[len('@auto'):].strip()
        p.b = vc.at_auto_view_body(root)
    return p
#@+node:ekr.20150312225028.97: *7* vc.find_clones_node
def find_at_clones_node(self,root):
    '''
    Find the @clones node for root, an @auto node.
    Create the @clones node if it does not exist.
    '''
    vc = self
    c = vc.c
    h = '@clones'
    auto_view = vc.find_at_auto_view_node(root)
    p = g.findNodeInTree(c,auto_view,h)
    if not p:
        p = auto_view.insertAsLastChild()
        p.h = h
    return p
#@+node:ekr.20150312225028.98: *7* vc.find_at_headlines_node
def find_at_headlines_node(self,root):
    '''
    Find the @headlines node for root, an @auto node.
    Create the @headlines node if it does not exist.
    '''
    vc = self
    c = vc.c
    h = '@headlines'
    auto_view = vc.find_at_auto_view_node(root)
    p = g.findNodeInTree(c,auto_view,h)
    if not p:
        p = auto_view.insertAsLastChild()
        p.h = h
    return p
#@+node:ekr.20150312225028.99: *7* vc.find_gnx_node
def find_gnx_node(self,gnx):
    '''Return the first position having the given gnx.'''
    # This is part of the read logic, so newly-imported
    # nodes will never have the given gnx.
    vc = self
    for p in vc.c.all_unique_positions():
        if p.v.gnx == gnx:
            return p
    return None
#@+node:ekr.20150312225028.100: *7* vc.find_organizers_node
def find_at_organizers_node(self,root):
    '''
    Find the @organizers node for root, and @auto node.
    Create the @organizers node if it doesn't exist.
    '''
    vc = self
    c = vc.c
    h = '@organizers'
    auto_view = vc.find_at_auto_view_node(root)
    p = g.findNodeInTree(c,auto_view,h)
    if not p:
        p = auto_view.insertAsLastChild()
        p.h = h
    return p
#@+node:ekr.20150312225028.101: *7* vc.find_position_for_relative_unl
def find_position_for_relative_unl(self,parent,unl,priority_header=False):
    '''
    Return the node in parent's subtree matching the given unl.
    The unl is relative to the parent position.
    If priority_header == True and the node is not found, it will return the longest matching UNL starting from the tail
    '''
    # This is called from finish_create_organizers & compute_all_organized_positions.
    trace = False # and not g.unitTesting
    trace_loop = True
    trace_success = False
    vc = self
    if not unl:
        if trace and trace_success:
            g.trace('return parent for empty unl:',parent.h)
        return parent
    # The new, simpler way: drop components of the unl automatically.
    drop,p = [],parent # for debugging.
    # if trace: g.trace('p:',p.h,'unl:',unl)
    import re
    pos_pattern = re.compile(r':(\d+),?(\d+)?$')
    for s in unl.split('-->'):
        found = False # The last part must match.
        if 1:
            # Create the list of children on the fly.
            aList = vc.headlines_dict.get(p.v)
            if aList is None:
                aList = [z.h for z in p.children()]
                vc.headlines_dict[p.v] = aList
            try:
                pos = re.findall(pos_pattern,s)
                nth_sib,pos = pos[0] if pos else (0,0)
                pos = int(pos) if pos else 0
                nth_sib = int(nth_sib)
                s = re.sub(pos_pattern,"",s).replace('--%3E','-->')
                indices = [i for i, x in enumerate(aList) if x == s]
                if len(indices)>pos:
                    #First we try the nth node with same header
                    n = indices[pos]
                    p = p.nthChild(n)
                    found = True
                elif len(indices)>0:
                    #Then we try any node with same header
                    n = indices[-1]
                    p = p.nthChild(n)
                    found = True
                elif not priority_header:
                    #Then we go for the child index if return_pos is true
                    if len(aList)>nth_sib:
                        n = nth_sib
                    else:
                        n = len(aList)-1
                    if n>-1:
                        p = p.nthChild(n)
                    else:
                        g.es('Partial UNL match: Referenced level is higher than '+str(p.level()))
                    found = True
                if trace and trace_loop: g.trace('match:',s)
            except ValueError: # s not in aList.
                if trace and trace_loop: g.trace('drop:',s)
                drop.append(s)
        else: # old code.
            for child in p.children():
                if child.h == s:
                    p = child
                    found = True
                    if trace and trace_loop: g.trace('match:',s)
                    break
                # elif trace and trace_loop: g.trace('no match:',child.h)
            else:
                if trace and trace_loop: g.trace('drop:',s)
                drop.append(s)
    if not found and priority_header:
        aList = []
        for p in vc.c.all_unique_positions():
            if p.h.replace('--%3E','-->') in unl:
                aList.append((p.copy(),p.get_UNL(False,False,True)))
        unl_list = [re.sub(pos_pattern,"",x).replace('--%3E','-->') for x in unl.split('-->')]
        for iter_unl in aList:
            maxcount = 0
            count = 0
            compare_list = unl_list[:]
            for header in reversed(iter_unl[1].split('-->')):
                if re.sub(pos_pattern,"",header).replace('--%3E','-->') == compare_list[-1]:
                    count = count+1
                    compare_list.pop(-1)
                else:
                    break
            if count > maxcount:
                p = iter_unl[0]
                found = True
    if found:
        if trace and trace_success:
            g.trace('found unl:',unl,'parent:',p.h,'drop',drop)
    else:
        if trace: g.trace('===== unl not found:',unl,'parent:',p.h,'drop',drop)
    return p if found else None
#@+node:ekr.20150312225028.102: *7* vc.find_representative_node
def find_representative_node (self,root,target):
    '''
    root is an @auto node. target is a clones node within root's tree.
    Return a node *outside* of root's tree that is cloned to target,
    preferring nodes outside any @<file> tree.
    Never return any node in any @views or @view tree.
    '''
    trace = False and not g.unitTesting
    assert target
    assert root
    vc = self
    # Pass 1: accept only nodes outside any @file tree.
    p = vc.c.rootPosition()
    while p:
        if p.h.startswith('@view'):
            p.moveToNodeAfterTree()
        elif p.isAnyAtFileNode():
            p.moveToNodeAfterTree()
        elif p.v == target.v:
            if trace: g.trace('success 1:',p,p.parent())
            return p
        else:
            p.moveToThreadNext()
    # Pass 2: accept any node outside the root tree.
    p = vc.c.rootPosition()
    while p:
        if p.h.startswith('@view'):
            p.moveToNodeAfterTree()
        elif p == root:
            p.moveToNodeAfterTree()
        elif p.v == target.v:
            if trace: g.trace('success 2:',p,p.parent())
            return p
        else:
            p.moveToThreadNext()
    g.trace('no representative node for:',target,'parent:',target.parent())
    return None
#@+node:ekr.20150312225028.103: *7* vc.find_views_node
def find_at_views_node(self):
    '''
    Find the first @views node in the outline.
    If it does not exist, create it as the *last* top-level node,
    so that no existing positions become invalid.
    '''
    vc = self
    c = vc.c
    p = g.findNodeAnywhere(c,'@views')
    if not p:
        last = c.rootPosition()
        while last.hasNext():
            last.moveToNext()
        p = last.insertAfter()
        p.h = '@views'
        # c.selectPosition(p)
        # c.redraw()
    return p
#@+node:ekr.20150312225028.104: *6* vc.has...
# The has commands return None if the node does not exist.
#@+node:ekr.20150312225028.105: *7* vc.has_at_auto_view_node
def has_at_auto_view_node(self,root):
    '''
    Return the @auto-view node corresponding to root, an @root node.
    Return None if no such node exists.
    '''
    vc = self
    c = vc.c
    assert vc.is_at_auto_node(root) or vc.is_at_file_node(root),root
    views = g.findNodeAnywhere(c,'@views')
    if views:
        # Find a direct child of views with matching headline and body.
        for p in views.children():
            if vc.match_at_auto_body(p,root):
                return p
    return None
#@+node:ekr.20150312225028.106: *7* vc.has_clones_node
def has_at_clones_node(self,root):
    '''
    Find the @clones node for an @auto node with the given unl.
    Return None if it does not exist.
    '''
    vc = self
    p = vc.has_at_auto_view_node(root)
    return p and g.findNodeInTree(vc.c,p,'@clones')
#@+node:ekr.20150312225028.107: *7* vc.has_at_headlines_node
def has_at_headlines_node(self,root):
    '''
    Find the @clones node for an @auto node with the given unl.
    Return None if it does not exist.
    '''
    vc = self
    p = vc.has_at_auto_view_node(root)
    return p and g.findNodeInTree(vc.c,p,'@headlines')
#@+node:ekr.20150312225028.108: *7* vc.has_organizers_node
def has_at_organizers_node(self,root):
    '''
    Find the @organizers node for root, an @auto node.
    Return None if it does not exist.
    '''
    vc = self
    p = vc.has_at_auto_view_node(root)
    return p and g.findNodeInTree(vc.c,p,'@organizers')
#@+node:ekr.20150312225028.109: *7* vc.has_views_node
def has_at_views_node(self):
    '''Return the @views or None if it does not exist.'''
    vc = self
    return g.findNodeAnywhere(vc.c,'@views')
#@+node:ekr.20150312225028.110: *6* vc.is...
#@+node:ekr.20150312225028.111: *7* vc.is_at_auto_node
def is_at_auto_node(self,p):
    '''Return True if p is an @auto node.'''
    return g.match_word(p.h,0,'@auto') and not g.match(p.h,0,'@auto-')
        # Does not match @auto-rst, etc.

def is_at_file_node(self,p):
    '''Return True if p is an @file node.'''
    return g.match_word(p.h,0,'@file')
#@+node:ekr.20150312225028.112: *7* vc.is_cloned_outside_parent_tree
def is_cloned_outside_parent_tree(self,p):
    '''Return True if a clone of p exists outside the tree of p.parent().'''
    return len(list(set(p.v.parents))) > 1
#@+node:ekr.20150312225028.113: *7* vc.is_organizer_node
def is_organizer_node(self,p,root):
    '''
    Return True if p is an organizer node in the given @auto tree.
    '''
    vc = self
    return p.hasChildren() and vc.is_comment_node(p,root)

#@+node:ekr.20150312225028.114: *6* vc.testing...
#@+node:ekr.20150312225028.115: *7* vc.compare_test_trees
def compare_test_trees(self,root1,root2):
    '''
    Compare the subtrees whose roots are given.
    This is called only from unit tests.
    '''
    vc = self
    s1,s2 = vc.trial_write(root1),vc.trial_write(root2)
    if s1 == s2:
        return True
    g.trace('Compare:',root1.h,root2.h)
    p2 = root2.copy().moveToThreadNext()
    for p1 in root1.subtree():
        if p1.h == p2.h:
            g.trace('Match:',p1.h)
        else:
            g.trace('Fail: %s != %s' % (p1.h,p2.h))
            break
        p2.moveToThreadNext()
    return False
#@+node:ekr.20150312225028.116: *7* vc.compare_trial_writes
def compare_trial_writes(self,s1,s2):
    '''
    Compare the two strings, the results of trial writes.
    Stop the comparison after the first mismatch.
    '''
    trace_matches = False
    full_compare = False
    lines1,lines2 = g.splitLines(s1),g.splitLines(s2)
    i,n1,n2 = 0,len(lines1),len(lines2)
    while i < n1 and i < n2:
        s1,s2 = lines1[i].rstrip(),lines2[i].rstrip()
        i += 1
        if s1 == s2:
            if trace_matches: g.trace('Match:',s1)
        else:
            g.trace('Fail:  %s != %s' % (s1,s2))
            if not full_compare: return
    if i < n1:
        g.trace('Extra line 1:',lines1[i])
    if i < n2:
        g.trace('Extra line 2:',lines2[i])
#@+node:ekr.20150312225028.117: *7* vc.dump_list
def dump_list(self,aList,indent=4):
    '''Dump a list, one item per line.'''
    lead = '\n' + ' '*indent
    return lead+lead.join(sorted(aList))
#@+node:ekr.20150312225028.118: *7* vc.trial_write
def trial_write(self,root):
    '''
    Return a trial write of outline whose root is given.
    
    **Important**: the @auto import and write code end all nodes with
    newlines. Because no imported nodes are empty, the code below is
    *exactly* equivalent to the @auto write code as far as trailing
    newlines are concerned. Furthermore, we can treat Leo directives as
    ordinary text here.
    '''
    vc = self
    if 1:
        # Do a full trial write, exactly as will be done later.
        at = vc.c.atFileCommands
        ok = at.writeOneAtAutoNode(root,
            toString=True,force=True,trialWrite=True)
        if ok:
            return at.stringOutput
        else:
            g.trace('===== can not happen')
            return ''
    elif 1:
        # Concatenate all body text.  Close, but not exact.
        return ''.join([p.b for p in root.self_and_subtree()])
    else:
        # Compare headlines, ignoring nodes without body text and comment nodes.
        # This was handy during early development.
        return '\n'.join([p.h for p in root.self_and_subtree()
            if p.b and not p.h.startswith('#')])
#@+node:ekr.20150312225028.119: *6* vc.unls...
#@+node:ekr.20150312225028.120: *7* vc.drop_all_organizers_in_unl
def drop_all_organizers_in_unl(self,organizer_unls,unl):
    '''Drop all organizer unl's in unl, recreating the imported unl.'''
    vc = self
    def unl_sort_key(s):
        return s.count('-->')
    for s in reversed(sorted(organizer_unls,key=unl_sort_key)):
        if unl.startswith(s):
            s2 = vc.drop_unl_tail(s)
            unl = s2 + unl[len(s):]
    return unl[3:] if unl.startswith('-->') else unl
#@+node:ekr.20150312225028.121: *7* vc.drop_unl_tail & vc.drop_unl_parent
def drop_unl_tail(self,unl):
    '''Drop the last part of the unl.'''
    return '-->'.join(unl.split('-->')[:-1])

def drop_unl_parent(self,unl):
    '''Drop the penultimate part of the unl.'''
    aList = unl.split('-->')
    return '-->'.join(aList[:-2] + aList[-1:])
#@+node:ekr.20150312225028.122: *7* vc.get_at_organizer_unls
def get_at_organizer_unls(self,p):
    '''Return the unl: lines in an @organizer: node.'''
    return [s[len('unl:'):].strip()
        for s in g.splitLines(p.b)
            if s.startswith('unl:')]

#@+node:ekr.20150312225028.123: *7* vc.relative_unl & unl
def relative_unl(self,p,root):
    '''Return the unl of p relative to the root position.'''
    vc = self
    result = []
    ivar = vc.headline_ivar
    for p in p.self_and_parents():
        if p == root:
            break
        else:
            h = getattr(p.v,ivar,p.h)
            result.append(h)
    return '-->'.join(reversed(result))

def unl(self,p):
    '''Return the unl corresponding to the given position.'''
    vc = self
    return '-->'.join(reversed([
        getattr(p.v,vc.headline_ivar,p.h)
            for p in p.self_and_parents()]))
    # return '-->'.join(reversed([p.h for p in p.self_and_parents()]))
#@+node:ekr.20150312225028.124: *7* vc.source_unl
def source_unl(self,organizer_unls,organizer_unl):
    '''Return the unl of the source node for the given organizer_unl.'''
    vc = self
    return vc.drop_all_organizers_in_unl(organizer_unls,organizer_unl)
#@+node:ekr.20150312225028.125: *7* vc.unl_tail
def unl_tail(self,unl):
    '''Return the last part of a unl.'''
    return unl.split('-->')[:-1][0]
#@+node:ekr.20150312225028.126: *4* vc.Commands
@g.command('view-pack')
def view_pack_command(event):
    c = event.get('c')
    if c and c.viewController:
        c.viewController.pack()

@g.command('view-unpack')
def view_unpack_command(event):
    c = event.get('c')
    if c and c.viewController:
        c.viewController.unpack()
        
@g.command('at-file-to-at-auto')
def at_file_to_at_auto_command(event):
    c = event.get('c')
    if c and c.viewController:
        c.viewController.convert_at_file_to_at_auto(c.p)
#@+node:ekr.20140711111623.17795: *4* class ConvertController (leoPersistence.py)
class ConvertController(object):
    '''A class to convert @file trees to @auto trees.'''

    def __init__(self, c, p):
        self.c = c
        self.pd = c.persistenceController
        self.root = p.copy()
    @others
#@+node:ekr.20140711111623.17796: *5* convert.delete_at_data_nodes
def delete_at_data_nodes(self, root):
    '''Delete all @data nodes pertaining to root.'''
    cc = self
    pd = cc.pd
    while True:
        p = pd.has_at_data_node(root)
        if not p: break
        p.doDelete()
#@+node:ekr.20140711111623.17797: *5* convert.import_from_string
def import_from_string(self, s):
    '''Import from s into a temp outline.'''
    cc = self # (ConvertController)
    c = cc.c
    # ic = c.importCommands
    root = cc.root
    language = g.scanForAtLanguage(c, root)
    ext = '.' + g.app.language_extension_dict.get(language)
    scanner = g.app.scanner_for_ext(c, ext)
    # g.trace(language,ext,scanner.__name__)
    p = root.insertAfter()
    ok = scanner(atAuto=True, c=c, parent=p, s=s)
    p.h = root.h.replace('@file', '@auto' if ok else '@@auto')
    return ok, p
#@+node:ekr.20140711111623.17798: *5* convert.run
def run(self):
    '''Convert an @file tree to @auto tree.'''
    trace = True and not g.unitTesting
    trace_s = False
    cc = self
    c = cc.c
    root, pd = cc.root, c.persistenceController
    # set the expected imported headline for all vnodes.
    t1 = time.time()
    cc.set_expected_imported_headlines(root)
    t2 = time.time()
    # Delete all previous @data nodes for this tree.
    cc.delete_at_data_nodes(root)
    t3 = time.time()
    # Ensure that all nodes of the tree are regularized.
    ok = pd.prepass(root)
    t4 = time.time()
    if not ok:
        g.es_print('Can not convert', root.h, color='red')
        if trace: g.trace(
            '\n  set_expected_imported_headlines: %4.2f sec' % (t2 - t1),
            # '\n  delete_at_data_nodes:          %4.2f sec' % (t3-t2),
            '\n  prepass:                         %4.2f sec' % (t4 - t3),
            '\n  total:                           %4.2f sec' % (t4 - t1))
        return
    # Create the appropriate @data node.
    at_auto_view = pd.update_before_write_foreign_file(root)
    t5 = time.time()
    # Write the @file node as if it were an @auto node.
    s = cc.strip_sentinels()
    t6 = time.time()
    if trace and trace_s:
        g.trace('source file...\n', s)
    # Import the @auto string.
    ok, p = cc.import_from_string(s)
    t7 = time.time()
    if ok:
        # Change at_auto_view.b so it matches p.gnx.
        at_auto_view.b = pd.at_data_body(p)
        # Recreate the organizer nodes, headlines, etc.
        pd.update_after_read_foreign_file(p)
        t8 = time.time()
        # if not ok:
            # p.h = '@@' + p.h
            # g.trace('restoring original @auto file')
            # ok,p = cc.import_from_string(s)
            # if ok:
                # p.h = '@@' + p.h + ' (restored)'
                # if p.next():
                    # p.moveAfter(p.next())
        t9 = time.time()
    else:
        t8 = t9 = time.time()
    if trace: g.trace(
        '\n  set_expected_imported_headlines: %4.2f sec' % (t2 - t1),
        # '\n  delete_at_data_nodes:          %4.2f sec' % (t3-t2),
        '\n  prepass:                         %4.2f sec' % (t4 - t3),
        '\n  update_before_write_foreign_file:%4.2f sec' % (t5 - t4),
        '\n  strip_sentinels:                 %4.2f sec' % (t6 - t5),
        '\n  import_from_string:              %4.2f sec' % (t7 - t6),
        '\n  update_after_read_foreign_file   %4.2f sec' % (t8 - t7),
        '\n  import_from_string (restore)     %4.2f sec' % (t9 - t8),
        '\n  total:                           %4.2f sec' % (t9 - t1))
    if p:
        c.selectPosition(p)
    c.redraw()
#@+node:ekr.20140711111623.17799: *5* convert.set_expected_imported_headlines
def set_expected_imported_headlines(self, root):
    '''Set v._imported_headline for every vnode.'''
    trace = False and not g.unitTesting
    cc = self
    c = cc.c
    ic = cc.c.importCommands
    language = g.scanForAtLanguage(c, root)
    ext = '.' + g.app.language_extension_dict.get(language)
    aClass = g.app.classDispatchDict.get(ext)
    scanner = aClass(importCommands=ic, atAuto=True)
    # Duplicate the fn logic from ic.createOutline.
    theDir = g.setDefaultDirectory(c, root, importing=True)
    fn = c.os_path_finalize_join(theDir, root.h)
    fn = root.h.replace('\\', '/')
    junk, fn = g.os_path_split(fn)
    fn, junk = g.os_path_splitext(fn)
    if aClass and hasattr(scanner, 'headlineForNode'):
        for p in root.subtree():
            if not hasattr(p.v, '_imported_headline'):
                h = scanner.headlineForNode(fn, p)
                setattr(p.v, '_imported_headline', h)
                if trace and h != p.h:
                    g.trace('==>', h) # p.h,'==>',h
#@+node:ekr.20140711111623.17800: *5* convert.strip_sentinels
def strip_sentinels(self):
    '''Write the file to a string without headlines or sentinels.'''
    trace = False and not g.unitTesting
    cc = self
    at = cc.c.atFileCommands
    # ok = at.writeOneAtAutoNode(cc.root,
        # toString=True,force=True,trialWrite=True)
    at.errors = 0
    at.write(cc.root,
        kind='@file',
        nosentinels=True,
        perfectImportFlag=False,
        scriptWrite=False,
        thinFile=True,
        toString=True)
    ok = at.errors == 0
    s = at.stringOutput
    if trace: g.trace('ok:', ok, 's:...\n' + s)
    return s
#@+node:ekr.20140711111623.17794: *4* pd.convert_at_file_to_at_auto
def convert_at_file_to_at_auto(self, root):
    if root.isAtFileNode():
        ConvertController(self.c, root).run()
    else:
        g.es_print('not an @file node:', root.h)
#@+node:ekr.20140131101641.15495: *4* pd.prepass & helper
def prepass(self, root):
    '''Make sure root's tree has no hard-to-handle nodes.'''
    c, pd = self.c, self
    ic = c.importCommands
    ic.tab_width = c.getTabWidth(root)
    language = g.scanForAtLanguage(c, root)
    ext = g.app.language_extension_dict.get(language)
    if not ext: return
    if not ext.startswith('.'): ext = '.' + ext
    scanner = g.app.scanner_for_ext(c, ext)
    if not scanner:
        g.trace('no scanner for', root.h)
        return True # Pretend all went well.
    # Pass 1: determine the nodes to be inserted.
    ok = True
    # parts_list = []
    for p in root.subtree():
        ok2 = pd.regularize_node(p, scanner)
        ok = ok and ok2
    return ok
#@+node:ekr.20140131101641.15496: *5* pd.regularize_node
def regularize_node(self, p, scanner):
    '''Regularize node p so that it will not cause problems.'''
    c = self.c
    ok = scanner(atAuto=True, c=c, parent=p, s=p.b)
        # The scanner is a callback returned by g.app.scanner_for_ext.
        # It must have a c argument.
    if not ok:
        g.es_print('please regularize:', p.h)
    return ok
#@+node:ekr.20190506094028.1: ** Demo stuff
#@+node:ekr.20190506094028.2: *3* @@button demo1 @key=Ctrl-8
g.cls()
if c.isChanged(): c.save()
import imp
import leo.plugins.demo as demo
imp.reload(demo)
<< class MyDemo >>
h = 'demo1-commands'
button_p = g.findNodeAnywhere(c, '@button demo1 @key=Ctrl-8')
commands = g.findNodeInTree(c, button_p, h)
if commands:
    MyDemo(c).start(commands)
else:
    print('not found', h, c.p.h)
#@+node:ekr.20190506094028.3: *4* << class MyDemo >>
class MyDemo(demo.Demo):
    
    @others
    
#@+node:ekr.20190506094028.4: *5* setup
def setup(self, p):
    
    c = self.c
    self.delta = 0
    self.clear_log()
    p = g.findNodeAnywhere(c, 'Demo area')
    if p:
        c.selectPosition(p)
#@+node:ekr.20190506094028.5: *5* teardown
def teardown(self):
    
    c = self.c
    if self.delta:
        self.set_text_delta(-self.delta)
    p = g.findNodeAnywhere(c, 'Demo area')
    if p:
        c.selectPosition(p)
        next = p.next()
        if next and next.h == 'This is a test':
            c.selectPosition(next)
            next.doDelete()
            c.selectPosition(p)
            c.setChanged(False)
            c.redraw()
    

#@+node:ekr.20190506094028.6: *4* demo1-commands
print('demo1-commands')
# c.contractAllHeadlines()
#@+node:ekr.20190506094028.7: *5* @ignore-tree
#@+node:ekr.20190506094028.8: *6* set_text_delta
print('increasing text size by 10')
demo.delta = 10
demo.set_text_delta(demo.delta)
#@+node:ekr.20190506094028.9: *6* undo
undo_type = c.undoer.undoType
if undo_type == 'Insert Node':
    c.undoer.undo()
#@+node:ekr.20190506094028.10: *6* caption
demo.caption('My Caption', 'body')
#@+node:ekr.20190506094028.11: *6* @image
demo.delete_widgets()
fn = 'SplashScreen.ico'
demo.image('body', fn, center=True, height=None, width=None)
#@+node:ekr.20190506094028.12: *6* open menu
demo.delete_widgets()
demo.open_menu('Import')
#@+node:ekr.20190506094028.13: *6* close menu
demo.dismiss_menu_bar()
#@+node:ekr.20190506094028.14: *6* Alt-X insert-node
demo.key('Alt+x') # Not the same as Alt-X
demo.keys('insert-node')
# demo.wait(0.8)
# demo.key('\n') # Works.
#@+node:ekr.20190506094028.15: *6* Return
demo.key('\n')
#@+node:ekr.20190506094028.16: *5* headline
c.k.simulateCommand('insert-node')
demo.head_keys('This is a test')
#@+node:ekr.20190506094028.17: *3* Test: import c:\test\demo-it.el
g.cls()
import imp
import leo.plugins.importers.linescanner as linescanner
import leo.plugins.importers.elisp as elisp
imp.reload(linescanner)
imp.reload(elisp)
x = elisp.Elisp_Importer(c.importCommands, atAuto=False)
with open('c:/test/demo-it.el') as f:
    s = f.read()
parent = p.next()
assert parent.h == 'demo.el', parent.h
parent.b = ''
parent.deleteAllChildren()
try:
    x.run(s, parent)
except Exception:
    g.es_exception()
parent.expand()
c.selectPosition(parent)
c.redraw()
# g.printList(g.splitLines(s))
#@+node:ekr.20190506094028.18: *3* demo.image & helper
def image(self, fn, center=None, height=None, pane=None, width=None):
    '''Put an image in the indicated pane.'''
    parent = self.pane_widget(pane or 'body')
    if parent:
        w = QtWidgets.QLabel('label', parent)
        fn = self.resolve_icon_fn(fn)
        if not fn: return None
        pixmap = QtGui.QPixmap(fn)
        if not pixmap:
            return g.trace('Not a pixmap: %s' % (fn))
        if height:
            pixmap = pixmap.scaledToHeight(height)
        if width:
            pixmap = pixmap.scaledToWidth(width)
        w.setPixmap(pixmap)
        if center:
            g_w = w.geometry()
            g_p = parent.geometry()
            dx = (g_p.width() - g_w.width()) / 2
            w.move(g_w.x() + dx, g_w.y() + 10)
        w.show()
        self.widgets.append(w)
        return w
    else:
        g.trace('bad pane: %s' % (pane))
        return None
#@+node:ekr.20190506094028.19: *4* demo.resolve_icon_fn
def resolve_icon_fn(self, fn):
    '''Resolve fn relative to the Icons directory.'''
    dir_ = g.os_path_finalize_join(g.app.loadDir, '..', 'Icons')
    path = g.os_path_finalize_join(dir_, fn)
    if g.os_path_exists(path):
        return path
    else:
        g.trace('does not exist: %s' % (path))
        return None
#@+node:ekr.20190506094028.20: *3* demo.caption & body, log, tree
def caption(self, s, pane): # To do: center option.
    '''Pop up a QPlainTextEdit in the indicated pane.'''
    parent = self.pane_widget(pane)
    if parent:
        s = s.rstrip()
        if s and s[-1].isalpha(): s = s + '.'
        w = QtWidgets.QPlainTextEdit(s, parent)
        w.setObjectName('screencastcaption')
        self.widgets.append(w)
        w2 = self.pane_widget(pane)
        geom = w2.geometry()
        w.resize(geom.width(), min(150, geom.height() / 2))
        off = QtCore.Qt.ScrollBarAlwaysOff
        w.setHorizontalScrollBarPolicy(off)
        w.setVerticalScrollBarPolicy(off)
        w.show()
        return w
    else:
        g.trace('bad pane: %s' % (pane))
        return None

def body(self, s):
    return self.caption(s, 'body')

def log(self, s):
    return self.caption(s, 'log')

def tree(self, s):
    return self.caption(s, 'tree')
#@+node:ekr.20190506094028.21: *3* demo.body, log, tree
def body(self, s):
    return TextEdit(s, 'body')

def log(self, s):
    return TextEdit(s, 'log')

def tree(self, s):
    return TextEdit(s, 'tree')
#@+node:ekr.20190506094028.22: *3* Demo area
@language python

# A python comment.
#@+node:ekr.20190508062044.1: ** Do not delete
#@+node:ekr.20150312225028.6: *3* class LogManager (not used yet)
class LogManager:

    '''A class to handle the global log, and especially
    switching the log from commander to commander.'''

    def __init__ (self):

        trace = (False or g.trace_startup) and not g.unitTesting
        if trace: g.es_debug('(LogManager)')

        self.log = None             # The LeoFrame containing the present log.
        self.logInited = False      # False: all log message go to logWaiting list.
        self.logIsLocked = False    # True: no changes to log are allowed.
        self.logWaiting = []        # List of messages waiting to go to a log.
        self.printWaiting = []      # Queue of messages to be sent to the printer.
        self.signon_printed = False # True: the global signon has been printed.

    @others
#@+node:ekr.20150312225028.7: *4* LogM.setLog, lockLog, unlocklog
def setLog (self,log):

    """set the frame to which log messages will go"""

    # print("app.setLog:",log,g.callers())
    if not self.logIsLocked:
        self.log = log

def lockLog(self):
    """Disable changes to the log"""
    self.logIsLocked = True

def unlockLog(self):
    """Enable changes to the log"""
    self.logIsLocked = False
#@+node:ekr.20150312225028.8: *4* LogM.writeWaitingLog
def writeWaitingLog (self,c):
    '''Write all waiting lines to the log.'''
    trace = True
    lm = self
    if trace:
        # Do not call g.es, g.es_print, g.pr or g.trace here!
        print('** writeWaitingLog','silent',g.app.silentMode,c.shortFileName())
        # print('writeWaitingLog',g.callers())
        # import sys ; print('writeWaitingLog: argv',sys.argv)
    if not c or not c.exists:
        return
    if g.unitTesting:
        lm.printWaiting = []
        lm.logWaiting = []
        g.app.setLog(None) # Prepare to requeue for other commanders.
        return
    table = [
        ('Leo Log Window','red'),
        (g.app.signon,'black'),
        (g.app.signon2,'black'),
    ]
    table.reverse()
    c.setLog()
    lm.logInited = True # Prevent recursive call.
    if not lm.signon_printed:
        lm.signon_printed = True
        if not g.app.silentMode:
            print('')
            print('** isPython3: %s' % g.isPython3)
            if not g.enableDB:
                print('** caching disabled')
            print(g.app.signon)
            print(g.app.signon2)
    if not g.app.silentMode:
        for s in lm.printWaiting:
            print(s)
    lm.printWaiting = []
    if not g.app.silentMode:
        for s,color in table:
            lm.logWaiting.insert(0,(s+'\n',color),)
        for s,color in lm.logWaiting:
            g.es('',s,color=color,newline=0)
                # The caller must write the newlines.
    lm.logWaiting = []
    # Essential when opening multiple files...
    lm.setLog(None)
#@+node:ekr.20150526115312.1: *3* compare_ast (can hang!?!)
# http://stackoverflow.com/questions/3312989/
# elegant-way-to-test-python-asts-for-equality-not-reference-or-object-identity

def compare_ast(node1, node2):

    trace = False and not g.unitTesting

    def fail(node1, node2, tag):
        '''Report a failed mismatch in the beautifier. This is a bug.'''
        name1 = node1.__class__.__name__
        name2 = node2.__class__.__name__
        format = 'compare_ast failed: %s: %s %s %r %r'
        if name1 == 'str':
            print(format % (tag, name1, name2, node1, node2))
        elif name1 == 'Str':
            print(format % (tag, name1, name2, node1.s, node2.s))
        elif 1:
            format = 'compare_ast failed: %s: %s %s\n%r\n%r'
            print(format % (tag, name1, name2, node1, node2))
        else:
            format = 'compare_ast failed: %s: %s %s\n%r\n%r\n%r %r'
            attr1 = getattr(node1, 'lineno', '<no lineno>')
            attr2 = getattr(node2, 'lineno', '<no lineno>')
            print(format % (tag, name1, name2, node1, node2, attr1, attr2))

    # pylint: disable=unidiomatic-typecheck
    if type(node1) != type(node2):
        if trace: fail(node1, node2, 'type mismatch')
        return False
    # The types of node1 and node2 match. Recursively compare components.
    if isinstance(node1, ast.AST):
        for kind, var in vars(node1).items():
            if kind not in ('lineno', 'col_offset', 'ctx'):
                var2 = vars(node2).get(kind) # Bug fix: 2016/05/16.
                if not compare_ast(var, var2):
                    if trace: fail(var, var2, 'AST subnode mismatch')
                    return False
        return True
    elif isinstance(node1, list):
        if len(node1) != len(node2):
            if trace: fail(node1, node2, 'list len mismatch')
            return False
        for i in range(len(node1)):
            if not compare_ast(node1[i], node2[i]):
                if trace: fail(node1, node2, 'list element mismatch')
                return False
        return True
    elif node1 != node2:
        if trace: fail(node1, node2, 'node mismatch')
        return False
    else:
        return True
#@+node:ekr.20180826065640.1: *3* vr.embed_pyplot_widget (not used)
def embed_pyplot_widget(self):

    pc = self
    c = pc.c
    # Careful: we may be unit testing.
    splitter = c.free_layout.get_top_splitter()
    if not splitter:
        return
    if not pc.pyplot_canvas:

        # TODO Create the widgets.
        w = None
        ### Ref
        # pc.gs = QtWidgets.QGraphicsScene(splitter)
        # pc.gv = QtWidgets.QGraphicsView(pc.gs)
        # w = pc.gv.viewport() # A QWidget
        # Embed the widgets.
        pc.pyplot_canvas = w

        def delete_callback():
            pc.pyplot_canvas.deleteLater()
            pc.pyplot_canvas = None

    if pc.pyplot_canvas:
        pc.embed_widget(w, delete_callback=delete_callback)
#@+node:ekr.20150521114057.1: *3* test_beautifier (prints stats)
def test_beautifier(c, h, p, settings):
    '''Test Leo's beautifier code'''
    if not p:
        g.trace('not found: %s' % h)
        return None
    s = g.getScript(c, p,
            useSelectedText=False,
            forcePythonSentinels=True,
            useSentinels=False)
    g.trace(h.strip())
    t1 = time.time()
    s1 = g.toEncodedString(s)
    node1 = ast.parse(s1, filename='before', mode='exec')
    t2 = time.time()
    readlines = g.ReadLinesClass(s).next
    tokens = list(tokenize.generate_tokens(readlines))
    t3 = time.time()
    beautifier = PythonTokenBeautifier(c)
    keep_blank_lines = settings.get('tidy-keep-blank-lines')
    if keep_blank_lines is not None:
        beautifier.delete_blank_lines = not keep_blank_lines
    s2 = beautifier.run(tokens)
    t4 = time.time()
    try:
        s2_e = g.toEncodedString(s2)
        node2 = ast.parse(s2_e, filename='before', mode='exec')
        ok = compare_ast(node1, node2)
    except Exception:
        g.es_exception()
        ok = False
    t5 = time.time()
    #  Update the stats
    beautifier.n_input_tokens += len(tokens)
    beautifier.n_output_tokens += len(beautifier.code_list)
    beautifier.n_strings += len(s2)
    beautifier.parse_time += (t2 - t1)
    beautifier.tokenize_time += (t3 - t2)
    beautifier.beautify_time += (t4 - t3)
    beautifier.check_time += (t5 - t4)
    beautifier.total_time += (t5 - t1)
    if settings.get('input_string'):
        print('==================== input_string')
        for i, z in enumerate(g.splitLines(s)):
            print('%4s %s' % (i + 1, z.rstrip()))
    if settings.get('input_lines'):
        print('==================== input_lines')
        dump_tokens(tokens, verbose=False)
    if settings.get('input_tokens'):
        print('==================== input_tokens')
        dump_tokens(tokens, verbose=True)
    if settings.get('output_tokens'):
        print('==================== code_list')
        for i, z in enumerate(beautifier.code_list):
            print('%4s %s' % (i, z))
    if settings.get('output_string'):
        print('==================== output_string')
        for i, z in enumerate(g.splitLines(s2)):
            if z == '\n':
                print('%4s' % (i + 1))
            elif z.rstrip():
                print('%4s %s' % (i + 1, z.rstrip()))
            else:
                print('%4s %r' % (i + 1, str(z)))
    if settings.get('stats'):
        beautifier.print_stats()
    if not ok:
        print('*************** fail: %s ***************' % (h))
    return beautifier
        # For statistics.
#@+node:ekr.20190910021032.1: ** From leoBeautify
#@+node:ekr.20150527143619.1: *3* show_lws
def show_lws(s):
    '''Show leading whitespace in a convenient format.'''
    return repr(s) if s.strip(' ') else len(s)
#@+node:ekr.20150524215322.1: *3* dump_tokens & dump_token
def dump_tokens(tokens, verbose=True):
    last_line_number = 0
    for token5tuple in tokens:
        last_line_number = dump_token(last_line_number, token5tuple, verbose)

def dump_token(last_line_number, token5tuple, verbose):
    '''Dump the given input token.'''
    t1, t2, t3, t4, t5 = token5tuple
    name = token_module.tok_name[t1].lower()
    val = str(t2) # can fail
    srow, scol = t3
    erow, ecol = t4
    line = str(t5) # can fail
    if last_line_number != srow:
        if verbose:
            print("\n---- line: %3s %3s %r" % (srow, erow, line))
        else:
            print('%3s %7s %r' % (srow, name, line))
    if verbose:
        if name in ('dedent', 'indent', 'newline', 'nl'):
            val = repr(val)
        # print("%10s %3d %3d %-8s" % (name,scol,ecol,val))
        # print('%10s srow: %s erow: %s %s' % (name,srow,erow,val))
        print('%10s %s' % (name, val))
            # line[scol:ecol]
    last_line_number = srow
    return last_line_number
#@+node:ekr.20190508111530.1: ** From leoflexx.py
#@+node:ekr.20190508111539.1: *3* app.open_bridge
def open_bridge(self):
    '''Can't be in JS.'''
    bridge = leoBridge.controller(gui = None,
        loadPlugins = False,
        readSettings = True, # Required to get bindings!
        silent = True, # Use silentmode to keep queuing log message.
        tracePlugins = False,
        verbose = True, # True: prints log messages.
    )
    print('')
    print('Stand-alone operation of leoflexx.py is for testing only.')
    print('Opening unitTest.leo...')
    print('')
    if not bridge.isOpen():
        flx.logger.error('Error opening leoBridge')
        return
    g = bridge.globals()
    path = g.os_path_finalize_join(g.app.loadDir, '..', 'test', 'unitTest.leo')
    if not g.os_path_exists(path):
        flx.logger.error('open_bridge: does not exist: %r' % path)
        return
    c = bridge.openLeoFile(path)
    return c, g
#@+node:ekr.20190508063206.1: ** From LeoTree.select
#@+node:ekr.20140831085423.18637: *3* LeoTree.is_qt_body (from LeoTree.select)
if 0:

    def is_qt_body(self):
        '''Return True if the body widget is a QTextEdit.'''
        c = self.c
        import leo.plugins.qt_text as qt_text
        w = c.frame.body.wrapper.widget
        val = isinstance(w, qt_text.LeoQTextBrowser)
            # c.frame.body.wrapper.widget is a LeoQTextBrowser.
            # c.frame.body.wrapper is a QTextEditWrapper or QScintillaWrapper.
        return val
#@+node:ekr.20190609205751.1: ** Retired from Leo 6.0
#@+node:ekr.20190622094635.1: *3* leoSettings.leo: class-based css
/* ----- Minibuffer ----- */

QLineEdit#lineEdit[style_class ~= 'minibuffer_warning'] {
    background-color: @minibuffer_warning_color;
}

QLineEdit#lineEdit[style_class ~= 'minibuffer_error'] {
    color: @minibuffer_error_color;
    background-color: @minibuffer_warning_color;
}

/* ----- Status line ----- */

QLineEdit#status1[style_class ~= 'info'], QLineEdit#status2[style_class ~= 'info'] {
    background-color:   @find-found-bg;
    color:              @find-found-fg;
}

QLineEdit#status1[style_class ~= 'fail'], QLineEdit#status2[style_class ~= 'fail'] {
    background-color:   @find-not-found-bg;
    color:              @find-not-found-fg;
}

/* Vim mode borders... */

QTextEdit#richTextEdit[vim_state~="vim_normal"] {
  border: @vim-mode-normal-border;
}

QTextEdit#richTextEdit[vim_state~="vim_insert"] {
  border: @vim-mode-insert-border;
}

QTextEdit#richTextEdit[vim_state~="vim_visual"] {
  border: @vim-mode-visual-border;
}
#@+node:ekr.20190608104256.1: *3* Retired commands & menu items
#@+node:ekr.20190608105206.1: *4* @menu &Show Or Hide Panes
#@+node:ekr.20190608105206.2: *5* @item gui-&all-hide
#@+node:ekr.20190608105206.3: *5* @item gui-&all-show
#@+node:ekr.20190608105206.4: *5* @item gui-&all-toggle
#@+node:ekr.20190608105206.5: *5* @item-
#@+node:ekr.20190608105206.6: *5* @item gui-&iconbar-hide
#@+node:ekr.20190608105206.7: *5* @item gui-&iconbar-show
#@+node:ekr.20190608105206.8: *5* @item gui-&iconbar-toggle
#@+node:ekr.20190608105206.9: *5* @item -
#@+node:ekr.20190608105206.10: *5* @item gui-&menu-hide
#@+node:ekr.20190608105206.11: *5* @item gui-&menu-show
#@+node:ekr.20190608105206.12: *5* @item gui-&menu-toggle
#@+node:ekr.20190608105206.13: *5* @item -
#@+node:ekr.20190608105206.14: *5* @item gui-&minibuffer-hide
#@+node:ekr.20190608105206.15: *5* @item gui-&minibuffer-show
#@+node:ekr.20190608105206.16: *5* @item gui-&minibuffer-toggle
#@+node:ekr.20190608105206.17: *5* @item -
#@+node:ekr.20190608105206.18: *5* @item gui-&statusbar-hide
#@+node:ekr.20190608105206.19: *5* @item gui-&statusbar-show
#@+node:ekr.20190608105206.20: *5* @item gui-&statusbar-toggle
#@+node:ekr.20190608105206.21: *5* @item -
#@+node:ekr.20190608105206.22: *5* @item gui-&tabbar-hide
#@+node:ekr.20190608105206.23: *5* @item gui-&tabbar-show
#@+node:ekr.20190608105206.24: *5* @item gui-&tabbar-toggle
#@+node:ekr.20190608105941.1: *4* @menu &Expand/Contract Panes
#@+node:ekr.20190608105941.2: *5* @item &equal-sized-panes
#@+node:ekr.20190608105941.3: *5* @item -
#@+node:ekr.20190608105941.4: *5* @item contract-&body-pane
#@+node:ekr.20190608105941.5: *5* @item contract-&log-pane
#@+node:ekr.20190608105941.6: *5* @item contract-&outline-pane
#@+node:ekr.20190608105941.7: *5* @item contract-&pane
#@+node:ekr.20190608105941.8: *5* @item -
#@+node:ekr.20190608105941.9: *5* @item expand-bo&dy-pane
#@+node:ekr.20190608105941.10: *5* @item expand-lo&g-pane
#@+node:ekr.20190608105941.11: *5* @item expand-o&utline-pane
#@+node:ekr.20190608105941.12: *5* @item expand-pa&ne
#@+node:ekr.20190608105941.13: *5* @item -
#@+node:ekr.20190608105941.14: *5* @item &fully-expand-body-pane
#@+node:ekr.20190608105941.15: *5* @item full&y-expand-log-pane
#@+node:ekr.20190608105941.16: *5* @item fully-e&xpand-outline-pane
#@+node:ekr.20190608105941.17: *5* @item fully-exp&and-pane
#@+node:ekr.20150514063305.198: *4* ec.cycleAllFocus
editWidgetCount = 0

def cycleAllFocus(self, event):
    '''
    Cycle the keyboard focus between Leo's outline,
    all body editors and all tabs in the log pane.
    '''
    c, k = self.c, self.c.k
    w = event and event.widget # Does **not** require a text widget.
    pane = None # The widget that will get the new focus.
    log = c.frame.log
    w_name = c.widget_name(w)
    # w may not be the present body widget, so test its name, not its id.
    if w_name.find('tree') > -1 or w_name.startswith('head'):
        pane = c.frame.body.wrapper
    elif w_name.startswith('body'):
        # Cycle through the *body* editor if there are several.
        n = c.frame.body.numberOfEditors
        if n > 1:
            self.editWidgetCount += 1
            if self.editWidgetCount == 1:
                pane = c.frame.body.wrapper
            elif self.editWidgetCount > n:
                self.editWidgetCount = 0
                c.frame.log.selectTab('Log')
                pane = c.frame.log.logCtrl
            else:
                c.frame.body.cycleEditorFocus(event)
                pane = None
        else:
            self.editWidgetCount = 0
            c.frame.log.selectTab('Log')
            pane = c.frame.log.logCtrl
    elif log.isLogWidget(w):
        # A log widget.  Cycle until we come back to 'Log'.
        log.cycleTabFocus()
        pane = c.frame.tree.canvas if log.tabName == 'Log' else None
    else:
        # A safe default: go to the body.
        pane = c.frame.body.wrapper
    if pane:
        k.newMinibufferWidget = pane
        c.widgetWantsFocusNow(pane)
        k.showStateAndMode()
#@+node:ekr.20150514063305.197: *4* ec.cycleFocus
@cmd('cycle-focus')
def cycleFocus(self, event):
    '''Cycle the keyboard focus between Leo's outline, body and log panes.'''
    c, k = self.c, self.c.k
    w = event and event.widget
    body = c.frame.body.wrapper
    log = c.frame.log.logCtrl
    tree = c.frame.tree.canvas
    # A hack for the Qt gui.
    if hasattr(w, 'logCtrl'):
        w = w.logCtrl
    panes = [body, log, tree]
    if w in panes:
        i = panes.index(w) + 1
        if i >= len(panes): i = 0
        pane = panes[i]
    else:
        pane = body
    # Warning: traces mess up the focus
        # g.pr(g.app.gui.widget_name(w),g.app.gui.widget_name(pane))
    #
    # This works from the minibuffer *only* if there is no typing completion.
    c.widgetWantsFocusNow(pane)
    k.newMinibufferWidget = pane
    k.showStateAndMode()
#@+node:ekr.20060528170438: *4* LeoBody.cycleEditorFocus
@cmd('editor-cycle-focus')
@cmd('cycle-editor-focus') # There is no LeoQtBody method
def cycleEditorFocus(self, event=None):
    '''Cycle keyboard focus between the body text editors.'''
    c = self.c
    d = self.editorWidgets
    w = c.frame.body.wrapper
    values = list(d.values())
    if len(values) > 1:
        i = values.index(w) + 1
        if i == len(values): i = 0
        w2 = values[i]
        assert(w != w2)
        self.selectEditor(w2)
        c.frame.body.wrapper = w2
#@+node:ekr.20070302094848.4: *4* LeoLog.cycleTabFocus
### @cmd('cycle-tab-focus')
def cycleTabFocus(self, event=None):
    '''Cycle keyboard focus between the tabs in the log pane.'''
    d = self.frameDict # Keys are page names. Values are Frames.
    w = d.get(self.tabName)
    values = list(d.values())
    if self.numberOfVisibleTabs() > 1:
        try:
            i = values.index(w)
            if i >= len(values)-1:
                i = 0
        except ValueError:
            i = 0
        tabName = list(d.keys())[i]
        self.selectTab(tabName)
        return i
    return None
#@+node:ekr.20110605121601.18327: *4* LeoQtLog.cycleTabFocus
@cmd('cycle-tab-focus')
def cycleTabFocus(self, event=None):
    '''Cycle keyboard focus between the tabs in the log pane.'''
    w = self.tabWidget
    i = w.currentIndex()
    i += 1
    if i >= w.count():
        i = 0
    tabName = w.tabText(i)
    self.selectTab(tabName, createText=False)
    return i
#@+node:tbrown.20140620095406.40066: *4* qt: gui-show/hide/toggle
# create the commands gui-<menu|iconbar|statusbar|minibuffer>-<hide|show>
widgets = [
    ('menu', lambda c: c.frame.top.menuBar()),
    ('iconbar', lambda c: c.frame.top.iconBar),
    ('statusbar', lambda c: c.frame.top.statusBar),
    ('minibuffer', lambda c: c.frame.miniBufferWidget.widget.parent()),
    ('tabbar', lambda c: g.app.gui.frameFactory.masterFrame.tabBar()),
]
for vis in 'hide', 'show', 'toggle':
    for name, widget in widgets:

        def dovis(event, widget=widget, vis=vis):
            c = event['c']
            w = widget(c)
            if vis == 'toggle':
                vis = 'hide' if w.isVisible() else 'show'
            # Executes either w.hide() or w.show()
            getattr(w, vis)()

        g.command("gui-%s-%s" % (name, vis))(dovis)

    def doall(event, vis=vis):
        c = event['c']
        for name, widget in widgets:
            w = widget(c)
            if vis == 'toggle':
                # note, this *intentionally* toggles all to on/off
                # based on the state of the first
                vis = 'hide' if w.isVisible() else 'show'
            getattr(w, vis)()

    g.command("gui-all-%s" % vis)(doall)
#@+node:ekr.20110605121601.18294: *4* qtFrame.Minibuffer commands...
#@+node:ekr.20110605121601.18295: *5* qtFrame.contractPane
@cmd('contract-pane')
def contractPane(self, event=None):
    '''Contract the selected pane.'''
    f = self; c = f.c
    w = c.get_requested_focus() or g.app.gui.get_focus(c)
    wname = c.widget_name(w)
    if not w: return
    if wname.startswith('body'):
        f.contractBodyPane()
        c.bodyWantsFocus()
    elif wname.startswith('log'):
        f.contractLogPane()
        c.logWantsFocus()
    else:
        for z in ('head', 'canvas', 'tree'):
            if wname.startswith(z):
                f.contractOutlinePane()
                c.treeWantsFocus()
                break
#@+node:ekr.20110605121601.18296: *5* qtFrame.expandPane
@cmd('expand-pane')
def expandPane(self, event=None):
    '''Expand the selected pane.'''
    f = self; c = f.c
    w = c.get_requested_focus() or g.app.gui.get_focus(c)
    wname = c.widget_name(w)
    if not w: return
    if wname.startswith('body'):
        f.expandBodyPane()
        c.bodyWantsFocus()
    elif wname.startswith('log'):
        f.expandLogPane()
        c.logWantsFocus()
    else:
        for z in ('head', 'canvas', 'tree'):
            if wname.startswith(z):
                f.expandOutlinePane()
                c.treeWantsFocus()
                break
#@+node:ekr.20110605121601.18297: *5* qtFrame.fullyExpandPane
@cmd('fully-expand-pane')
def fullyExpandPane(self, event=None):
    '''Fully expand the selected pane.'''
    f = self; c = f.c
    w = c.get_requested_focus() or g.app.gui.get_focus(c)
    wname = c.widget_name(w)
    if not w: return
    if wname.startswith('body'):
        f.fullyExpandBodyPane()
        c.treeWantsFocus()
    elif wname.startswith('log'):
        f.fullyExpandLogPane()
        c.bodyWantsFocus()
    else:
        for z in ('head', 'canvas', 'tree'):
            if wname.startswith(z):
                f.fullyExpandOutlinePane()
                c.bodyWantsFocus()
                break
#@+node:ekr.20110605121601.18298: *5* qtFrame.hidePane
@cmd('hide-pane')
def hidePane(self, event=None):
    '''Completely contract the selected pane.'''
    f = self; c = f.c
    w = c.get_requested_focus() or g.app.gui.get_focus(c)
    wname = c.widget_name(w)
    if not w: return
    if wname.startswith('body'):
        f.hideBodyPane()
        c.treeWantsFocus()
    elif wname.startswith('log'):
        f.hideLogPane()
        c.bodyWantsFocus()
    else:
        for z in ('head', 'canvas', 'tree'):
            if wname.startswith(z):
                f.hideOutlinePane()
                c.bodyWantsFocus()
                break
#@+node:ekr.20110605121601.18299: *5* qtFrame.expand/contract...Pane
@cmd('contract-body-pane')
@cmd('expand-outline-pane')
def contractBodyPane(self, event=None):
    '''Contract the body pane.'''
    r = min(1.0, self.ratio + 0.1)
    self.divideLeoSplitter1(r)

expandOutlinePane = contractBodyPane

@cmd('contract-outline-pane')
@cmd('expand-body-pane')
def contractOutlinePane(self, event=None):
    '''Contract the outline pane.'''
    r = max(0.0, self.ratio - 0.1)
    self.divideLeoSplitter1(r)

expandBodyPane = contractOutlinePane

@cmd('contract-log-pane')
def contractLogPane(self, event=None):
    '''Contract the log pane.'''
    r = min(1.0, self.secondary_ratio + 0.1)
    self.divideLeoSplitter2(r)

@cmd('expand-log-pane')
def expandLogPane(self, event=None):
    '''Expand the log pane.'''
    r = max(0.0, self.secondary_ratio - 0.1)
    self.divideLeoSplitter2(r)
#@+node:ekr.20110605121601.18300: *5* qtFrame.fullyExpand/hide...Pane
@cmd('fully-expand-body-pane')
@cmd('hide-outline-pane')
def fullyExpandBodyPane(self, event=None):
    '''Fully expand the body pane.'''
    self.divideLeoSplitter1(0.0)

@cmd('fully-expand-outline-pane')
@cmd('hide-body-pane')
def fullyExpandOutlinePane(self, event=None):
    '''Fully expand the outline pane.'''
    self.divideLeoSplitter1(1.0)

@cmd('fully-expand-log-pane')
def fullyExpandLogPane(self, event=None):
    '''Fully expand the log pane.'''
    self.divideLeoSplitter2(0.0)

@cmd('hide-log-pane')
def hideLogPane(self, event=None):
    '''Completely contract the log pane.'''
    self.divideLeoSplitter2(1.0)
#@+node:ekr.20140815160132.18822: *4* vc.cycle_focus & cycle_all_focus (:gt & :gT)
@cmd(':gt')
def cycle_focus(self, event=None):
    '''Cycle focus'''
    event = VimEvent(c=self.c, char='', stroke='', w=self.colon_w)
    self.do('cycle-focus', event=event)

@cmd(':gT')
def cycle_all_focus(self, event=None):
    '''Cycle all focus'''
    event = VimEvent(c=self.c, char='', stroke='', w=self.colon_w)
    self.do('cycle-all-focus', event=event)
#@+node:ekr.20110605121601.18462: *3* class SDIFrameFactory (not used)
class SDIFrameFactory:
    """
    Top-level frame builder for Qt Gui without tabs.

    This only deals with Qt level widgets, not Leo wrappers
    """
    @others
#@+node:ekr.20110605121601.18463: *4* createFrame (SDIFrameFactory) (not used)
def createFrame(self, leoFrame):
    c = leoFrame.c
    dw = DynamicWindow(c)
    dw.construct()
    g.app.gui.attachLeoIcon(dw)
    dw.setWindowTitle(leoFrame.title)
    g.app.gui.setFilter(c, dw, dw, tag='sdi-frame')
    if g.app.start_minimized:
        dw.showMinimized()
    elif g.app.start_maximized:
        dw.showMaximized()
    elif g.app.start_fullscreen:
        dw.showFullScreen()
    else:
        dw.show()
    return dw

def deleteFrame(self, wdg):
    # Do not delete.  Called from destroySelf.
    pass
#@+node:ekr.20190923093951.1: ** leo_excepthook
def leo_excepthook(type, value, tb):
    # Note: Leo will still exit!
    g.trace(f"Uncaught Python exception: {value}\n")
    g.es_exception()
#@-all
#@@nosearch
#@-leo
