#@+leo-ver=5-thin
#@+node:ekr.20090430075506.3: * @file leoPluginNotes.txt
#@+all
#@+node:ekr.20090430075506.6: ** To do
#@+node:ekr.20090430102730.3: *3* Add expand_noweb_references for rst3 plugin
@nocolor

http://groups.google.com/group/leo-editor/browse_thread/thread/3cd5cb06d32264d

> What would work for me is if named sections in a @rst subtree
> would work exactly as they work for other derived files: they
> get inserted at the place where they are referenced.

- Add support for the following options:
    - expand_noweb_references: default False for compatibility.
    - ignore_noweb definitions: default False for compatibility.

- When expand_noweb_references is True, definitions (typically clones)
  must be descendants of the referencing node (in the @rst tree)
#@+node:ekr.20090430120622.2: *4* post
@nocolor-node

I want to write the documentation for the source program in a @rst3 subtree. In
this @rst3 subtree I want to refer to fragments of the program, like:

In the following code fragment::

  <<code fragment>>

Unfortunately, <<code fragment>> will not be expanded. Furthermore, in order to
get to this work, I should have <<code fragment>> under the @rst3 subtree as
well, but this is then also treated as @rst3 input (which in this case, is not
what I want).
#@+node:ekr.20050811101550.1: *4* writeBody & helpers
def writeBody (self,p):

    # remove trailing cruft and split into lines.
    lines = p.b.rstrip().split('\n') 

    if self.getOption('code_mode'):
        if not self.getOption('show_options_doc_parts'):
            lines = self.handleSpecialDocParts(lines,'@rst-options',
                retainContents=False)
        if not self.getOption('show_markup_doc_parts'):
            lines = self.handleSpecialDocParts(lines,'@rst-markup',
                retainContents=False)
        if not self.getOption('show_leo_directives'):
            lines = self.removeLeoDirectives(lines)
        lines = self.handleCodeMode(lines)
    elif self.getOption('doc_only_mode'):
        # New in version 1.15
        lines = self.handleDocOnlyMode(p,lines)
    else:
        lines = self.handleSpecialDocParts(lines,'@rst-options',
            retainContents=False)
        lines = self.handleSpecialDocParts(lines,'@rst-markup',
            retainContents=self.getOption('generate_rst'))
        if self.getOption('show_doc_parts_in_rst_mode') is True:
            pass  # original behaviour, treat as plain text
        elif self.getOption('show_doc_parts_in_rst_mode'):
            # use value as class for content
            lines = self.handleSpecialDocParts(lines,None,
                retainContents=True, asClass=self.getOption('show_doc_parts_in_rst_mode'))
        else:  # option evaluates to false, cut them out
            lines = self.handleSpecialDocParts(lines,None,
                retainContents=False)
        lines = self.removeLeoDirectives(lines)
        if self.getOption('generate_rst') and self.getOption('use_alternate_code_block'):
            lines = self.replaceCodeBlockDirectives(lines)

    s = '\n'.join(lines).strip()
    if s:
        self.write('%s\n\n' % s)
#@+node:ekr.20050811150541: *5* handleCodeMode & helper
def handleCodeMode (self,lines):

    '''Handle the preprocessed body text in code mode as follows:

    - Blank lines are copied after being cleaned.
    - @ @rst-markup lines get copied as is.
    - Everything else gets put into a code-block directive.'''

    result = [] ; n = 0 ; code = []
    while n < len(lines):
        s = lines [n] ; n += 1
        if (
            self.isSpecialDocPart(s,'@rst-markup') or
            (self.getOption('show_doc_parts_as_paragraphs') and self.isSpecialDocPart(s,None))
        ):
            if code:
                self.finishCodePart(result,code)
                code = []
            result.append('')
            n, lines2 = self.getDocPart(lines,n)
            result.extend(lines2)
        elif not s.strip() and not code:
            pass # Ignore blank lines before the first code block.
        elif not code: # Start the code block.
            result.append('')
            result.append(self.code_block_string)
            code.append(s)
        else: # Continue the code block.
            code.append(s)

    if code:
        self.finishCodePart(result,code)
        code = []
    return self.rstripList(result)
#@+node:ekr.20050811152104: *6* formatCodeModeLine
def formatCodeModeLine (self,s,n,numberOption):

    if not s.strip(): s = ''

    if numberOption:
        return '\t%d: %s' % (n,s)
    else:
        return '\t%s' % s
#@+node:ekr.20050813155021: *6* rstripList
def rstripList (self,theList):

    '''Removed trailing blank lines from theList.'''

    s = '\n'.join(theList).rstrip()
    return s.split('\n')
#@+node:ekr.20050813160208: *6* finishCodePart
def finishCodePart (self,result,code):

    numberOption = self.getOption('number_code_lines')
    code = self.rstripList(code)
    i = 0
    for line in code:
        i += 1
        result.append(self.formatCodeModeLine(line,i,numberOption))
#@+node:ekr.20060608094815: *5* handleDocOnlyMode
def handleDocOnlyMode (self,p,lines):

    '''Handle the preprocessed body text in doc_only mode as follows:

    - Blank lines are copied after being cleaned.
    - @ @rst-markup lines get copied as is.
    - All doc parts get copied.
    - All code parts are ignored.'''

    ignore              = self.getOption('ignore_this_headline')
    showHeadlines       = self.getOption('show_headlines')
    showThisHeadline    = self.getOption('show_this_headline')
    showOrganizers      = self.getOption('show_organizer_nodes')

    result = [] ; n = 0
    while n < len(lines):
        s = lines [n] ; n += 1
        if self.isSpecialDocPart(s,'@rst-options'):
            n, lines2 = self.getDocPart(lines,n) # ignore.
        elif self.isAnyDocPart(s):
            # Handle any other doc part, including @rst-markup.
            n, lines2 = self.getDocPart(lines,n)
            if lines2: result.extend(lines2)
    if not result: result = []
    if showHeadlines:
        if result or showThisHeadline or showOrganizers or p == self.topNode:
            # g.trace(len(result),p.h)
            self.writeHeadlineHelper(p)
    return result
#@+node:ekr.20060608094815.1: *5* isAnyDocPart
def isAnyDocPart (self,s):

    if s.startswith('@doc'):
        return True
    elif not s.startswith('@'):
        return False
    else:
        return len(s) == 1 or s[1].isspace()
#@+node:ekr.20050811153208: *5* isSpecialDocPart
def isSpecialDocPart (self,s,kind):

    '''Return True if s is a special doc part of the indicated kind.

    If kind is None, return True if s is any doc part.'''

    if s.startswith('@') and len(s) > 1 and s[1].isspace():
        if kind:
            i = g.skip_ws(s,1)
            result = g.match_word(s,i,kind)
        else:
            result = True
    elif not kind:
        result = g.match_word(s,0,'@doc') or g.match_word(s,0,'@')
    else:
        result = False

    return result
#@+node:ekr.20050811163802: *5* isAnySpecialDocPart
def isAnySpecialDocPart (self,s):

    for kind in (
        '@rst-markup',
        '@rst-option',
        '@rst-options',
    ):
        if self.isSpecialDocPart(s,kind):
            return True

    return False
#@+node:ekr.20050811105438: *5* removeLeoDirectives
def removeLeoDirectives (self,lines):

    '''Remove all Leo directives, except within literal blocks.'''

    n = 0 ; result = []
    while n < len(lines):
        s = lines [n] ; n += 1
        if s.strip().endswith('::'):
            n, lit = self.skip_literal_block(lines,n-1)
            result.extend(lit)
        elif s.startswith('@') and not self.isAnySpecialDocPart(s):
            for key in self.leoDirectivesList:
                if g.match_word(s,0,key):
                    # g.trace('removing %s' % s)
                    break
            else:
                result.append(s)
        else:
            result.append(s)

    return result
#@+node:ekr.20050811105438.1: *5* handleSpecialDocParts
def handleSpecialDocParts (self,lines,kind,retainContents,asClass=None):

    # g.trace(kind,g.listToString(lines))

    result = [] ; n = 0
    while n < len(lines):
        s = lines [n] ; n += 1
        if s.strip().endswith('::'):
            n, lit = self.skip_literal_block(lines,n-1)
            result.extend(lit)
        elif self.isSpecialDocPart(s,kind):
            n, lines2 = self.getDocPart(lines,n)
            if retainContents:
                result.extend([''])
                if asClass:
                    result.extend(['.. container:: '+asClass, ''])
                    if 'literal' in asClass.split():
                        result.extend(['  ::', ''])
                    for l2 in lines2: result.append('    '+l2)
                else:
                    result.extend(lines2)
                result.extend([''])
        else:
            result.append(s)

    return result
#@+node:ekr.20050805162550.30: *5* replaceCodeBlockDirectives
def replaceCodeBlockDirectives (self,lines):

    '''Replace code-block directive, but not in literal blocks.'''

    n = 0 ; result = []
    while n < len(lines):
        s = lines [n] ; n += 1
        if s.strip().endswith('::'):
            n, lit = self.skip_literal_block(lines,n-1)
            result.extend(lit)
        else:
            i = g.skip_ws(s,0)
            if g.match(s,i,'..'):
                i = g.skip_ws(s,i+2)
                if g.match_word(s,i,'code-block'):
                    if 1: # Create a literal block to hold the code.
                        result.append('::\n')
                    else: # This 'annotated' literal block is confusing.
                        result.append('%s code::\n' % s[i+len('code-block'):])
                else:
                    result.append(s)
            else:
                result.append(s)

    return result
#@+node:ekr.20110317080650.14388: ** Unused code
#@+node:leohag.20081203143921.18: *3* show (not used from scrolledmessage)
def show(self):
    
    g.trace(g.callers())
    
    self.ui.show()
#@+node:ekr.20110321072702.14506: *3* Unused viewrendered commands
#@+node:tbrown.20101127112443.14856: *4* g.command('viewrendered-html') (not used)
# @g.command('viewrendered-html')
# def viewrendered(event):
    # """Open view of html which would be rendered"""

    # c = event.get('c')
    # if c:
        # pc = controllers.get(c.hash())
        # if pc: pc.view('html')
#@+node:tbrown.20101127112443.14854: *4* g.command('viewrendered-big') (not used)
# @g.command('viewrendered-big')
# def viewrendered(event):
    # """Open render view for commander, with big text

    # (useful for presentations)

    # """

    # c = event.get('c')
    # if c:
        # pc = controllers.get(c.hash())
        # if pc:
            # w = pc.w
            # pc.view('big')
            # w.zoomIn(4)
#@+node:ekr.20110317080650.14383: *4* g.command('hide-rendering-pane') (not used)
# @g.command('hide-rendering-pane')
# def hide_rendering_pane(event):
    
    # '''Hide the rendering pane, but do not delete it.'''

    # c = event.get('c')
    # if c:
        # pc = controllers.get(c.hash())
        # if pc: pc.hide()
#@+node:ekr.20110526083709.18343: ** Supported auto-hide in viewrendered plugin
#@+node:ekr.20110317080650.14380: *3* ctor & helpers
def __init__ (self,c):
        
    self.active = False
    self.c = c
    self.badColors = []
    self.delete_callback = None
    self.gnx = None
    self.inited = False
    self.free_layout_pc = None # Set later by embed.
    self.gs = None # For @graphics-script: a QGraphicsScene 
    self.gv = None # For @graphics-script: a QGraphicsView
    self.kind = 'rst' # in self.dispatch_dict.keys()
    self.length = 0 # The length of previous p.b.
    self.locked = False
    self.s = '' # The plugin's docstring to be rendered temporarily.
    self.scrollbar_pos_dict = {} # Keys are vnodes, values are positions.
    self.sizes = [] # Saved splitter sizes.
    self.splitter = None # The free_layout splitter containing the rendering pane.
    self.splitter_index = None # The index of the rendering pane in the splitter.
    self.svg_class = QtSvg.QSvgWidget
    self.text_class = QtGui.QTextEdit
    self.graphics_class = QtGui.QGraphicsWidget
    self.vp = None # The present video player.
    self.w = None # The present widget in the rendering pane.
    
    # User-options:
    self.default_kind = c.config.getString('view-rendered-default-kind') or 'rst'
    self.auto_create  = c.config.getBool('view-rendered-auto-create',False)
    self.auto_hide    = c.config.getBool('view-rendered-auto-hide',False)
    self.background_color = c.config.getColor('rendering-pane-background-color') or 'white'
    self.scrolled_message_use_viewrendered = c.config.getBool('scrolledmessage_use_viewrendered',True)
    
    # Init.
    c.viewrendered = self # For free_layout
    # w.setReadOnly(True)
    self.create_dispatch_dict()
    self.load_free_layout()

    if self.auto_create:
        self.view(self.default_kind)
#@+node:ekr.20110320120020.14478: *4* create_dispatch_dict
def create_dispatch_dict (self):
    
    pc = self
    
    pc.dispatch_dict = {
        'big':          pc.update_rst,
        'html':         pc.update_html,
        'graphics-script':  pc.update_graphics_script,
        'image':        pc.update_image,
        'movie':        pc.update_movie,
        'networkx':     pc.update_networkx,
        'rst':          pc.update_rst,
        'svg':          pc.update_svg,
        'url':          pc.update_url,
    }
#@+node:ekr.20110319013946.14467: *4* load_free_layout
def load_free_layout (self):
    
    c = self.c
    
    fl = hasattr(c,'free_layout') and c.free_layout

    if not fl:
        # g.trace('auto-loading free_layout.py')
        m = g.loadOnePlugin('free_layout.py',verbose=False)
        m.onCreate(tag='viewrendered',keys={'c':c})
#@+node:ekr.20110320120020.14486: *3* embed_widget & helper
def embed_widget (self,w,delete_callback=None,opaque_resize=True):
    
    '''Embed widget w in the free_layout splitter.'''
    
    pc = self ; c = pc.c ; splitter = pc.splitter
    
    assert splitter,'no splitter'
    assert pc.must_change_widget(w)
    
    # Call the previous delete callback if it exists.
    if pc.delete_callback:
        pc.delete_callback()
    elif pc.w:
        pc.w.deleteLater()

    # Remember the new delete callback.
    pc.delete_callback = delete_callback
    
    # Special inits for text widgets...
    if w.__class__ == pc.text_class:
        text_name = 'body-text-renderer'
        # pc.w = w = widget_class()
        w.setObjectName(text_name)
        pc.setBackgroundColor(pc.background_color,text_name,w)
        w.setReadOnly(True)
        
        # Create the standard Leo bindings.
        wrapper_name = 'rendering-pane-wrapper'
        wrapper = qtGui.leoQTextEditWidget(w,wrapper_name,c)
        c.k.completeAllBindingsForWidget(wrapper)
        w.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
       
    # Insert w into the splitter, retaining the splitter ratio.
    sizes = splitter.sizes()
    splitter.replace_widget_at_index(pc.splitter_index,w)
    splitter.setOpaqueResize(opaque_resize)
    splitter.setSizes(sizes)
    
    # Set pc.w
    pc.w = w
#@+node:ekr.20110321072702.14510: *4* setBackgroundColor
def setBackgroundColor (self,colorName,name,w):
    
    pc = self
    
    if not colorName: return

    styleSheet = 'QTextEdit#%s { background-color: %s; }' % (name,colorName)
        
    # g.trace(name,colorName)

    if QtGui.QColor(colorName).isValid():
        w.setStyleSheet(styleSheet)

    elif colorName not in pc.badColors:
        pc.badColors.append(colorName)
        g.es_print('invalid body background color: %s' % (colorName),color='blue')
#@+node:ekr.20110322031455.5764: *3* ensure_text_widget
def ensure_text_widget (self):
    
    '''Swap a text widget into the rendering pane if necessary.'''
    
    pc = self
    
    if pc.must_change_widget(pc.text_class):
        w = pc.text_class()
        pc.embed_widget(w)
        assert (w == pc.w)
        return pc.w
    else:
        return pc.w
#@+node:ekr.20110320120020.14476: *3* must_update
def must_update (self,keywords):
    
    '''Return True if we must update the rendering pane.'''
    
    trace = False and not g.unitTesting
    verbose = False
    pc = self ; c = pc.c ; p = c.p
    
    if c != keywords.get('c') or not pc.active or pc.locked or g.unitTesting:
        if trace: g.trace('not active')
        return None,False
    
    if pc.s:
        s = pc.s
        if trace: g.trace('self.s exists',len(s))
        return s,True
    else:
        s = p.b
        val = pc.gnx != p.v.gnx
        if val:
            if trace: g.trace('changed node')
            return s,val
        val = len(s) != pc.length
        if val:
            if trace: g.trace('text changed')
        return s,val

        # try:
            # # Can fail if the window has been deleted.
            # w.setWindowTitle(p.h)
        # except exception:
            # pc.splitter = None
            # return
#@+node:ekr.20110317080650.14384: *3* show & hide & helper
def hide (self):
    
    trace = False and not g.unitTesting
    pc = self
    if pc.auto_hide:
        sizes = pc.splitter.sizes()
        new_sizes = self.validSizes(sizes,'hide')
        if new_sizes: pc.sizes = new_sizes
    else:
        pc.deactivate()
    if pc.w:
        pc.w.hide()
    else:
        g.trace('** no pc.w')
    
def show (self):
    
    trace = False and not g.unitTesting
    pc = self

    # First, show the pane so sizes will be valid.
    pc.w.show()
        
    if pc.auto_hide:
        new_sizes = self.validSizes(pc.sizes,'show-saved')
        if new_sizes:
            pc.splitter.setSizes(new_sizes)
            return
            
    sizes = pc.splitter.sizes()
    new_sizes = self.validSizes(sizes,'show')
    if new_sizes:
        pc.splitter.setSizes(new_sizes)
        return
        
    total = sum(sizes)
    if total:
        if trace: g.trace('Setting sizes',total/2)
        pc.splitter.setSizes([total/2,total/2])
    else:
        if trace: g.trace('** empty sizes!')
#@+node:ekr.20110526131737.18370: *4* validSizes
def validSizes (self,sizes,tag):
    
    trace = False and not g.unitTesting

    if len(sizes) == 2:
        if sizes[0] and sizes[1]:
            result = sizes
            kind = 'valid'
        elif sizes[0] or sizes[1]:
            total = sizes[0] + sizes[1]
            if tag == 'hide':
                # Important: don't change the saved size here!
                result = []
                kind = 'invalid'
            else:
                kind = '** average'
                result = [total/2,total/2]
        else:
            result = []
            kind = 'empty'
    elif len(sizes) == 3 and sizes[0] and sizes[1] and not sizes[2]:
        result = [sizes[0],sizes[1]]
        kind = 'truncated'
    else:
        result = []
        kind = 'invalid'
        
    if trace: g.trace(repr(tag),repr(kind),sizes)
    return result
#@+node:ekr.20101112195628.5426: *3* update & helpers
def update(self,tag,keywords):
    
    trace = False and not g.unitTesting
    pc = self ; c = pc.c ; p = c.p ; w = pc.w
    force = keywords.get('force')
    s, val = pc.must_update(keywords)
    if not force and not val:
        # Save the scroll position.
        if w.__class__ == pc.text_class:
            sb = w.verticalScrollBar()
            if sb:
                pc.scrollbar_pos_dict[p.v] = sb.sliderPosition()
        # g.trace('no update')
        return
        
    # Suppress updates until we change nodes.
    pc.node_changed = pc.gnx != p.v.gnx
    pc.gnx = p.v.gnx
    pc.length = len(p.b) # Use p.b, not s.

    if pc.s:
        if trace: g.trace('docstring',len(pc.s))
        # A plugin docstring.
        s = pc.s
        pc.s = None
        keywords['force']=True
        pc.update_rst(s,keywords)
    else:
        # Remove Leo directives.
        s = pc.remove_directives(s)
        # Dispatch based on the computed kind.
        kind = pc.get_kind(p)
        f = pc.dispatch_dict.get(kind)
        if f:
            if trace: g.trace(f.__name__)
        else:
            g.trace('no handler for kind: %s' % kind)
            f = pc.update_rst
        f(s,keywords)
#@+node:ekr.20110320120020.14486: *4* embed_widget & helper
def embed_widget (self,w,delete_callback=None,opaque_resize=True):
    
    '''Embed widget w in the free_layout splitter.'''
    
    pc = self ; c = pc.c ; splitter = pc.splitter
    
    assert splitter,'no splitter'
    assert pc.must_change_widget(w)
    
    # Call the previous delete callback if it exists.
    if pc.delete_callback:
        pc.delete_callback()
    elif pc.w:
        pc.w.deleteLater()

    # Remember the new delete callback.
    pc.delete_callback = delete_callback
    
    # Special inits for text widgets...
    if w.__class__ == pc.text_class:
        text_name = 'body-text-renderer'
        # pc.w = w = widget_class()
        w.setObjectName(text_name)
        pc.setBackgroundColor(pc.background_color,text_name,w)
        w.setReadOnly(True)
        
        # Create the standard Leo bindings.
        wrapper_name = 'rendering-pane-wrapper'
        wrapper = qtGui.leoQTextEditWidget(w,wrapper_name,c)
        c.k.completeAllBindingsForWidget(wrapper)
        w.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere)
       
    # Insert w into the splitter, retaining the splitter ratio.
    sizes = splitter.sizes()
    splitter.replace_widget_at_index(pc.splitter_index,w)
    splitter.setOpaqueResize(opaque_resize)
    splitter.setSizes(sizes)
    
    # Set pc.w
    pc.w = w
#@+node:ekr.20110321072702.14510: *5* setBackgroundColor
def setBackgroundColor (self,colorName,name,w):
    
    pc = self
    
    if not colorName: return

    styleSheet = 'QTextEdit#%s { background-color: %s; }' % (name,colorName)
        
    # g.trace(name,colorName)

    if QtGui.QColor(colorName).isValid():
        w.setStyleSheet(styleSheet)

    elif colorName not in pc.badColors:
        pc.badColors.append(colorName)
        g.es_print('invalid body background color: %s' % (colorName),color='blue')
#@+node:ekr.20110320120020.14476: *4* must_update
def must_update (self,keywords):
    
    '''Return True if we must update the rendering pane.'''
    
    trace = False and not g.unitTesting
    verbose = False
    pc = self ; c = pc.c ; p = c.p
    
    if c != keywords.get('c') or not pc.active or pc.locked or g.unitTesting:
        if trace: g.trace('not active')
        return None,False
    
    if pc.s:
        s = pc.s
        if trace: g.trace('self.s exists',len(s))
        return s,True
    else:
        s = p.b
        val = pc.gnx != p.v.gnx
        if val:
            if trace: g.trace('changed node')
            return s,val
        val = len(s) != pc.length
        if val:
            if trace: g.trace('text changed')
        return s,val

        # try:
            # # Can fail if the window has been deleted.
            # w.setWindowTitle(p.h)
        # except exception:
            # pc.splitter = None
            # return
#@+node:ekr.20110321151523.14463: *4* update_graphics_script
def update_graphics_script (self,s,keywords):
    
    pc = self ; c = pc.c
    
    force = keywords.get('force')
    
    if pc.gs and not force: return

    if not pc.gs:
        # Create the widgets.
        pc.gs = QtGui.QGraphicsScene(pc.splitter)
        pc.gv = QtGui.QGraphicsView(pc.gs)
        w = pc.gv.viewport() # A QWidget
        
        # Embed the widgets.
        def delete_callback():
            for w in (pc.gs,pc.gv):
                w.deleteLater()
            pc.gs = pc.gv = None

        pc.embed_widget(w,delete_callback=delete_callback)

    c.executeScript(
        script=s,
        namespace={'gs':pc.gs,'gv':pc.gv})
#@+node:ekr.20110321005148.14534: *4* update_html
def update_html (self,s,keywords):
    
    pc = self
    
    pc.show()
    w = pc.ensure_text_widget()
    w.setReadOnly(False)
    w.setHtml(s)
    w.setReadOnly(True)
#@+node:ekr.20110320120020.14482: *4* update_image
def update_image (self,s,keywords):
    
    pc = self
    
    w = pc.ensure_text_widget()
    ok,path = pc.get_fn(s,'@image')
    if not ok:
        w.setPlainText('@image: file not found:\n%s' % (path))
        return
        
    template = '''
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head></head>
<body bgcolor="#fffbdc">
<img src="%s">
</body>
</html>
''' % (path)

    pc.show()
    w.setReadOnly(False)
    w.setHtml(template)
    w.setReadOnly(True)
    
#@+node:ekr.20110320120020.14481: *4* update_movie
def update_movie (self,s,keywords):
    
    pc = self
    
    ok,path = pc.get_fn(s,'@movie')
    if not ok:
        w = pc.ensure_text_widget()
        w.setPlainText('Movie\n\nfile not found: %s' % (path))
        return
    
    if not phonon:
        w = pc.ensure_text_widget()
        w.setPlainText('Movie\n\nno movie player: %s' % (path))
        return
        
    if not pc.vp:
        # Create the widgets.
        pc.vp = vp = phonon.VideoPlayer(phonon.VideoCategory)
        vw = vp.videoWidget()
        vw.setObjectName('video-renderer')
        
        # Embed the widgets
        def delete_callback():
            if pc.vp:
                pc.vp.stop()
                pc.vp.deleteLater()
                pc.vp = None

        pc.embed_widget(vp,delete_callback=delete_callback)

    pc.show()
    vp = pc.vp
    vp.load(phonon.MediaSource(path))
    vp.play()
#@+node:ekr.20110320120020.14484: *4* update_networkx
def update_networkx (self,s,keywords):
    
    pc = self
    w = pc.ensure_text_widget()
    w.setPlainText('') # 'Networkx: len: %s' % (len(s)))
    pc.show()
#@+node:ekr.20110320120020.14477: *4* update_rst
def update_rst (self,s,keywords):
    
    trace = False and not g.unitTesting
    pc = self ; c = pc.c ;  p = c.p
    s = s.strip().strip('"""').strip("'''").strip()
    isHtml = s.startswith('<') and not s.startswith('<<')
    
    if trace: g.trace('isHtml',isHtml)
    
    # Do this regardless of whether we show the widget or not.
    w = pc.ensure_text_widget()
    assert pc.w
    
    if s:
        pc.show()
    else:
        if pc.auto_hide:
            pc.hide()
        return
    
    if got_docutils and not isHtml:
        # Not html: convert to html.
        path = g.scanAllAtPathDirectives(c,p) or c.getNodePath(p)
        if not os.path.isdir(path):
            path = os.path.dirname(path)
        if os.path.isdir(path):
            os.chdir(path)

        try:
            msg = '' # The error message from docutils.
            if pc.title:
                s = pc.underline(pc.title) + s
                pc.title = None
            s = publish_string(s,writer_name='html')
            s = g.toUnicode(s) # 2011/03/15
            show = True
        except SystemMessage as sm:
            # g.trace(sm,sm.args)
            msg = sm.args[0]
            if 'SEVERE' in msg or 'FATAL' in msg:
                s = 'RST error:\n%s\n\n%s' % (msg,s)

    sb = w.verticalScrollBar()

    if sb:
        d = pc.scrollbar_pos_dict
        if pc.node_changed:
            # Set the scrollbar.
            pos = d.get(p.v,sb.sliderPosition())
            sb.setSliderPosition(pos)
        else:
            # Save the scrollbars
            d[p.v] = pos = sb.sliderPosition()

    if pc.kind in ('big','rst','html'):
        w.setHtml(s)
        if pc.kind == 'big':
            w.zoomIn(4) # Doesn't work.
    else:
        w.setPlainText(s)
        
    if sb and pos:
        # Restore the scrollbars
        sb.setSliderPosition(pos)
#@+node:ekr.20110320120020.14479: *4* update_svg
# http://doc.trolltech.com/4.4/qtsvg.html 
# http://doc.trolltech.com/4.4/painting-svgviewer.html

def update_svg (self,s,keywords):

    pc = self
    
    if pc.must_change_widget(pc.svg_class):
        w = pc.svg_class()
        pc.embed_widget(w)
        assert (w == pc.w)
    else:
        w = pc.w
    
    if s.strip().startswith('<'):
        # Assume it is the svg (xml) source.
        s = g.adjustTripleString(s,pc.c.tab_width).strip() # Sensitive to leading blank lines.
        s = g.toEncodedString(s)
        pc.show()
        w.load(s)
        w.show()
    else:
        # Get a filename from the headline or body text.
        ok,path = pc.get_fn(s,'@svg')
        if ok:
            pc.show()
            w.load(path)
            w.show()
#@+node:ekr.20110321005148.14537: *4* update_url
def update_url (self,s,keywords):
    
    pc = self
    
    w = pc.ensure_text_widget()
    pc.show()
    
    if 1:
        w.setPlainText('')
    else:
        url = pc.get_url(s,'@url')
        
        if url:
            w.setPlainText('@url %s' % url)
        else:
            w.setPlainText('@url: no url given')
        
    # w.setReadOnly(False)
    # w.setHtml(s)
    # w.setReadOnly(True)
#@+node:ekr.20110322031455.5765: *4* utils for update helpers...
#@+node:ekr.20110322031455.5764: *5* ensure_text_widget
def ensure_text_widget (self):
    
    '''Swap a text widget into the rendering pane if necessary.'''
    
    pc = self
    
    if pc.must_change_widget(pc.text_class):
        w = pc.text_class()
        pc.embed_widget(w)
        assert (w == pc.w)
        return pc.w
    else:
        return pc.w
#@+node:ekr.20110320233639.5776: *5* get_fn
def get_fn (self,s,tag):
    
    pc = self
    p = pc.c.p
    fn = s or p.h[len(tag):]
    fn = fn.strip()
    path = g.os_path_finalize_join(g.app.loadDir,fn)
    ok = g.os_path_exists(path)
    return ok,path
#@+node:ekr.20110320120020.14483: *5* get_kind
def get_kind(self,p):
    
    '''Return the proper rendering kind for node p.'''
    
    pc = self ; h = p.h

    if h.startswith('@'):
        i = g.skip_id(h,1,chars='-')
        word = h[1:i].lower().strip()
        if word in pc.dispatch_dict:
            return word
            
    # To do: look at ancestors, or uA's.

    return pc.kind # The default.
#@+node:ekr.20110321005148.14536: *5* get_url
def get_url (self,s,tag):
    
    p = self.c.p
    url = s or p.h[len(tag):]
    url = url.strip()
    return url
#@+node:ekr.20110322031455.5763: *5* must_change_widget
def must_change_widget (self,widget_class):
    
    pc = self
    return not pc.w or pc.w.__class__ != widget_class
#@+node:ekr.20110320120020.14485: *5* remove_directives
def remove_directives (self,s):
    
    lines = g.splitLines(s)
    result = []
    for s in lines:
        if s.startswith('@'):
            i = g.skip_id(s,1)
            word = s[1:i]
            if word in g.globalDirectiveList:
                continue
        result.append(s)
    
    return ''.join(result)
#@+node:ekr.20110317024548.14379: *3* view & helper
def view(self,kind,s=None,title=None):
    
    pc = self
    pc.kind = kind
    
    # g.trace(kind,s and len(s))
    
    pc.embed_renderer()
    pc.s = s
    pc.title = title
    
    pc.activate()
    pc.update(tag='view',keywords={'c':pc.c})
        # May set pc.w.
    pc.show()

    # if big:
        # pc.w.zoomIn(4)
#@+node:ekr.20110318080425.14394: *4* embed_renderer
def embed_renderer (self):
    
    '''Use the free_layout plugin to embed self.w in a splitter.'''
    
    pc = self ; c = pc.c
    
    # g.trace(pc.splitter,pc.w)
    
    if pc.splitter:
        return

    fl_pc = hasattr(c,'free_layout') and c.free_layout
    if fl_pc:
        pc.free_layout_pc = fl_pc
        fl_pc.create_renderer(pc.w)
            # Calls set_renderer, which sets pc.splitter.
#@-all
#@-leo
