Plugin - Get Position of selected Element

Having trouble installing Oxygen? Got a bug to report? Post it all here.
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Plugin - Get Position of selected Element

Post by patrick »

Hi,

is it possible to get the current absolute xpath or a position of a selection through a custom plugin? Eg. i have the following structure:

<doc>
<sec/>
<sec/>
<sec/>
</doc>

and I want to click e.g. on one of the sec's and get a message like "You are here: /doc/sec[2]/p/xy" or "You are in the 2. Section"

I hope you understand my question :)

Thanks,
Patrick
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

It would be also great if I can access these information in the oultine view through the NodeRendererCustomizer to number the sections there.
Radu
Posts: 9048
Joined: Fri Jul 09, 2004 5:18 pm

Re: Plugin - Get Position of selected Element

Post by Radu »

Hi Patrick,
is it possible to get the current absolute xpath or a position of a selection through a custom plugin?
For the Text or for the Author mode?
What kind of plugin did you implement?

In the Text mode the API of the ro.sync.exml.workspace.api.editor.page.text.xml.WSXMLTextEditorPage contains a method called:evaluateXPath(String).

The XPath is evaluated in the context of the XML element on which the caret is located. So if you evaluate and XPath like . you will get the DOM node equivalent of the current XML element. From this DOM node you can compose the XPath string you are after by navigating it.

Equivalent, in the Author mode you have WSAuthorEditorPage.getDocumentController().evaluateXPath(...)
It would be also great if I can access these information in the oultine view through the NodeRendererCustomizer to number the sections there.
The NodeRendererCustomizer does not have access to this API but it has access to the current AuthorNode which can be used to navigate the AuthorNode structure and thus look for siblings of the current node and so on.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Its the Text mode. In the author mode it's already implemented in the stylesheet but in the text mode such a function would be helpful in our large documents.

I will try it with these methods.
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Can you give me an short example how to include this into a plugin and/or extend the Outline view?
Radu
Posts: 9048
Joined: Fri Jul 09, 2004 5:18 pm

Re: Plugin - Get Position of selected Element

Post by Radu »

Hi Patrick,

I do not quite understand what your final goal is: a contextual menu actions in the Text page or to customize the name (or tooltip) of a node in the Outline view. Or both?
If you right click in the Text page there is already a "Copy XPath" operation which sets in the clipboard the XPath of the current node.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Ok, the perfect solution would be sth. like this (equal to the navigation tree in Word):
  • Highlight current position (given in outline view)
  • Linked with document on click (given in outline view)
  • Set filter and hide other elements (given in outline view, example sec)
  • Number the structure elements (e.g. 1st level n, 2nd n.n, 3rd n.n.n) which are not excluded (e.g. xapth sec[not(@counter='no')])
  • Set start number (eg. xpath /doc/@startnumber)
  • Specify an element which content is displayed as title of the structured element (e.g. xpath title[1])
  • Possibility to predefine some settings through a framework (will write an separate post)
Example:

Code: Select all

<doc startnumber="15">
<sec counter="no">
<!-- Some comment -->
<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>
The result should be:

doc "15"
sec Introduction
sec 15 Section title
sec 15.1 Section title
sec 16 Section title

2nd the "Copy Xpath": Is it possible to configure the "Xpath update on caret move" to display the exact Xpath as Copy Xpath does?
Radu
Posts: 9048
Joined: Fri Jul 09, 2004 5:18 pm

Re: Plugin - Get Position of selected Element

Post by Radu »

Hi Patrick,
Ok, the perfect solution would be sth. like this (equal to the navigation tree in Word):....
This surpasses our current API possibilities. What you could do would probably be to implement your own custom view and set it up from a Workspace Access plugin. Then you would have full control over what needs to be presented in the Outline tree.
2nd the "Copy Xpath": Is it possible to configure the "Xpath update on caret move" to display the exact Xpath as Copy Xpath does?
No, it is not. The idea of the XPath toolbar auto-updating when the user moved the caret has that you could copy the XPath from there and paste it in an XSLT stylesheet so it was intended to be more generic without pointing to a specific node index. But we will discuss this internally and if we decide to do something I'll update this thread.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Are any of these points possible within the current api, even if its not the perfect solution?
Radu
Posts: 9048
Joined: Fri Jul 09, 2004 5:18 pm

Re: Plugin - Get Position of selected Element

Post by Radu »

Hi Patrick,

Using our Plugins SDK:

http://www.oxygenxml.com/oxygen_sdk.htm ... er_Plugins

you can write a Workspace Access plugin which contributes with a custom view showing a simplified outline structure of the XML opened in the Text mode.
I tried writing such an example using the API to perform XPaths over the XML document and present its simplified structure in a list:

Code: Select all

/**
* Simple Outline for the 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 caret listener when we select from the caret listener.
*/
private boolean enableCaretListener = true;

/**
* @see ro.sync.exml.plugin.workspace.WorkspaceAccessPluginExtension#applicationStarted(ro.sync.exml.workspace.api.standalone.StandalonePluginWorkspace)
*/
@Override
public void applicationStarted(final StandalonePluginWorkspace pluginWorkspaceAccess) {
pluginWorkspaceAccess.addViewComponentCustomizer(new ViewComponentCustomizer() {
/**
* @see ro.sync.exml.workspace.api.standalone.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 ro.sync.exml.workspace.api.listeners.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 ro.sync.exml.workspace.api.listeners.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 ro.sync.exml.plugin.workspace.WorkspaceAccessPluginExtension#applicationClosing()
*/
@Override
public boolean applicationClosing() {
return true;
}
}
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Hi Radu,

it's a long time ago, but now I have written a plugin based up on your example. To count the section numbers, I call a function with the root Element and process it.

But what is the fastest way to get the root Element Node?

Currently I use Object[] evaluateXPath = xmlTP.evaluateXPath("/*") where xmlTP is a WSXMLTextEditorPage and use evaluateXPath[0] as root Element. It works but it takes ~700ms for the XPath Evaluation, which is 3/4 of the total running time of the plugin.

Thanks,
Patrick
Radu
Posts: 9048
Joined: Fri Jul 09, 2004 5:18 pm

Re: Plugin - Get Position of selected Element

Post by Radu »

Hi Patrick,

There's nothing wrong with your XPath, afak it is the fastest way to get the root element but the XPath is run over the entire XML content and as the XML content might be quite large, the XPath will take some time.
For out Outline implementations we use a technique called event coalescing.
For example if you are editing in the Text mode and you insert fast an new element, the quickly insert content in it, the Outline does not change during these quick modifications, it waits for the events to die out before it reacts and reconfigures. This is usually done using a timer and scheduling a task on it after the previous task has been canceled.

Code: Select all

TimerTask currentTask = null;
.......................
final JTextArea textArea = (JTextArea) xmlTextPage.getTextComponent();
final java.util.Timer timer = new Timer();
textArea.addCaretListener(new CaretListener() {
@Override
public void caretUpdate(CaretEvent e) {
//caret moved
if(currentTask != null) {
//Cancel pending tasks, we will update the outline 500 ms after the last caret change has occured.
currentTask.cancel();
}
currentTask = new TimerTask() {
@Override
public void run() {
//Here you find nodes by XPath
//This is done on thread so it will not block the UI
// xmlTextPage.findElementsByXPath("/*");
//Then reconfigure the outline on the AWT thread.
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
//Maybe based on the caret location you select the proper node
// textArea.getCaretPosition();
}
});
} catch (Exception e) {
e.printStackTrace();
e.printStackTrace();
}
}
};
timer.schedule(currentTask, 500);
}
});
So as a conclusion you should avoid running many XPath queries one after the other and run the queries on another thread so that the GUI is not blocked, then update the Outline on the AWT thread when the query is done.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Great! Thank you for the hint, it works now :-)
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Hi Radu,

another question: I select The Text from the Selected Outline item with WSXMLTextEditorPage.select() but how to get the editor view to scroll to the beginning of the selection like the original outline view does? Currently I think it scrolls to the middle of the selection.
Radu
Posts: 9048
Joined: Fri Jul 09, 2004 5:18 pm

Re: Plugin - Get Position of selected Element

Post by Radu »

Hi Patrick,

You could try to call the API to make the selection from the end offset to the start one like WSTextBasedEditorPage.select(endSelOffset, startSelOffset), this should move the scroll to the beginning of the element as you want, that's how we do it in our Outline.
In the worst case you can get access to the Swing JTextArea WSTextEditorPage.getTextComponent() and scroll to whatever offset you want.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Hi Radu,

can you help me how to do that? I have tried it this way but then nothing is selected anymore. It seems that select needs the minor value first:

Code: Select all

currentTextPage.select(
currentTextPage.getOffsetOfLineStart(currentOutlineRanges[endsel].getEndLine()) + currentOutlineRanges[endsel].getEndColumn(),
currentTextPage.getOffsetOfLineStart(currentOutlineRanges[startsel].getStartLine()) + currentOutlineRanges[startsel].getStartColumn() + 1
);
Radu
Posts: 9048
Joined: Fri Jul 09, 2004 5:18 pm

Re: Plugin - Get Position of selected Element

Post by Radu »

Hi Patrick,

I tested with an Oxygen 15.2 standalone installation with the same code sample for the XPath based outliner I previously posted on the thread and the selection worked for me.
This might not work if you are using Oxygen 15.0 or older (there was an issue for the same selection API fixed in 15.1).

An alternative would be something like this:

Code: Select all

                  final int startOffset = currentTextPage.getOffsetOfLineStart(currentOutlineRanges[sel].getStartLine()) + currentOutlineRanges[sel].getStartColumn() - 1;
currentTextPage.select(startOffset,
currentTextPage.getOffsetOfLineStart(currentOutlineRanges[sel].getEndLine()) + currentOutlineRanges[sel].getEndColumn());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JTextArea textComponent = (JTextArea) currentTextPage.getTextComponent();
try {
Rectangle startOffsetRect = textComponent.modelToView(startOffset);
textComponent.scrollRectToVisible(startOffsetRect);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
});
Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Thanks, with Oxygen 15.2 it works fine! I will try your workaround to support the currently installed 15.0 version.
patrick
Posts: 96
Joined: Mon May 09, 2011 11:54 am

Re: Plugin - Get Position of selected Element

Post by patrick »

Hi Radu,

one more question :)

How can I disable the plugin if I hide the view? Currently it continues to be active and consumes ressources, even if it is not visible.
Radu
Posts: 9048
Joined: Fri Jul 09, 2004 5:18 pm

Re: Plugin - Get Position of selected Element

Post by Radu »

Hi Patrick,

You could try do add an ancestor listener to the JTree something like:

Code: Select all

          boolean currentShowing = jTree.isShowing();
jTree.addAncestorListener(new AncestorListener() {
@Override
public void ancestorRemoved(AncestorEvent event) {
reconfigureCurrentShowing();
}
@Override
public void ancestorMoved(AncestorEvent event) {
}
@Override
public void ancestorAdded(AncestorEvent event) {
reconfigureCurrentShowing();
}
private void reconfigureCurrentShowing() {
if(currentShowing != jTree.isShowing()) {
//Tree was either hidden or shown
}
}
});
and look if the isShowing() state has changed on it. If it has changed, look if the view was hidden and disconnect it from the event listeners or look if it was shown and re-connect it to the listeners + refresh its content.

Regards,
Radu
Radu Coravu
<oXygen/> XML Editor
http://www.oxygenxml.com
Post Reply