Page 1 of 1

Plugin - Get Position of selected Element

Posted: Wed May 29, 2013 11:23 am
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

Re: Plugin - Get Position of selected Element

Posted: Wed May 29, 2013 11:37 am
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.

Re: Plugin - Get Position of selected Element

Posted: Wed May 29, 2013 11:56 am
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

Re: Plugin - Get Position of selected Element

Posted: Wed May 29, 2013 12:37 pm
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.

Re: Plugin - Get Position of selected Element

Posted: Wed May 29, 2013 2:13 pm
by patrick
Can you give me an short example how to include this into a plugin and/or extend the Outline view?

Re: Plugin - Get Position of selected Element

Posted: Wed May 29, 2013 3:24 pm
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

Re: Plugin - Get Position of selected Element

Posted: Wed Jun 12, 2013 10:42 am
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?

Re: Plugin - Get Position of selected Element

Posted: Thu Jun 13, 2013 11:06 am
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

Re: Plugin - Get Position of selected Element

Posted: Thu Jun 27, 2013 11:11 am
by patrick
Are any of these points possible within the current api, even if its not the perfect solution?

Re: Plugin - Get Position of selected Element

Posted: Thu Jun 27, 2013 12:42 pm
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

Re: Plugin - Get Position of selected Element

Posted: Fri Mar 07, 2014 2:04 pm
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

Re: Plugin - Get Position of selected Element

Posted: Fri Mar 07, 2014 3:37 pm
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

Re: Plugin - Get Position of selected Element

Posted: Fri Mar 07, 2014 4:04 pm
by patrick
Great! Thank you for the hint, it works now :-)

Re: Plugin - Get Position of selected Element

Posted: Tue Mar 11, 2014 6:09 pm
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.

Re: Plugin - Get Position of selected Element

Posted: Tue Mar 11, 2014 6:17 pm
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

Re: Plugin - Get Position of selected Element

Posted: Wed Mar 12, 2014 1:49 pm
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
);

Re: Plugin - Get Position of selected Element

Posted: Wed Mar 12, 2014 3:01 pm
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

Re: Plugin - Get Position of selected Element

Posted: Wed Mar 12, 2014 3:44 pm
by patrick
Thanks, with Oxygen 15.2 it works fine! I will try your workaround to support the currently installed 15.0 version.

Re: Plugin - Get Position of selected Element

Posted: Wed Mar 26, 2014 12:34 pm
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.

Re: Plugin - Get Position of selected Element

Posted: Wed Mar 26, 2014 1:22 pm
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