Customize the Outline View in Text Mode?
Question
How do I customize the Outline view in Text mode?
Answer
Suppose that you have the following XML document:
<doc startnumber="15">
    <sec counter="no">
        <info/>
        <title>Introduction</title>   
      </sec>
    <sec>
        <title>Section title</title>       
      <para>Content</para>
        <sec>
            <title>Section title</title> 
                <para>Content</para>
        </sec>
    </sec>
    <sec> 
            <title>Section title</title>
        <para>Content</para>   
      </sec>
</doc>and
               you want to display the XML content in a simplified Outline view like
               this:doc "15" sec Introduction sec 15 Section title sec 15.1 Section title sec 16 Section title
Usually an Outline should have the following characteristics:
- Double clicking in the Outline the corresponding XML content would get selected.
- When the cursor moves in the opened XML document the Outline would select the proper entry.
- When modifications occur in the document, the Outline would refresh.
A simple implementation using a Workspace Access plugin type could be something like this:
/**
 * Simple Outline for Text mode based on executing XPaths over the text content.
 */
 public class CustomWorkspaceAccessPluginExtension implements 
WorkspaceAccessPluginExtension {
  /**
   * The custom outline list.
   */
  private JList customOutlineList;
  
  /**
   * Maps outline nodes to ranges in document
   */
  private WSXMLTextNodeRange[] currentOutlineRanges; 
  
  /**
   * The current text page
   */
  private WSXMLTextEditorPage currentTextPage;
  
  /**
   * Disable CaretListener when we select from the CaretListener.
   */
  private boolean enableCaretListener = true;
  
  /**
   * @see WorkspaceAccessPluginExtension#applicationStarted
(ro.sync.exml.workspace.api.standalone.StandalonePluginWorkspace)
   */
  @Override
  public void applicationStarted
(final StandalonePluginWorkspace pluginWorkspaceAccess) {
    pluginWorkspaceAccess.addViewComponentCustomizer
(new ViewComponentCustomizer() {
      /**
       * @see ViewComponentCustomizer#customizeView
(ro.sync.exml.workspace.api.standalone.ViewInfo)
       */
      @Override
      public void customizeView(ViewInfo viewInfo) {
        if(
            //The view ID defined in the "plugin.xml"
            "SampleWorkspaceAccessID".equals(viewInfo.getViewID())) {
          customOutlineList = new JList();
          //Render the content in the Outline.
          customOutlineList.setCellRenderer(new DefaultListCellRenderer() {
       /**
        * @see javax.swing.DefaultListCellRenderer#getListCellRendererComponent
(javax.swing.JList, java.lang.Object, int, boolean, boolean)
        */
      @Override
      public Component getListCellRendererComponent
(JList<?> list, Object value, int index,
          boolean isSelected, boolean cellHasFocus) {
        JLabel label = (JLabel) super.getListCellRendererComponent
(list, value, index, isSelected, cellHasFocus);
        String val = null;
        if(value instanceof Element) {
          Element element = ((Element)value);
          val = element.getNodeName();
          if(!"".equals(element.getAttribute("startnumber"))) {
            val += " " + "'" + element.getAttribute("startnumber") + "'";
          }
          NodeList titles = element.getElementsByTagName("title");
          if(titles.getLength() > 0) {
            val += " \"" + titles.item(0).getTextContent() + "\"";
         }
       }
        label.setText(val);
        return label;
      }
    });
    //When we click a node, select it in the text page.
    customOutlineList.addMouseListener(new MouseAdapter() {
     @Override
     public void mouseClicked(MouseEvent e) {
       if(SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
          int sel = customOutlineList.getSelectedIndex();
          enableCaretListener = false;
         try {
          currentTextPage.select(currentTextPage.getOffsetOfLineStart
(currentOutlineRanges[sel].getStartLine()) + 
currentOutlineRanges[sel].getStartColumn() - 1, 
            currentTextPage.getOffsetOfLineStart
(currentOutlineRanges[sel].getEndLine()) + 
currentOutlineRanges[sel].getEndColumn());
          } catch (BadLocationException e1) {
           e1.printStackTrace();
         }
          enableCaretListener = true;
        }
      }
    });
     viewInfo.setComponent(new JScrollPane(customOutlineList));
     viewInfo.setTitle("Custom Outline");
  } 
 }
}); 
    
pluginWorkspaceAccess.addEditorChangeListener(new WSEditorChangeListener() {
  /**
  * @see WSEditorChangeListener#editorOpened(java.net.URL)
  */
  @Override
  public void editorOpened(URL editorLocation) {
   //An editor was opened
   WSEditor editorAccess = pluginWorkspaceAccess.getEditorAccess
(editorLocation, StandalonePluginWorkspace.MAIN_EDITING_AREA);
        if(editorAccess != null) {
          WSEditorPage currentPage = editorAccess.getCurrentPage();
          if(currentPage instanceof WSXMLTextEditorPage) {
            //User editing in Text mode an opened XML document.
            final WSXMLTextEditorPage xmlTP = (WSXMLTextEditorPage) currentPage;
            //Reconfigure outline on each change.
            xmlTP.getDocument().addDocumentListener(new DocumentListener() {
              @Override
              public void removeUpdate(DocumentEvent e) {
                reconfigureOutline(xmlTP);
              }
              @Override
              public void insertUpdate(DocumentEvent e) {
                reconfigureOutline(xmlTP);
              }
              @Override
              public void changedUpdate(DocumentEvent e) {
                reconfigureOutline(xmlTP);
              }
            });
            JTextArea textComponent = (JTextArea) xmlTP.getTextComponent();
            textComponent.addCaretListener(new CaretListener() {
              @Override
              public void caretUpdate(CaretEvent e) {
              if(currentOutlineRanges != null && currentTextPage != null && 
enableCaretListener) {
                  enableCaretListener = false;
                  //Find the node to select in the outline.
                  try {
                    int line = xmlTP.getLineOfOffset(e.getDot());
                    for (int i = currentOutlineRanges.length - 1; i >= 0; i--) {
              if(line > currentOutlineRanges[i].getStartLine() && 
line < currentOutlineRanges[i].getEndLine()) {
                        customOutlineList.setSelectedIndex(i);
                        break;
                      }
                    }
                  } catch (BadLocationException e1) {
                    e1.printStackTrace();
                  }
                  enableCaretListener = true;
                }
              }
            });
          }
        }
      }
      /**
       * @see WSEditorChangeListener#editorActivated(java.net.URL)
       */
      @Override
      public void editorActivated(URL editorLocation) {
        //An editor was selected, reconfigure the common outline
        WSEditor editorAccess = pluginWorkspaceAccess.getEditorAccess
(editorLocation, StandalonePluginWorkspace.MAIN_EDITING_AREA);
        if(editorAccess != null) {
          WSEditorPage currentPage = editorAccess.getCurrentPage();
          if(currentPage instanceof WSXMLTextEditorPage) {
            //User editing in Text mode an opened XML document.
            WSXMLTextEditorPage xmlTP = (WSXMLTextEditorPage) currentPage;
            reconfigureOutline(xmlTP);
          }
        }
      }
    }, StandalonePluginWorkspace.MAIN_EDITING_AREA);
  }
  
  /**
   * Reconfigure the outline
   * 
   * @param xmlTP The XML Text page.
   */
  protected void reconfigureOutline(final WSXMLTextEditorPage xmlTP) {
    try {
      //These are DOM nodes.
      Object[] evaluateXPath = xmlTP.evaluateXPath("//doc | //sec");
      //These are the ranges each node takes in the document.
      currentOutlineRanges = xmlTP.findElementsByXPath("//doc | //sec");
      currentTextPage = xmlTP;
      DefaultListModel listModel = new DefaultListModel();
      if(evaluateXPath != null) {
        for (int i = 0; i < evaluateXPath.length; i++) { 
          listModel.addElement(evaluateXPath[i]);
        }
      }
      customOutlineList.setModel(listModel);
    } catch(XPathException ex) {
      ex.printStackTrace();
    }
  }
  /**
   * @see WorkspaceAccessPluginExtension#applicationClosing()
   */
  @Override
  public boolean applicationClosing() {
    return true;
  }
}
            
         