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; } }