blob: a1b4d150b3094c013a57f86b580f53fa0c5979c8 [file] [log] [blame]
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<!-- -*- xhtml -*- -->
<title>NetBeans Hyperlink Navigation Tutorial</title>
<link rel="stylesheet" type="text/css" href="https://netbeans.org/netbeans.css">
<meta name="AUDIENCE" content="NBUSER">
<meta name="TYPE" content="ARTICLE">
<meta name="EXPIRES" content="N">
<meta name="developer" content="tboudreau@netbeans.org">
<meta name="indexed" content="y">
<meta name="description"
content="A hyperlink in/to HTML files.">
<!-- Copyright (c) 2006 Sun Microsystems, Inc. All rights reserved. -->
<!-- Use is subject to license terms.-->
</head>
<body>
<h1>NetBeans Hyperlink Navigation Tutorial</h1>
<p>In this tutorial, you learn how to create hyperlinks in HTML files, programmatically. You will
do this by implementing the NetBeans API <a href="https://netbeans.org/download/dev/javadoc/org-netbeans-modules-editor-lib/org/netbeans/lib/editor/hyperlink/spi/HyperlinkProvider.html">HyperlinkProvider</a> class.
The hyperlink will let the user navigate from an HREF attribute in an HTML link to the referenced
HTML file.
<p>The new hyperlink will appear when the user holds down the Ctrl
key and moves the mouse over the value of the HREF attribute, as shown here:
<p><img border="1" src="../images/tutorials/hyperlink/hyperlink.png"/>
<p>When the hyperlink is clicked, the referenced file opens and the cursor
lands on the first H2 tag, if one exists.</p>
<p>This is what the completed project will look like in the Projects window:
<p><img border="1" src="../images/tutorials/hyperlink/hyperlink2.png"/>
<p>Though the focus of this tutorial is on hyperlinking from one HTML file to another,
the principles shown here could equally be applied to other types of files,
such as to Java source files, XML files, and JSP files.
<!-- ===================================================================================== -->
<p><h3 class="tutorial"><a name="installing-software"></a>Installing the Software</h3>
<p>Before you begin, you need to install the following software on your
computer:</p>
<ul>
<li>NetBeans IDE 5.x.
(<a href="http://www.netbeans.info/downloads/download.php?type=5.5b2">Click here</a>
to download NetBeans IDE 5.5 Beta 2.)</li>
<li>Java Standard Development Kit (JDK&trade;) version
1.4.2 (<a href="http://java.sun.com/j2se/1.4.2/download.html">download</a>)
or 5.0 (<a href="http://java.sun.com/j2se/1.5.0/download.jsp">download</a>)</li>
</ul>
<br />
<p><h2><a name="gettingtoknowthesample"></a>Getting Started</h2>
<p>In this section, we use a wizard to create a module project.
We declare dependencies on modules that provide the NetBeans API classes
needed by our hyperlink module.
<ol>
<p><li>Choose File &gt; New Project. In the New Project wizard,
choose NetBeans Plug-in Modules under Categories and Module Project
under Projects. Click Next. Type <tt>AHrefHyperlink</tt> in Project Name
and set Project Location to an appropriate folder on your disk. If they
are not selected, select
Standalone Module and Set as Main Project. Click Next.
<p><li>Type <tt>org.netbeans.modules.ahrefhyperlink</tt> in Code Name Base.
Click Finish.
<p><li>Right-click the project, choose Properties, click Libraries
in the Project Properties dialog box and declare a dependency on the following APIs:
<p><ul>
<li>Datasystems API
<li>Editor
<li>Editor Library
<li>File System API
<li>HTML Editor Library
<li>Nodes API
<li>Text API
<li>Utilities API
</ul>
</ol>
<br />
<!-- ===================================================================================== -->
<p><h2><a name="hyperlinkprovider"></a>Implementing the HyperlinkProvider Class</h2>
<p>The <a href="https://netbeans.org/download/dev/javadoc/org-netbeans-modules-editor-lib/org/netbeans/lib/editor/hyperlink/spi/HyperlinkProvider.html">HyperlinkProvider</a> class
implements three methods, each of which is discussed in detail below, accompanied by a
practical example in the context of our module. First we set up the class and then
we implement each of the three methods in turn.
<br />
<div class="indent">
<p><h3 class="tutorial"><a name="method1"></a>Setting Up the HyperlinkProvider Class</h3>
<p>Setting up our class means implementing <a href="https://netbeans.org/download/dev/javadoc/org-netbeans-modules-editor-lib/org/netbeans/lib/editor/hyperlink/spi/HyperlinkProvider.html">HyperlinkProvider</a>
and initializing some values that we will need in our implementation.
<ol>
<p><li>Create a Java class in <tt>org.netbeans.modules.ahrefhyperlink</tt>,
and call it AHrefHyperlinkProvider.
<p><li>Change the signature so that HyperlinkProvider is implemented.
<p><li>Note that the following import statements will be needed:
<p><pre class="examplecode">import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.TokenItem;
import org.netbeans.editor.ext.html.HTMLSyntaxSupport;
import org.netbeans.editor.ext.html.HTMLTokenContext;
import org.netbeans.lib.editor.hyperlink.spi.HyperlinkProvider;
import org.openide.util.RequestProcessor;</pre>
<p><li>Add the following initial values at the top of the class:
<p><pre class="examplecode">private static String AHREF_IDENTIFIER = "href";
private Document lastDocument;
private int startOffset;
private int endOffset;
private String identifier;</pre>
<p><li>Define the Constructor as follows:
<p><pre class="examplecode">public AHrefHyperlinkProvider() {
lastDocument = null;
}</pre>
</ol>
</div>
<br />
<br />
<div class="indent">
<p><h3 class="tutorial"><a name="method1"></a>isHyperlinkPoint(Document doc, int offset)</h3>
<p><a href="https://netbeans.org/download/dev/javadoc/org-netbeans-modules-editor-lib/org/netbeans/lib/editor/hyperlink/spi/HyperlinkProvider.html#isHyperlinkPoint(javax.swing.text.Document,%20int)">isHyperlinkPoint(Document doc, int offset)</a>
determines whether there should be a hyperlink at the given offset within the given document. The inline
comments in the method below, as well as in the code in the remainder of this tutorial,
serve to explain the purpose of the code.
<p><pre class="examplecode">public boolean isHyperlinkPoint(Document doc, int offset) {
<b>// We want to work only with org.netbeans.editor.BaseDocument:</b>
if (!(doc instanceof BaseDocument)) {
return false;
}
BaseDocument bdoc = (BaseDocument) doc;
JTextComponent target = org.netbeans.editor.Utilities.getFocusedComponent();
<b>// We want to work only with the open editor and
// the editor has to be the active component:</b>
if ((target == null) || (target.getDocument() != bdoc)) {
return false;
}
<b>// We want to work only with HTMLSyntaxSupport:</b>
if (!(bdoc.getSyntaxSupport() instanceof HTMLSyntaxSupport)) {
return false;
}
HTMLSyntaxSupport sup = (HTMLSyntaxSupport) bdoc.getSyntaxSupport();
try {
<b>// Get the token on the offset:</b>
TokenItem token = sup.getTokenChain(offset, offset + 1);
<b>// We are interested only in the value of a tag:</b>
if ((token != null) && (token.getTokenID().getNumericID()
== HTMLTokenContext.VALUE_ID)) {
TokenItem firstToken = token;
<b>// We want to find the nearest argument:</b>
while ((token != null) &&
(token.getTokenID().getNumericID()
!= HTMLTokenContext.ARGUMENT_ID))
token = token.getPrevious();
<b>// If the token is an argument and
// it is an HREF attribute identifier...</b>
if ((token != null)
&& (token.getTokenID().getNumericID()
== HTMLTokenContext.ARGUMENT_ID)
&& token.getImage().equals(AHREF_IDENTIFIER)) {
<b>//...then remember the identifier:</b>
identifier = firstToken.getImage();
<b>// Here we exclude certain types of HREF links,
// those that start with 'nbdocs' and those that
// are internal links, marked by the '#' symbol:</b>
if ((identifier.startsWith("nbdocs",1) == false) &&
(identifier.startsWith("#",1) == false)) {
<b>// Remove " or ' from the hyperlink:</b>
identifier = identifier.substring(0,
identifier.length() - 1).substring(1);
<b>// Set the real start and end of the hyperlink,
// which is after char " or ':</b>
startOffset = firstToken.getOffset() + 1;
endOffset = (firstToken.getOffset() +
firstToken.getImage().length()) - 2;
<b>// Remember this document:</b>
lastDocument = bdoc;
} else {
return false;
}
return true;
}
}
} catch (BadLocationException ex) {
ex.printStackTrace();
}
return false;
}</pre>
</div>
<br />
<div class="indent">
<p><h3 class="tutorial"><a name="method2"></a>getHyperlinkSpan(Document doc, int offset)</h3>
<p><tt><a href="https://netbeans.org/download/dev/javadoc/org-netbeans-modules-editor-lib/org/netbeans/lib/editor/hyperlink/spi/HyperlinkProvider.html#getHyperlinkSpan(javax.swing.text.Document,%20int)">getHyperlinkSpan(Document doc, int offset)</a></tt> determines the length of the hyperlink.
<p><pre class="examplecode">public int[] getHyperlinkSpan(Document doc, int offset) {
<b>// First check that we are working with BaseDocument:</b>
if (!(doc instanceof BaseDocument)) {
return null;
}
BaseDocument bdoc = (BaseDocument) doc;
JTextComponent target = org.netbeans.editor.Utilities.getFocusedComponent();
<b>// We want to work only with the open editor
// and the editor has to be the active component and
// the document has to be the same as was used in the isHyperlinkPoint method:</b>
if ((target == null) || (lastDocument != bdoc)) {
return null;
}
<b>// Return the position that we defined in the isHyperlinkPoint method:</b>
return new int[] { startOffset, endOffset };
}</pre>
</div>
<br />
<div class="indent">
<h3 class="tutorial"><a name="method3"></a>performClickAction(Document doc, int offset)</h3>
<p><a href="https://netbeans.org/download/dev/javadoc/org-netbeans-modules-editor-lib/org/netbeans/lib/editor/hyperlink/spi/HyperlinkProvider.html#performClickAction(javax.swing.text.Document,%20int)">performClickAction(Document doc, int offset)</a>
determines what happens when the hyperlink is clicked. In general, a document
should open, the cursor should move to a certain place in a document, or both.
<p><pre class="examplecode">public void performClickAction(Document doc, int offset) {
<b>// First check that we are working with BaseDocument:</b>
if (!(doc instanceof BaseDocument)) {
return;
}
BaseDocument bdoc = (BaseDocument) doc;
JTextComponent target = org.netbeans.editor.Utilities.getFocusedComponent();
<b>// We want to work only with the open editor and
// the editor has to be active component and
// the document has to be the same as was used in the isHyperlinkPoint method:</b>
if ((target == null) || (lastDocument != bdoc)) {
return;
}
<b>//Start a new thread for opening the HTML document:</b>
OpenHTMLThread run = new OpenHTMLThread(bdoc,identifier);
RequestProcessor.getDefault().post(run);
}</pre>
</div>
<br />
<!-- ===================================================================================== -->
<p><h2><a name="gettingtoknowthesample"></a>Opening the Referenced HTML File</h2>
<p>Next, you need to create a class that opens an HTML file in a separate thread.
Here, the class is called <tt>OpenHTMLThread</tt>. It makes use of the
following import statements:
<p><pre class="examplecode">import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.JEditorPane;
import javax.swing.text.BadLocationException;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.URLMapper;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;</pre>
<p>The token identified in the <tt>isHyperlinkPoint</tt> method is
received by this class. Then the token is analyzed to see whether
it contains a slash, which indicates that it is a relative link. In
that case, the file object is extrapolated from the URL to the file.
Otherwise, the file object is created from the token itself. Next, the
document with the name of the file object is opened and the cursor
is positioned at the H2 tag, if found.
<pre class="examplecode">public class OpenHTMLThread implements Runnable {
private String fqn;
private BaseDocument doc;
private String identifier;
public OpenHTMLThread(BaseDocument doc, String identifier) {
super();
this.doc = doc;
this.identifier = identifier;
}
public void run() {
FileObject fo = NbEditorUtilities.getFileObject(doc);
FileObject foHtml = null;
<b>// Here we're working out whether we're dealing with a relative link or not:</b>
if (identifier.contains("/")){
String fullPath = fo.getPath();
try {
URL f = new File(fullPath).toURI().resolve(identifier).toURL();
foHtml = URLMapper.findFileObject(f);
} catch (MalformedURLException ex) {
ex.printStackTrace();
}
} else {
foHtml = fo.getParent().getFileObject(identifier);
}
<b>//Here we're finding our HTML file:</b>
DataObject dObject;
try {
dObject = DataObject.find(foHtml);
final EditorCookie.Observable ec = (EditorCookie.Observable)
dObject.getCookie(EditorCookie.Observable.class);
if (ec != null) {
org.netbeans.editor.Utilities.runInEventDispatchThread(new Runnable() {
public void run() {
final JEditorPane[] panes = ec.getOpenedPanes();
<b>//Here we're positioning the cursor,
//if the document isn't open, we need to open it first:</b>
if ((panes != null) && (panes.length > 0)) {
setPosition(panes[0],identifier);
} else {
ec.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (EditorCookie.Observable.
PROP_OPENED_PANES.equals(evt.getPropertyName())) {
final JEditorPane[] panes = ec.getOpenedPanes();
if ((panes != null) && (panes.length > 0)) {
setPosition(panes[0],identifier);
}
ec.removePropertyChangeListener(this);
}
}
});
}
ec.open();
}
});
}
} catch (DataObjectNotFoundException ex) {
ex.printStackTrace();
}
}
<b>//Here we specify where the cursor will land:</b>
private void setPosition(JEditorPane pane, String identifier) {
try {
<b>//The whole text:</b>
String text = pane.getDocument().getText(0,
pane.getDocument().getLength() - 1);
<b>//The place where we want the cursor to be:</b>
int index = text.indexOf("&lt;h2&gt;");
<b>//If we can find it, we place the cursor there:</b>
if (index > 0) {
pane.setCaretPosition(index);
}
} catch (BadLocationException ex) {
ex.printStackTrace();
}
}
}</pre>
</div>
<br />
<!-- ===================================================================================== -->
<p><h2><a name="gettingtoknowthesample"></a>Registering the HyperlinkProvider Implementation Class</h2>
<p>Finally, you need to register the hyperlink provider implementation class
in the module's <tt>layer.xml</tt> file. Do this as follows, while making sure
that the line in bold below is the fully qualified class name of the class that
implements HyperlinkProvider:
<pre class="examplecode">&lt;folder name="Editors"&gt;
&lt;folder name="text"&gt;
&lt;folder name="html"&gt;
&lt;folder name="HyperlinkProviders"&gt;
&lt;file name="AHrefHyperlinkProvider.instance"&gt;
&lt;attr name="instanceClass"
stringvalue="<b>org.netbeans.modules.
ahrefhyperlink.AHrefHyperlinkProvider</b>"/&gt;
&lt;attr name="instanceOf"
stringvalue="org.netbeans.lib.editor.
hyperlink.spi.HyperlinkProvider"/&gt;
&lt;/file&gt;
&lt;/folder&gt;
&lt;/folder&gt;
&lt;/folder&gt;
&lt;/folder&gt;</pre>
<p>If you create a hyperlink for a different MIME type, you need to change the
<tt>text/html</tt> folders above to the appropriate MIME type.</p>
<p>Now that the HyperlinkProvider is registered, you can install the module and
try out your new hyperlinks. Hold down the Ctrl key, move the mouse over an HREF
attribute as shown at the start of this tutorial:
<p><img border="1" src="../images/tutorials/hyperlink/hyperlink.png"/>
<p>When the hyperlink appears,
you can click it and let the IDE navigate to the referenced HTML file.
<br />
<br>
<div class="feedback-box"><a href="https://netbeans.org/about/contact_form.html?to=3&amp;subject=Feedback:%20Hyperlink%20Module%20Tutorial">Send Us Your Feedback</a></div>
<br style="clear:both;" />
<!-- ===================================================================================== -->
<p><h2><a name="gettingtoknowthesample"></a>Next Steps</h2>
<ul>
<li>Utility method for finding and opening Java source files.
<li>Working with JSP and XML documents. (Same principle as above.)
<li>Need to provide for the situation where the referenced HTML file doesn't exist.
<li>Show hyperlink within same document.
<li>Implement external links, i.e., http links should go to external browser.
<li>Provide links to NetBeans sources, such as StrutsHyperlinkProvider, etc.
</ul>
<hr><p>
</body>
</html>