# -*- coding: utf-8 -*-
#@+leo-ver=5-thin
#@+node:ekr.20170419092835.1: * @file cursesGui2.py
#@@first
#@+<< cursesGui2 docstring >>
#@+node:ekr.20170608073034.1: ** << cursesGui2 docstring >>
'''
A curses gui for Leo using npyscreen.

The ``--gui=curses`` command-line option enables this plugin.

**Warnings**

- Leo will crash on startup if the console is not big enough.

- This is beta-level code. Be prepared to recover from data loss. Testing
  on files under git control gives you diffs and easy reverts.
  
- There are many limitations: see http://leoeditor.com/console-gui.html

Please report any problem here:
https://github.com/leo-editor/leo-editor/issues/488

Devs, please read:
http://leoeditor.com/console-gui.html#developing-the-cursesgui2-plugin
'''
#@-<< cursesGui2 docstring >>
#@+<< cursesGui2 imports >>
#@+node:ekr.20170419172102.1: ** << cursesGui2 imports >>
import copy
import logging
import logging.handlers
import re
import sys
try:
    from tkinter import Tk
except ImportError:
    print('cursesGui.py: Tk required for clipboard handling.')
    Tk = None
import leo.core.leoGlobals as g
import leo.core.leoFrame as leoFrame
import leo.core.leoGui as leoGui
import leo.core.leoMenu as leoMenu
import leo.core.leoNodes as leoNodes
try:
    import curses
except ImportError:
    curses = None
npyscreen = g.importExtension(
    'npyscreen',
    pluginName=None,
    required=True,
    verbose=True, # Issue a warning if the import fails.
)
if npyscreen:
    # pylint: disable=import-error
        # These imports *will* work, because we are using g.importExtension.
    import npyscreen.utilNotify as utilNotify
    assert utilNotify
    from npyscreen.wgwidget import  EXITED_DOWN, EXITED_ESCAPE, EXITED_MOUSE, EXITED_UP
#@-<< cursesGui2 imports >>
# pylint: disable=arguments-differ,logging-not-lazy
# pylint: disable=not-an-iterable,unsubscriptable-object,unsupported-delete-operation
native = True
    # True: use native Leo data structures, replacing the
    # the values property by a singleton LeoValues object.
#@+<< forward reference classes >>
#@+node:ekr.20170511053555.1: **  << forward reference classes >>
# These classes aren't necessarily base classes, but
# they must be defined before classes that refer to them.
#@+others
#@+node:ekr.20170602094648.1: *3* class LeoBodyTextfield (npyscreen.Textfield)
class LeoBodyTextfield (npyscreen.Textfield):
    '''
    A class to allow an overridden h_addch for body text.
    MultiLines are *not* Textfields, the *contain* Textfields.
    '''

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_handlers()

    #@+others
    #@+node:ekr.20170604182251.1: *4* LeoBodyTextfield handlers
    # All h_exit_* methods call self.leo_parent.set_box_name.
    # In addition, h_exit_down inserts a blank(!!) for '\n'.
    #@+node:ekr.20170602095236.1: *5* LeoBodyTextfield.h_addch
    def h_addch(self, ch_i):
        '''
        Update a single line of the body text, carefully recomputing c.p.b.
        Also, update v.insertSpot, v.selectionLength, and v.selectionStart.
        '''
        trace = False and not g.unitTesting
        if not self.editable:
            if trace: g.trace('LeoBodyTextfiedl: not editable')
            return
        parent_w = self.leo_parent
        assert isinstance(parent_w, LeoBody), repr(parent_w)
        c = parent_w.leo_c
        p = c.p
        if trace: g.trace('LeoBodyTextfield. row: %s len(p.b): %4s ch_i: %s' % (
            parent_w.cursor_line, len(p.b), ch_i))
        try:
            # Careful: chr can fail.
            ch = g.toUnicode(chr(ch_i))
        except Exception:
            if trace: g.es_exception()
            return
        # Update this line...
        i = self.cursor_position
        s = self.value
        self.value = s[:i] + ch + s[i:]
        self.cursor_position += len(ch)
        self.update()
        # Update the parent and Leo's data structures.
        parent_w.update_body(self.cursor_position, self.value)
    #@+node:ekr.20170603131317.1: *5* LeoBodyTextfield.h_cursor_left
    def h_cursor_left(self, ch_i):

        self.cursor_position -= 1
            # -1 Means something.

    #@+node:ekr.20170603131253.1: *5* LeoBodyTextfield.h_delete_left
    def h_delete_left(self, ch_i):

        # pylint: disable=no-member
        i = self.cursor_position
        if self.editable and i > 0:
            self.value = self.value[:i-1] + self.value[i:]
        self.cursor_position -= 1
        self.begin_at -= 1
    #@+node:ekr.20170602110807.2: *5* LeoBodyTextfield.h_exit_down
    def h_exit_down(self, ch_i):
        '''
        From InputHandler.h_exit_down
        Terminate editing for *this* line, but not overall editing.
        '''
        if ch_i in (curses.ascii.CR, curses.ascii.NL):
            # A kludge, but much better than adding a blank.
            self.h_addch(ord('\n'))
            self.cursor_position = 0
        if not self._test_safe_to_exit():
            return False
        # Don't end overall editing.
            # self.leo_parent.set_box_name('Body Pane')
        self.editing = False
        self.how_exited = EXITED_DOWN
        return None
    #@+node:ekr.20170602110807.3: *5* LeoBodyTextfield.h_exit_escape
    def h_exit_escape(self, ch_i):
        '''From InputHandler.h_exit_escape'''
        # Leo-specific exit code.
        self.leo_parent.set_box_name('Body Pane')
        # Boilerplate exit code...
        if not self._test_safe_to_exit():
            return False
        self.editing = False
        self.how_exited = EXITED_ESCAPE
        return None
    #@+node:ekr.20170602110807.4: *5* LeoBodyTextfield.h_exit_mouse
    def h_exit_mouse(self, ch_i):
        '''From InputHandler.h_exit_mouse'''
        # pylint: disable=no-member
        parent_w = self.leo_parent
        parent_w.set_box_name('Body Pane')
        mouse_event = self.parent.safe_get_mouse_event()
        if mouse_event and self.intersted_in_mouse_event(mouse_event):
            self.handle_mouse_event(mouse_event)
        else:
            if mouse_event and self._test_safe_to_exit():
                curses.ungetmouse(*mouse_event)
                ch = self.parent.curses_pad.getch()
                assert ch == curses.KEY_MOUSE
            self.editing = False
            self.how_exited = EXITED_MOUSE
    #@+node:ekr.20170602110807.5: *5* LeoBodyTextfield.h_exit_up
    def h_exit_up(self, ch_i):
        '''LeoBodyTextfield.h_exit_up.'''
        # Don't end overall editing.
            # self.leo_parent.set_box_name('Body Pane')
        self.editing = False
        self.how_exited = EXITED_UP
    #@+node:ekr.20170604180351.1: *5* LeoBodyTextfield.set_handlers
    def set_handlers(self):

        # pylint: disable=no-member
        self.handlers = {
            # From InputHandler...
            curses.ascii.NL:    self.h_exit_down,
            curses.ascii.CR:    self.h_exit_down,
            curses.KEY_DOWN:    self.h_exit_down,
            curses.KEY_UP:      self.h_exit_up, # 2017/06/06.
            curses.ascii.ESC:   self.h_exit_escape,
            curses.KEY_MOUSE:   self.h_exit_mouse,
            # From Textfield...
            curses.KEY_BACKSPACE: self.h_delete_left,
            curses.KEY_DC:      self.h_delete_right,
            curses.KEY_LEFT:    self.h_cursor_left,
            curses.KEY_RIGHT:   self.h_cursor_right,
            curses.ascii.BS:    self.h_delete_left,
            curses.ascii.DEL:   self.h_delete_left,
            # New bindings...
            curses.ascii.TAB:   self.h_addch,
        }
        # dump_handlers(self)
    #@-others
#@+node:ekr.20170603104320.1: *3* class LeoLogTextfield (npyscreen.Textfield)
class LeoLogTextfield (npyscreen.Textfield):
    '''
    A class to allow an overridden h_addch for body text.
    MultiLines are *not* Textfields, the *contain* Textfields.
    '''

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.set_handlers()

    #@+others
    #@+node:ekr.20170604113445.1: *4* LeoLogTextfield handlers
    # All h_exit_* methods call self.leo_parent.set_box_name.
    # In addition, h_exit_down inserts a blank(!!) for '\n'.
    #@+node:ekr.20170603104320.5: *5* LeoLogTextfield.h_exit_escape
    def h_exit_escape(self, ch_i):
        '''From InputHandler.h_exit_escape'''
        parent_w = self.leo_parent
        parent_w.set_box_name('Log Pane')
        if not self._test_safe_to_exit():
            return False
        self.editing = False
        self.how_exited = EXITED_ESCAPE
        return None
    #@+node:ekr.20170603104320.6: *5* LeoLogTextfield.h_exit_mouse
    def h_exit_mouse(self, ch_i):
        '''From InputHandler.h_exit_mouse'''
        # pylint: disable=no-member
        parent_w = self.leo_parent
        parent_w.set_box_name('Log Pane')
        mouse_event = self.parent.safe_get_mouse_event()
        if mouse_event and self.intersted_in_mouse_event(mouse_event):
            self.handle_mouse_event(mouse_event)
        else:
            if mouse_event and self._test_safe_to_exit():
                curses.ungetmouse(*mouse_event)
                ch = self.parent.curses_pad.getch()
                assert ch == curses.KEY_MOUSE
            self.editing = False
            self.how_exited = EXITED_MOUSE
    #@+node:ekr.20170603104320.8: *5* LeoLogTextfield.h_exit_down
    def h_exit_down(self, ch_i):
        '''LeoLogTextfield.h_exit_up. Delegate to LeoLog.'''
        # g.trace('(LeoLogTextfield)', self._test_safe_to_exit())
        if ch_i in (curses.ascii.CR, curses.ascii.NL):
            # A pretty horrible kludge.
            self.h_addch(ord(' '))
            self.cursor_position = 0
        else:
            parent_w = self.leo_parent
            parent_w.set_box_name('Log Pane')
        if not self._test_safe_to_exit():
            return False
        self.editing = False
        self.how_exited = EXITED_DOWN
        return None
    #@+node:ekr.20170603104320.9: *5* LeoLogTextfield.h_exit_up
    def h_exit_up(self, ch_i):
        '''LeoLogTextfield.h_exit_up. Delegate to LeoLog.'''
        parent_w = self.leo_parent
        if not self._test_safe_to_exit():
            return False
        parent_w.set_box_name('Log Pane')
        self.editing = False
        self.how_exited = EXITED_DOWN
        return None
    #@+node:ekr.20170604075324.1: *4* LeoLogTextfield.set_handlers
    def set_handlers(self):

        # pylint: disable=no-member
        self.handlers = {
            # From InputHandler...
            curses.ascii.NL:    self.h_exit_down,
            curses.ascii.CR:    self.h_exit_down,
            curses.KEY_DOWN:    self.h_exit_down,
            curses.KEY_UP:      self.h_exit_up, # 2017/06/06.
            curses.ascii.ESC:   self.h_exit_escape,
            curses.KEY_MOUSE:   self.h_exit_mouse,
            # From Textfield...
            curses.KEY_BACKSPACE: self.h_delete_left,
            curses.KEY_DC:      self.h_delete_right,
            curses.KEY_LEFT:    self.h_cursor_left,
            curses.KEY_RIGHT:   self.h_cursor_right,
            curses.ascii.BS:    self.h_delete_left,
            curses.ascii.DEL:   self.h_delete_left,
            # New bindings...
            curses.ascii.TAB:   self.h_addch,
        }
        # dump_handlers(self)
    #@-others
#@+node:ekr.20170507184329.1: *3* class LeoTreeData (npyscreen.TreeData)
class LeoTreeData(npyscreen.TreeData):
    '''A TreeData class that has a len and new_first_child methods.'''
    #@+<< about LeoTreeData ivars >>
    #@+node:ekr.20170516143500.1: *4* << about LeoTreeData ivars >>
    # EKR: TreeData.__init__ sets the following ivars for keyword args.
        # self._parent # None or weakref.proxy(parent)
        # self.content.
        # self.selectable = selectable
        # self.selected = selected
        # self.highlight = highlight
        # self.expanded = expanded
        # self._children = []
        # self.ignore_root = ignore_root
        # self.sort = False
        # self.sort_function = sort_function
        # self.sort_function_wrapper = True
    #@-<< about LeoTreeData ivars >>

    def __len__(self):
        if native:
            p = self.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            content = p.h
        else:
            content = self.content
        return len(content)

    def __repr__ (self):
        if native:
            p = self.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            return '<LeoTreeData: %s, %s>' % (id(p), p.h)
        return '<LeoTreeData: %r>' % self.content
    __str__ = __repr__

    #@+others
    #@+node:ekr.20170516153211.1: *4* LeoTreeData.__getitem__
    def __getitem__(self, n):
        '''Return the n'th item in this tree.'''
        aList = self.get_tree_as_list()
        data = aList[n] if n < len(aList) else None
        # g.trace(n, len(aList), repr(data))
        return data
    #@+node:ekr.20170516093009.1: *4* LeoTreeData.is_ancestor_of
    def is_ancestor_of(self, node):

        assert isinstance(node, LeoTreeData), repr(node)
        parent = node._parent
        while parent:
            if parent == self:
                return True
            parent = parent._parent
        return False
    #@+node:ekr.20170516085427.1: *4* LeoTreeData.overrides
    # Don't use weakrefs!
    #@+node:ekr.20170518103807.6: *5* LeoTreeData.find_depth
    def find_depth(self, d=0):
        if native:
            p = self.content
            n = p.level()
            return n
        parent = self.get_parent()
        while parent:
            d += 1
            parent = parent.get_parent()
        return d
    #@+node:ekr.20170516085427.2: *5* LeoTreeData.get_children
    def get_children(self):

        if native:
            p = self.content
            return p.children()
        return self._children
    #@+node:ekr.20170518103807.11: *5* LeoTreeData.get_parent
    def get_parent(self):

        if native:
            p = self.content
            return p.parent()
        return self._parent
    #@+node:ekr.20170516085427.3: *5* LeoTreeData.get_tree_as_list
    def get_tree_as_list(self): # only_expanded=True, sort=None, key=None):
        '''
        Called only from LeoMLTree.values._getValues.

        Return the result of converting this node and its *visible* descendants
        to a list of LeoTreeData nodes.
        '''
        assert g.callers(1) == '_getValues', g.callers()
        aList = [z for z in self.walk_tree(only_expanded=True)]
        # g.trace('LeoTreeData', len(aList))
        return aList
    #@+node:ekr.20170516085427.4: *5* LeoTreeData.new_child
    def new_child(self, *args, **keywords):

        if self.CHILDCLASS:
            cld = self.CHILDCLASS
        else:
            cld = type(self)
        child = cld(parent=self, *args, **keywords)
        self._children.append(child)
        return child
    #@+node:ekr.20170516085742.1: *5* LeoTreeData.new_child_at
    def new_child_at(self, index, *args, **keywords):
        '''Same as new_child, with insert(index, c) instead of append(c)'''
        # g.trace('LeoTreeData')
        if self.CHILDCLASS:
            cld = self.CHILDCLASS
        else:
            cld = type(self)
        child = cld(parent=self, *args, **keywords)
        self._children.insert(index, child)
        return child
    #@+node:ekr.20170516085427.5: *5* LeoTreeData.remove_child
    def remove_child(self, child):

        if native:
            p = self.content
            # g.trace('LeoTreeData', p.h, g.callers())
            p.doDelete()
        else:
            self._children = [z for z in self._children if z != child]
                # May be useful when child is cloned.
    #@+node:ekr.20170518103807.21: *5* LeoTreeData.set_content
    def set_content(self, content):

        if native:
            if content is None:
                self.content = None
            elif g.isString(content):
                # This is a dummy node, not actually used.
                assert content == '<HIDDEN>', repr(content)
                self.content = content
            else:
                p = content
                assert p and isinstance(p, leoNodes.Position), repr(p)
                self.content = content.copy()
        else:
            self.content = content
    #@+node:ekr.20170516085427.6: *5* LeoTreeData.set_parent
    def set_parent(self, parent):

        self._parent = parent

    #@+node:ekr.20170518103807.24: *5* LeoTreeData.walk_tree (native only)
    if native:

        def walk_tree(self,
            only_expanded=True,
            ignore_root=True,
            sort=None,
            sort_function=None,
        ):
            p = self.content.copy()
                # Never change the stored position!
                # LeoTreeData(p) makes a copy of p.
            # g.trace('LeoTreeData: only_expanded:', only_expanded, p.h)
            if not ignore_root:
                yield self # The hidden root. Probably not needed.
            if only_expanded:
                while p:
                    if p.has_children() and p.isExpanded():
                        p.moveToFirstChild()
                        yield LeoTreeData(p)
                    elif p.next():
                        p.moveToNext()
                        yield LeoTreeData(p)
                    elif p.parent():
                        p.moveToParent()
                        yield LeoTreeData(p)
                    else:
                        return # raise StopIteration
            else:
                while p:
                    yield LeoTreeData(p)
                    p.moveToThreadNext()

    # else use the base TreeData.walk_tree method.
    #@-others
#@+node:ekr.20170508085942.1: *3* class LeoTreeLine (npyscreen.TreeLine)
class LeoTreeLine(npyscreen.TreeLine):
    '''A editable TreeLine class.'''

    def __init__(self, *args, **kwargs):

        super(LeoTreeLine, self).__init__(*args, **kwargs)
        # Done in TreeLine.init:
            # self._tree_real_value   = None
                # A weakproxy to LeoTreeData.
            # self._tree_ignore_root  = None
            # self._tree_depth        = False
            # self._tree_sibling_next = False
            # self._tree_has_children = False
            # self._tree_expanded     = True
            # self._tree_last_line    = False
            # self._tree_depth_next   = False
            # self.safe_depth_display = False
            # self.show_v_lines       = True
        self.set_handlers()

    def __repr__(self):
        val = self._tree_real_value
        if native:
            p = val and val.content
            if p is not None:
                assert p and isinstance(p, leoNodes.Position), repr(p)
            return '<LeoTreeLine: %s>' % (p.h if p else 'None')
        return '<LeoTreeLine: %s>' % (val.content if val else 'None')

    __str__ = __repr__

    #@+others
    #@+node:ekr.20170514104550.1: *4* LeoTreeLine._get_string_to_print
    def _get_string_to_print(self):

        # From TextfieldBase.
        if native:
            if self.value:
                assert isinstance(self.value, LeoTreeData)
                p = self.value.content
                assert p and isinstance(p, leoNodes.Position), repr(p)
                return p.h or ' '
            return ''
        s = self.value.content if self.value else None
        return g.toUnicode(s) if s else None
    #@+node:ekr.20170522032805.1: *4* LeoTreeLine._print
    def _print(self, left_margin=0):
        '''LeoTreeLine._print. Adapted from TreeLine._print.'''
        # pylint: disable=no-member

        def put(char):
            self.parent.curses_pad.addch(
                self.rely, self.relx+self.left_margin,
                ord(char), curses.A_NORMAL)
            self.left_margin += 1

        self.left_margin = left_margin
        self.parent.curses_pad.bkgdset(' ',curses.A_NORMAL)
        if native:
            c = getattr(self, 'leo_c', None)
            val = self._tree_real_value
            if val is None:
                return # startup
            p = val.content
            assert isinstance(p, leoNodes.Position), repr(p)
            self.left_margin += 2*p.level()
            if p.hasChildren():
                put('-' if p.isExpanded() else '+')
            else:
                put (' ')
            put(':')
            put('*' if c and p == c.p else ' ')
            put('C' if p and p.isCloned() else ' ')
            put('M' if p and p.isMarked() else ' ')
            put('T' if p and p.b.strip() else ' ')
            put(':')
        else:
            self.left_margin += self._print_tree(self.relx)
        # Now draw the actual line.
        if self.highlight:
            self.parent.curses_pad.bkgdset(' ',curses.A_STANDOUT)
        # This draws the actual line.
        super(npyscreen.TreeLine, self)._print()
    #@+node:ekr.20170514183049.1: *4* LeoTreeLine.display_value
    def display_value(self, vl):

        # vl is a LeoTreeData.
        if native:
            p = vl.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            return p.h or ' '
        return vl.content if vl else ''
    #@+node:ekr.20170510210908.1: *4* LeoTreeLine.edit
    def edit(self):
        """Allow the user to edit the widget: ie. start handling keypresses."""
        # g.trace('LeoTreeLine 1')
        self.editing = True
        # self._pre_edit()
        self.highlight = True
        self.how_exited = False
        # self._edit_loop()
        old_parent_editing = self.parent.editing
        self.parent.editing = True
        while self.editing and self.parent.editing:
            self.display()
            self.get_and_use_key_press()
                # A base TreeLine method.
        self.parent.editing = old_parent_editing
        self.editing = False
        self.how_exited = True
        # return self._post_edit()
        self.highlight = False
        self.update()
    #@+node:ekr.20170508130016.1: *4* LeoTreeLine.handlers
    #@+node:ekr.20170508130946.1: *5* LeoTreeLine.h_cursor_beginning
    def h_cursor_beginning(self, ch):

        self.cursor_position = 0
    #@+node:ekr.20170508131043.1: *5* LeoTreeLine.h_cursor_end
    def h_cursor_end(self, ch):

        # self.value is a LeoTreeData.
        # native: content is a position.
        content = self.value.content
        s = content.h if native else content
        self.cursor_position = max(0, len(s)-1)
    #@+node:ekr.20170508130328.1: *5* LeoTreeLine.h_cursor_left
    def h_cursor_left(self, input):
        
        # self.value is a LeoTreeData.
        # native: content is a position.
        content = self.value.content
        s = content.h if native else content
        i = min(self.cursor_position, len(s)-1)
        self.cursor_position = max(0, i-1)
    #@+node:ekr.20170508130339.1: *5* LeoTreeLine.h_cursor_right
    def h_cursor_right(self, input):

        # self.value is a LeoTreeData.
        # native: content is a position.
        content = self.value.content
        s = content.h if native else content
        i = self.cursor_position
        i = min(i+1, len(s)-1)
        self.cursor_position = max(0, i)



    #@+node:ekr.20170508130349.1: *5* LeoTreeLine.h_delete_left
    def h_delete_left(self, input):

        # self.value is a LeoTreeData.
        n = self.cursor_position
        if native:
            p = self.value.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            s = p.h
            if 0 <= n <= len(s):
                c = p.v.context
                h = s[:n] + s[n+1:]
                c.frame.tree.onHeadChanged(p, s=h, undoType='Typing')
                    # Sets p.h and handles undo.
        else:
            s = self.value.content
            if 0 <= n <= len(s):
                self.value.content = s[:n] + s[n+1:]
        self.cursor_position -= 1
    #@+node:ekr.20170510212007.1: *5* LeoTreeLine.h_end_editing
    def h_end_editing(self, ch):

        c = self.leo_c
        c.endEditing()
        self.editing = False
        self.how_exited = None
    #@+node:ekr.20170508125632.1: *5* LeoTreeLine.h_insert (to do: honor v.selection)
    def h_insert(self, i):

        # self.value is a LeoTreeData.
        n = self.cursor_position + 1
        if native:
            p = self.value.content
            c = p.v.context
            s = p.h
            h = s[:n] + chr(i) + s[n:]
            c.frame.tree.onHeadChanged(p, s=h, undoType='Typing')
                # Sets p.h and handles undo.
        else:
            s = self.value.content
            self.value.content = s[:n] + chr(i) + s[n:]
        self.cursor_position += 1
    #@+node:ekr.20170508130025.1: *5* LeoTreeLine.set_handlers
    #@@nobeautify

    def set_handlers(self):

        # pylint: disable=no-member
        # Override *all* other complex handlers.
        self.complex_handlers = (
            (curses.ascii.isprint, self.h_insert),
        )
        self.handlers.update({
            curses.ascii.ESC:       self.h_end_editing,
            curses.ascii.NL:        self.h_end_editing,
            curses.ascii.LF:        self.h_end_editing,
            curses.KEY_HOME:        self.h_cursor_beginning,  # 262
            curses.KEY_END:         self.h_cursor_end,        # 358.
            curses.KEY_LEFT:        self.h_cursor_left,
            curses.KEY_RIGHT:       self.h_cursor_right,
            curses.ascii.BS:        self.h_delete_left,
            curses.KEY_BACKSPACE:   self.h_delete_left,
            curses.KEY_MOUSE:       self.h_exit_mouse, # New
        })
    #@+node:ekr.20170519023802.1: *4* LeoTreeLine.when_check_value_changed
    if native:

        def when_check_value_changed(self):
            '''Check whether the widget's value has changed and call when_valued_edited if so.'''
            return True
    #@-others
#@+node:ekr.20170618103742.1: *3* class QuitButton (npyscreen.MiniButton)
class QuitButton (npyscreen.MiniButtonPress):
    '''Override the "Quit Leo" button so it prompts for save if needed.'''

    def whenPressed(self):
        g.app.onQuit()
#@-others
#@-<< forward reference classes >>
#@+others
#@+node:ekr.20170501043944.1: **   curses2: top-level functions
#@+node:ekr.20170603110639.1: *3* curses2: dump_handlers
def dump_handlers(obj,
    dump_complex=True,
    dump_handlers=True,
    dump_keys=True,
):
    tag = obj.__class__.__name__
    if dump_keys:
        g.trace('%s: keys' % tag)
        aList = []
        for z in obj.handlers:
            kind = repr(chr(z)) if isinstance(z, int) and 32 <= z < 127 else ''
            name = method_name(obj.handlers.get(z))
            aList.append('%3s %3s %4s %s' % (z, type(z).__name__, kind, name))
        g.printList(sorted(aList))
    if dump_handlers:
        g.trace('%s: handlers' % tag)
        aList = [method_name(obj.handlers.get(z))
            for z in obj.handlers]
        g.printList(sorted(set(aList)))
    if dump_complex:
        g.trace('%s: complex_handlers' % tag)
        aList = []
        for data in obj.complex_handlers:
            f1, f2 = data
            aList.append('%25s predicate: %s' % (method_name(f2), method_name(f1)))
        g.printList(sorted(set(aList)))
#@+node:ekr.20170419094705.1: *3* curses2: init
def init():
    '''
    top-level init for cursesGui2.py pseudo-plugin.
    This plugin should be loaded only from leoApp.py.
    '''
    if g.app.gui:
        if not g.app.unitTesting:
            s = "Can't install text gui: previous gui installed"
            g.es_print(s, color="red")
        return False
    return curses and not g.app.unitTesting
        # Not Ok for unit testing!
#@+node:ekr.20170501032705.1: *3* curses2: leoGlobals replacements
# CGui.init_logger monkey-patches leoGlobals with these functions.
#@+node:ekr.20170430112645.1: *4* curses2: es
def es(*args, **keys):
    '''Monkey-patch for g.es.'''
    d = {
        'color': None,
        'commas': False,
        'newline': True,
        'spaces': True,
        'tabName': 'Log',
    }
    d = g.doKeywordArgs(keys, d)
    color = d.get('color')
    s = g.translateArgs(args, d)
    if isinstance(g.app.gui, LeoCursesGui):
        if g.app.gui.log_inited:
            g.app.gui.log.put(s, color=color)
        else:
            g.app.gui.wait_list.append((s, color),)
    # else: logging.info(' KILL: %r' % s)
#@+node:ekr.20170613144149.1: *4* curses2: has_logger (not used)
def has_logger():

    logger = logging.getLogger()
    return any([
        isinstance(z, logging.handlers.SocketHandler) for z in logger.handlers or []
    ])
#@+node:ekr.20170501043411.1: *4* curses2: pr
def pr(*args, **keys):
    '''Monkey-patch for g.pr.'''
    d = {'commas': False, 'newline': True, 'spaces': True}
    d = g.doKeywordArgs(keys, d)
    s = g.translateArgs(args, d)
    for line in g.splitLines(s):
        if line.strip(): # No need to print blank logging lines.
            line = '   pr: %s' % line.rstrip()
            logging.info(line)
#@+node:ekr.20170429165242.1: *4* curses2: trace
def trace(*args, **keys):
    '''Monkey-patch for g.trace.'''
    d = {
        'align': 0,
        'before': '',
        'newline': True,
        'caller_level': 1,
        'noname': False,
    }
    d = g.doKeywordArgs(keys, d)
    align = d.get('align', 0)
    caller_level = d.get('caller_level', 1)
    # Compute the caller name.
    if d.get('noname'):
        name = ''
    else:
        try: # get the function name from the call stack.
            f1 = sys._getframe(caller_level) # The stack frame, one level up.
            code1 = f1.f_code # The code object
            name = code1.co_name # The code name
        except Exception:
            name = g.shortFileName(__file__)
        if name == '<module>':
            name = g.shortFileName(__file__)
        if name.endswith('.pyc'):
            name = name[: -1]
    # Pad the caller name.
    if align != 0 and len(name) < abs(align):
        pad = ' ' * (abs(align) - len(name))
        if align > 0: name = name + pad
        else: name = pad + name
    # Munge *args into s.
    result = [name] if name else []
    for arg in args:
        if g.isString(arg):
            pass
        elif g.isBytes(arg):
            arg = g.toUnicode(arg)
        else:
            arg = repr(arg)
        if result:
            result.append(" " + arg)
        else:
            result.append(arg)
    s = ''.join(result)
    line = 'trace: %s' % s.rstrip()
    logging.info(line)
#@+node:ekr.20170526075024.1: *3* method_name
def method_name(f):
    '''Print a method name is a simplified format.'''
    pattern = r'<bound method ([\w\.]*\.)?(\w+) of <([\w\.]*\.)?(\w+) object at (.+)>>'
    m = re.match(pattern, repr(f))
    if m:
        return '%20s%s' % (m.group(1), m.group(2))
            # Shows actual method: very useful
    return repr(f)
#@+node:ekr.20170524123950.1: ** Gui classes
#@+node:ekr.20171128051435.1: *3* class StringFindTabManager
class StringFindTabManager:
    '''A helper class for the LeoFind class.'''
    # A complete rewrite of the FindTabManager in qt_frame.py.
    #@+others
    #@+node:ekr.20171128051435.2: *4*  ftm.ctor
    def __init__(self, c):
        '''Ctor for the FindTabManager class.'''
        self.c = c
        assert(c.findCommands)
        c.findCommands.minibuffer_mode = True
        self.entry_focus = None # The widget that had focus before find-pane entered.
        # Find/change text boxes.
        self.find_findbox = None
        self.find_replacebox = None
        # Check boxes.
        self.check_box_ignore_case = None
        self.check_box_mark_changes = None
        self.check_box_mark_finds = None
        self.check_box_regexp = None
        self.check_box_search_body = None
        self.check_box_search_headline = None
        self.check_box_whole_word = None
        self.check_box_wrap_around = None
        # Radio buttons
        self.radio_button_entire_outline = None
        self.radio_button_node_only = None
        self.radio_button_suboutline_only = None
        # Push buttons
        self.find_next_button = None
        self.find_prev_button = None
        self.find_all_button = None
        self.help_for_find_commands_button = None
        self.replace_button = None
        self.replace_then_find_button = None
        self.replace_all_button = None
    #@+node:ekr.20171128051435.3: *4* ftm.text getters/setters
    def getFindText(self):
        return self.find_findbox.text()

    def getReplaceText(self):
        return self.find_replacebox.text()

    getChangeText = getReplaceText

    def setFindText(self, s):
        w = self.find_findbox
        s = g.toUnicode(s)
        w.clear()
        w.insert(s)

    def setReplaceText(self, s):
        w = self.find_replacebox
        s = g.toUnicode(s)
        w.clear()
        w.insert(s)

    setChangeText = setReplaceText
    #@+node:ekr.20171128051435.4: *4* ftm.*_focus
    def clear_focus(self):
        pass

    def init_focus(self):
        pass

    def set_entry_focus(self):
        pass
    #@+node:ekr.20171128051435.5: *4* ftm.set_ignore_case
    def set_ignore_case(self, aBool):
        '''Set the ignore-case checkbox to the given value.'''
        c = self.c
        c.findCommands.ignore_case = aBool
        w = self.check_box_ignore_case
        w.setChecked(aBool)
    #@+node:ekr.20171128051435.6: *4* ftm.init_widgets
    def init_widgets(self):
        '''
        Init widgets and ivars from c.config settings.
        Create callbacks that always keep the LeoFind ivars up to date.
        '''
        c = self.c
        find = c.findCommands
        # Find/change text boxes.
        table = (
            ('find_findbox', 'find_text', '<find pattern here>'),
            ('find_replacebox', 'change_text', ''),
        )
        for ivar, setting_name, default in table:
            s = c.config.getString(setting_name) or default
            w = getattr(self, ivar)
            w.insert(s)
        # Check boxes.
        table = (
            ('ignore_case', self.check_box_ignore_case),
            ('mark_changes', self.check_box_mark_changes),
            ('mark_finds', self.check_box_mark_finds),
            ('pattern_match', self.check_box_regexp),
            ('search_body', self.check_box_search_body),
            ('search_headline', self.check_box_search_headline),
            ('whole_word', self.check_box_whole_word),
            ('wrap', self.check_box_wrap_around),
        )
        for setting_name, w in table:
            val = c.config.getBool(setting_name, default=False)
            # The setting name is also the name of the LeoFind ivar.
            assert hasattr(find, setting_name), setting_name
            setattr(find, setting_name, val)
            if val:
                w.toggle()

            def check_box_callback(n, setting_name=setting_name, w=w):
                val = w.isChecked()
                assert hasattr(find, setting_name), setting_name
                setattr(find, setting_name, val)

        # Radio buttons
        table = (
            ('node_only', 'node_only', self.radio_button_node_only),
            ('entire_outline', None, self.radio_button_entire_outline),
            ('suboutline_only', 'suboutline_only', self.radio_button_suboutline_only),
        )
        for setting_name, ivar, w in table:
            val = c.config.getBool(setting_name, default=False)
            # The setting name is also the name of the LeoFind ivar.
            if ivar is not None:
                assert hasattr(find, setting_name), setting_name
                setattr(find, setting_name, val)
                w.toggle()

            def radio_button_callback(n, ivar=ivar, setting_name=setting_name, w=w):
                val = w.isChecked()
                find.radioButtonsChanged = True
                if ivar:
                    assert hasattr(find, ivar), ivar
                    setattr(find, ivar, val)

        # Ensure one radio button is set.
        if not find.node_only and not find.suboutline_only:
            w = self.radio_button_entire_outline
            w.toggle()
    #@+node:ekr.20171128051435.7: *4* ftm.set_radio_button
    #@@nobeautify

    def set_radio_button(self, name):
        '''Set the value of the radio buttons'''
        c = self.c
        fc = c.findCommands
        d = {
            # commandName       fc.ivar            # radio button.
            'node-only':       ('node_only',       self.radio_button_node_only),
            'entire-outline':  (None,              self.radio_button_entire_outline),
            'suboutline-only': ('suboutline_only', self.radio_button_suboutline_only)
        }
        ivar, w = d.get(name)
        assert w, repr(w)
        if not w.isChecked():
            w.toggle() # This just sets the radio button's value.
        # First, clear the ivars.
        fc.node_only = False
        fc.suboutline_only = False
        # Next, set the ivar.
        if ivar:
            setattr(fc, ivar, True)
       
    #@+node:ekr.20171128051435.8: *4* ftm.toggle_checkbox
    #@@nobeautify

    def toggle_checkbox(self,checkbox_name):
        '''Toggle the value of the checkbox whose name is given.'''
        c = self.c
        fc = c.findCommands
        if not fc:
            return
        d = {
            'ignore_case':     self.check_box_ignore_case,
            'mark_changes':    self.check_box_mark_changes,
            'mark_finds':      self.check_box_mark_finds,
            'pattern_match':   self.check_box_regexp,
            'search_body':     self.check_box_search_body,
            'search_headline': self.check_box_search_headline,
            'whole_word':      self.check_box_whole_word,
            'wrap':            self.check_box_wrap_around,
        }
        w = d.get(checkbox_name)
        assert w, repr(w)
        assert hasattr(fc, checkbox_name),checkbox_name
        setattr(fc, checkbox_name, not getattr(fc, checkbox_name))
        w.toggle() # Only toggles w's internal value.
        # g.trace(checkbox_name, getattr(fc, checkbox_name, None))
    #@-others
#@+node:edward.20170428174322.1: *3* class KeyEvent (object)
class KeyEvent:
    '''A gui-independent wrapper for gui events.'''
    #@+others
    #@+node:edward.20170428174322.2: *4* KeyEvent.__init__
    def __init__(self, c, char, event, shortcut, w,
        x=None,
        y=None,
        x_root=None,
        y_root=None,
    ):
        '''Ctor for KeyEvent class.'''
        assert not g.isStroke(shortcut), g.callers()
        stroke = g.KeyStroke(shortcut) if shortcut else None
        # g.trace('KeyEvent: stroke', stroke)
        self.c = c
        self.char = char or ''
        self.event = event
        self.stroke = stroke
        self.w = self.widget = w
        # Optional ivars
        self.x = x
        self.y = y
        # Support for fastGotoNode plugin
        self.x_root = x_root
        self.y_root = y_root
    #@+node:edward.20170428174322.3: *4* KeyEvent.__repr__
    def __repr__(self):
        return 'KeyEvent: stroke: %s, char: %s, w: %s' % (
            repr(self.stroke), repr(self.char), repr(self.w))
    #@+node:edward.20170428174322.4: *4* KeyEvent.get & __getitem__
    def get(self, attr):
        '''Compatibility with g.bunch: return an attr.'''
        return getattr(self, attr, None)

    def __getitem__(self, attr):
        '''Compatibility with g.bunch: return an attr.'''
        return getattr(self, attr, None)
    #@+node:edward.20170428174322.5: *4* KeyEvent.type
    def type(self):
        return 'KeyEvent'
    #@-others
#@+node:ekr.20170430114840.1: *3* class KeyHandler (object)
class KeyHandler:

    #@+others
    #@+node:ekr.20170430114930.1: *4* CKey.do_key & helpers
    def do_key(self, ch_i):
        '''
        Handle a key event by calling k.masterKeyHandler.
        Return True if the event was completely handled.
        '''
        #  This is a rewrite of LeoQtEventFilter code.
        c = g.app.log and g.app.log.c
        k = c and c.k
        if not c:
            return True # We are shutting down.
        if self.is_key_event(ch_i):
            try:
                ch = chr(ch_i)
            except Exception:
                ch = '<no ch>'
            char, shortcut = self.to_key(ch_i)
            if g.app.gui.in_dialog:
                if 0: g.trace('(CKey) dialog key', ch)
            elif shortcut:
                try:
                    w = c.frame.body.wrapper
                    event = self.create_key_event(c, w, char, shortcut)
                    k.masterKeyHandler(event)
                except Exception:
                    g.es_exception()
            g.app.gui.show_label(c)
            return bool(shortcut)
        g.app.gui.show_label(c)
        return False
    #@+node:ekr.20170430115131.4: *5* CKey.char_to_tk_name
    tk_dict = {
        # Part 1: same as g.app.guiBindNamesDict
        "&": "ampersand",
        "^": "asciicircum",
        "~": "asciitilde",
        "*": "asterisk",
        "@": "at",
        "\\": "backslash",
        "|": "bar",
        "{": "braceleft",
        "}": "braceright",
        "[": "bracketleft",
        "]": "bracketright",
        ":": "colon",
        ",": "comma",
        "$": "dollar",
        "=": "equal",
        "!": "exclam",
        ">": "greater",
        "<": "less",
        "-": "minus",
        "#": "numbersign",
        '"': "quotedbl",
        "'": "quoteright",
        "(": "parenleft",
        ")": "parenright",
        "%": "percent",
        ".": "period",
        "+": "plus",
        "?": "question",
        "`": "quoteleft",
        ";": "semicolon",
        "/": "slash",
        " ": "space",
        "_": "underscore",
        # Curses.
        # Qt
            # # Part 2: special Qt translations.
            # 'Backspace': 'BackSpace',
            # 'Backtab': 'Tab', # The shift mod will convert to 'Shift+Tab',
            # 'Esc': 'Escape',
            # 'Del': 'Delete',
            # 'Ins': 'Insert', # was 'Return',
            # # Comment these out to pass the key to the QTextWidget.
            # # Use these to enable Leo's page-up/down commands.
            # 'PgDown': 'Next',
            # 'PgUp': 'Prior',
            # # New entries.  These simplify code.
            # 'Down': 'Down', 'Left': 'Left', 'Right': 'Right', 'Up': 'Up',
            # 'End': 'End',
            # 'F1': 'F1', 'F2': 'F2', 'F3': 'F3', 'F4': 'F4', 'F5': 'F5',
            # 'F6': 'F6', 'F7': 'F7', 'F8': 'F8', 'F9': 'F9',
            # 'F10': 'F10', 'F11': 'F11', 'F12': 'F12',
            # 'Home': 'Home',
            # # 'Insert':'Insert',
            # 'Return': 'Return',
            # 'Tab': 'Tab',
            # # 'Tab':'\t', # A hack for QLineEdit.
            # # Unused: Break, Caps_Lock,Linefeed,Num_lock
    }

    def char_to_tk_name(self, ch):
        return self.tk_dict.get(ch, ch)
    #@+node:ekr.20170430115131.2: *5* CKey.create_key_event
    def create_key_event(self, c, w, ch, binding):
        trace = False
        # Last-minute adjustments...
        if binding == 'Return':
            ch = '\n' # Somehow Qt wants to return '\r'.
        elif binding == 'Escape':
            ch = 'Escape'
        # Switch the Shift modifier to handle the cap-lock key.
        if isinstance(ch, int):
            g.trace('can not happen: ch: %r binding: %r' % (ch, binding))
        elif (
            ch and len(ch) == 1 and
            binding and len(binding) == 1 and
            ch.isalpha() and binding.isalpha()
        ):
            if ch != binding:
                if trace: g.trace('caps-lock')
                binding = ch
        if trace: g.trace('ch: %r, binding: %r' % (ch, binding))
        import leo.core.leoGui as leoGui
        return leoGui.LeoKeyEvent(
            c=c,
            char=ch,
            event={'c': c, 'w': w},
            binding=binding,
            w=w, x=0, y=0, x_root=0, y_root=0,
        )
    #@+node:ekr.20170430115030.1: *5* CKey.is_key_event
    def is_key_event(self, ch_i):
        # pylint: disable=no-member
        return ch_i not in (curses.KEY_MOUSE,)
    #@+node:ekr.20170430115131.3: *5* CKey.to_key
    def to_key(self, i):
        '''Convert int i to a char and shortcut.'''
        trace = False
        a = curses.ascii
        char, shortcut = '', ''
        s = a.unctrl(i)
        if i <= 32:
            d = {
                8:'Backspace',  9:'Tab',
                10:'Return',    13:'Linefeed',
                27:'Escape',    32: ' ',
            }
            shortcut = d.get(i, '')
            # All real ctrl keys lie between 1 and 26.
            if shortcut and shortcut != 'Escape':
                char = chr(i)
            elif len(s) >= 2 and s.startswith('^'):
                shortcut = 'Ctrl+' + self.char_to_tk_name(s[1:].lower())
        elif i == 127:
            pass
        elif i < 128:
            char = shortcut = chr(i)
            if char.isupper():
                shortcut = 'Shift+' + char
            else:
                shortcut = self.char_to_tk_name(char)
        elif 265 <= i <= 276:
            # Special case for F-keys
            shortcut = 'F%s' % (i-265+1)
        elif i == 351:
            shortcut = 'Shift+Tab'
        elif s.startswith('\\x'):
            pass
        elif len(s) >= 3 and s.startswith('!^'):
            shortcut = 'Alt+' + self.char_to_tk_name(s[2:])
        else:
            pass
        if trace: g.trace('i: %s s: %s char: %r shortcut: %r' % (i, s, char, shortcut))
        return char, shortcut
    #@-others
#@+node:ekr.20170419094731.1: *3* class LeoCursesGui (leoGui.LeoGui)
class LeoCursesGui(leoGui.LeoGui):
    '''
    Leo's curses gui wrapper.
    This is g.app.gui, when --gui=curses.
    '''

    #@+others
    #@+node:ekr.20171128041849.1: *4* CGui.Birth & death
    #@+node:ekr.20170608112335.1: *5* CGui.__init__
    def __init__(self):
        '''Ctor for the CursesGui class.'''
        super().__init__('curses')
            # Init the base class.
        self.consoleOnly = False
            # Required attribute.
        self.curses_app = None
            # The singleton LeoApp instance.
        self.curses_form = None
            # The top-level curses Form instance.
            # Form.editw is the widget with focus.
        self.curses_gui_arg = None
            # A hack for interfacing with k.getArg.
        self.in_dialog = False
            # True: executing a modal dialog.
        self.log = None
            # The present log. Used by g.es
        self.log_inited = False
            # True: don't use the wait_list.
        self.minibuffer_label = ''
            # The label set by k.setLabel.
        self.wait_list = []
            # Queued log messages.
        self.init_logger()
            # Do this as early as possible.
            # It monkey-patches g.pr and g.trace.
        self.top_form = None
            # The top-level form. Set in createCursesTop.
        self.key_handler = KeyHandler()
    #@+node:ekr.20170502083158.1: *5* CGui.createCursesTop & helpers
    def createCursesTop(self):
        '''Create the top-level curses Form.'''
        trace = False and not g.unitTesting
        # Assert the key relationships required by the startup code.
        assert self == g.app.gui
        c = g.app.log.c
        assert c == g.app.windowList[0].c
        assert isinstance(c.frame, CoreFrame), repr(c.frame)
        if trace:
            g.trace('commanders in g.app.windowList')
            g.printList([z.c.shortFileName() for z in g.app.windowList])
        # Create the top-level form.
        self.curses_form = form = LeoForm(name='Dummy Name')
            # This call clears the screen.
        self.createCursesLog(c, form)
        self.createCursesTree(c, form)
        self.createCursesBody(c, form)
        self.createCursesMinibuffer(c, form)
        self.createCursesStatusLine(c, form)
        self.monkeyPatch(c)
        self.redraw_in_context(c)
        c.frame.tree.set_status_line(c.p)
        # self.focus_to_body(c)
        return form
    #@+node:ekr.20170502084106.1: *6* CGui.createCursesBody
    def createCursesBody(self, c, form):
        '''
        Create the curses body widget in the given curses Form.
        Populate it with c.p.b.
        '''
        trace = False

        class BoxTitleBody(npyscreen.BoxTitle):
            # pylint: disable=used-before-assignment
            _contained_widget = LeoBody
            how_exited = None

        box = form.add(
            BoxTitleBody,
            max_height=8, # Subtract 4 lines
            name='Body Pane',
            footer="Press e to edit line, esc to end editing, d to delete line",
            values=g.splitLines(c.p.b),
            slow_scroll=True,
        )
        assert isinstance(box, BoxTitleBody), repr(box)
        # Get the contained widget.
        widgets = box._my_widgets
        assert len(widgets) == 1
        w = widgets[0]
        if trace: g.trace('\nBODY', w, '\nBOX', box)
        assert isinstance(w, LeoBody), repr(w)
        # Link and check.
        assert isinstance(c.frame, leoFrame.LeoFrame), repr(c.frame)
            # The generic LeoFrame class
        assert isinstance(c.frame.body, leoFrame.LeoBody), repr(c.frame.body)
            # The generic LeoBody class
        assert c.frame.body.widget is None, repr(c.frame.body.widget)
        c.frame.body.widget = w
        assert c.frame.body.wrapper is None, repr(c.frame.body.wrapper)
        c.frame.body.wrapper = wrapper = BodyWrapper(c, 'body', w)
        # Inject the wrapper for get_focus.
        box.leo_wrapper = wrapper
        w.leo_wrapper = wrapper
        # Inject leo_c.
        w.leo_c = c
        w.leo_box = box

    #@+node:ekr.20170502083613.1: *6* CGui.createCursesLog
    def createCursesLog(self, c, form):
        '''
        Create the curses log widget in the given curses Form.
        Populate the widget with the queued log messages.
        '''
        class BoxTitleLog(npyscreen.BoxTitle):
            # pylint: disable=used-before-assignment
            _contained_widget = LeoLog
            how_exited = None

        box = form.add(
            BoxTitleLog,
            max_height=8, # Subtract 4 lines
            name='Log Pane',
            footer="Press e to edit line, esc to end editing, d to delete line",
            values=[s for s, color in self.wait_list],
            slow_scroll=False,
        )
        assert isinstance(box, BoxTitleLog), repr(box)
        # Clear the wait list and disable it.
        self.wait_list = []
        self.log_inited = True
        widgets = box._my_widgets
        assert len(widgets) == 1
        w = widgets[0]
        assert isinstance(w, LeoLog), repr(w)
        # Link and check...
        assert isinstance(self.log, CoreLog), repr(self.log)
        self.log.widget = w
        w.firstScroll()
        assert isinstance(c.frame, leoFrame.LeoFrame), repr(c.frame)
            # The generic LeoFrame class
        c.frame.log.wrapper = wrapper = LogWrapper(c, 'log', w)
        # Inject the wrapper for get_focus.
        box.leo_wrapper = wrapper
        w.leo_wrapper = wrapper
        w.leo_box = box
    #@+node:ekr.20170502084249.1: *6* CGui.createCursesMinibuffer
    def createCursesMinibuffer(self, c, form):
        '''Create the curses minibuffer widget in the given curses Form.'''
        trace = False

        class MiniBufferBox(npyscreen.BoxTitle):
            '''An npyscreen class representing Leo's minibuffer, with binding.'''
            # pylint: disable=used-before-assignment
            _contained_widget = LeoMiniBuffer
            how_exited = None

        box = form.add(MiniBufferBox, name='Mini-buffer', max_height=3)
        assert isinstance(box, MiniBufferBox)
        # Link and check...
        widgets = box._my_widgets
        assert len(widgets) == 1
        w = widgets[0]
        if trace: g.trace('\nMINI', w, '\nBOX', box)
        assert isinstance(w, LeoMiniBuffer), repr(w)
        assert isinstance(c.frame, CoreFrame), repr(c.frame)
        assert c.frame.miniBufferWidget is None
        wrapper = MiniBufferWrapper(c, 'minibuffer', w)
        assert wrapper.widget == w, repr(wrapper.widget)
        c.frame.miniBufferWidget = wrapper
        # Inject the box into the wrapper
        wrapper.box = box
        # Inject the wrapper for get_focus.
        box.leo_wrapper = wrapper
        w.leo_c = c
        w.leo_wrapper = wrapper

    #@+node:ekr.20171129193946.1: *6* CGui.createCursesStatusLine
    def createCursesStatusLine(self, c, form):
        '''Create the curses minibuffer widget in the given curses Form.'''

        class StatusLineBox(npyscreen.BoxTitle):
            '''An npyscreen class representing Leo's status line.'''
            # pylint: disable=used-before-assignment
            _contained_widget = LeoStatusLine
            how_exited = None

        box = form.add(StatusLineBox, name='Status Line', max_height=3)
        assert isinstance(box, StatusLineBox)
        # Link and check...
        widgets = box._my_widgets
        assert len(widgets) == 1
        w = widgets[0]
        assert isinstance(w, LeoStatusLine), repr(w)
        assert isinstance(c.frame, CoreFrame), repr(c.frame)
        wrapper = StatusLineWrapper(c, 'status-line', w)
        assert wrapper.widget == w, repr(wrapper.widget)
        assert isinstance(c.frame.statusLine, g.NullObject)
        # assert c.frame.statusLine is None
        c.frame.statusLine = wrapper
        c.frame.statusText = wrapper
        # Inject the wrapper for get_focus.
        box.leo_wrapper = wrapper
        w.leo_c = c
        w.leo_wrapper = wrapper
    #@+node:ekr.20170502083754.1: *6* CGui.createCursesTree
    def createCursesTree(self, c, form):
        '''Create the curses tree widget in the given curses Form.'''

        class BoxTitleTree(npyscreen.BoxTitle):
            # pylint: disable=used-before-assignment
            _contained_widget = LeoMLTree
            how_exited = None

        hidden_root_node = LeoTreeData(content='<HIDDEN>', ignore_root=True)
        if native:
            pass # cacher created below.
        else:
            for i in range(3):
                node = hidden_root_node.new_child(content='node %s' % (i))
                for j in range(2):
                    child = node.new_child(content='child %s.%s' % (i, j))
                    for k in range(4):
                        grand_child = child.new_child(
                            content='grand-child %s.%s.%s' % (i, j, k))
                        assert grand_child # for pyflakes.
        box = form.add(
            BoxTitleTree,
            max_height=8, # Subtract 4 lines
            name='Tree Pane',
            footer="Press d to delete node, e to edit headline (return to end), i to insert node",
            values=hidden_root_node,
            slow_scroll=False,
        )
        assert isinstance(box, BoxTitleTree), repr(box)
        # Link and check...
        widgets = box._my_widgets
        assert len(widgets) == 1
        w = leo_tree = widgets[0]
        assert isinstance(w, LeoMLTree), repr(w)
        leo_tree.leo_c = c
        if native:
            leo_tree.values = LeoValues(c=c, tree = leo_tree)
        assert getattr(leo_tree, 'hidden_root_node') is None, leo_tree
        leo_tree.hidden_root_node = hidden_root_node
        assert isinstance(c.frame, leoFrame.LeoFrame), repr(c.frame)
            # CoreFrame is a LeoFrame
        assert isinstance(c.frame.tree, CoreTree), repr(c.frame.tree)
        assert c.frame.tree.canvas is None, repr(c.frame.canvas)
            # A standard ivar, used by Leo's core.
        c.frame.canvas = leo_tree
            # A LeoMLTree.
        assert not hasattr(c.frame.tree, 'treeWidget'), repr(c.frame.tree.treeWidget)
        c.frame.tree.treeWidget = leo_tree
            # treeWidget is an official ivar.
        assert c.frame.tree.widget is None
        c.frame.tree.widget = leo_tree
            # Set CoreTree.widget.
        # Inject the wrapper for get_focus.
        wrapper = c.frame.tree
        assert wrapper
        box.leo_wrapper = wrapper
        w.leo_wrapper = wrapper
    #@+node:ekr.20171126191726.1: *6* CGui.monkeyPatch
    def monkeyPatch(self, c):
        '''Monkey patch commands'''
        table = (
            ('start-search', self.startSearch),
        )
        for commandName, func in table:
            g.global_commands_dict[commandName] = func
            c.k.overrideCommand(commandName, func)
        # A new ivar.
        c.inFindCommand = False
    #@+node:ekr.20170419110052.1: *5* CGui.createLeoFrame
    def createLeoFrame(self, c, title):
        '''
        Create a LeoFrame for the current gui.
        Called from Leo's core (c.initObjects).
        '''
        return CoreFrame(c, title)
    #@+node:ekr.20170502103338.1: *5* CGui.destroySelf
    def destroySelf(self):
        '''
        Terminate the curses gui application.
        Leo's core calls this only if the user agrees to terminate the app.
        '''
        sys.exit(0)
    #@+node:ekr.20170501032447.1: *5* CGui.init_logger
    def init_logger(self):

        self.rootLogger = logging.getLogger('')
        self.rootLogger.setLevel(logging.DEBUG)
        socketHandler = logging.handlers.SocketHandler(
            'localhost',
            logging.handlers.DEFAULT_TCP_LOGGING_PORT,
        )
        self.rootLogger.addHandler(socketHandler)
        logging.info('-' * 20)
        # Monkey-patch leoGlobals functions.
        g.es = es
        g.pr = pr # Most ouput goes through here, including g.es_exception.
        g.trace = trace
    #@+node:ekr.20170419140914.1: *5* CGui.runMainLoop
    def runMainLoop(self):
        '''The curses gui main loop.'''
        # pylint: disable=no-member
        #
        # Do NOT change g.app!
        self.curses_app = LeoApp()
        stdscr = curses.initscr()
        if 1: # Must follow initscr.
            self.dump_keys()
        try:
            self.curses_app.run()
                # run calls CApp.main(), which calls CGui.run().
        finally:
            curses.nocbreak()
            stdscr.keypad(0)
            curses.echo()
            curses.endwin()
            if 'shutdown' in g.app.debug:
                g.pr('Exiting Leo...')
    #@+node:ekr.20170502020354.1: *5* CGui.run
    def run(self):
        '''
        Create and run the top-level curses form.
        '''
        self.top_form = self.createCursesTop()
        # g.trace('(CGui) top_form', self.top_form)
        self.top_form.edit()
    #@+node:ekr.20170504112655.1: *4* CGui.Clipboard
    # Yes, using Tkinter seems to be the standard way.
    #@+node:ekr.20170504112744.3: *5* CGui.getTextFromClipboard
    def getTextFromClipboard(self):
        '''Get a unicode string from the clipboard.'''
        if not Tk:
            return ''
        root = Tk()
        root.withdraw()
        try:
            s = root.clipboard_get()
        except Exception: # _tkinter.TclError:
            s = ''
        root.destroy()
        return g.toUnicode(s)
    #@+node:ekr.20170504112744.2: *5* CGui.replaceClipboardWith
    def replaceClipboardWith(self, s):
        '''Replace the clipboard with the string s.'''
        if not Tk:
            return
        root = Tk()
        root.withdraw()
        root.clipboard_clear()
        root.clipboard_append(s)
        root.destroy()

    # Do *not* define setClipboardSelection.
    # setClipboardSelection = replaceClipboardWith
    #@+node:ekr.20170502021145.1: *4* CGui.dialogs
    #@+node:ekr.20170712145632.2: *5* CGui.createFindDialog
    def createFindDialog(self, c):
        '''Create and init a non-modal Find dialog.'''
        g.trace('not implemented')
    #@+node:ekr.20171126182120.1: *5* CGui.dialog_message
    def dialog_message(self, message):
        '''No longer used: a placeholder for dialogs.'''
        if not g.unitTesting:
            for s in g.splitLines(message):
                g.pr(s.rstrip())

    #@+node:ekr.20171126182120.2: *5* CGui.runAboutLeoDialog
    def runAboutLeoDialog(self, c, version, theCopyright, url, email):
        """Create and run Leo's About Leo dialog."""
        if not g.unitTesting:
            message =  '%s\n%s\n%s\n%s' % (version, theCopyright, url, email)
            utilNotify.notify_confirm(message, title="About Leo")
                # form_color='STANDOUT', wrap=True, wide=False, editw=0)

    #@+node:ekr.20171126182120.3: *5* CGui.runAskLeoIDDialog
    def runAskLeoIDDialog(self):
        """Create and run a dialog to get g.app.LeoID."""
        if not g.unitTesting:
            message = (
                "leoID.txt not found\n\n" +
                "The curses gui can not set this file." +
                "Exiting..."
            )
            g.trace(message)
            sys.exit(0)
                # "Please enter an id that identifies you uniquely.\n" +
                # "Your cvs/bzr login name is a good choice.\n\n" +
                # "Leo uses this id to uniquely identify nodes.\n\n" +
                # "Your id must contain only letters and numbers\n" +
                # "and must be at least 3 characters in length."

    #@+node:ekr.20171126182120.5: *5* CGui.runAskOkCancelNumberDialog
    def runAskOkCancelNumberDialog(self, c, title, message,
        cancelButtonText=None,
        okButtonText=None,
    ):
        """Create and run askOkCancelNumber dialog ."""
        if g.unitTesting:
            return False
        if self.curses_app:
            self.in_dialog = True
            val = utilNotify.notify_ok_cancel(message=message,title=title)
            self.in_dialog = False
            return val
        return False
    #@+node:ekr.20171126182120.6: *5* CGui.runAskOkCancelStringDialog
    def runAskOkCancelStringDialog(self, c, title, message,
        cancelButtonText=None,
        okButtonText=None,
        default="",
        wide=False,
    ):
        """Create and run askOkCancelString dialog ."""
        if g.unitTesting:
            return False
        self.in_dialog = True
        val = utilNotify.notify_ok_cancel(message=message,title=title)
            # val is True/False
        self.in_dialog = False
        return 'yes' if val else 'no'
    #@+node:ekr.20171126182120.4: *5* CGui.runAskOkDialog
    def runAskOkDialog(self, c, title,
        message=None,
        text="Ok",
    ):
        """Create and run an askOK dialog ."""
        if g.unitTesting:
            return False
        if self.curses_app:
            self.in_dialog = True
            val = utilNotify.notify_confirm(message=message,title=title)
            self.in_dialog = False
            return val
        return False

    #@+node:ekr.20171126182120.8: *5* CGui.runAskYesNoCancelDialog
    def runAskYesNoCancelDialog(self, c, title,
        message=None,
        yesMessage="Yes",
        noMessage="No",
        yesToAllMessage=None,
        defaultButton="Yes",
        cancelMessage=None,
    ):
        """Create and run an askYesNoCancel dialog ."""
        if g.unitTesting:
            return False
        self.in_dialog = True
        val = utilNotify.notify_yes_no(message=message,title=title)
            # Important: don't use notify_ok_cancel.
        self.in_dialog = False
        return 'yes' if val else 'no'

    #@+node:ekr.20171126182120.7: *5* CGui.runAskYesNoDialog
    def runAskYesNoDialog(self, c, title,
        message=None,
        yes_all=False,
        no_all=False,
    ):
        """Create and run an askYesNo dialog."""
        if g.unitTesting:
            return False
        self.in_dialog = True
        val = utilNotify.notify_ok_cancel(message=message,title=title)
        self.in_dialog = False
        return 'yes' if val else 'no'

    #@+node:ekr.20171126182120.9: *5* CGui.runOpenFileDialog
    def runOpenFileDialog(self, c, title, filetypes, defaultextension, multiple=False, startpath=None):
        if not g.unitTesting:
            g.trace('not ready yet', title)

    #@+node:ekr.20171126182120.10: *5* CGui.runPropertiesDialog
    def runPropertiesDialog(self,
        title='Properties',
        data=None,
        callback=None,
        buttons=None,
    ):
        """Dispay a modal TkPropertiesDialog"""
        if not g.unitTesting:
            g.trace('not ready yet', title)

    #@+node:ekr.20171126182120.11: *5* CGui.runSaveFileDialog
    def runSaveFileDialog(self, c, initialfile, title, filetypes, defaultextension):
        if g.unitTesting:
            return None
        # Not tested.
        self.in_dialog = True
        s = utilNotify.selectFile(
            select_dir=False,
            must_exist=False,
            confirm_if_exists=True,
            sort_by_extension=True,
        )
        self.in_dialog = False
        s = s or ''
        if s:
            c.last_dir = g.os_path_dirname(s)
        return s
    #@+node:ekr.20170430114709.1: *4* CGui.do_key
    def do_key(self, ch_i):

        # Ignore all printable characters.
        if not self.in_dialog and 32 <= ch_i < 128:
            g.trace('ignoring', chr(ch_i))
            return True
        return self.key_handler.do_key(ch_i)
    #@+node:ekr.20170526051256.1: *4* CGui.dump_keys
    def dump_keys(self):
        '''Show all defined curses.KEY_ constants.'''
        if 0:
            aList = ['%3s %s' % (getattr(curses,z), z)
                for z in dir(curses)
                    if isinstance(getattr(curses,z), int)]
            g.trace()
            g.printList(sorted(aList))
    #@+node:ekr.20170522005855.1: *4* CGui.event_generate
    def event_generate(self, c, char, shortcut, w):
        
        k = c.k
        event = KeyEvent(
            c=c,
            char=char,
            event=g.NullObject(),
            shortcut=shortcut,
            w=w,
        )
        k.masterKeyHandler(event)
        g.app.gui.show_label(c)
        c.outerUpdate()
    #@+node:ekr.20171128041920.1: *4* CGui.Focus
    #@+node:ekr.20171127171659.1: *5* CGui.focus_to_body
    def focus_to_body(self, c):
        '''Put focus in minibuffer text widget.'''
        w = self.set_focus(c, c.frame.body)
        assert w
        # w.edit()
    #@+node:ekr.20171202092838.1: *5* CGui.focus_to_head
    def focus_to_head(self, c, p):
        '''Put focus in minibuffer text widget.'''
        w = self.set_focus(c, c.frame.tree)
        assert w
        # w.edit()
    #@+node:ekr.20171127162649.1: *5* CGui.focus_to_minibuffer
    def focus_to_minibuffer(self, c):
        '''Put focus in minibuffer text widget.'''
        w = self.set_focus(c, c.frame.miniBufferWidget)
        assert w
        # w.edit()
    #@+node:ekr.20170502101347.1: *5* CGui.get_focus
    def get_focus(self, c=None, raw=False, at_idle=False):
        '''
        Return the Leo wrapper for the npyscreen widget that is being edited.
        '''
        # Careful during startup.
        trace = 'focus' in g.app.debug
        editw = getattr(g.app.gui.curses_form, 'editw', None)
        if editw is None:
            if trace: g.trace('(CursesGui) no editw')
            return None
        widget = self.curses_form._widgets__[editw]
        if hasattr(widget, 'leo_wrapper'):
            if trace:
                g.trace('(CursesGui)', widget.leo_wrapper.__class__.__name__)
                g.trace(g.callers())
            return widget.leo_wrapper
        g.trace('(CursesGui) ===== no leo_wrapper', widget)
            # At present, HeadWrappers have no widgets.
        return None
    #@+node:ekr.20171128041805.1: *5* CGui.set_focus & helpers
    set_focus_fail = []

    def set_focus(self, c, w):
        '''Given a Leo wrapper, set focus to the underlying npyscreen widget.'''
        new_focus = False
        if new_focus:
            return self.NEW_set_focus(c,w)
        return self.OLD_set_focus(c, w)
    #@+node:ekr.20171204040620.1: *6* CGui.NEW_set_focus & helper
    def NEW_set_focus(self, c, w):
        '''
        Given a Leo wrapper w, set focus to the underlying npyscreen widget.
        '''
        trace = 'focus' in g.app.debug
        verbose = True # verbose trace of callers.
        # Get the wrapper's npyscreen widget.
        widget = getattr(w, 'widget', None)
        if trace:
            g.trace('widget', widget.__class__.__name__)
            g.trace(g.callers(verbose=verbose))
        if not widget:
            g.trace('no widget', repr(w))
            return
        if not isinstance(widget, npyscreen.wgwidget.Widget):
            g.trace('not an npyscreen.Widget', repr(w))
            return
        form = self.curses_form
        # Find the widget to be edited
        for i, widget2 in enumerate(form._widgets__):
            if widget == widget2:
                # if trace: g.trace('FOUND', i, widget.__class__.__name__)
                self.switch_editing(i, widget)
                return
            for j, widget3 in enumerate(getattr(widget2, '_my_widgets', [])):
                if widget == widget3 or repr(widget) == repr(widget3):
                    # if trace: g.trace('FOUND INNER', i, j, widget2.__class__.__name__)
                    self.switch_editing(i, widget)
                    return
        if trace and widget not in self.set_focus_fail:
            self.set_focus_fail.append(widget)
            g.trace('Fail\n%r\n%r' % (widget, w))
    #@+node:ekr.20171204040620.2: *7* CGui.switch_editing
    def switch_editing(self, i, w):
        '''Clear editing for *all* widgets and set form.editw to i'''
        trace = 'focus' in g.app.debug
        how = None # 'leo-set-focus'
        form = self.curses_form
        if i == form.editw:
            if trace: g.trace('NO CHANGE', i, w.__class__.__name__)
            return
        if trace: g.trace('-----', i, w.__class__.__name__)
        
        # Select the widget for editing.
        form.editw = i

        if 0:
            # Inject 'leo-set-focus' into form.how_exited_handers
            
            def switch_focus_callback(form=form, i=i, w=w):
                g.trace(i, w.__class__.__name__)
                g.trace(g.callers(verbose=True))
                w.display()
                form.display()
            
            form.how_exited_handers[how] = switch_focus_callback
        if 1:
            # Clear editing for the editw widget:
            w = form._widgets__[i]
            # if trace: g.trace('editing', getattr(w, 'editing', None))
            if hasattr(w, 'editing'):
                w.editing = False
            w.how_exited = how
        if 1: 
            # Clear editing for all widgets.
            for i, w1 in enumerate(form._widgets__):
                # if trace: g.trace('CLEAR',  w.__class__.__name__)
                if getattr(w1, 'editing', None):
                    if trace: g.trace('End EDITING', w1.__class__.__name__)
                    w1.editing = False
                # w1.how_exited = how
                w1.display()
                for j, w2 in enumerate(getattr(w, '_my_widgets', [])):
                    # if trace: g.trace('CLEAR INNER',  w.__class__.__name__)
                    if getattr(w2, 'editing', None):
                        if trace: g.trace('END EDITING', w2.__class__.__name__)
                        w2.editing = False
                    # w2.how_exited = how
                    w2.display()
                    
        # Start editing the widget.
        w.editing = True
        w.display()
        form.display()
        w.edit()
            # Does not return
    #@+node:ekr.20171204100910.1: *6* CGui.OLD_set_focus
    def OLD_set_focus(self, c, w):
        '''Given a Leo wrapper, set focus to the underlying npyscreen widget.'''
        trace = 'focus' in g.app.debug
        verbose = True # Full trace of callers.
        # Get the wrapper's npyscreen widget.
        widget = getattr(w, 'widget', None)
        if trace:
            g.trace('widget', widget.__class__.__name__)
            g.trace(g.callers(verbose=verbose))
        if not widget:
            if trace or not w: g.trace('no widget', repr(w))
            return
        if not isinstance(widget, npyscreen.wgwidget.Widget):
            g.trace('not an npyscreen.Widget', repr(w))
            return
        form = self.curses_form
        if 1: # Seems to cause problems.
            # End editing in the previous form.
            i = form.editw
            w = form._widgets__[i]
            if trace and verbose:
                g.trace('CLEAR FOCUS', i, w.__class__.__name__)
            if w:
                w.editing = False
                w.how_exited = EXITED_ESCAPE
                w.display()
        for i, widget2 in enumerate(form._widgets__):
            if widget == widget2:
                if trace: g.trace('FOUND', i, widget)
                form.editw = i
                form.display()
                return
            for j, widget3 in enumerate(getattr(widget2, '_my_widgets', [])):
                if widget == widget3 or repr(widget) == repr(widget3):
                    if trace: g.trace('FOUND INNER', i, j, widget2)
                    form.editw = i
                        # Select the *outer* widget.
                    # Like BoxTitle.edit.
                    if 1:
                        # So weird.
                        widget2.editing=True
                        widget2.display()
                        # widget2.entry_widget.edit()
                        widget3.edit()
                        widget2.how_exited = widget3.how_exited
                        widget2.editing = False
                        widget2.display()
                    form.display()
                    return
        if trace and widget not in self.set_focus_fail:
            self.set_focus_fail.append(widget)
            g.trace('Fail\n%r\n%r' % (widget, w))
    #@+node:ekr.20170514060742.1: *4* CGui.Fonts
    def getFontFromParams(self, family, size, slant, weight, defaultSize=12):

        return None
    #@+node:ekr.20170504052119.1: *4* CGui.isTextWrapper
    def isTextWrapper(self, w):
        '''Return True if w is a Text widget suitable for text-oriented commands.'''
        return w and getattr(w, 'supportsHighLevelInterface', None)
    #@+node:ekr.20170504052042.1: *4* CGui.oops
    def oops(self):
        '''Ignore do-nothing methods.'''
        g.pr("CursesGui oops:", g.callers(4), "should be overridden in subclass")
    #@+node:ekr.20170612063102.1: *4* CGui.put_help
    def put_help(self, c, s, short_title):
        '''Put a help message in a dialog.'''
        if not g.unitTesting:
            utilNotify.notify_confirm(
                message=s,
                title=short_title or 'Help',
            )
    #@+node:ekr.20171130195357.1: *4* CGui.redraw_in_context
    def redraw_in_context(self, c):
        '''Redraw p in context.'''
        w = c.frame.tree.widget
        c.expandAllAncestors(c.p)
        g.app.gui.show_label(c)
        w.values.clear_cache()
        w.select_leo_node(c.p)
        w.update(forceInit=True)
        g.app.gui.curses_form.display()
    #@+node:ekr.20171130181722.1: *4* CGui.repeatComplexCommand
    def repeatComplexCommand(self, c):
        '''An override of the 'repeat-complex-command' command.'''
        trace = False and not g.unitTesting
        k = c.k
        if k.mb_history:
            commandName = k.mb_history[0]
            if trace:
                g.trace(commandName)
                g.printObj(k.mb_history)
            k.masterCommand(
                commandName=commandName,
                event=KeyEvent(c,char='',event='',shortcut='',w=None),
                func=None,
                stroke=None,
            )
        else:
            g.warning('no previous command')
    #@+node:ekr.20171201084211.1: *4* CGui.set_minibuffer_label
    def set_minibuffer_label(self, c, s):
        '''Remember the minibuffer label.'''
        self.minibuffer_label = s
        self.show_label(c)
    #@+node:ekr.20171202092230.1: *4* CGui.show_find_success
    def show_find_success(self, c, in_headline, insert, p):
        '''Handle a successful find match.'''
        trace = False and not g.unitTesting
        if in_headline:
            if trace: g.trace('HEADLINE', p.h)
            c.frame.tree.widget.select_leo_node(p)
            self.focus_to_head(c, p)
                # Does not return.
        else:
            w = c.frame.body.widget
            row, col = g.convertPythonIndexToRowCol(p.b, insert)
            if trace: g.trace('BODY ROW', row, p.h)
            w.cursor_line = row
            self.focus_to_body(c)
                # Does not return.
    #@+node:ekr.20171201081700.1: *4* CGui.show_label
    def show_label(self, c):
        '''
        Set the minibuffer's label the value set by set_minibuffer_label.
        '''
        trace = False and not g.unitTesting
        wrapper = c.frame.miniBufferWidget
        if not wrapper: return
        box = wrapper.box
        if not box: return
        s = self.minibuffer_label
        if trace: g.trace(repr(s))
        box.name = 'Mini-buffer: %s' % s.strip()
        box.update()
        g.app.gui.curses_form.display()
       
    #@+node:ekr.20171126192144.1: *4* CGui.startSearch
    def startSearch(self, event):
        c = event.get('c')
        if not c: return
        # This does not work because the console doesn't show the message!
            # if not isinstance(w, MiniBufferWrapper):
                # g.es_print('Sorry, Ctrl-F must be run from the minibuffer.')
                # return
        fc = c.findCommands
        ftm = c.frame.ftm
        c.inCommand = False
        c.inFindCommand = True
            # A new flag.
        fc.minibuffer_mode = True
        if 0: # Allow hard settings, for tests.
            table = (
                ('pattern_match', ftm.check_box_regexp, True),
            )
            for setting_name, w, val in table:
                assert hasattr(fc, setting_name), setting_name
                setattr(fc, setting_name, val)
                w.setCheckState(val)
        c.findCommands.startSearch(event)
        options = fc.computeFindOptionsInStatusArea()
        c.frame.statusLine.put(options)
        self.focus_to_minibuffer(c)
            # Does not return!
    #@-others
#@+node:ekr.20170524124010.1: ** Leo widget classes
# Most are subclasses Leo's base gui classes.
# All classes have a "c" ivar.
#@+node:ekr.20170501024433.1: *3* class CoreBody (leoFrame.LeoBody)
class CoreBody (leoFrame.LeoBody):
    '''
    A class that represents curses body pane.
    This is c.frame.body.
    '''

    def __init__(self, c):

        super().__init__(frame=c.frame, parentFrame=None)
            # Init the base class.
        self.c = c
        self.colorizer = leoFrame.NullColorizer(c)
        self.widget = None
        self.wrapper = None # Set in createCursesBody.
#@+node:ekr.20170419105852.1: *3* class CoreFrame (leoFrame.LeoFrame)
class CoreFrame (leoFrame.LeoFrame):
    '''The LeoFrame when --gui=curses is in effect.'''

    #@+others
    #@+node:ekr.20170501155347.1: *4* CFrame.birth
    def __init__ (self, c, title):

        leoFrame.LeoFrame.instances += 1 # Increment the class var.
        super().__init__(c, gui=g.app.gui)
            # Init the base class.
        assert c and self.c == c
        c.frame = self # Bug fix: 2017/05/10.
        self.log = CoreLog(c)
        g.app.gui.log = self.log
        self.title = title
        # Standard ivars.
        self.ratio = self.secondary_ratio = 0.5
        # Widgets
        self.top = TopFrame(c)
        self.body = CoreBody(c)
        self.menu = CoreMenu(c)
        self.miniBufferWidget = None
            # Set later.
        self.statusLine = g.NullObject() # For unit tests.
        assert self.tree is None, self.tree
        self.tree = CoreTree(c)
        # Official ivars...
            # self.iconBar = None
            # self.iconBarClass = None # self.QtIconBarClass
            # self.initComplete = False # Set by initCompleteHint().
            # self.minibufferVisible = True
            # self.statusLineClass = None # self.QtStatusLineClass
            #
            # Config settings.
            # self.trace_status_line = c.config.getBool('trace_status_line')
            # self.use_chapters = c.config.getBool('use_chapters')
            # self.use_chapter_tabs = c.config.getBool('use_chapter_tabs')
            #
            # self.set_ivars()
            #
            # "Official ivars created in createLeoFrame and its allies.
            # self.bar1 = None
            # self.bar2 = None
            # self.f1 = self.f2 = None
            # self.findPanel = None # Inited when first opened.
            # self.iconBarComponentName = 'iconBar'
            # self.iconFrame = None
            # self.canvas = None
            # self.outerFrame = None
            # self.statusFrame = None
            # self.statusLineComponentName = 'statusLine'
            # self.statusText = None
            # self.statusLabel = None
            # self.top = None # This will be a class Window object.
            # Used by event handlers...
            # self.controlKeyIsDown = False # For control-drags
            # self.isActive = True
            # self.redrawCount = 0
            # self.wantedWidget = None
            # self.wantedCallbackScheduled = False
            # self.scrollWay = None
    #@+node:ekr.20170420163932.1: *5* CFrame.finishCreate
    def finishCreate(self):

        c = self.c
        g.app.windowList.append(self)
        ftm = StringFindTabManager(c)
        c.findCommands.ftm = ftm
        self.ftm = ftm
        self.createFindTab()
        self.createFirstTreeNode()
            # Call the base-class method.
    #@+node:ekr.20171128052121.1: *5* CFrame.createFindTab & helpers
    def createFindTab(self):
        '''Create a Find Tab in the given parent.'''
        # Like DynamicWindow.createFindTab.
        ftm = self.ftm
        assert ftm
        self.create_find_findbox()
        self.create_find_replacebox()
        self.create_find_checkboxes()
        # Official ivars (in addition to checkbox ivars).
        self.leo_find_widget = None
        ftm.init_widgets()
    #@+node:ekr.20171128052121.4: *6* CFrame.create_find_findbox
    def create_find_findbox(self):
        '''Create the Find: label and text area.'''
        c = self.c
        fc = c.findCommands
        ftm = self.ftm
        assert ftm
        assert ftm.find_findbox is None
        ftm.find_findbox = self.createLineEdit('findPattern', disabled=fc.expert_mode)
    #@+node:ekr.20171128052121.5: *6* CFrame.create_find_replacebox
    def create_find_replacebox(self):
        '''Create the Replace: label and text area.'''
        c = self.c
        fc = c.findCommands
        ftm = self.ftm
        assert ftm
        assert ftm.find_replacebox is None
        ftm.find_replacebox = self.createLineEdit('findChange', disabled=fc.expert_mode)
    #@+node:ekr.20171128052121.6: *6* CFrame.create_find_checkboxes
    def create_find_checkboxes(self):
        '''Create check boxes and radio buttons.'''
        # c = self.c
        ftm = self.ftm

        def mungeName(kind, label):
            # The returned value is the name of an ivar.
            kind = 'check_box_' if kind == 'box' else 'radio_button_'
            name = label.replace(' ', '_').replace('&', '').lower()
            return '%s%s' % (kind, name)

        d = {
            'box': self.createCheckBox,
            'rb': self.createRadioButton,
        }
        table = (
            # First row.
            ('box', 'whole &Word'),
            ('rb', '&Entire outline'),
            # Second row.
            ('box', '&Ignore case'),
            ('rb', '&Suboutline only'),
            # Third row.
            ('box', 'wrap &Around'),
            ('rb', '&Node only'),
            # Fourth row.
            ('box', 'rege&Xp'),
            ('box', 'search &Headline'),
            # Fifth row.
            ('box', 'mark &Finds'),
            ('box', 'search &Body'),
            # Sixth row.
            ('box', 'mark &Changes'),
        )
        for kind, label in table:
            name = mungeName(kind, label)
            func = d.get(kind)
            assert func
            label = label.replace('&', '')
            w = func(name, label)
            assert getattr(ftm, name) is None
            setattr(ftm, name, w)
    #@+node:ekr.20171128053531.3: *6* CFrame.createCheckBox
    def createCheckBox(self, name, label):

        return leoGui.StringCheckBox(name, label)
    #@+node:ekr.20171128053531.8: *6* CFrame.createLineEdit
    def createLineEdit(self, name, disabled=True):

        return leoGui.StringLineEdit(name, disabled)
    #@+node:ekr.20171128053531.9: *6* CFrame.createRadioButton
    def createRadioButton(self, name, label):

        return leoGui.StringRadioButton(name, label)
    #@+node:ekr.20170524145750.1: *4* CFrame.cmd (decorator)
    def cmd(name):
        '''Command decorator for the LeoFrame class.'''
        # pylint: disable=no-self-argument
        return g.new_cmd_decorator(name, ['c', 'frame',])
    #@+node:ekr.20170501161029.1: *4* CFrame.do nothings
    def bringToFront(self):
        pass

    def contractPane(self, event=None):
        pass

    def deiconify(self):
        pass

    def destroySelf(self):
        pass

    def get_window_info(self):
        '''Return width, height, left, top.'''
        return 700, 500, 50, 50

    def iconify(self):
        pass

    def lift(self):
        pass

    def getShortCut(self, *args, **kwargs):
        return None

    def getTitle(self):
        return self.title

    def minimizeAll(self, event=None):
        pass

    def oops(self):
        '''Ignore do-nothing methods.'''
        g.pr("CoreFrame oops:", g.callers(4), "should be overridden in subclass")

    def resizePanesToRatio(self, ratio, secondary_ratio):
        '''Resize splitter1 and splitter2 using the given ratios.'''
        # self.divideLeoSplitter1(ratio)
        # self.divideLeoSplitter2(secondary_ratio)

    def resizeToScreen(self, event=None):
        pass

    def setInitialWindowGeometry(self):
        pass

    def setTitle(self, title):
        self.title = g.toUnicode(title)

    def setTopGeometry(self, w, h, x, y):
        pass

    def setWrap(self, p):
        pass

    def update(self, *args, **keys):
        pass
    #@+node:ekr.20170524144717.1: *4* CFrame.get_focus
    def getFocus(self):

        return g.app.gui.get_focus()
    #@+node:ekr.20170522015906.1: *4* CFrame.pasteText
    @cmd('paste-text')
    def pasteText(self, event=None, middleButton=False):
        '''
        Paste the clipboard into a widget.
        If middleButton is True, support x-windows middle-mouse-button easter-egg.
        '''
        trace = False and not g.unitTesting
        c = self.c
        w = event and event.widget
        if not isinstance(w, leoFrame.StringTextWrapper):
            g.trace('not a StringTextWrapper', repr(w))
            return
        wname = c.widget_name(w)
        i, j = oldSel = w.getSelectionRange()
            # Returns insert point if no selection.
        oldText = w.getAllText()
        s = g.app.gui.getTextFromClipboard()
        s = g.toUnicode(s)
        if trace: g.trace('wname', wname, 'len(s)', len(s))
        single_line = any([wname.startswith(z) for z in ('head', 'minibuffer')])
        if single_line:
            # Strip trailing newlines so the truncation doesn't cause confusion.
            while s and s[-1] in ('\n', '\r'):
                s = s[: -1]
        # Update the widget.
        if i != j:
            w.delete(i, j)
        w.insert(i, s)
        w.see(i + len(s) + 2)
        if wname.startswith('body'):
            c.frame.body.onBodyChanged('Paste', oldSel=oldSel, oldText=oldText)
        elif wname.startswith('head'):
            c.frame.tree.onHeadChanged(c.p, s=w.getAllText(), undoType='Paste')
                # New for Curses gui.

    OnPasteFromMenu = pasteText
    #@-others
#@+node:ekr.20170419143731.1: *3* class CoreLog (leoFrame.LeoLog)
class CoreLog (leoFrame.LeoLog):
    '''
    A class that represents curses log pane.
    This is c.frame.log.
    '''

    #@+others
    #@+node:ekr.20170419143731.4: *4* CLog.__init__
    def __init__(self, c):
        '''Ctor for CLog class.'''
        super().__init__(frame=None, parentFrame=None)
        self.c = c
        self.enabled = True
            # Required by Leo's core.
        self.isNull = False
            # Required by Leo's core.
        self.widget = None
            # The npyscreen log widget. Queue all output until set.
            # Set in CApp.main.
        #
        self.contentsDict = {}
            # Keys are tab names.  Values are widgets.
        self.logDict = {}
            # Keys are tab names text widgets.  Values are the widgets.
        self.tabWidget = None
    #@+node:ekr.20170419143731.2: *4*  CLog.cmd (decorator)
    def cmd(name):
        '''Command decorator for the c.frame.log class.'''
        # pylint: disable=no-self-argument
        return g.new_cmd_decorator(name, ['c', 'frame', 'log'])
    #@+node:ekr.20170419143731.7: *4* CLog.clearLog
    @cmd('clear-log')
    def clearLog(self, event=None):
        '''Clear the log pane.'''
    #@+node:ekr.20170420035717.1: *4* CLog.enable/disable
    def disable(self):
        self.enabled = False

    def enable(self, enabled=True):
        self.enabled = enabled
    #@+node:ekr.20170420041119.1: *4* CLog.finishCreate
    def finishCreate(self):
        '''CoreLog.finishCreate.'''

    #@+node:ekr.20170513183826.1: *4* CLog.isLogWidget
    def isLogWidget(self, w):
        return w == self or w in list(self.contentsDict.values())
    #@+node:ekr.20170513184115.1: *4* CLog.orderedTabNames
    def orderedTabNames(self, LeoLog=None): # Unused: LeoLog
        '''Return a list of tab names in the order in which they appear in the QTabbedWidget.'''
        return []
        # w = self.tabWidget
        #return [w.tabText(i) for i in range(w.count())]
    #@+node:ekr.20170419143731.15: *4* CLog.put
    def put(self, s, color=None, tabName='Log', from_redirect=False):
        '''All output to the log stream eventually comes here.'''
        c, w = self.c, self.widget
        if not c or not c.exists or not w:
            # logging.info('CLog.put: no c: %r' % s)
            return
        assert isinstance(w, npyscreen. MultiLineEditable), repr(w)
        # Fix #508: Part 1: Handle newlines correctly.
        lines = s.split('\n')
        for line in lines:
            w.values.append(line)
        # Fix #508: Part 2: Scroll last line into view.
        w.cursor_line += len(lines)
        w.start_display_at += len(lines)
        w.update()
    #@+node:ekr.20170419143731.16: *4* CLog.putnl
    def putnl(self, tabName='Log'):
        '''Put a newline to the Qt log.'''
        # This is not called normally.
        # print('CLog.put: %s' % g.callers())
        if g.app.quitting:
            return
    #@-others
#@+node:ekr.20170419111515.1: *3* class CoreMenu (leoMenu.LeoMenu)
class CoreMenu (leoMenu.LeoMenu):

    def __init__ (self, c):

        dummy_frame = g.Bunch(c=c)
        super().__init__(dummy_frame)
        self.c = c
        self.d = {}

    def oops(self):
        '''Ignore do-nothing methods.'''
        # g.pr("CoreMenu oops:", g.callers(4), "should be overridden in subclass")


#@+node:ekr.20170501024424.1: *3* class CoreTree (leoFrame.LeoTree)
class CoreTree (leoFrame.LeoTree):
    '''
    A class that represents curses tree pane.

    This is the c.frame.tree instance.
    '''

    #@+others
    #@+node:ekr.20170511111242.1: *4*  CTree.ctor
    class DummyFrame:
        def __init__(self, c):
            self.c = c

    def __init__(self, c):

        dummy_frame = self.DummyFrame(c)
        super().__init__(dummy_frame)
            # Init the base class.
        assert self.c
        assert not hasattr(self, 'widget')
        self.revertVnode = None # A hack for onHeadChanged.
        self.redrawCount = 0 # For unit tests.
        self.widget = None
            # A LeoMLTree set by CGui.createCursesTree.
        # self.setConfigIvars()
        #
        # Status flags, for busy()
        self.contracting = False
        self.expanding = False
        self.redrawing = False
        self.selecting = False
    #@+node:ekr.20170511094217.1: *4* CTree.Drawing
    #@+node:ekr.20170511094217.3: *5* CTree.redraw
    def redraw(self, p=None, scroll=True, forceDraw=False):
        '''
        Redraw all visible nodes of the tree.
        Preserve the vertical scrolling unless scroll is True.
        '''
        trace = False and not g.unitTesting
        if g.unitTesting:
            return # There is no need. At present, the tests hang.
        if trace: g.trace(g.callers())
        if self.widget and not self.busy():
            self.redrawCount += 1 # To keep a unit test happy.
            self.widget.update()
    # Compatibility

    full_redraw = redraw
    redraw_now = redraw
    repaint = redraw
    #@+node:ekr.20170511100356.1: *5* CTree.redraw_after...
    def redraw_after_contract(self, p=None):
        self.redraw(p, scroll=False)

    def redraw_after_expand(self, p=None):
        self.redraw(p)

    def redraw_after_head_changed(self):
        self.redraw()

    def redraw_after_icons_changed(self):
        self.redraw()

    def redraw_after_select(self, p=None):
        '''Redraw the entire tree when an invisible node is selected.'''
        # Prevent the selecting lockout from disabling the redraw.
        oldSelecting = self.selecting
        self.selecting = False
        try:
            if not self.busy():
                self.redraw(p=p, scroll=False)
        finally:
            self.selecting = oldSelecting
        # Do *not* call redraw_after_select here!
    #@+node:ekr.20170511104032.1: *4* CTree.error
    def error(self, s):
        if not g.app.unitTesting:
            g.trace('LeoQtTree Error: %s' % (s), g.callers())
    #@+node:ekr.20170511104533.1: *4* CTree.Event handlers
    #@+node:ekr.20170511104533.10: *5* CTree.busy
    def busy(self):
        '''Return True (actually, a debugging string)
        if any lockout is set.'''
        trace = False
        table = ('contracting','expanding','redrawing','selecting')
        kinds = ','.join([z for z in table if getattr(self, z)])
        if kinds and trace: g.trace(kinds)
        return kinds # Return the string for debugging
    #@+node:ekr.20170511104533.12: *5* CTree.onHeadChanged
    # Tricky code: do not change without careful thought and testing.

    def onHeadChanged(self, p, s=None, undoType='Typing'):
        '''
        Officially change a headline.   
        This is c.frame.tree.onHeadChanged.
        '''
        trace = False
        c, u = self.c, self.c.undoer
        if not c.frame.body.wrapper:
            if trace: g.trace('NO wrapper')
            return # Startup.
        if not self.revertVnode:
            if trace: g.trace('no headline ever edited')
            self.revertHeadline = ''
                # Fix #1238: A hack to force an update.
        w = self.edit_widget(p)
        if c.suppressHeadChanged:
            if trace: g.trace('c.suppressHeadChanged')
            return
        if not w:
            if trace: g.trace('****** no w for p: %s', repr(p))
            return
        ch = '\n' # New in 4.4: we only report the final keystroke.
        if s is None:
            s = w.getAllText()
        # if trace: g.trace('CoreTree: %r ==> %r' % (p and p.h, s))
        #@+<< truncate s if it has multiple lines >>
        #@+node:ekr.20170511104533.13: *6* << truncate s if it has multiple lines >>
        # Remove trailing newlines before warning of truncation.
        while s and s[-1] == '\n':
            s = s[: -1]
        # Warn if there are multiple lines.
        i = s.find('\n')
        if i > -1:
            s = s[: i]
            # if s != oldHead:
                # g.warning("truncating headline to one line")
        limit = 1000
        if len(s) > limit:
            s = s[: limit]
            # if s != oldHead:
                # g.warning("truncating headline to", limit, "characters")
        #@-<< truncate s if it has multiple lines >>
        # Make the change official, but undo to the *old* revert point.
        oldRevert = self.revertHeadline
        changed = s != oldRevert
        self.revertHeadline = s
        p.initHeadString(s)
        if trace: g.trace('changed', changed, 'new', repr(s))
        if g.doHook("headkey1", c=c, p=p, ch=ch, changed=changed):
            return # The hook claims to have handled the event.
        if changed:
            undoData = u.beforeChangeNodeContents(p, oldHead=oldRevert)
            if not c.changed:
                c.setChanged(True)
            # New in Leo 4.4.5: we must recolor the body because
            # the headline may contain directives.
                # c.frame.scanForTabWidth(p)
                # c.frame.body.recolor(p)
            dirtyVnodeList = p.setDirty()
            u.afterChangeNodeContents(p, undoType, undoData,
                dirtyVnodeList=dirtyVnodeList, inHead=True)
        # if changed:
        #    c.redraw_after_head_changed()
            # Fix bug 1280689: don't call the non-existent c.treeEditFocusHelper
        g.doHook("headkey2", c=c, p=p, ch=ch, changed=changed)
    #@+node:ekr.20170511104121.1: *4* CTree.Scroll bars
    #@+node:ekr.20170511104121.2: *5* Ctree.getScroll
    def getScroll(self):
        '''Return the hPos,vPos for the tree's scrollbars.'''
        return 0, 0
    #@+node:ekr.20170511104121.4: *5* Ctree.setH/VScroll
    def setHScroll(self, hPos):
        pass
        # w = self.widget
        # hScroll = w.horizontalScrollBar()
        # hScroll.setValue(hPos)

    def setVScroll(self, vPos):
        pass
        # w = self.widget
        # vScroll = w.verticalScrollBar()
        # vScroll.setValue(vPos)
    #@+node:ekr.20170511105355.1: *4* CTree.Selecting & editing
    #@+node:ekr.20170511105355.4: *5* CTree.edit_widget
    def edit_widget(self, p):
        """Returns the edit widget for position p."""
        wrapper = HeadWrapper(c=self.c, name='head', p=p)
        return wrapper
    #@+node:ekr.20170511095353.1: *5* CTree.editLabel (not used)
    def editLabel(self, p, selectAll=False, selection=None):
        """Start editing p's headline."""
        self.revertHeadline = p.h
        return None, None
    #@+node:ekr.20170511105355.7: *5* CTree.endEditLabel
    def endEditLabel(self):
        '''Override LeoTree.endEditLabel.
        End editing of the presently-selected headline.
        '''
        c = self.c
        p = c.currentPosition()
        self.onHeadChanged(p)
    #@+node:ekr.20170511105355.8: *5* CTree.getSelectedPositions (called from Leo's core)
    def getSelectedPositions(self):
        '''This can be called from Leo's core.'''
        # Not called from unit tests.
        return [self.c.p]
    #@+node:ekr.20170511105355.9: *5* CTree.setHeadline
    def setHeadline(self, p, s):
        '''Force the actual text of the headline widget to p.h.'''
        trace = False and not g.unitTesting
        # This is used by unit tests to force the headline and p into alignment.
        if not p:
            if trace: g.trace('*** no p')
            return
        # Don't do this here: the caller should do it.
        # p.setHeadString(s)
        e = self.edit_widget(p)
        assert isinstance(e, HeadWrapper), repr(e)
        e.setAllText(s)
        if trace: g.trace(e)
    #@+node:ekr.20170523115818.1: *5* CTree.set_body_text_after_select
    def set_body_text_after_select(self, p, old_p, traceTime=False, force=False):
        '''Set the text after selecting a node.'''
        c = self.c
        wrapper = c.frame.body.wrapper
        widget = c.frame.body.widget
        c.setCurrentPosition(p)
            # Important: do this *before* setting text,
            # so that the colorizer will have the proper c.p.
        s = p.v.b
        wrapper.setAllText(s)
        widget.values = g.splitLines(s)
        widget.update()
            # Now done after c.p has been changed.
                # p.restoreCursorAndScroll()
    #@-others
#@+node:ekr.20171129200050.1: *3* class CoreStatusLine (object)
class CoreStatusLine:
    '''A do-nothing status line.'''

    def __init__(self, c, parentFrame):
        '''Ctor for CoreStatusLine class.'''
        # g.trace('(CoreStatusLine)', c)
        self.c = c
        self.enabled = False
        self.parentFrame = parentFrame
        self.textWidget = None
        # The official ivars. 
        c.frame.statusFrame = None
        c.frame.statusLabel = None
        c.frame.statusText = None

    #@+others
    #@-others
#@+node:ekr.20170502093200.1: *3* class TopFrame (object)
class TopFrame :
    '''A representation of c.frame.top.'''

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

    def select(self, *args, **kwargs):
        pass

    def findChild(self, *args, **kwargs):
        # Called by nested_splitter.py.
        return g.NullObject()

    def finishCreateLogPane(self, *args, **kwargs):
        pass
#@+node:ekr.20170524124449.1: ** Npyscreen classes
# These are subclasses of npyscreen base classes.
# These classes have "leo_c" ivars.
#@+node:ekr.20170420054211.1: *3* class LeoApp (npyscreen.NPSApp)
class LeoApp(npyscreen.NPSApp):
    '''
    The *anonymous* npyscreen application object, created from
    CGui.runMainLoop. This is *not* g.app.
    '''

    # No ctor needed.
        # def __init__(self):
            # super().__init__()

    def main(self):
        '''
        Called automatically from the ctor.
        Create and start Leo's singleton npyscreen window.
        '''
        g.app.gui.run()
#@+node:ekr.20170526054750.1: *3* class LeoBody (npyscreen.MultiLineEditable)
class LeoBody (npyscreen.MultiLineEditable):

    continuation_line = "- more -" # value of contination line.
    _contained_widgets = LeoBodyTextfield

    def __init__ (self, *args, **kwargs):
        super(LeoBody, self).__init__(*args, **kwargs)
        self.leo_box = None
        self.leo_c = None
        self.leo_wrapper = None
        self.set_handlers()
        # createCursesBody  sets the leo_box and leo_c ivars.

    #@+others
    #@+node:ekr.20170604183231.1: *4*  LeoBody handlers
    #@+node:ekr.20170526114040.4: *5* LeoBody.h_cursor_line_down
    def h_cursor_line_down(self, ch_i):
        '''
        From MultiLine.h_cursor_line_down. Never exit.
        '''
        # pylint: disable=access-member-before-definition
        #
        # Reset editing mode.
        self.set_box_name('Body Pane')
        # Boilerplate...
        i = self.cursor_line
        j = self.start_display_at
        self.cursor_line = min(len(self.values)-1, i+1)
        if self._my_widgets[i-j].task == self.continuation_line:
            if self.slow_scroll:
                self.start_display_at += 1
            else:
                self.start_display_at = self.cursor_line
    #@+node:ekr.20170526114040.5: *5* LeoBody.h_cursor_line_up
    def h_cursor_line_up(self, ch_i):
        '''From MultiLine.h_cursor_line_up. Never exit here.'''
        # Reset editing mode.
        self.set_box_name('Body Pane')
        self.cursor_line = max(0, self.cursor_line-1)
    #@+node:ekr.20170604181755.1: *5* LeoBody.h_exit_down
    def h_exit_down(self, ch_i):
        """Called when user leaves the widget to the next widget"""
        if ch_i in (curses.ascii.CR, curses.ascii.NL):
            return False
        self.set_box_name('Body Pane')
        if not self._test_safe_to_exit():
            return False
        self.editing = False
        self.how_exited = EXITED_DOWN
        return None
    #@+node:ekr.20170604181821.1: *5* LeoBody.h_exit_up
    def h_exit_up(self, ch_i):

        self.set_box_name('Body Pane')
        if not self._test_safe_to_exit():
            return False
        # Called when the user leaves the widget to the previous widget
        self.editing = False
        self.how_exited = EXITED_UP
        return None
    #@+node:ekr.20170526114452.2: *5* LeoBody.h_edit_cursor_line_value
    def h_edit_cursor_line_value(self, ch_i):
       '''From MultiLineEditable.h_edit_cursor_line_value'''
       self.set_box_name('Body Pane (Editing)')
       continue_line = self.edit_cursor_line_value()
       if continue_line and self.CONTINUE_EDITING_AFTER_EDITING_ONE_LINE:
           self._continue_editing()
    #@+node:ekr.20170604185028.1: *4* LeoBody.delete_line_value
    def delete_line_value(self, ch_i=None):

        c = self.leo_c
        if self.values:
            del self.values[self.cursor_line]
            self.display()
            # #1224:
            c.p.b = ''.join(self.values)
    #@+node:ekr.20170602103122.1: *4* LeoBody.make_contained_widgets
    def make_contained_widgets(self):
        '''
        LeoBody.make_contained_widgets.
        Make widgets and inject the leo_parent ivar for later access to leo_c.
        '''
        # pylint: disable=no-member
        trace_widgets = False
        self._my_widgets = []
        height = self.height // self.__class__._contained_widget_height
        # g.trace(self.__class__.__name__, height)
        for h in range(height):
            self._my_widgets.append(
                self._contained_widgets(
                    self.parent,
                    rely=(h*self._contained_widget_height)+self.rely,
                    relx = self.relx,
                    max_width=self.width,
                    max_height=self.__class__._contained_widget_height
            ))
        # Inject leo_parent ivar so the contained widgets can get leo_c later.
        for w in self._my_widgets:
            w.leo_parent = self
        if trace and trace_widgets:
            g.printList(self._my_widgets)
            g.printList(['value: %r' % (z.value) for z in self._my_widgets])
    #@+node:ekr.20170526080455.1: *4* LeoBody.onBodyChanged
    def onBodyChanged(self, undoType, oldSel=None, oldText=None, oldYview=None):
        '''
        Update Leo after the body has been changed.
        Called by LeoBodyTextfield.h_addch.
        '''
        trace = False and not g.unitTesting
        c = self.leo_c
        wrapper = self.leo_wrapper
        p = c.p
        insert = wrapper.getInsertPoint()
        ch = '' if insert == 0 else wrapper.get(insert - 1)
        ch = g.toUnicode(ch)
        newText = wrapper.getAllText() # Note: getAllText converts to unicode.
        newSel = wrapper.getSelectionRange()
        if not oldText:
            oldText = p.b
        if oldText == newText:
            return
        if trace: g.trace('oldSel', oldSel, 'newSel', newSel)
        c.undoer.setUndoTypingParams(p, undoType,
            oldText=oldText, newText=newText, oldSel=oldSel, newSel=newSel, oldYview=oldYview)
        p.v.setBodyString(newText)
        p.v.insertSpot = wrapper.getInsertPoint()
        # Don't recolor the body.
        if g.app.unitTesting:
            g.app.unitTestDict['colorized'] = True
        if not c.changed:
            c.setChanged(True)
        ### self.updateEditors()
        p.v.contentModified()
        # Don't update icons.
    #@+node:ekr.20170604073733.1: *4* LeoBody.set_box_name
    def set_box_name(self, name):
        '''Update the title of the Form surrounding the Leo Body.'''
        box = self.leo_box
        box.name = name
        box.update()
    #@+node:ekr.20170526064136.1: *4* LeoBody.set_handlers
    #@@nobeautify
    def set_handlers(self):
        '''LeoBody.set_handlers.'''
        # pylint: disable=no-member
        self.handlers = {
            # From InputHandler...
            curses.KEY_BTAB:    self.h_exit_up,
            curses.ascii.TAB:   self.h_exit_down,
            curses.ascii.ESC:   self.h_exit_escape,
            curses.KEY_MOUSE:   self.h_exit_mouse,
            # From MultiLine...
            curses.KEY_DOWN:    self.h_cursor_line_down,
            curses.KEY_END:     self.h_cursor_end,
            curses.KEY_HOME:    self.h_cursor_beginning,
            curses.KEY_NPAGE:   self.h_cursor_page_down,
            curses.KEY_PPAGE:   self.h_cursor_page_up,
            curses.KEY_UP:      self.h_cursor_line_up,
            # From MultiLineEditable...
                # ord('i'):     self.h_insert_value,
                # ord('o'):     self.h_insert_next_line,
            # New bindings...
            ord('d'):           self.delete_line_value,
            ord('e'):           self.h_edit_cursor_line_value,
        }
        # self.dump_handlers()
    #@+node:ekr.20170606100707.1: *4* LeoBody.update_body
    def update_body(self, ins, s):
        '''
        Update self.values and p.b and vnode ivars after the present line changes.
        '''
        # pylint: disable=no-member,access-member-before-definition
        trace = False and not g.unitTesting
        c = self.leo_c
        p = c.p
        v = p.v
        i = self.cursor_line
        wrapper = c.frame.body.wrapper
        assert isinstance(wrapper, BodyWrapper), repr(wrapper)
        lines = self.values
        if trace: g.trace(i, len(lines), s.endswith('\n'), repr(s))
        head = lines[:i]
        tail = lines[i+1:]
        if i < len(lines):
            if not s.endswith('\n'):
                s = s + '\n'
            aList = head + [s] + tail
            self.values = aList
            c.p.b = ''.join(aList)
            v.selectionLength = 0
            v.selectionStart = ins
            wrapper.ins = ins
            wrapper.sel = ins, ins
            self.onBodyChanged(undoType='Typing')
        elif i == len(lines):
            aList = head + [s]
            self.values = aList
            c.p.b = ''.join(aList)
            v.selectionLength = 0
            v.selectionStart = ins
            wrapper.ins = ins
            wrapper.sel = ins, ins
            self.onBodyChanged(undoType='Typing')
        else:
            g.trace('Can not happen', i, len(lines), repr(s))
            v.selectionLength = 0
            v.selectionStart = 0
        if g.splitLines(c.p.b) != self.values:
            g.trace('self.values')
            g.printList(self.values)
            g.trace('g.splitLines(c.p.b)')
            g.printList(g.splitLines(c.p.b))
    #@-others
#@+node:ekr.20170603103946.1: *3* class LeoLog (npyscreen.MultiLineEditable)
class LeoLog (npyscreen.MultiLineEditable):

    continuation_line = "- more -" # value of contination line.
    _contained_widgets = LeoLogTextfield

    def __init__ (self, *args, **kwargs):
        super(LeoLog, self).__init__(*args, **kwargs)
        self.set_handlers()
        self.leo_box = None
        self.leo_c = None
        # createCursesLog sets the leo_c and leo_box ivars.

    #@+others
    #@+node:ekr.20170604184928.2: *4* LeoLog.delete_line_value
    def delete_line_value(self, ch_i=None):
        if self.values:
            del self.values[self.cursor_line]
            self.display()
    #@+node:ekr.20170604183417.1: *4*  LeoLog handlers
    #@+node:ekr.20170603103946.32: *5* LeoLog.h_cursor_line_down
    def h_cursor_line_down(self, ch_i):
        '''
        From MultiLine.h_cursor_line_down. Never exit.
        '''
        # pylint: disable=no-member,access-member-before-definition
        trace = False and not g.unitTesting
        self.set_box_name('Log Pane')
        i = self.cursor_line
        j = self.start_display_at
        n = len(self.values)
        n2 = len(self._my_widgets)
        # Scroll only if there are more lines left.
        if i < n-2:
            # Update self.cursor_line
            self.cursor_line = i2 = max(0, min(i+1, n-1))
            # Update self.start_display_at.
            if self._my_widgets[i-j].task == self.continuation_line:
                self.start_display_at = min(j, max(i2+1, n2-1))
        if trace:
            g.trace('n: %s, start: %s, line: %s' % (
                n, self.start_display_at, self.cursor_line))
    #@+node:ekr.20170603103946.31: *5* LeoLog.h_cursor_line_up
    def h_cursor_line_up(self, ch_i):
        '''From MultiLine.h_cursor_line_up. Never exit here.'''
        self.set_box_name('Log Pane')
        self.cursor_line = max(0, self.cursor_line-1)

    #@+node:ekr.20170604061933.4: *5* LeoLog.h_edit_cursor_line_value
    def h_edit_cursor_line_value(self, ch_i):
       '''From MultiLineEditable.h_edit_cursor_line_value'''
       self.set_box_name('Log Pane (Editing)')
       continue_line = self.edit_cursor_line_value()
       if continue_line and self.CONTINUE_EDITING_AFTER_EDITING_ONE_LINE:
           self._continue_editing()
    #@+node:ekr.20170604113733.2: *5* LeoLog.h_exit_down
    def h_exit_down(self, ch_i):
        """Called when user leaves the widget to the next widget"""
        trace = False and not g.unitTesting
        if ch_i in (curses.ascii.CR, curses.ascii.NL):
            return False
        if trace:
            g.trace('(LeoLog) safe:', self._test_safe_to_exit())
            g.trace(g.callers())
        self.set_box_name('Log Pane')
        if not self._test_safe_to_exit():
            return False
        self.editing = False
        self.how_exited = EXITED_DOWN
        return None
    #@+node:ekr.20170604113733.4: *5* LeoLog.h_exit_up
    def h_exit_up(self, ch_i):
        self.set_box_name('Log Pane')
        if not self._test_safe_to_exit():
            return False
        # Called when the user leaves the widget to the previous widget
        self.editing = False
        self.how_exited = EXITED_UP
        return None
    #@+node:ekr.20170603103946.34: *4* LeoLog.make_contained_widgets
    def make_contained_widgets(self):
        '''
        LeoLog.make_contained_widgets.
        Make widgets and inject the leo_parent ivar for later access to leo_c.
        '''
        # pylint: disable=no-member
        trace = False
        trace_widgets = False
        self._my_widgets = []
        height = self.height // self.__class__._contained_widget_height
        if trace: g.trace(self.__class__.__name__, height)
        for h in range(height):
            self._my_widgets.append(
                self._contained_widgets(
                    self.parent,
                    rely=(h*self._contained_widget_height)+self.rely,
                    relx = self.relx,
                    max_width=self.width,
                    max_height=self.__class__._contained_widget_height
            ))
        # Inject leo_parent ivar so the contained widgets can get leo_c later.
        for w in self._my_widgets:
            w.leo_parent = self
        if trace and trace_widgets:
            g.printList(self._my_widgets)
            g.printList(['value: %r' % (z.value) for z in self._my_widgets])
    #@+node:ekr.20170604073322.1: *4* LeoLog.set_box_name
    def set_box_name(self, name):
        '''Update the title of the Form surrounding the Leo Log.'''
        box = self.leo_box
        box.name = name
        box.update()
    #@+node:ekr.20170603103946.33: *4* LeoLog.set_handlers
    def set_handlers(self):
        '''LeoLog.set_handlers.'''
        # pylint: disable=no-member
        self.handlers = {
            # From InputHandler...
            curses.KEY_BTAB:    self.h_exit_up,
            curses.KEY_MOUSE:   self.h_exit_mouse,
            curses.ascii.CR:    self.h_exit_down,
            curses.ascii.ESC:   self.h_exit_escape,
            curses.ascii.NL:    self.h_exit_down,
            curses.ascii.TAB:   self.h_exit_down,
            # From MultiLine...
            curses.KEY_DOWN:    self.h_cursor_line_down,
            curses.KEY_END:     self.h_cursor_end,
            curses.KEY_HOME:    self.h_cursor_beginning,
            curses.KEY_NPAGE:   self.h_cursor_page_down,
            curses.KEY_PPAGE:   self.h_cursor_page_up,
            curses.KEY_UP:      self.h_cursor_line_up,
            # From MultiLineEditable...
                # ord('i'):     self.h_insert_value,
                # ord('o'):     self.h_insert_next_line,
            # New bindings...
            ord('d'):           self.delete_line_value,
            ord('e'):           self.h_edit_cursor_line_value,
        }
        # dump_handlers(self)
    #@+node:ekr.20170708181422.1: *4* LeoLog.firstScroll
    def firstScroll(self):
        '''Scroll the log pane so the last lines are in view.'''
        # Fix #508: Part 0.
        n = len(self.values)
        self.cursor_line = max(0, n-2)
        self.start_display_at = max(0, n - len(self._my_widgets))
        self.update()
    #@-others
#@+node:ekr.20170507194035.1: *3* class LeoForm (npyscreen.Form)
class LeoForm (npyscreen.Form):

    OK_BUTTON_TEXT = 'Quit Leo'
    OKBUTTON_TYPE = QuitButton
    how_exited = None

    def display(self, *args, **kwargs):
        changed = any([z.c.isChanged() for z in g.app.windowList])
        c = g.app.log.c
        self.name = 'Welcome to Leo: %s%s' % (
            '(changed) ' if changed else '',
            c.fileName() if c else '')
        super(LeoForm, self).display(*args, **kwargs)
#@+node:ekr.20170510092721.1: *3* class LeoMiniBuffer (npyscreen.Textfield)
class LeoMiniBuffer(npyscreen.Textfield):
    '''An npyscreen class representing Leo's minibuffer, with binding.'''

    def __init__(self, *args, **kwargs):
        super(LeoMiniBuffer, self).__init__(*args, **kwargs)
        self.leo_c = None # Set later
        self.leo_wrapper = None # Set later.
        self.leo_completion_index = 0
        self.leo_completion_list = []
        self.leo_completion_prefix = ''
        self.set_handlers()

    #@+others
    #@+node:ekr.20170510172335.1: *4* LeoMiniBuffer.Handlers
    #@+node:ekr.20171201054825.1: *5* LeoMiniBuffer.do_tab_completion
    def do_tab_completion(self):
        '''Perform tab completion.'''
        trace = False and not g.unitTesting
        c = self.leo_c
        # import leo.core.leoKeys as leoKeys
        # ga = leoKeys.GetArg()
        command = self.value
        i = self.leo_completion_index
        if trace: g.trace('command: %r prefix: %r' % (command, self.leo_completion_prefix))
        # Restart the completions if necessary.
        if not command.startswith(self.leo_completion_prefix):
            i = 0
        if i == 0:
            # Compute new completions.
            self.leo_completion_prefix = command
            command_list = sorted(c.k.c.commandsDict.keys())
            tab_list, common_prefix = g.itemsMatchingPrefixInList(command, command_list)
            self.leo_completion_list = tab_list
            if trace: g.printObj(tab_list)
        # Update the index and the widget.
        if self.leo_completion_list:
            tab_list = self.leo_completion_list
            self.value = tab_list[i]
            i = 0 if i+1 >= len(tab_list) else i+1
            self.leo_completion_index = i
            self.update()
        elif trace:
            g.trace('no completions for', command)
    #@+node:ekr.20170510095136.2: *5* LeoMiniBuffer.h_cursor_beginning
    def h_cursor_beginning(self, ch):

        self.cursor_position = 0
    #@+node:ekr.20170510095136.3: *5* LeoMiniBuffer.h_cursor_end
    def h_cursor_end(self, ch):

        self.cursor_position = len(self.value)
    #@+node:ekr.20170510095136.4: *5* LeoMiniBuffer.h_cursor_left
    def h_cursor_left(self, ch):

        self.cursor_position = max(0, self.cursor_position -1)
    #@+node:ekr.20170510095136.5: *5* LeoMiniBuffer.h_cursor_right
    def h_cursor_right(self, ch):

        self.cursor_position = min(len(self.value), self.cursor_position+1)


    #@+node:ekr.20170510095136.6: *5* LeoMiniBuffer.h_delete_left
    def h_delete_left(self, ch):

        n = self.cursor_position
        s = self.value
        if n == 0:
            # Delete the character under the cursor.
            self.value = s[1:]
        else:
            # Delete the character to the left of the cursor
            self.value = s[:n-1] + s[n:]
            self.cursor_position -= 1
    #@+node:ekr.20171201053817.1: *5* LeoMiniBuffer.h_exit_down
    def h_exit_down(self, ch):
        '''LeoMiniBuffer.h_exit_down.  Override InputHandler.h_exit_down.'''
        trace = False and not g.unitTesting
        c = self.leo_c
        if trace: g.trace('(LeoMiniBuffer)', repr(ch))
        if c and self.value.strip():
            self.do_tab_completion()
        else:
            # Restart completion cycling.
            self.leo_completion_index = 0
            # The code in InputHandler.h_exit_down.
            if not self._test_safe_to_exit():
                return False
            self.editing = False
            self.how_exited = EXITED_DOWN
        return None
    #@+node:ekr.20170510095136.7: *5* LeoMiniBuffer.h_insert
    def h_insert(self, ch):

        n = self.cursor_position + 1
        s = self.value
        self.value = s[:n] + chr(ch) + s[n:]
        self.cursor_position += 1
    #@+node:ekr.20170510100003.1: *5* LeoMiniBuffer.h_return (executes command)
    def h_return (self, ch):
        '''
        Handle the return key in the minibuffer.
        Send the contents to k.masterKeyHandler.
        '''
        c = self.leo_c
        k = c.k
        val = self.value.strip()
        self.value = ''
        self.update()
        # g.trace('===== inState: %r val: %r' % (k.inState(), val))
        commandName = val
        c.frame.tree.set_status_line(c.p)
            # This may be changed by the command.
        if k.inState():
            # Handle the key.
            k.w = self.leo_wrapper
            k.arg = val
            g.app.gui.curses_gui_arg = val
            k.masterKeyHandler(
                event=KeyEvent(c, char='\n', event='', shortcut='\n', w=None))
            g.app.gui.curses_gui_arg = None
            k.clearState()
        elif commandName == 'repeat-complex-command':
            g.app.gui.repeatComplexCommand(c)
        else:
            # All other alt-x command
            k.masterCommand(
                commandName=commandName,
                event=KeyEvent(c,char='',event='',shortcut='',w=None),
                func=None,
                stroke=None,
            )
            # Support repeat-complex-command.
            c.setComplexCommand(commandName=commandName)
        # Do a full redraw, with c.p as the first visible node.
        # g.trace('----- after command')
        g.app.gui.redraw_in_context(c)
    #@+node:ekr.20170510094104.1: *5* LeoMiniBuffer.set_handlers
    def set_handlers(self):

        # pylint: disable=no-member
        # Override *all* other complex handlers.
        self.complex_handlers = [
            (curses.ascii.isprint, self.h_insert),
        ]
        self.handlers.update({
            # All other keys are passed on.
                # curses.ascii.TAB:    self.h_exit_down,
                # curses.KEY_BTAB:     self.h_exit_up,
            curses.ascii.NL:        self.h_return,
            curses.ascii.CR:        self.h_return,
            curses.KEY_HOME:        self.h_cursor_beginning,  # 262
            curses.KEY_END:         self.h_cursor_end,        # 358.
            curses.KEY_LEFT:        self.h_cursor_left,
            curses.KEY_RIGHT:       self.h_cursor_right,
            curses.ascii.BS:        self.h_delete_left,
            curses.KEY_BACKSPACE:   self.h_delete_left,
        })
    #@-others
#@+node:ekr.20171129194909.1: *3* class LeoStatusLine (npyscreen.Textfield)
class LeoStatusLine(npyscreen.Textfield):
    '''An npyscreen class representing Leo's status line'''

    def __init__(self, *args, **kwargs):
        super(LeoStatusLine, self).__init__(*args, **kwargs)
        # These are injected later.
        self.leo_c = None
        self.leo_wrapper = None
        # Use the default handlers.
            # self.set_handlers()

    #@+others
    #@-others
#@+node:ekr.20170506035146.1: *3* class LeoMLTree (npyscreen.MLTree, object)
class LeoMLTree(npyscreen.MLTree):

    # pylint: disable=used-before-assignment
    _contained_widgets = LeoTreeLine
    continuation_line = "- more -" # value of contination line.

    # def __init__ (self, *args, **kwargs):
        # super(LeoMLTree, self).__init__(*args, **kwargs)

    # Note: The startup sequence sets leo_c and the value property/ivar.

    #@+others
    #@+node:ekr.20170510171826.1: *4* LeoMLTree.Entries
    #@+node:ekr.20170506044733.6: *5* LeoMLTree.delete_line
    def delete_line(self):

        trace = False
        trace_values = True
        node = self.values[self.cursor_line]
        assert isinstance(node, LeoTreeData), repr(node)
        if trace:
            g.trace('before', node.content)
            if trace_values: self.dump_values()
        if native:
            p = node.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            if p.hasParent() or p.hasNext() or p.hasBack():
                p.doDelete()
                self.values.clear_cache()
            else:
                g.trace('Can not delete the last top-level node')
                return
        else:
            parent = node.get_parent()
            grand_parent = parent and parent._parent
            if not grand_parent and len(parent._children) == 1:
                g.trace('Can not delete the last top-level node')
                return
            # Remove node and all its descendants.
            i = self.cursor_line
            nodes = [(i,node)]
            j = i + 1
            while j < len(self.values):
                node2 = self.values[j]
                if node.is_ancestor_of(node2):
                    nodes.append((j,node2),)
                    j += 1
                else:
                    break
            for j, node2 in reversed(nodes):
                del self.values[j]
            # Update the parent.
            parent._children = [z for z in parent._children if z != node]
        # Clearing these caches suffice to do a proper redraw
        self._last_values = None
        self._last_value = None
        if trace and trace_values:
            g.trace('after')
            self.dump_values()
        self.display()
            # This is widget.display:
                # (when not self.hidden)
                #self.update()
                    # LeoMLTree.update or MultiLine.update
                # self.parent.refresh()
                    # LeoForm.refresh
    #@+node:ekr.20170507171518.1: *5* LeoMLTree.dump_code/values/widgets
    def dump_code(self, code):
        d = {
            -2: 'left', -1: 'up', 1: 'down', 2: 'right',
            127: 'escape', 130: 'mouse',
        }
        return d.get(code) or 'unknown how_exited: %r' % code

    def dump_values(self):
        def info(z):
            return '%15s: %s' % (z._parent.get_content(), z.get_content())
        g.printList([info(z) for z in self.values])

    def dump_widgets(self):
        def info(z):
            return '%s.%s' % (id(z), z.__class__.__name__)
        g.printList([info(z) for z in self._my_widgets])
    #@+node:ekr.20170506044733.4: *5* LeoMLTree.edit_headline
    def edit_headline(self):

        trace = False and not g.unitTesting
        assert self.values, g.callers()
        try:
            active_line = self._my_widgets[(self.cursor_line-self.start_display_at)]
            assert isinstance(active_line, LeoTreeLine)
            if trace: g.trace('LeoMLTree.active_line: %r' % active_line)
        except IndexError:
            # pylint: disable=pointless-statement
            self._my_widgets[0]
                # Does this have something to do with weakrefs?
            self.cursor_line = 0
            self.insert_line()
            return True
        active_line.highlight = False
        active_line.edit()
        if native:
            self.values.clear_cache()
        else:
            try:
                # pylint: disable=unsupported-assignment-operation
                self.values[self.cursor_line] = active_line.value
            except IndexError:
                self.values.append(active_line.value)
                if not self.cursor_line:
                    self.cursor_line = 0
                self.cursor_line = len(self.values) - 1
        self.reset_display_cache()
        self.display()
        return True
    #@+node:ekr.20170523113530.1: *5* LeoMLTree.get_nth_visible_position
    def get_nth_visible_position(self, n):
        '''Return the n'th visible position.'''
        c = self.leo_c
        limit, junk = c.visLimit()
        p = limit.copy() if limit else c.rootPosition()
        i = 0
        while p:
            if i == n:
                return p
            p.moveToVisNext(c)
            i += 1
        g.trace('Can not happen', n)
        return None
    #@+node:ekr.20171128191134.1: *5* LeoMLTree.select_leo_node
    def select_leo_node(self, p):
        '''
        Set .start_display_at and .cursor_line ivars to display node p, with 2
        lines of preceding context if possible.
        '''
        trace = False and not g.unitTesting
        c = self.leo_c
        limit, junk = c.visLimit()
        p2 = limit.copy() if limit else c.rootPosition()
        i = 0
        while p2:
            if p2 == p:
                if trace: g.trace('FOUND', i, p.h)
                # Provide context if possible.
                self.cursor_line = i
                for j in range(2):
                    if p.hasVisBack(c):
                        p.moveToVisBack(c)
                        i -= 1
                self.start_display_at = i
                return
            p2.moveToVisNext(c)
            i += 1
        if trace:
            # Can happen during unit tests.
            g.trace('Not found', p and p.h)
    #@+node:ekr.20170514065422.1: *5* LeoMLTree.intraFileDrop
    def intraFileDrop(self, fn, p1, p2):
        pass
    #@+node:ekr.20170506044733.2: *5* LeoMLTree.new_mltree_node
    def new_mltree_node(self):
        '''
        Insert a new outline TreeData widget at the current line.
        As with Leo, insert as the first child of the current line if
        the current line is expanded. Otherwise insert after the current line.
        '''
        trace = False
        trace_values = True
        node = self.values[self.cursor_line]
        headline = 'New headline'
        if node.has_children() and node.expanded:
            node = node.new_child_at(index=0, content=headline)
        elif node.get_parent():
            parent = node.get_parent()
            node = parent.new_child(content=headline)
        else:
            parent = self.hidden_root_node
            index = parent._children.index(node)
            node = parent.new_child_at(index=index, content=headline)
        if trace:
            g.trace('LeoMLTree: line: %s %s' % (self.cursor_line, headline))
            if trace_values: self.dump_values()
        return node
    #@+node:ekr.20170506044733.5: *5* LeoMLTree.insert_line
    def insert_line(self):
        '''Insert an MLTree line and mark c changed.'''
        trace = False
        c = self.leo_c
        c.changed = True # Just set the changed bit.
        # c.p.setDirty()
        n = 0 if self.cursor_line is None else self.cursor_line
        if trace: g.trace('line: %r', n)
        if native:
            data = self.values[n] # data is a LeoTreeData
            p = data.content
            assert p and isinstance(p, leoNodes.Position)
            if p.hasChildren() and p.isExpanded():
                p2 = p.insertAsFirstChild()
            else:
                p2 = p.insertAfter()
            p2.h = 'New Headline'
            self.cursor_line += 1
            c.selectPosition(p2)
            g.app.gui.redraw_in_context(c)
        else:
            self.values.insert(n+1, self.new_mltree_node())
            self.cursor_line += 1
        self.display()
        self.edit_headline()
        
    #@+node:ekr.20170506045346.1: *4* LeoMLTree.Handlers
    # These insert or delete entire outline nodes.
    #@+node:ekr.20170523112839.1: *5* LeoMLTree.handle_mouse_event
    def handle_mouse_event(self, mouse_event):
        '''Called from InputHandler.h_exit_mouse.'''
        # pylint: disable=no-member
        #
        # From MultiLine...
        data = self.interpret_mouse_event(mouse_event)
        mouse_id, rel_x, rel_y, z, bstate = data
        self.cursor_line = (
            rel_y //
            self._contained_widget_height + self.start_display_at
        )
        # Now, set the correct position.
        if native:
            c = self.leo_c
            p = self.get_nth_visible_position(self.cursor_line)
            c.frame.tree.select(p)
    #@+node:ekr.20170516055435.4: *5* LeoMLTree.h_collapse_all
    def h_collapse_all(self, ch):

        if native:
            c = self.leo_c
            for p in c.all_unique_positions():
                p.v.contract()
            self.values.clear_cache()
        else:
            for v in self._walk_tree(self._myFullValues, only_expanded=True):
                v.expanded = False
        self._cached_tree = None
        self.cursor_line = 0
        self.display()

    #@+node:ekr.20170516055435.2: *5* LeoMLTree.h_collapse_tree
    def h_collapse_tree(self, ch):

        node = self.values[self.cursor_line]
        if native:
            p = node.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            p.contract()
            self.values.clear_cache()
        else:
            if node.expanded and self._has_children(node):
                # Collapse the node.
                node.expanded = False
            elif 0: # Optional.
                # Collapse all the children.
                depth = self._find_depth(node) - 1
                cursor_line = self.cursor_line - 1
                while cursor_line >= 0:
                    if depth == self._find_depth(node):
                        self.cursor_line = cursor_line
                        node.expanded = False
                        break
                    else:
                        cursor_line -= 1
        self._cached_tree = None
            # Invalidate the display cache.
        self.display()
    #@+node:ekr.20170513091821.1: *5* LeoMLTree.h_cursor_line_down
    def h_cursor_line_down(self, ch):

        c = self.leo_c
        self.cursor_line = min(len(self.values)-1, self.cursor_line+1)
        if native:
            p = self.get_nth_visible_position(self.cursor_line)
            c.frame.tree.select(p)
    #@+node:ekr.20170513091928.1: *5* LeoMLTree.h_cursor_line_up
    def h_cursor_line_up(self, ch):

        c = self.leo_c
        self.cursor_line = max(0, self.cursor_line-1)
        if native:
            p = self.get_nth_visible_position(self.cursor_line)
            c.frame.tree.select(p)
    #@+node:ekr.20170506044733.12: *5* LeoMLTree.h_delete
    def h_delete(self, ch):

        c = self.leo_c
        c.changed = True # Just set the changed bit.
        self.delete_line()
        if native:
            p = self.get_nth_visible_position(self.cursor_line)
            c.frame.tree.select(p)
    #@+node:ekr.20170506044733.10: *5* LeoMLTree.h_edit_headline
    def h_edit_headline(self, ch):
        '''Called when the user types "h".'''
        c = self.leo_c
        # Remember the starting headline, for CTree.onHeadChanged.
        tree = c.frame.tree # CTree
        tree.revertVnode = c.p.v
        tree.revertHeadline = c.p.h
        self.edit_headline()
    #@+node:ekr.20170516055435.5: *5* LeoMLTree.h_expand_all
    def h_expand_all(self, ch):

        if native:
            c = self.leo_c
            for p in c.all_unique_positions():
                p.v.expand()
            self.values.clear_cache()
        else:
            for v in self._walk_tree(self._myFullValues, only_expanded=False):
                v.expanded    = True
        self._cached_tree = None
        self.cursor_line  = 0
        self.display()
    #@+node:ekr.20170516055435.3: *5* LeoMLTree.h_expand_tree
    def h_expand_tree(self, ch):

        node = self.values[self.cursor_line]
        if native:
            p = node.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            p.expand() # Don't use p.v.expand()
            self.values.clear_cache()
        else:
            # First, expand the node.
            if not node.expanded:
                node.expanded = True
            elif 0: # Optional.
                # Next, expand all children.
                for z in self._walk_tree(node, only_expanded=False):
                    z.expanded = True
        self._cached_tree = None
            # Invalidate the cache.
        self.display()
    #@+node:ekr.20170506044733.11: *5* LeoMLTree.h_insert
    def h_insert(self, ch):

        self.insert_line()
        if native:
            c = self.leo_c
            p = self.get_nth_visible_position(self.cursor_line)
            c.frame.tree.select(p)
    #@+node:ekr.20170506035413.1: *5* LeoMLTree.h_move_left
    def h_move_left(self, ch):

        trace = False and not g.unitTesting
        node = self.values[self.cursor_line]
        if not node:
            g.trace('no node', self.cursor_line, repr(node))
            return
        if native:
            c = self.leo_c
            p = node.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            if p.hasChildren() and p.isExpanded():
                self.h_collapse_tree(ch)
                self.values.clear_cache()
            elif p.hasParent():
                parent = p.parent()
                parent.contract() # Don't use parent.v.contract.
                # Set the cursor to the parent's index.
                i = self.cursor_line - 1
                while i >= 0:
                    node2 = self.values[i]
                    if node2 is None:
                        g.trace('oops: no node2', i)
                        break
                    p2 = node2.content
                    if p2 == parent:
                        break
                    i -= 1
                self.cursor_line = max(0, i)
                if trace: g.trace('new line', self.cursor_line)
                self.values.clear_cache()
                self._cached_tree = None
                    # Invalidate the cache.
                self.display()
                c.frame.tree.select(parent)
            else:
                if trace: g.trace('no parent')
                # This is what Leo does.
        else:
            if self._has_children(node) and node.expanded:
                self.h_collapse_tree(ch)
            else:
                self.h_cursor_line_up(ch)
    #@+node:ekr.20170506035419.1: *5* LeoMLTree.h_move_right
    def h_move_right(self, ch):

        node = self.values[self.cursor_line]
        if not node:
            g.trace('no node')
            return
        if native:
            p = node.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            if p.hasChildren():
                if p.isExpanded():
                    self.h_cursor_line_down(ch)
                else:
                    self.h_expand_tree(ch)
            else:
                pass # This is what Leo does.
        else:
            if self._has_children(node):
                if node.expanded:
                    self.h_cursor_line_down(ch)
                else:
                    self.h_expand_tree(ch)
            else:
                self.h_cursor_line_down(ch)
    #@+node:ekr.20170507175304.1: *5* LeoMLTree.set_handlers
    #@@nobeautify
    def set_handlers(self):

        # pylint: disable=no-member
        d = {
            curses.KEY_MOUSE:   self.h_exit_mouse,
            curses.KEY_BTAB:    self.h_exit_up,
            curses.KEY_DOWN:    self.h_cursor_line_down,
            curses.KEY_LEFT:    self.h_move_left,
            curses.KEY_RIGHT:   self.h_move_right,
            curses.KEY_UP:      self.h_cursor_line_up,
            curses.ascii.TAB:   self.h_exit_down,
            ord('d'):           self.h_delete,
            ord('e'):           self.h_edit_headline,
            ord('i'):           self.h_insert,
            # curses.ascii.CR:        self.h_edit_cursor_line_value,
            # curses.ascii.NL:        self.h_edit_cursor_line_value,
            # curses.ascii.SP:        self.h_edit_cursor_line_value,
            # curses.ascii.DEL:       self.h_delete_line_value,
            # curses.ascii.BS:        self.h_delete_line_value,
            # curses.KEY_BACKSPACE:   self.h_delete_line_value,
            # ord('<'): self.h_collapse_tree,
            # ord('>'): self.h_expand_tree,
            # ord('['): self.h_collapse_tree,
            # ord(']'): self.h_expand_tree,
            # ord('{'): self.h_collapse_all,
            # ord('}'): self.h_expand_all,
            # ord('h'): self.h_collapse_tree,
            # ord('l'): self.h_expand_tree,
        }
        # self.handlers.update(d)
        # dump_handlers(self)
        self.handlers = d
    #@+node:ekr.20170516100256.1: *5* LeoMLTree.set_up_handlers
    def set_up_handlers(self):
        super(LeoMLTree, self).set_up_handlers()
        assert not hasattr(self, 'hidden_root_node'), repr(self)
        self.leo_c = None # Set later.
        self.currentItem = None
            # Used by CoreTree class.
        self.hidden_root_node = None
        self.set_handlers()

    #@+node:ekr.20170513032502.1: *4* LeoMLTree.update & helpers
    def update(self, clear=True, forceInit=False):
        '''Redraw the tree.'''
        # This is a major refactoring of MultiLine.update.
        trace = False and not g.unitTesting
        c = self.leo_c
        if trace:
            g.trace('(LeoMLTree)', c.p and c.p.h)
            g.trace(g.callers())
        self.select_leo_node(c.p)
            # Ensures that the selected node is always highlighted.
        if self.editing or forceInit:
            self._init_update()
        if self._must_redraw(clear):
            self._redraw(clear)
        # Remember the previous values.
        self._last_start_display_at = self.start_display_at
        self._last_cursor_line = self.cursor_line
        if native:
            pass
        else:
            self._last_values = copy.copy(self.values)
            self._last_value = copy.copy(self.value)
    #@+node:ekr.20170513122253.1: *5* LeoMLTree._init_update
    def _init_update(self):
        '''Put self.cursor_line and self.start_display_at in range.'''
        # pylint: disable=access-member-before-definition
        display_length = len(self._my_widgets)
        self.cursor_line = max(0, min(len(self.values)-1, self.cursor_line))
        if self.slow_scroll:
            # Scroll by lines.
            if self.cursor_line > self.start_display_at + display_length - 1:
                self.start_display_at = self.cursor_line - (display_length - 1)
            if self.cursor_line < self.start_display_at:
                self.start_display_at = self.cursor_line
        else:
            # Scroll by pages.
            if self.cursor_line > self.start_display_at + (display_length - 2):
                self.start_display_at = self.cursor_line
            if self.cursor_line < self.start_display_at:
                self.start_display_at = self.cursor_line - (display_length - 2)
                if self.start_display_at < 0: self.start_display_at = 0
    #@+node:ekr.20170513123010.1: *5* LeoMLTree._must_redraw
    def _must_redraw(self, clear):
        '''Return a list of reasons why we must redraw.'''
        trace = False and not g.unitTesting
        table = (
            ('cache', not self._safe_to_display_cache or self.never_cache),
            ('value', self._last_value is not self.value),
            ('values', self.values != self._last_values),
            ('start', self.start_display_at != self._last_start_display_at),
            ('clear', not clear),
            ('cursor', self._last_cursor_line != self.cursor_line),
            ('filter', self._last_filter != self._filter),
            ('editing', not self.editing),
        )
        reasons = (reason for (reason, cond) in table if cond)
        if trace: g.trace('line: %2s %-20s %s' % (
            self.cursor_line,
            ','.join(reasons),
            self.values[self.cursor_line].content))
        return reasons
    #@+node:ekr.20170513122427.1: *5* LeoMLTree._redraw & helpers
    def _redraw(self, clear):
        '''Do the actual redraw.'''
        trace = False and not g.unitTesting
        # pylint: disable=no-member
        #
        # self.clear is Widget.clear. It does *not* use _myWidgets.
        if trace: g.trace('start: %r cursor: %r' % (
            self.start_display_at, self.cursor_line))
        if (clear is True or
            clear is None and self._last_start_display_at != self.start_display_at
        ):
            self.clear()
        self._last_start_display_at = start = self.start_display_at
        i = self.start_display_at
        for line in self._my_widgets[:-1]:
            assert isinstance(line, LeoTreeLine), repr(line)
            self._print_line(line, i)
            line.update(clear=True)
            i += 1
        # Do the last line
        line = self._my_widgets[-1]
        if (len(self.values) <= i + 1):
            self._print_line(line, i)
            line.update(clear=False)
        elif len((self._my_widgets) * self._contained_widget_height) < self.height:
            self._print_line(line, i)
            line.update(clear=False)
            self._put_continuation_line()
        else:
            line.clear() # This is Widget.clear.
            self._put_continuation_line()
        # Assert that print_line leaves start_display_at unchanged.
        assert start == self.start_display_at, (start, self.start_display_at)
        # Finish
        if self.editing:
            line = self._my_widgets[(self.cursor_line - start)]
            line.highlight = True
            line.update(clear=True)
    #@+node:ekr.20170513032717.1: *6* LeoMLTree._print_line
    def _print_line(self, line, i):

        # if self.widgets_inherit_color and self.do_colors():
            # line.color = self.color
        self._set_line_values(line, i)
            # Sets line.value
        if line.value is not None:
            assert isinstance(line.value, LeoTreeData), repr(line.value)
        self._set_line_highlighting(line, i)
    #@+node:ekr.20170513102428.1: *6* LeoMLTree._put_continuation_line
    def _put_continuation_line(self):
        '''Print the line indicating there are more lines left.'''
        s = self.continuation_line
        x = self.relx
        y = self.rely + self.height - 1
        if self.do_colors():
            style = self.parent.theme_manager.findPair(self, 'CONTROL')
            self.parent.curses_pad.addstr(y, x, s, style)
        else:
            self.parent.curses_pad.addstr(y, x, s)
    #@+node:ekr.20170513075423.1: *6* LeoMLTree._set_line_values
    def _set_line_values(self, line, i):
        '''Set internal values of line using self.values[i] and self.values[i+1]'''
        trace = False
        trace_ok = True
        trace_empty = True
        line.leo_c = self.leo_c
            # Inject the ivar.
        values = self.values
        n = len(values)
        val = values[i] if 0 <= i < n else None
        if trace: g.trace(repr(val))
        if val is None:
            line._tree_depth        = False
            line._tree_depth_next   = False
            line._tree_expanded     = False
            line._tree_has_children = False
            line._tree_ignore_root  = None
            line._tree_last_line    = True #
            line._tree_real_value   = None
            line._tree_sibling_next = False
            line.value = None
            if trace and trace_empty: g.trace(i, n, '<empty>', repr(val))
            return
        assert isinstance(val, LeoTreeData), repr(val)
        # Aliases
        val1 = values[i+1] if i+1 < n else None
        val1_depth = val1.find_depth() if val1 else False
        # Common settings.
        line._tree_depth = val.find_depth()
        line._tree_depth_next = val1_depth
        line._tree_ignore_root = self._get_ignore_root(self._myFullValues)
        line._tree_sibling_next = line._tree_depth == val1_depth
        line._tree_real_value = val
        line.value = val
        if native:
            p = val.content
            assert p and isinstance(p, leoNodes.Position), repr(p)
            line._tree_expanded = p.isExpanded()
            line._tree_has_children = p.hasChildren()
            line._tree_last_line = not p.hasNext()
        else:
            line._tree_expanded = val.expanded
            line._tree_has_children = bool(val._children)
            line._tree_last_line = not bool(line._tree_sibling_next)
        if trace and trace_ok:
            content = line.value.content
            s = content.h if native else content
            g.trace(i, n, s)
    #@+node:ekr.20170516101203.1: *4* LeoMLTree.values
    if native:
        _myFullValues = LeoTreeData()
        values = None
    else:
        #
        # This property converts the (possibly cached) result of converting
        # the root node (_myFullValues) and its *visible* decendants to a list.
        # To invalidate the cache, set __cached_tree = None
        #@+others
        #@+node:ekr.20170517142822.1: *5* _getValues
        def _getValues(self):
            '''
            Return the (possibly cached) list returned by self._myFullValues.get_tree_as_list().

            Setting _cached_tree to None invalidates the cache.
            '''
            # pylint: disable=access-member-before-definition
            if getattr(self, '_cached_tree', None):
                return self._cached_tree_as_list
            self._cached_tree = self._myFullValues
            self._cached_tree_as_list = self._myFullValues.get_tree_as_list()
            return self._cached_tree_as_list

        #@+node:ekr.20170518054457.1: *5* _setValues
        def _setValues(self, tree):
            self._myFullValues = tree or LeoTreeData()
        #@-others
        values = property(_getValues, _setValues)
    #@-others
#@+node:ekr.20170517072429.1: *3* class LeoValues (npyscreen.TreeData)
class LeoValues(npyscreen.TreeData):
    '''
    A class to replace the MLTree.values property.
    This is formally an subclass of TreeData.
    '''

    #@+others
    #@+node:ekr.20170619070717.1: *4* values.__init__
    def __init__(self, c, tree):
        '''Ctor for LeoValues class.'''
        super(LeoValues, self).__init__()
            # Init the base class.
        self.c = c
            # The commander of this outline.
        self.data_cache = {}
            # Keys are ints, values are LeoTreeData objects.
        self.last_generation = -1
            # The last value of c.frame.tree.generation.
        self.last_len = 0
            # The last computed value of the number of visible nodes.
        self.n_refreshes = 0
            # Number of calls to refresh_cache.
        self.tree = tree
            # A LeoMLTree. (not used here)
    #@+node:ekr.20170517090738.1: *4* values.__getitem__ and get_data
    def __getitem__(self, n):
        '''Called from LeoMLTree._setLineValues.'''
        return self.get_data(n)

    def get_data(self, n):
        '''Return a LeoTreeData for the n'th visible position of the outline.'''
        c = self.c
        # This will almost always be true, because __len__ updates the cache.
        if self.last_len > -1 and c.frame.tree.generation == self.last_generation:
            return self.data_cache.get(n)
        self.refresh_cache()
        data = self.data_cache.get(n)
        g.trace('uncached', data)
        return data
    #@+node:ekr.20170518060014.1: *4* values.__len__
    def __len__(self):
        '''
        Return the putative length of the values array,
        that is, the number of visible nodes in the outline.

        Return self.last_len if the tree generations match.
        Otherwise, find and cache all visible node.

        This is called often from the npyscreen core.
        '''
        c = self.c
        tree_gen = c.frame.tree.generation
        if self.last_len > -1 and  tree_gen == self.last_generation:
            return self.last_len
        self.last_len = self.refresh_cache()
        return self.last_len
    #@+node:ekr.20170519041459.1: *4* values.clear_cache
    def clear_cache(self):
        '''Called only from this file.'''
        self.data_cache = {}
        self.last_len = -1
    #@+node:ekr.20170619072048.1: *4* values.refresh_cache
    def refresh_cache(self):
        '''Update all cached values.'''
        trace = False
        c = self.c
        self.n_refreshes += 1
        self.clear_cache()
        self.last_generation = c.frame.tree.generation
        n, p = 0, c.rootPosition()
        while p:
            self.data_cache[n] = LeoTreeData(p)
            n += 1
            p.moveToVisNext(c)
        self.last_len = n
        if trace:
            g.trace('%3s vis: %3s generation: %s' % (
                self.n_refreshes, n, c.frame.tree.generation))
        return n
    #@-others
#@+node:ekr.20170522081122.1: ** Wrapper classes
#@+others
#@+node:ekr.20170511053143.1: *3*  class TextMixin (object)
class TextMixin:
    '''A minimal mixin class for QTextEditWrapper and QScintillaWrapper classes.'''
    #@+others
    #@+node:ekr.20170511053143.2: *4* tm.ctor & helper
    def __init__(self, c=None):
        '''Ctor for TextMixin class'''
        self.c = c
        self.changingText = False
            # A lockout for onTextChanged.
        self.enabled = True
        self.supportsHighLevelInterface = True
            # A flag for k.masterKeyHandler and isTextWrapper.
        self.tags = {}
        self.configDict = {}
            # Keys are tags, values are colors (names or values).
        self.configUnderlineDict = {}
            # Keys are tags, values are True
        self.virtualInsertPoint = None
        if c:
            self.injectIvars(c)
    #@+node:ekr.20170511053143.3: *5* tm.injectIvars
    def injectIvars(self, name='1', parentFrame=None):
        '''Inject standard leo ivars into the QTextEdit or QsciScintilla widget.'''
        p = self.c.currentPosition()
        if name == '1':
            self.leo_p = None # Will be set when the second editor is created.
        else:
            self.leo_p = p and p.copy()
        self.leo_active = True
        # Inject the scrollbar items into the text widget.
        self.leo_bodyBar = None
        self.leo_bodyXBar = None
        self.leo_chapter = None
        self.leo_frame = None
        self.leo_name = name
        self.leo_label = None
        return self
    #@+node:ekr.20170511053143.4: *4* tm.getName
    def getName(self):
        return self.name # Essential.
    #@+node:ekr.20170511053143.8: *4* tm.Generic high-level interface
    # These call only wrapper methods.
    #@+node:ekr.20170511053143.13: *5* tm.appendText
    def appendText(self, s):
        '''TextMixin'''
        s2 = self.getAllText()
        self.setAllText(s2 + s)
        self.setInsertPoint(len(s2))
    #@+node:ekr.20170511053143.10: *5* tm.clipboard_clear/append
    def clipboard_append(self, s):
        s1 = g.app.gui.getTextFromClipboard()
        g.app.gui.replaceClipboardWith(s1 + s)

    def clipboard_clear(self):
        g.app.gui.replaceClipboardWith('')
    #@+node:ekr.20170511053143.14: *5* tm.delete
    def delete(self, i, j=None):
        '''TextMixin'''
        i = self.toPythonIndex(i)
        if j is None: j = i + 1
        j = self.toPythonIndex(j)
        # This allows subclasses to use this base class method.
        if i > j: i, j = j, i
        s = self.getAllText()
        self.setAllText(s[: i] + s[j:])
        # Bug fix: Significant in external tests.
        self.setSelectionRange(i, i, insert=i)
    #@+node:ekr.20170511053143.15: *5* tm.deleteTextSelection
    def deleteTextSelection(self):
        '''TextMixin'''
        i, j = self.getSelectionRange()
        self.delete(i, j)
    #@+node:ekr.20170511053143.9: *5* tm.Enable/disable
    def disable(self):
        self.enabled = False

    def enable(self, enabled=True):
        self.enabled = enabled
    #@+node:ekr.20170511053143.16: *5* tm.get
    def get(self, i, j=None):
        '''TextMixin'''
        # 2012/04/12: fix the following two bugs by using the vanilla code:
        # https://bugs.launchpad.net/leo-editor/+bug/979142
        # https://bugs.launchpad.net/leo-editor/+bug/971166
        s = self.getAllText()
        i = self.toPythonIndex(i)
        j = self.toPythonIndex(j)
        return s[i: j]
    #@+node:ekr.20170511053143.17: *5* tm.getLastPosition & getLength
    def getLastPosition(self, s=None):
        '''TextMixin'''
        return len(self.getAllText()) if s is None else len(s)

    def getLength(self, s=None):
        '''TextMixin'''
        return len(self.getAllText()) if s is None else len(s)
    #@+node:ekr.20170511053143.18: *5* tm.getSelectedText
    def getSelectedText(self):
        '''TextMixin'''
        i, j = self.getSelectionRange()
        if i == j:
            return ''
        s = self.getAllText()
        return s[i: j]
    #@+node:ekr.20170511053143.19: *5* tm.insert
    def insert(self, i, s):
        '''TextMixin'''
        s2 = self.getAllText()
        i = self.toPythonIndex(i)
        self.setAllText(s2[: i] + s + s2[i:])
        self.setInsertPoint(i + len(s))
        return i
    #@+node:ekr.20170511053143.24: *5* tm.rememberSelectionAndScroll
    def rememberSelectionAndScroll(self):

        v = self.c.p.v # Always accurate.
        v.insertSpot = self.getInsertPoint()
        i, j = self.getSelectionRange()
        if i > j: i, j = j, i
        assert(i <= j)
        v.selectionStart = i
        v.selectionLength = j - i
        v.scrollBarSpot = self.getYScrollPosition()
    #@+node:ekr.20170511053143.20: *5* tm.seeInsertPoint
    def seeInsertPoint(self):
        '''Ensure the insert point is visible.'''
        self.see(self.getInsertPoint())
            # getInsertPoint defined in client classes.
    #@+node:ekr.20170511053143.21: *5* tm.selectAllText
    def selectAllText(self, s=None):
        '''TextMixin.'''
        self.setSelectionRange(0, self.getLength(s))
    #@+node:ekr.20170511053143.11: *5* tm.setFocus
    def setFocus(self):
        '''TextMixin.setFocus'''
        g.app.gui.set_focus(self)

    #@+node:ekr.20170511053143.25: *5* tm.tag_configure
    def tag_configure(self, *args, **keys):

        trace = False and not g.unitTesting
        if trace: g.trace(args, keys)
        if len(args) == 1:
            key = args[0]
            self.tags[key] = keys
            val = keys.get('foreground')
            underline = keys.get('underline')
            if val:
                self.configDict[key] = val
            if underline:
                self.configUnderlineDict[key] = True
        else:
            g.trace('oops', args, keys)

    tag_config = tag_configure
    #@+node:ekr.20170511053143.22: *5* tm.toPythonIndex
    def toPythonIndex(self, index, s=None):
        '''TextMixin'''
        if s is None:
            s = self.getAllText()
        i = g.toPythonIndex(s, index)
        return i
    #@+node:ekr.20170511053143.23: *5* tm.toPythonIndexRowCol
    def toPythonIndexRowCol(self, index):
        '''TextMixin'''
        s = self.getAllText()
        i = self.toPythonIndex(index)
        row, col = g.convertPythonIndexToRowCol(s, i)
        return i, row, col
    #@-others
#@+node:ekr.20170504034655.1: *3* class BodyWrapper (leoFrame.StringTextWrapper)
class BodyWrapper(leoFrame.StringTextWrapper):
    '''
    A Wrapper class for Leo's body.
    This is c.frame.body.wrapper.
    '''

    def __init__(self, c, name, w):
        '''Ctor for BodyWrapper class'''
        super().__init__(c, name)
        self.changingText = False
            # A lockout for onTextChanged.
        self.widget = w
        self.injectIvars(c)
            # These are used by Leo's core.

    #@+others
    #@+node:ekr.20170504034655.3: *4* bw.injectIvars
    def injectIvars(self, name='1', parentFrame=None):
        '''Inject standard leo ivars into the QTextEdit or QsciScintilla widget.'''
        p = self.c.currentPosition()
        if name == '1':
            self.leo_p = None # Will be set when the second editor is created.
        else:
            self.leo_p = p and p.copy()
        self.leo_active = True
        # Inject the scrollbar items into the text widget.
        self.leo_bodyBar = None
        self.leo_bodyXBar = None
        self.leo_chapter = None
        self.leo_frame = None
        self.leo_name = name
        self.leo_label = None
    #@+node:ekr.20170504034655.6: *4* bw.onCursorPositionChanged
    def onCursorPositionChanged(self, event=None):
        if 0: g.trace('=====', event)
    #@-others
#@+node:ekr.20170522002403.1: *3* class HeadWrapper (leoFrame.StringTextWrapper)
class HeadWrapper(leoFrame.StringTextWrapper):
    '''
    A Wrapper class for headline widgets, returned by c.edit_widget(p)
    '''

    def __init__(self, c, name, p):
        '''Ctor for HeadWrapper class'''
        super().__init__(c, name)
        self.trace = False # For tracing in base class.
        self.p = p.copy()
        self.s = p.v._headString

    #@+others
    #@+node:ekr.20170522014009.1: *4* hw.setAllText
    def setAllText(self, s):
        '''HeadWrapper.setAllText'''
        # Don't allow newlines.
        self.s = s.replace('\n','').replace('\r','')
        i = len(self.s)
        self.ins = i
        self.sel = i, i
        self.p.v._headString = self.s
    #@-others
#@+node:ekr.20170525062512.1: *3* class LogWrapper (leoFrame.StringTextWrapper)
class LogWrapper(leoFrame.StringTextWrapper):
    '''A Wrapper class for the log pane.'''

    def __init__(self, c, name, w):
        '''Ctor for LogWrapper class'''
        super().__init__(c, name)
        self.trace = False # For tracing in base class.
        self.widget = w

    #@+others
    #@-others
#@+node:ekr.20170525105707.1: *3* class MiniBufferWrapper (leoFrame.StringTextWrapper)
class MiniBufferWrapper(leoFrame.StringTextWrapper):
    '''A Wrapper class for the minibuffer.'''

    def __init__(self, c, name, w):
        '''Ctor for MiniBufferWrapper class'''
        super().__init__(c, name)
        self.trace = False # For tracing in base class.
        self.box = None # Injected
        self.widget = w
#@+node:ekr.20171129194610.1: *3* class StatusLineWrapper (leoFrame.StringTextWrapper)
class StatusLineWrapper(leoFrame.StringTextWrapper):
    '''A Wrapper class for the status line.'''

    def __init__(self, c, name, w):
        '''Ctor for StatusLineWrapper class'''
        super().__init__(c, name)
        self.trace = False # For tracing in base class.
        self.widget = w
        
    def isEnabled(self):
        return True

    #@+others
    #@+node:ekr.20171129204751.1: *4* StatusLineWrapper.do nothings
    def disable(self, *args, **kwargs):
        pass

    def enable(self, *args, **kwargs):
        pass
        
    def setFocus(self):
        pass
    #@+node:ekr.20171129204736.1: *4* StatusLineWrapper.redirectors
    def clear(self):
        self.widget.value = ''
        self.widget.display()

    def get(self):
        return self.widget.value

    def put(self, s, *args, **kwargs):
        i = s.find('#')
        if i > -1:
            s = s[i+1:]
        self.widget.value = s
        self.widget.display()
        
    def update(self, *args, **kwargs):
        self.widget.update()
    #@-others
#@-others
#@-others
#@@language python
#@@tabwidth -4
#@-leo
