#@+leo-ver=5-thin
#@+node:ekr.20081121105001.147: * @file qtNotes.txt
#@+all
#@+node:ekr.20081121105001.148: **  qt To do
#@+node:ekr.20101101111704.3761: *3* Remove or complete x.createBindings
#@+node:ekr.20100223133144.3680: *3* Create color picker
createColorPicker
#@+node:ekr.20100223114506.3699: *3* Support cascade menu
leoQtFrame.cascade.
#@+node:ekr.20081121105001.306: *4* cascade
def cascade (self,event=None):

    '''Cascade all Leo windows.'''

    x,y,delta = 50,50,50
    for frame in g.app.windowList:

        w = frame and frame.top
        if w:
            r = w.geometry() # a Qt.Rect
            w.setGeometry(x,y,r.width(),r.height())

            # Compute the new offsets.
            x += 30 ; y += 30
            if x > 200:
                x = 10 + delta ; y = 40 + delta
                delta += 10
#@+node:ekr.20100223114506.3698: *3* Should leoQtMenu.index do something?
#@+node:ekr.20090429101847.10: *3* Support canvas widgets in all panes
@nocolor-node

http://groups.google.com/group/leo-editor/browse_thread/thread/9ac06147e75fc042

add-canvas: Like add-editor, but it creates a canvas area, not a text area.
#@+node:ekr.20090418064921.12: *3* Qt, vim bindings
http://groups.google.com/group/leo-editor/browse_thread/thread/7285ac185355efb1
#@+node:ekr.20081215162017.4: *3* Allow coloring of script buttons
@nocolor-node

Use @string qt-button-color = lightSteelBlue

The following does *not* work in @data qt-gui-plugin-style-sheet::

    QPushButton {
        background-color: red;
    }
#@+node:ekr.20081124094918.1: *3* Fix problems with scim
@nocolor-node

http://groups.google.com/group/leo-editor/browse_thread/thread/59c1e5d6acaf4de0

I've some more information about the problems with accent previously
reported:
- I can confirm that they are caused by the interaction scim+leo (with
and without the qt plugin). If scim is not started leo works fine. If
scim is running the problems appear and are slightly different with
and without the qt plugin. The workaround is obvious: don't use leo
and scim at the same time :-)
- accents are not working in the qt plugin when scintilla is used. If
qt-use-scintilla=False I can write this:

àáä (the same for the rest of vowels)

but if qt-use-scintilla=True when I enter the same sequence in the
keyboard I get:

`aaa (the same for the rest of vowels)

- the problem with the ñ character (reported previously too) is only
present in the qt plugin (both with and without scintilla)
#@+node:ekr.20100330091222.3703: ** Leo 4.8 devel
#@+node:ekr.20100329115035.3692: *3* Don't put &nbs; in log message
#@+node:ekr.20081121110412.264: *4* put & putnl (tkLog)
@ Printing uses self.logCtrl, so this code need not concern itself
with which tab is active.

Also, selectTab switches the contents of colorTags, so that is not concern.
It may be that Pmw will allow us to dispense with the colorTags logic...
#@+node:ekr.20081121110412.265: *5* put
# All output to the log stream eventually comes here.
def put (self,s,color=None,tabName='Log'):

    c = self.c

    # g.pr('tkLog.put',s)
    # g.pr('tkLog.put',len(s),g.callers())

    if g.app.quitting or not c or not c.exists:
        return

    if tabName:
        self.selectTab(tabName)

    # Note: this must be done after the call to selectTab.
    w = self.logCtrl
    if w:
        << put s to log control >>
        self.logCtrl.update_idletasks()
    else:
        << put s to logWaiting and print s >>
#@+node:ekr.20081121110412.266: *6* << put s to log control >>
if color:
    if color not in self.colorTags:
        self.colorTags.append(color)
        w.tag_config(color,foreground=color)
    w.insert("end",s)
    w.tag_add(color,"end-%dc" % (len(s)+1),"end-1c")
    w.tag_add("black","end")
else:
    w.insert("end",s)

w.see('end')
self.forceLogUpdate(s)
#@+node:ekr.20081121110412.267: *6* << put s to logWaiting and print s >>
g.app.logWaiting.append((s,color),)

g.pr("Null tkinter log")

if g.isUnicode(s):
    s = g.toEncodedString(s,"ascii")

g.pr(s)
#@+node:ekr.20081121110412.268: *5* putnl
def putnl (self,tabName='Log'):

    if g.app.quitting:
        return

    # g.pr('tkLog.putnl' # ,g.callers())

    if tabName:
        self.selectTab(tabName)

    w = self.logCtrl

    if w:
        w.insert("end",'\n')
        w.see('end')
        self.forceLogUpdate('\n')
    else:
        # Put a newline to logWaiting and print newline
        g.app.logWaiting.append(('\n',"black"),)
        g.pr("Null tkinter log")
#@+node:ekr.20081121105001.325: *4* put & putnl (qtLog)
#@+node:ekr.20081121105001.326: *5* put (qtLog)
# All output to the log stream eventually comes here.
def put (self,s,color=None,tabName='Log'):

    c = self.c
    if g.app.quitting or not c or not c.exists:
        print('qtGui.log.put fails',repr(s))
        return

    if color:
        color = leoColor.getColor(color,'black')
    else:
        color = leoColor.getColor('black')

    self.selectTab(tabName or 'Log')
    # print('qtLog.put',tabName,'%3s' % (len(s)),self.logCtrl)

    # Note: this must be done after the call to selectTab.
    w = self.logCtrl.widget # w is a QTextBrowser

    # print('qtGui.log.put',bool(w),repr(s))

    if w:
        sb = w.horizontalScrollBar()
        pos = sb.sliderPosition()
        s=s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
        if not self.wrap: # 2010/02/21: Use &nbsp; only when not wrapping!
            s = s.replace(' ','&nbsp;')
        s = s.rstrip().replace('\n','<br>')
        s = '<font color="%s">%s</font>' % (color,s)
        w.append(s)
        w.moveCursor(QtGui.QTextCursor.End)
        sb.setSliderPosition(pos)
    else:
        # put s to logWaiting and print s
        g.app.logWaiting.append((s,color),)
        if g.isUnicode(s):
            s = g.toEncodedString(s,"ascii")
        print(s)
#@+node:ekr.20081121105001.327: *5* putnl
def putnl (self,tabName='Log'):

    if g.app.quitting:
        return

    if tabName:
        self.selectTab(tabName)

    w = self.logCtrl.widget
    if w:
        sb = w.horizontalScrollBar()
        pos = sb.sliderPosition()
        # Not needed!
            # contents = w.toHtml()
            # w.setHtml(contents + '\n')
        w.moveCursor(QtGui.QTextCursor.End)
        sb.setSliderPosition(pos)
        w.repaint() # Slow, but essential.
    else:
        # put s to logWaiting and print  a newline
        g.app.logWaiting.append(('\n','black'),)
#@+node:ekr.20100330091222.3704: *3* Support for @language pseudoplain
One change was made to colorRangeWithTag.
#@+node:ekr.20090614134853.3706: *4* init_mode & helpers
def init_mode (self,name):

    '''Name may be a language name or a delegate name.'''

    trace = False and not g.unitTesting
    if not name: return False
    h = self.highlighter
    language,rulesetName = self.nameToRulesetName(name)
    # if trace: g.trace(name,list(self.modes.keys()))
    bunch = self.modes.get(rulesetName)
    if bunch:
        if trace: g.trace('found',language,rulesetName,g.callers(2))
        self.initModeFromBunch(bunch)
        return True
    else:
        if trace: g.trace(language,rulesetName)
        path = g.os_path_join(g.app.loadDir,'..','modes')
        # Bug fix: 2008/2/10: Don't try to import a non-existent language.
        fileName = g.os_path_join(path,'%s.py' % (language))
        if g.os_path_exists(fileName):
            mode = g.importFromPath (language,path)
        else: mode = None

        if mode:
            # A hack to give modes/forth.py access to c.
            if hasattr(mode,'pre_init_mode'):
                mode.pre_init_mode(self.c)
        else:
            # Create a dummy bunch to limit recursion.
            self.modes [rulesetName] = self.modeBunch = g.Bunch(
                attributesDict  = {},
                defaultColor    = None,
                keywordsDict    = {},
                language        = language,
                mode            = mode,
                properties      = {},
                rulesDict       = {},
                rulesetName     = rulesetName,
            )
            if trace: g.trace('***** No colorizer file: %s.py' % language)
            self.rulesetName = rulesetName
            return False
        self.colorizer.language = language
        self.rulesetName = rulesetName
        self.properties = hasattr(mode,'properties') and mode.properties or {}
        self.keywordsDict = hasattr(mode,'keywordsDictDict') and mode.keywordsDictDict.get(rulesetName,{}) or {}
        self.setKeywords()
        self.attributesDict = hasattr(mode,'attributesDictDict') and mode.attributesDictDict.get(rulesetName) or {}
        # g.trace('*******',rulesetName,self.attributesDict)
        self.setModeAttributes()
        self.rulesDict = hasattr(mode,'rulesDictDict') and mode.rulesDictDict.get(rulesetName) or {}
        self.addLeoRules(self.rulesDict)

        self.defaultColor = 'null'
        self.mode = mode
        self.modes [rulesetName] = self.modeBunch = g.Bunch(
            attributesDict  = self.attributesDict,
            defaultColor    = self.defaultColor,
            keywordsDict    = self.keywordsDict,
            language        = self.colorizer.language,
            mode            = self.mode,
            properties      = self.properties,
            rulesDict       = self.rulesDict,
            rulesetName     = self.rulesetName,
        )
        # Do this after 'officially' initing the mode, to limit recursion.
        self.addImportedRules(mode,self.rulesDict,rulesetName)
        self.updateDelimsTables()
        initialDelegate = self.properties.get('initialModeDelegate')
        if initialDelegate:
            if trace: g.trace('initialDelegate',initialDelegate)
            # Replace the original mode by the delegate mode.
            self.init_mode(initialDelegate)
            language2,rulesetName2 = self.nameToRulesetName(initialDelegate)
            self.modes[rulesetName] = self.modes.get(rulesetName2)
        return True
#@+node:ekr.20090614134853.3707: *5* nameToRulesetName
def nameToRulesetName (self,name):

    '''Compute language and rulesetName from name, which is either a language or a delegate name.'''

    if not name: return ''

    i = name.find('::')
    if i == -1:
        language = name
        rulesetName = '%s_main' % (language)
    else:
        language = name[:i]
        delegate = name[i+2:]
        rulesetName = self.munge('%s_%s' % (language,delegate))

    # if rulesetName == 'php_main': rulesetName = 'php_php'

    # g.trace(name,language,rulesetName)
    return language,rulesetName
#@+node:ekr.20090614134853.3708: *5* setKeywords
def setKeywords (self):

    '''Initialize the keywords for the present language.

     Set self.word_chars ivar to string.letters + string.digits
     plus any other character appearing in any keyword.'''

    # Add any new user keywords to leoKeywordsDict.
    d = self.keywordsDict
    keys = list(d.keys())
    for s in g.globalDirectiveList:
        key = '@' + s
        if key not in keys:
            d [key] = 'leoKeyword'

    # Create a temporary chars list.  It will be converted to a dict later.
    chars = [g.toUnicode(ch) for ch in (string.ascii_letters + string.digits)]

    for key in list(d.keys()):
        for ch in key:
            if ch not in chars:
                chars.append(g.toUnicode(ch))

    # jEdit2Py now does this check, so this isn't really needed.
    # But it is needed for forth.py.
    for ch in (' ', '\t'):
        if ch in chars:
            # g.es_print('removing %s from word_chars' % (repr(ch)))
            chars.remove(ch)

    # g.trace(self.colorizer.language,[str(z) for z in chars])

    # Convert chars to a dict for faster access.
    self.word_chars = {}
    for z in chars:
        self.word_chars[z] = z
#@+node:ekr.20090614134853.3709: *5* setModeAttributes
def setModeAttributes (self):

    '''Set the ivars from self.attributesDict,
    converting 'true'/'false' to True and False.'''

    d = self.attributesDict
    aList = (
        ('default',         'null'),
	    ('digit_re',        ''),
        ('escape',          ''), # New in Leo 4.4.2.
	    ('highlight_digits',True),
	    ('ignore_case',     True),
	    ('no_word_sep',     ''),
    )

    # g.trace(d)

    for key, default in aList:
        val = d.get(key,default)
        if val in ('true','True'): val = True
        if val in ('false','False'): val = False
        setattr(self,key,val)
        # g.trace(key,val)
#@+node:ekr.20090614134853.3710: *5* initModeFromBunch
def initModeFromBunch (self,bunch):

    self.modeBunch = bunch
    self.attributesDict = bunch.attributesDict
    self.setModeAttributes()
    self.defaultColor   = bunch.defaultColor
    self.keywordsDict   = bunch.keywordsDict
    self.colorizer.language = bunch.language
    self.mode           = bunch.mode
    self.properties     = bunch.properties
    self.rulesDict      = bunch.rulesDict
    self.rulesetName    = bunch.rulesetName

    # State stuff.
    # h = self.highlighter
    # h.setCurrentBlockState(bunch.currentState)
    # self.nextState      = bunch.nextState
    # self.restartDict    = bunch.restartDict
    # self.stateDict      = bunch.stateDict
    # self.stateNameDict  = bunch.stateNameDict

    # self.clearState()

    # g.trace(self.rulesetName)

#@+node:ekr.20090614134853.3711: *5* updateDelimsTables
def updateDelimsTables (self):

    '''Update g.app.language_delims_dict if no entry for the language exists.'''

    d = self.properties
    lineComment = d.get('lineComment')
    startComment = d.get('commentStart')
    endComment = d.get('commentEnd')

    if lineComment and startComment and endComment:
        delims = '%s %s %s' % (lineComment,startComment,endComment)
    elif startComment and endComment:
        delims = '%s %s' % (startComment,endComment)
    elif lineComment:
        delims = '%s' % lineComment
    else:
        delims = None

    if delims:
        d = g.app.language_delims_dict
        if not d.get(self.colorizer.language):
            d [self.colorizer.language] = delims
            # g.trace(self.colorizer.language,'delims:',repr(delims))
#@+node:ekr.20090614134853.3714: *4* colorRangeWithTag
def colorRangeWithTag (self,s,i,j,tag,delegate='',exclude_match=False):

    '''Actually colorize the selected range.

    This is called whenever a pattern matcher succeed.'''

    trace = False and not g.unitTesting

    # Pattern matcher may set the .flag ivar.
    if self.colorizer.killColorFlag or not self.colorizer.flag:
        if trace: g.trace('disabled')
        return

    if delegate:
        if trace: g.trace('delegate %-12s %3s %3s %10s %s' % (
            delegate,i,j,tag,s[i:j])) # ,g.callers(2))
        self.modeStack.append(self.modeBunch)
        self.init_mode(delegate)
        # Color everything now, using the same indices as the caller.
        while 0 <= i < j and i < len(s):
            progress = i
            assert j >= 0,j
            for f in self.rulesDict.get(s[i],[]):
                n = f(self,s,i)
                if n is None:
                    g.trace('Can not happen: delegate matcher returns None')
                elif n > 0:
                    # if trace: g.trace('delegate',delegate,i,n,f.__name__,repr(s[i:i+n]))
                    i += n ; break
            else:
                # New in Leo 4.6: Use the default chars for everything else.
                # New in Leo 4.8 devel: use the *delegate's* default characters if possible.
                default_tag = self.attributesDict.get('default')
                # g.trace(default_tag)
                self.setTag(default_tag or tag,s,i,i+1)
                i += 1
            assert i > progress
        bunch = self.modeStack.pop()
        self.initModeFromBunch(bunch)
    elif not exclude_match:
        self.setTag(tag,s,i,j)
#@+node:ekr.20090614134853.3737: *4* match_span & helper & restarter
def match_span (self,s,i,
    kind='',begin='',end='',
    at_line_start=False,at_whitespace_end=False,at_word_start=False,
    delegate='',exclude_match=False,
    no_escape=False,no_line_break=False,no_word_break=False):

    '''Succeed if s[i:] starts with 'begin' and contains a following 'end'.'''

    trace = False and not g.unitTesting
    if i >= len(s): return 0

    # g.trace(begin,end,no_escape,no_line_break,no_word_break)

    if at_line_start and i != 0 and s[i-1] != '\n':
        j = i
    elif at_whitespace_end and i != g.skip_ws(s,0):
        j = i
    elif at_word_start and i > 0 and s[i-1] in self.word_chars:
        j = i
    elif at_word_start and i + len(begin) + 1 < len(s) and s[i+len(begin)] in self.word_chars:
        j = i
    elif not g.match(s,i,begin):
        j = i
    else:
        # We have matched the start of the span.
        j = self.match_span_helper(s,i+len(begin),end,
            no_escape,no_line_break,no_word_break=no_word_break)
        # g.trace('** helper returns',j,len(s))
        if j == -1:
            j = i # A real failure.
        else:
            # A match
            i2 = i + len(begin) ; j2 = j + len(end)
            if delegate:
                self.colorRangeWithTag(s,i,i2,kind,delegate=None,    exclude_match=exclude_match)
                self.colorRangeWithTag(s,i2,j,kind,delegate=delegate,exclude_match=exclude_match)
                self.colorRangeWithTag(s,j,j2,kind,delegate=None,    exclude_match=exclude_match)
            else:
                self.colorRangeWithTag(s,i,j2,kind,delegate=None,exclude_match=exclude_match)
            j = j2
            self.prev = (i,j,kind)

    self.trace_match(kind,s,i,j)

    if j > len(s):
        j = len(s) + 1
        def boundRestartMatchSpan(s):
            # Note: bindings are frozen by this def.
            return self.restart_match_span(s,
                # Positional args, in alpha order
                delegate,end,exclude_match,kind,
                no_escape,no_line_break,no_word_break)

        self.setRestart(boundRestartMatchSpan,
            # These must be keywords args.
            delegate=delegate,end=end,
            exclude_match=exclude_match,
            kind=kind,
            no_escape=no_escape,
            no_line_break=no_line_break,
            no_word_break=no_word_break)

        if trace: g.trace('***Continuing',kind,i,j,len(s))
    elif j != i:
        if trace: g.trace('***Ending',kind,i,j,s[i:j])
        self.clearState()

    return j - i # Correct, whatever j is.
#@+node:ekr.20090614134853.3738: *5* match_span_helper
def match_span_helper (self,s,i,pattern,no_escape,no_line_break,no_word_break):

    '''Return n >= 0 if s[i] ends with a non-escaped 'end' string.'''

    esc = self.escape

    while 1:
        j = s.find(pattern,i)
        # g.trace(no_line_break,j,len(s))
        if j == -1:
            # Match to end of text if not found and no_line_break is False
            if no_line_break:
                return -1
            else:
                return len(s)+1
        elif no_word_break and j > 0 and s[j-1] in self.word_chars:
            return -1 # New in Leo 4.5.
        elif no_line_break and '\n' in s[i:j]:
            return -1
        elif esc and not no_escape:
            # Only an odd number of escapes is a 'real' escape.
            escapes = 0 ; k = 1
            while j-k >=0 and s[j-k] == esc:
                escapes += 1 ; k += 1
            if (escapes % 2) == 1:
                # Continue searching past the escaped pattern string.
                i = j + len(pattern) # Bug fix: 7/25/07.
                # g.trace('escapes',escapes,repr(s[i:]))
            else:
                return j
        else:
            return j
#@+node:ekr.20090614134853.3821: *5* restart_match_span
def restart_match_span (self,s,
    delegate,end,exclude_match,kind,
    no_escape,no_line_break,no_word_break):

    '''Remain in this state until 'end' is seen.'''

    trace = False and not g.unitTesting

    i = 0
    j = self.match_span_helper(s,i,end,no_escape,no_line_break,no_word_break)
    if j == -1:
        j2 = len(s)+1
    elif j > len(s):
        j2 = j
    else:
        j2 = j + len(end)

    if delegate:
        self.colorRangeWithTag(s,i,j,kind,delegate=delegate,exclude_match=exclude_match)
        self.colorRangeWithTag(s,j,j2,kind,delegate=None,    exclude_match=exclude_match)
    else: # avoid having to merge ranges in addTagsToList.
        self.colorRangeWithTag(s,i,j2,kind,delegate=None,exclude_match=exclude_match)
    j = j2

    self.trace_match(kind,s,i,j)

    if j > len(s):
        def boundRestartMatchSpan(s):
            return self.restart_match_span(s,
                # Positional args, in alpha order
                delegate,end,exclude_match,kind,
                no_escape,no_line_break,no_word_break)

        self.setRestart(boundRestartMatchSpan,
            # These must be keywords args.
            delegate=delegate,end=end,kind=kind,
            no_escape=no_escape,
            no_line_break=no_line_break,
            no_word_break=no_word_break)

        if trace: g.trace('***Re-continuing',i,j,len(s),s,g.callers(5))
    else:
        if trace: g.trace('***ending',i,j,len(s),s)
        self.clearState()

    return j # Return the new i, *not* the length of the match.
#@+node:ekr.20090614134853.3813: *4* setTag
def setTag (self,tag,s,i,j):

    trace = False and not g.unitTesting

    if i == j:
        if trace: g.trace('empty range')
        return

    w = self.w
    colorName = w.configDict.get(tag)

    # Munge the color name.
    if not colorName:
        if trace: g.trace('no color for %s' % tag)
        return

    if colorName[-1].isdigit() and colorName[0] != '#':
        colorName = colorName[:-1]

    # Get the actual color.
    color = self.actualColorDict.get(colorName)
    if not color:
        color = QtGui.QColor(colorName)
        if color.isValid():
            self.actualColorDict[colorName] = color
        else:
            return g.trace('unknown color name',colorName)

    underline = w.configUnderlineDict.get(tag)

    format = QtGui.QTextCharFormat()

    font = self.fonts.get(tag)
    if font:
        format.setFont(font)

    if trace:
        self.tagCount += 1
        g.trace(
            '%3s %3s %3s %9s %7s' % (i,j,len(s),font and id(font) or '<no font>',colorName),
            '%-10s %-25s' % (tag,s[i:j]),g.callers(2))

    if tag in ('blank','tab'):
        if tag == 'tab' or colorName == 'black':
            format.setFontUnderline(True)
        if colorName != 'black':
            format.setBackground(color)
    elif underline:
        format.setForeground(color)
        format.setFontUnderline(True)
    else:
        format.setForeground(color)

    self.highlighter.setFormat (i,j-i,format)

#@+node:ekr.20100618162506.3715: *3* Use chapter drop-down only if an @chapters node exists
#@+node:ekr.20081121105001.270: *4* addRowIfNeeded
def addRowIfNeeded (self):

    '''Add a new icon row if there are too many widgets.'''

    # n = g.app.iconWidgetCount

    # if n >= self.widgets_per_row:
        # g.app.iconWidgetCount = 0
        # self.addRow()

    # g.app.iconWidgetCount += 1
#@+node:ekr.20081121105001.254: *4* qtFrame.finishCreate & helpers
def finishCreate (self,c):

    f = self ; f.c = c

    # g.trace('(qtFrame)')

    # self.bigTree         = c.config.getBool('big_outline_pane')
    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')

    # returns DynamicWindow
    f.top = g.app.gui.frameFactory.createFrame(f)
    # g.trace('(leoQtFrame)',f.top)

    # hiding would remove flicker, but doesn't work with all
    # window managers

    f.createIconBar() # A base class method.
    f.createSplitterComponents()
    cc = c.chapterController
    # g.trace(cc,cc.findChaptersNode())
    if 0: # 2010/06/17: Now done in cc.createChaptersNode.
        if f.use_chapters and f.use_chapter_tabs: # and cc and cc.findChaptersNode():
            cc.tt = leoQtTreeTab(c,f.iconBar)
    f.createStatusLine() # A base class method.
    f.createFirstTreeNode() # Call the base-class method.
    f.menu = leoQtMenu(f)
    #### c.setLog()
    g.app.windowList.append(f)
    f.miniBufferWidget = leoQtMinibuffer(c)
    c.bodyWantsFocusNow()
#@+node:ekr.20081121105001.255: *5* createSplitterComponents (qtFrame)
def createSplitterComponents (self):

    f = self ; c = f.c

    f.tree  = leoQtTree(c,f)
    f.log   = leoQtLog(f,None)
    f.body  = leoQtBody(f,None)

    if f.use_chapters:
        c.chapterController = cc = leoChapters.chapterController(c)

    f.splitVerticalFlag,f.ratio,f.secondary_ratio = f.initialRatios()
    f.resizePanesToRatio(f.ratio,f.secondary_ratio)
#@+node:ekr.20100111202913.3765: *4* class leoQtTreeTab
class leoQtTreeTab:

    '''A class representing a so-called tree-tab.

    Actually, it represents a combo box'''

    @others
#@+node:ekr.20100111202913.3766: *5*  Birth & death
#@+node:ekr.20100111202913.3767: *6*  ctor (leoTreeTab)
def __init__ (self,c,iconBar):

    # g.trace('(leoTreeTab)',g.callers(4))

    self.c = c
    self.cc = c.chapterController
    assert self.cc
    self.iconBar = iconBar
    self.tabNames = []
        # The list of tab names. Changes when tabs are renamed.
    self.w = None # The QComboBox

    self.createControl()
#@+node:ekr.20100111202913.3768: *6* tt.createControl
def createControl (self):

    tt = self
    frame = QtGui.QLabel('Chapters: ')
    tt.iconBar.addWidget(frame)
    tt.w = w = QtGui.QComboBox()
    tt.setNames()
    tt.iconBar.addWidget(w)

    def onIndexChanged(s,tt=tt):
        if not s: return
        s = g.u(s)
        # g.trace(s)
        tt.selectTab(s)

    w.connect(w,
        QtCore.SIGNAL("currentIndexChanged(QString)"),
            onIndexChanged)
#@+node:ekr.20100111202913.3769: *5* Tabs...
#@+node:ekr.20100111202913.3770: *6* tt.createTab
def createTab (self,tabName,select=True):

    # g.trace(tabName,g.callers(4))

    tt = self

    # Avoid a glitch during initing.
    if tabName == 'main': return

    if tabName not in tt.tabNames:
        tt.tabNames.append(tabName)
        tt.setNames()
#@+node:ekr.20100111202913.3771: *6* tt.destroyTab
def destroyTab (self,tabName):

    tt = self

    if tabName in tt.tabNames:
        tt.tabNames.remove(tabName)
        tt.setNames()
#@+node:ekr.20100111202913.3772: *6* tt.selectTab
def selectTab (self,tabName):

    tt = self

    # g.trace(tabName)

    if tabName not in self.tabNames:
        tt.createTab(tabName)

    tt.cc.selectChapterByName(tabName)

    self.c.redraw()
    self.c.outerUpdate()
#@+node:ekr.20100111202913.3773: *6* tt.setTabLabel
def setTabLabel (self,tabName):

    tt = self ; w = tt.w
    # g.trace(tabName)
    i = w.findText (tabName)
    if i > -1:
        w.setCurrentIndex(i)
#@+node:ekr.20100111202913.3774: *6* tt.setNames
def setNames (self):

    '''Recreate the list of items.'''

    tt = self ; w = tt.w
    names = tt.tabNames[:]
    if 'main' in names: names.remove('main')
    names.sort()
    names.insert(0,'main')
    w.clear()
    w.insertItems(0,names)
#@+node:ekr.20081121105001.266: *4* class qtIconBarClass
class qtIconBarClass:

    '''A class representing the singleton Icon bar'''

    @others
#@+node:ekr.20081121105001.267: *5*  ctor
def __init__ (self,c,parentFrame):

    # g.trace('(qtIconBarClass)')

    self.c = c
    self.chapterController = None
    self.parentFrame = parentFrame
    self.toolbar = self
    self.w = c.frame.top.iconBar # A QToolBar.
    self.actions = []

    # Options
    self.buttonColor = c.config.getString('qt-button-color')

    # g.app.iconWidgetCount = 0
#@+node:ekr.20081121105001.268: *5*  do-nothings
def addRow(self,height=None):   pass
def getFrame (self):            return None
def getNewFrame (self):         return None
def pack (self):                pass
def unpack (self):              pass

hide = unpack
show = pack
#@+node:ekr.20081121105001.269: *5* add
def add(self,*args,**keys):

    '''Add a button to the icon bar.'''

    c = self.c
    command = keys.get('command')
    text = keys.get('text')
    # able to specify low-level QAction directly (QPushButton not forced)
    qaction = keys.get('qaction')

    if not text and not qaction:
        g.es('bad toolbar item')

    bg = keys.get('bg') or self.toolbar.buttonColor

    # imagefile = keys.get('imagefile')
    # image = keys.get('image')

    class leoIconBarButton (QtGui.QWidgetAction):
        def __init__ (self,parent,text,toolbar):
            QtGui.QWidgetAction.__init__(self,parent)
            self.button = None # set below
            self.text = text
            self.toolbar = toolbar
        def createWidget (self,parent):
            # g.trace('leoIconBarButton',self.toolbar.buttonColor)
            self.button = b = QtGui.QPushButton(self.text,parent)
            g.app.gui.setWidgetColor(b,
                widgetKind='QPushButton',
                selector='background-color',
                colorName = bg)
            return b

    if qaction is None:
        action = leoIconBarButton(parent=self.w,text=text,toolbar=self)
    else:
        action = qaction

    self.w.addAction(action)

    self.actions.append(action)
    b = self.w.widgetForAction(action)

    b.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)

    def delete_callback(action=action,):
        self.w.removeAction(action)

    b.leo_removeAction = rb = QtGui.QAction('Remove Button' ,b)

    b.addAction(rb)
    rb.connect(rb, QtCore.SIGNAL("triggered()"), delete_callback)

    if command:
        def button_callback(c=c,command=command):
            # g.trace('command',command.__name__)
            val = command()
            if c.exists:
                # c.bodyWantsFocus()
                c.outerUpdate()
            return val

        self.w.connect(b,
            QtCore.SIGNAL("clicked()"),
            button_callback)

    return action
#@+node:ekr.20081121105001.270: *5* addRowIfNeeded
def addRowIfNeeded (self):

    '''Add a new icon row if there are too many widgets.'''

    # n = g.app.iconWidgetCount

    # if n >= self.widgets_per_row:
        # g.app.iconWidgetCount = 0
        # self.addRow()

    # g.app.iconWidgetCount += 1
#@+node:ekr.20081121105001.271: *5* addWidget
def addWidget (self,w):

    self.w.addWidget(w)
#@+node:ekr.20081121105001.272: *5* clear
def clear(self):

    """Destroy all the widgets in the icon bar"""

    self.w.clear()
    self.actions = []

    g.app.iconWidgetCount = 0
#@+node:ekr.20100618162506.3716: *5* createChaptersIcon
def createChaptersIcon(self):

    # g.trace('(qtIconBarClass)')

    c = self.c
    cc = c.chapterController
    f = c.frame

    if f.use_chapters and f.use_chapter_tabs: # and cc and cc.findChaptersNode():
        cc.tt = leoQtTreeTab(c,f.iconBar)
#@+node:ekr.20081121105001.273: *5* deleteButton
def deleteButton (self,w):
    """ w is button """

    #g.trace(w, '##')    

    self.w.removeAction(w)

    self.c.bodyWantsFocus()
    self.c.outerUpdate()
#@+node:ekr.20081121105001.274: *5* setCommandForButton
def setCommandForButton(self,button,command):

    if command:
        # button is a leoIconBarButton.
        QtCore.QObject.connect(button.button,
            QtCore.SIGNAL("clicked()"),command)

        if not hasattr(command, 'p'):
            # can get here from @buttons in the current outline, in which
            # case p exists, or from @buttons in @settings elsewhere, in
            # which case it doesn't

            return

        # 20100518 - TNB command is instance of callable class with
        #   a c and p attribute, so we can add a context menu item...
        def goto_command(command = command):
            command.c.selectPosition(command.p)
            command.c.redraw()
        b = button.button
        b.goto_script = gts = QtGui.QAction('Goto Script', b)
        b.addAction(gts)
        gts.connect(gts, QtCore.SIGNAL("triggered()"), goto_command)

        # 20100519 - TNB also, scan @button's following sibs and childs
        #   for @rclick nodes
        rclicks = []
        if '@others' not in command.p.b:
            rclicks.extend([i.copy() for i in command.p.children()
              if i.h.startswith('@rclick ')])
        for i in command.p.following_siblings():
            if i.h.startswith('@rclick '):
                rclicks.append(i.copy())
            else:
                break

        if rclicks:
            b.setText(unicode(b.text())+(command.c.config.getString('mod_scripting_subtext') or ''))

        for rclick in rclicks:

            def cb(event=None, ctrl=command.controller, p=rclick, 
                   c=command.c, b=command.b, t=rclick.h[8:]):
                ctrl.executeScriptFromButton(p,b,t)
                if c.exists:
                    c.outerUpdate()

            rc = QtGui.QAction(rclick.h[8:], b)
            rc.connect(rc, QtCore.SIGNAL("triggered()"), cb)
            b.insertAction(b.actions()[-2], rc)  # insert rc before Remove Button

            # k.registerCommand(buttonText.lower(),
            #   shortcut=shortcut,func=atButtonCallback,
            #   pane='button',verbose=verbose)
#@+node:ekr.20101020110928.3844: *3* Fixed bugs re objective-c coloring
#@+node:ekr.20090614134853.3708: *4* setKeywords
def setKeywords (self):

    '''Initialize the keywords for the present language.

     Set self.word_chars ivar to string.letters + string.digits
     plus any other character appearing in any keyword.'''

    # Add any new user keywords to leoKeywordsDict.
    d = self.keywordsDict
    keys = list(d.keys())
    for s in g.globalDirectiveList:
        key = '@' + s
        if key not in keys:
            d [key] = 'leoKeyword'

    # Create a temporary chars list.  It will be converted to a dict later.
    chars = [g.toUnicode(ch) for ch in (string.ascii_letters + string.digits)]

    for key in list(d.keys()):
        for ch in key:
            if ch not in chars:
                chars.append(g.toUnicode(ch))

    # jEdit2Py now does this check, so this isn't really needed.
    # But it is needed for forth.py.
    for ch in (' ', '\t'):
        if ch in chars:
            # g.es_print('removing %s from word_chars' % (repr(ch)))
            chars.remove(ch)

    # g.trace(self.colorizer.language,[str(z) for z in chars])

    # Convert chars to a dict for faster access.
    self.word_chars = {}
    for z in chars:
        self.word_chars[z] = z
#@+node:ekr.20090614134853.3730: *4* match_keywords
# This is a time-critical method.
def match_keywords (self,s,i):

    '''Succeed if s[i:] is a keyword.'''

    # trace = False
    self.totalKeywordsCalls += 1

    # Important.  Return -len(word) for failure greatly reduces
    # the number of times this method is called.

    # We must be at the start of a word.
    if i > 0 and s[i-1] in self.word_chars:
        # if trace: g.trace('not at word start',s[i-1])
        return 0

    # Get the word as quickly as possible.
    j = i ; n = len(s) ; chars = self.word_chars
    while j < n and s[j] in chars:
        j += 1

    word = s[i:j]
    if self.ignore_case: word = word.lower()
    kind = self.keywordsDict.get(word)
    if kind:
        self.colorRangeWithTag(s,i,j,kind)
        self.prev = (i,j,kind)
        result = j - i
        # if trace: g.trace('success',word,kind,j-i)
        self.trace_match(kind,s,i,j)
        return result
    else:
        # if trace: g.trace('fail',word,kind)
        return -len(word) # An important new optimization.
#@+node:ekr.20090614134853.3754: *4* mainLoop & restart
def mainLoop(self,n,s):

    '''Colorize string s, starting in state n.'''

    trace = False and not g.unitTesting
    traceMatch = True ; traceState = True ; verbose = True

    if trace and traceState: g.trace('** start',self.showState(n),s)

    i = 0
    if n > -1:
        i = self.restart(n,s,trace and traceMatch)
    if i == 0:
        self.setState(self.prevState())

    while i < len(s):
        progress = i
        functions = self.rulesDict.get(s[i],[])
        for f in functions:
            n = f(self,s,i)
            if n is None:
                g.trace('Can not happen: n is None',repr(f))
                break
            elif n > 0: # Success.
                if trace and traceMatch and f.__name__!='match_blanks':
                    g.trace('match: %20s %s' % (
                        f.__name__,s[i:i+n]))
                i += n
                break # Stop searching the functions.
            elif n < 0: # Fail and skip n chars.
                if trace and traceMatch and verbose:
                    g.trace('fail: %20s %s' % (
                        f.__name__,s[i:i+n]))
                i += -n
                break # Stop searching the functions.
            else: # Fail. Try the next function.
                pass # Do not break or change i!
        else:
            i += 1
        assert i > progress

    # Don't even *think* about clearing state here.
    # We remain in the starting state unless a match happens.
    if trace and traceState:
        g.trace('** end',self.showCurrentState(),s)
#@+node:ekr.20090625061310.3864: *5* restart
def restart (self,n,s,traceMatch):

    f = self.restartDict.get(n)
    if f:
        i = f(s)
        fname = f.__name__
        if traceMatch:
            if i > 0:
                g.trace('** restart match',fname,s[:i])
            else:
                g.trace('** restart fail',fname,s)
    else:
        g.trace('**** no restart f')
        i = 0

    return i
#@+node:ekr.20090614134853.3724: *4* match_leo_keywords
def match_leo_keywords(self,s,i):

    '''Succeed if s[i:] is a Leo keyword.'''

    # g.trace(i,g.get_line(s,i))

    self.totalLeoKeywordsCalls += 1

    if s[i] != '@':
        return 0

    # fail if something besides whitespace precedes the word on the line.
    i2 = i-1
    while i2 >= 0:
        ch = s[i2]
        if ch == '\n':
            break
        elif ch in (' ','\t'):
            i2 -= 1
        else:
            # g.trace('not a word 1',repr(ch))
            return 0

    # Get the word as quickly as possible.
    j = i+1
    while j < len(s) and s[j] in self.word_chars:
        j += 1
    word = s[i+1:j] # entries in leoKeywordsDict do not start with '@'.

    if j < len(s) and s[j] not in (' ','\t','\n'):
        # g.trace('not a word 2',repr(word))
        return 0 # Fail, but allow a rescan, as in objective_c.

    if self.leoKeywordsDict.get(word):
        kind = 'leoKeyword'
        self.colorRangeWithTag(s,i,j,kind)
        self.prev = (i,j,kind)
        result = j-i+1 # Bug fix: skip the last character.
        self.trace_match(kind,s,i,j)
        # g.trace('*** match',repr(s))
        return result
    else:
        # 2010/10/20: also check the keywords dict here.
        # This allows for objective_c keywords starting with '@'
        # This will not slow down Leo, because it is called
        # for things that look like Leo directives.
        word = '@' + word
        kind = self.keywordsDict.get(word)
        if kind:
            self.colorRangeWithTag(s,i,j,kind)
            self.prev = (i,j,kind)
            self.trace_match(kind,s,i,j)
            # g.trace('found',word)
            return j-i
        else:
            # g.trace('fail',repr(word),repr(self.word_chars))
            return -(j-i+1) # An important optimization.
#@+node:ekr.20101021123808.3751: *3* Fixed DnD unicode error
@nocolor-node

Bug 659211: drag and drop unicode error

Current trunk, ubuntu 10.4.1, Qt interface

attempting to drag and drop a node:

Traceback (most recent call last):
  File "/mnt/usr1/usr1/home/tbrown/.gnome-desktop/Package/leo/bzr/leo.repo/trunk/leo/plugins/qtGui.py",
  line 5202, in dragEnterEvent
    self.setText(md)
  File "/mnt/usr1/usr1/home/tbrown/.gnome-desktop/Package/leo/bzr/leo.repo/trunk/leo/plugins/qtGui.py",
  line 5567, in setText
    md.setText('%s,%s' % (fn,s))
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 325: ordinal not in range(128)

This was a Python 2k error only.
#@+node:ekr.20100830205422.3714: *4* class LeoQTreeWidget
class LeoQTreeWidget(QtGui.QTreeWidget):

    # To do: Generate @auto or @file nodes when appropriate.

    def __init__(self,c,parent):

        QtGui.QTreeWidget.__init__(self, parent)
        self.setAcceptDrops(True)
        self.setDragEnabled(True)
        self.c = c
        self.trace = False

    def dragMoveEvent(self,ev):
        pass

    @others
#@+node:ekr.20100830205422.3715: *5* dragEnter
def dragEnterEvent(self,ev):

    '''Export c.p's tree as a Leo mime-data.'''

    trace = False and not g.unitTesting
    c = self.c ; tree = c.frame.tree
    if not ev:
        g.trace('no event!')
        return

    md = ev.mimeData()
    if not md:
        g.trace('No mimeData!') ; return

    c.endEditing()
    if g.app.dragging:
        if trace or self.trace: g.trace('** already dragging')
    else:
        g.app.dragging = True
        if self.trace: g.trace('set g.app.dragging')
        self.setText(md)
        if self.trace: self.dump(ev,c.p,'enter')

    # Always accept the drag, even if we are already dragging.
    ev.accept()
#@+node:ekr.20100830205422.3716: *5* dropEvent & helpers
def dropEvent(self,ev):

    trace = False and not g.unitTesting
    if not ev: return
    c = self.c ; tree = c.frame.tree ; u = c.undoer

    # Always clear the dragging flag, no matter what happens.
    g.app.dragging = False
    if self.trace: g.trace('clear g.app.dragging')

    # Set p to the target of the drop.
    item = self.itemAt(ev.pos())
    if not item: return
    itemHash = tree.itemHash(item)
    p = tree.item2positionDict.get(itemHash)
    if not p:
        if trace or self.trace: g.trace('no p!')
        return

    md = ev.mimeData()
    if not md:
        g.trace('no mimeData!') ; return

    ev.setDropAction(QtCore.Qt.IgnoreAction)
    ev.accept()

    if trace or self.trace: self.dump(ev,p,'drop ')

    if md.hasUrls():
        self.urlDrop(ev,p)
    else:
        self.outlineDrop(ev,p)
#@+node:ekr.20100830205422.3720: *6* outlineDrop & helpers
def outlineDrop (self,ev,p):

    trace = False and not g.unitTesting
    c = self.c ; tree = c.frame.tree
    mods = ev.keyboardModifiers()
    md = ev.mimeData()

    fn,s = self.parseText(md)
    if not s or not fn:
        if trace or self.trace: g.trace('no fn or no s')
        return

    if fn == self.fileName():
        if p and p == c.p:
            if trace or self.trace: g.trace('same node')
        else:
            cloneDrag = (int(mods) & QtCore.Qt.ControlModifier) != 0
            self.intraFileDrop(cloneDrag,fn,c.p,p)
    else:
        # Clone dragging between files is not allowed.
        self.interFileDrop(fn,p,s)
#@+node:ekr.20100830205422.3718: *7* interFileDrop
def interFileDrop (self,fn,p,s):

    '''Paste the mime data after (or as the first child of) p.'''

    c = self.c ; tree = c.frame.tree
    u = c.undoer ; undoType = 'Drag Outline'

    isLeo = g.match(s,0,g.app.prolog_prefix_string)
    if not isLeo: return

    c.selectPosition(p)
    pasted = c.fileCommands.getLeoOutlineFromClipboard(
        s,reassignIndices=True)
    if not pasted: return

    undoData = u.beforeInsertNode(p,
        pasteAsClone=False,copiedBunchList=[])
    c.validateOutline()
    c.selectPosition(pasted)
    pasted.setDirty()
    c.setChanged(True)
    back = pasted.back()
    if back and back.isExpanded():
        pasted.moveToNthChildOf(back,0)
    c.setRootPosition(c.findRootPosition(pasted))

    u.afterInsertNode(pasted,undoType,undoData)
    c.redraw_now(pasted)
    c.recolor()
#@+node:ekr.20100830205422.3719: *7* intraFileDrop
def intraFileDrop (self,cloneDrag,fn,p1,p2):

    '''Move p1 after (or as the first child of) p2.'''

    c = self.c ; u = c.undoer
    c.selectPosition(p1)

    if p2.hasChildren() and p2.isExpanded():
        # Attempt to move p1 to the first child of p2.
        parent = p2
        def move(p1,p2):
            if cloneDrag: p1 = p1.clone()
            p1.moveToNthChildOf(p2,0)
            return p1
    else:
        # Attempt to move p1 after p2.
        parent = p2.parent()
        def move(p1,p2):
            if cloneDrag: p1 = p1.clone()
            p1.moveAfter(p2)
            return p1

    ok = c.checkMoveWithParentWithWarning(p1,parent,True)
    if ok:
        undoData = u.beforeMoveNode(p1)
        dirtyVnodeList = p1.setAllAncestorAtFileNodesDirty()
        p1 = move(p1,p2)
        if cloneDrag:
            # Set dirty bits for ancestors of *all* cloned nodes.
            # Note: the setDescendentsDirty flag does not do what we want.
            for z in p1.self_and_subtree():
                z.setAllAncestorAtFileNodesDirty(
                    setDescendentsDirty=False)
        c.setChanged(True)
        u.afterMoveNode(p1,'Drag',undoData,dirtyVnodeList)
        c.redraw_now(p1)
    else:
        g.trace('** move failed')
#@+node:ekr.20100830205422.3721: *6* urlDrop & helpers
def urlDrop (self,ev,p):

    c = self.c ; u = c.undoer ; undoType = 'Drag Urls'
    md = ev.mimeData()
    urls = md.urls()
    if not urls: return

    c.undoer.beforeChangeGroup(c.p,undoType)

    changed = False
    for z in urls:
        url = QtCore.QUrl(z)
        scheme = url.scheme()
        if scheme == 'file':
            changed |= self.doFileUrl(p,url)
        elif scheme in ('http',): # 'ftp','mailto',
            changed |= self.doHttpUrl(p,url)
        # else: g.trace(url.scheme(),url)

    if changed:
        c.setChanged(True)
        u.afterChangeGroup(c.p,undoType,reportFlag=False,dirtyVnodeList=[])
        c.redraw_now()
#@+node:ekr.20100830205422.3722: *7* doFileUrl & helper
def doFileUrl (self,p,url):

    fn = str(url.path())
    if sys.platform.lower().startswith('win'):
        if fn.startswith('/'):
            fn = fn[1:]

    changed = False
    if g.os_path_exists(fn):
        try:
            f = open(fn,'r')
        except IOError:
            f = None
        if f:
            s = f.read()
            f.close()
            self.doFileUrlHelper(fn,p,s)
            return True

    g.es_print('not found: %s' % (fn))
    return False
#@+node:ekr.20100830205422.3723: *8* doFileUrlHelper & helper
def doFileUrlHelper (self,fn,p,s):

    '''Insert s in an @file, @auto or @edit node after p.'''

    c = self.c ; u = c.undoer ; undoType = 'Drag File'

    undoData = u.beforeInsertNode(p,pasteAsClone=False,copiedBunchList=[])

    if p.hasChildren() and p.isExpanded():
        p2 = p.insertAsNthChild(0)
    else:
        p2 = p.insertAfter()

    self.createAtFileNode(fn,p2,s)

    u.afterInsertNode(p2,undoType,undoData)

    c.selectPosition(p2)
#@+node:ekr.20100902095952.3740: *9* createAtFileNode & helpers
def createAtFileNode (self,fn,p,s):

    '''Set p's headline, body text and possibly descendants
    based on the file's name fn and contents s.

    If the file is an thin file, create an @file tree.
    Othewise, create an @auto tree.
    If all else fails, create an @auto node.

    Give a warning if a node with the same headline already exists.
    '''

    c = self.c ; d = c.importCommands.importDispatchDict
    if self.isThinFile(fn,s):
        self.createAtFileTree(fn,p,s)
    elif self.isAutoFile(fn,s):
        self.createAtAutoTree(fn,p,s)
    else:
        self.createAtEditNode(fn,p,s)
    self.warnIfNodeExists(p)
#@+node:ekr.20100902095952.3744: *10* createAtAutoTree
def createAtAutoTree (self,fn,p,s):

    '''Make p an @auto node and create the tree using
    s, the file's contents.
    '''

    c = self.c ; at = c.atFileCommands

    p.h = '@auto %s' % (fn)

    at.readOneAtAutoNode(fn,p)

    # No error recovery should be needed here.

    p.clearDirty() # Don't automatically rewrite this node.
#@+node:ekr.20100902165040.3738: *10* createAtEditNode
def createAtEditNode(self,fn,p,s):

    c = self.c ; at = c.atFileCommands

    # Use the full @edit logic, so dragging will be
    # exactly the same as reading.
    at.readOneAtEditNode(fn,p)
    p.h = '@edit %s' % (fn)
    p.clearDirty() # Don't automatically rewrite this node.
#@+node:ekr.20100902095952.3743: *10* createAtFileTree
def createAtFileTree (self,fn,p,s):

    '''Make p an @file node and create the tree using
    s, the file's contents.
    '''

    c = self.c ; at = c.atFileCommands

    p.h = '@file %s' % (fn)

    # Read the file into p.
    ok = at.read(root=p.copy(),
        importFileName=None,
        fromString=s,
        atShadow=False,
        force=True) # Disable caching.

    if not ok:
        g.es_print('Error reading',fn,color='red')
        p.b = '' # Safe: will not cause a write later.
        p.clearDirty() # Don't automatically rewrite this node.
#@+node:ekr.20100902095952.3741: *10* isThinFile
def isThinFile (self,fn,s):

    '''Return true if the file whose contents is s
    was created from an @thin or @file tree.'''

    c = self. c ; at = c.atFileCommands

    # Skip lines before the @+leo line.
    i = s.find('@+leo')
    if i == -1:
        return False
    else:
        # Like at.isFileLike.
        j,k = g.getLine(s,i)
        line = s[j:k]
        valid,new_df,start,end,isThin = at.parseLeoSentinel(line)
        # g.trace('valid',valid,'new_df',new_df,'isThin',isThin)
        return valid and new_df and isThin
#@+node:ekr.20100902095952.3742: *10* isAutoFile
def isAutoFile (self,fn,unused_s):

    '''Return true if the file whose name is fn
    can be parsed with an @auto parser.
    '''

    c = self.c
    d = c.importCommands.importDispatchDict
    junk,ext = g.os_path_splitext(fn)
    return d.get(ext)
#@+node:ekr.20100902095952.3745: *10* warnIfNodeExists
def warnIfNodeExists (self,p):

    c = self.c ; h = p.h
    for p2 in c.all_unique_positions():
        if p2.h == h and p2 != p:
            g.es('Warning: duplicate node:',h,color='blue')
            break
#@+node:ekr.20100830205422.3724: *7* doHttpUrl
def doHttpUrl (self,p,url):

    '''Insert the url in an @url node after p.'''

    c = self.c ; u = c.undoer ; undoType = 'Drag Url'

    s = str(url.toString()).strip()
    if not s: return False

    undoData = u.beforeInsertNode(p,pasteAsClone=False,copiedBunchList=[])

    if p.hasChildren() and p.isExpanded():
        p2 = p.insertAsNthChild(0)
    else:
        p2 = p.insertAfter()

    # p2.h,p2.b = '@url %s' % (s),''
    p2.h = '@url'
    p2.b = s
    p2.clearDirty() # Don't automatically rewrite this node.

    u.afterInsertNode(p2,undoType,undoData)
    return True
#@+node:ekr.20100902190250.3743: *5* utils...
#@+node:ekr.20100902190250.3741: *6* dump
def dump (self,ev,p,tag):

    if ev:
        md = ev.mimeData()
        assert md,'dump: no md'
        fn,s = self.parseText(md)
        if fn:
            g.trace('',tag,'fn',repr(g.shortFileName(fn)),
                'len(s)',len(s),p and p.h)
        else:
            g.trace('',tag,'no fn! s:',repr(s))
    else:
        g.trace('',tag,'** no event!')
#@+node:ekr.20100902190250.3740: *6* parseText
def parseText (self,md):

    '''Parse md.text() into (fn,s)'''

    fn = ''
    # g.trace(type(md.text()))
    s = str(md.text()) # Safe: md.text() is a QString.

    if s:
        i = s.find(',')
        if i == -1:
            pass
        else:
            fn = s[:i]
            s = s[i+1:]

    return fn,s
#@+node:ekr.20100902190250.3742: *6* setText & fileName
def fileName (self):

    return self.c.fileName() or '<unsaved file>'

def setText (self,md):

    c = self.c
    fn = self.fileName()
    s = c.fileCommands.putLeoOutline()
    if not g.isPython3:
        s = g.toEncodedString(s,encoding='utf-8',reportErrors=True)
        fn = g.toEncodedString(fn,encoding='utf-8', reportErrors=True)
    md.setText('%s,%s' % (fn,s))
#@+node:ekr.20100830205422.3715: *4* dragEnter
def dragEnterEvent(self,ev):

    '''Export c.p's tree as a Leo mime-data.'''

    trace = False and not g.unitTesting
    c = self.c ; tree = c.frame.tree
    if not ev:
        g.trace('no event!')
        return

    md = ev.mimeData()
    if not md:
        g.trace('No mimeData!') ; return

    c.endEditing()
    if g.app.dragging:
        if trace or self.trace: g.trace('** already dragging')
    else:
        g.app.dragging = True
        if self.trace: g.trace('set g.app.dragging')
        self.setText(md)
        if self.trace: self.dump(ev,c.p,'enter')

    # Always accept the drag, even if we are already dragging.
    ev.accept()
#@+node:ekr.20100902190250.3742: *4* setText & fileName
def fileName (self):

    return self.c.fileName() or '<unsaved file>'

def setText (self,md):

    c = self.c
    fn = self.fileName()
    s = c.fileCommands.putLeoOutline()
    if not g.isPython3:
        s = g.toEncodedString(s,encoding='utf-8',reportErrors=True)
        fn = g.toEncodedString(fn,encoding='utf-8', reportErrors=True)
    md.setText('%s,%s' % (fn,s))
#@+node:ekr.20101101085309.3765: ** Leo 4.8 a1
#@+node:ekr.20101007215714.3751: *3* Qcompleter test
w = c.frame.body

def callback(val):
    g.trace(val)

w.showCompleter(['hello','helloworld'],callback)
#@+node:ekr.20101018145309.3757: *3* Support for detect_urls
if 0:
    import PyQt4.QtGui as QtGui

    colorer = c.frame.body.colorizer.highlighter.colorer
    w = colorer.w
    w.tag_configure('URL',underline=1,foreground="orange")
    # w = c.frame.body.bodyCtrl
    s = w.getAllText()
    colorer.setTag('URL',s,len(s)-20,len(s))

    # highlighter = c.frame.body.colorizer.highlighter
    # QtGui.QSyntaxHighlighter.rehighlight(highlighter)

if 0:
    w = c.frame.top.richTextEdit
    g.es(w)
    w.setOpenExternalLinks(True)

    http://webpages.charter.net/edreamleo/front.html
#@+node:ekr.20101106154117.3780: ** Leo rc1
#@+node:ekr.20101106154117.3781: *3* Support for MacOS special keys
#@+node:ekr.20081121105001.514: *4* class leoKeyEvent
class leoKeyEvent:

    '''A wrapper for wrapper for qt events.

    This does *not* override leoGui.leoKeyevent because
    it is a separate class, not member of leoQtGui.'''

    def __init__ (self,event,c,w,ch,tkKey,stroke):

        trace = False and not g.unitTesting

        if trace: print(
            'leoKeyEvent.__init__: ch: %s, tkKey: %s, stroke: %s' % (
                repr(ch),repr(tkKey),repr(stroke)))

        # Last minute-munges to keysym.
        if tkKey in ('Return','Tab','Escape',): # 'Ins'
            ch = tkKey
        stroke = stroke.replace('\t','Tab')
        tkKey = tkKey.replace('\t','Tab')

        # Patch provided by resi147.
        # See the thread: special characters in MacOSX, like '@'.
        if sys.platform.startswith('darwin'):
            darwinmap = {
                'Alt-Key-5': '[',
                'Alt-Key-6': ']',
                'Alt-Key-7': '|',
                'Alt-slash': '\\',
                'Alt-Key-8': '{',
                'Alt-Key-9': '}',
                'Alt-e': '€',
                'Alt-l': '@',
            }
            if tkKey in darwinmap:
                tkKey = stroke = darwinmap[tkKey]

        # The main ivars.
        self.actualEvent = event
        self.c      = c
        self.char   = ch
        self.keysym = ch
        self.stroke = stroke
        self.w = self.widget = w # A leoQtX object

        # Auxiliary info.
        self.x      = hasattr(event,'x') and event.x or 0
        self.y      = hasattr(event,'y') and event.y or 0
        # Support for fastGotoNode plugin
        self.x_root = hasattr(event,'x_root') and event.x_root or 0
        self.y_root = hasattr(event,'y_root') and event.y_root or 0

        # g.trace('qt.leoKeyEvent: %s' % repr(self))

    def __repr__ (self):

        return 'qtGui.leoKeyEvent: stroke: %s' % (repr(self.stroke))

        # return 'qtGui.leoKeyEvent: stroke: %s, char: %s, keysym: %s' % (
            # repr(self.stroke),repr(self.char),repr(self.keysym))
#@-all
#@-leo
