| <!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 Platform Feed Reader Tutorial for NetBeans Platform for 7.1</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="geertjan.wielenga@oracle.com"/> |
| <meta name="indexed" content="y"/> |
| <meta name="description" content="FeedReader on 7.1."/> |
| <!-- Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved. --> |
| <!-- Use is subject to license terms.--> |
| </head> |
| |
| <body> |
| |
| <h1>NetBeans Platform Feed Reader Tutorial</h1> |
| |
| <p> |
| Welcome to the NetBeans Platform Feed Reader tutorial. |
| The Feed Reader that you create in this |
| tutorial is a simple RSS/Atom feed browser, modeled |
| after the Sage plug-in for Mozilla Firefox. It |
| presents a tree of feeds with subnodes representing |
| individual feed entries that you can open in a browser.</p> |
| |
| <p>To illustrate the end result, here you see the Feed Reader that |
| you will create in this tutorial, displaying a feed entry from the |
| <a href="https://netbeans.org/rss-091.xml">NetBeans Highlights feed</a>:</p> |
| |
| <p><img src="../../images/tutorials/feedreader/70/feedreader.png" alt="feedreader result"/></p> |
| |
| <p><b class="notes">Note:</b> This document uses NetBeans IDE 7.1 and NetBeans Platform 7.1. If you |
| are using an earlier version, see <a href="../70/nbm-feedreader.html">the previous version |
| of this document</a>.</p> |
| |
| <p><b>Contents</b></p> |
| |
| <p><img src="../../images/articles/71/netbeans-stamp.png" class="stamp" width="114" height="114" alt="Content on this page applies to NetBeans IDE 7.1" title="Content on this page applies to NetBeans IDE 7.1"/></p> |
| <ul class="toc"> |
| <li><a href="#knowledge">Prerequisite Knowledge</a></li> |
| <li><a href="#setting">Setting Up the Application</a></li> |
| <li><a href="#creating">Creating the Feed Reader Window</a></li> |
| <li><a href="#running">Trying Out the Application</a></li> |
| <li><a href="#adding">Adding Code to the Application</a> |
| <ul> |
| <li><a href="#dep1">Specifying the Application's Dependencies</a></li> |
| <li><a href="#dep2">Setting Dependencies Between Library Wrapper Modules</a></li> |
| <li><a href="#rssfeeds">Creating the RssFeeds Folder</a></li> |
| <li><a href="#feedobject">Creating the Feed Object</a></li> |
| <li><a href="#feedwindow">Extending the Feed Window</a></li> |
| <li><a href="#root">Creating the Root Node</a></li> |
| <li><a href="#rssfolderchildren">Creating the Root Node Children</a></li> |
| <li><a href="#action1">Creating the Add Feed Action</a></li> |
| <li><a href="#action2">Creating the Add Folder Action</a></li> |
| <li><a href="#feed">Creating the Feed Node</a></li> |
| <li><a href="#entry">Creating the Entry Node</a></li> |
| </ul> |
| </li> |
| <li><a href="#running">Running the Application</a></li> |
| <li><a href="#troubleshooting">Troubleshooting</a></li> |
| </ul> |
| |
| <p><b>To follow this tutorial, you need the software and resources listed in the following |
| table.</b></p> |
| |
| <table> |
| <tbody> |
| <tr> |
| <th class="tblheader" scope="col">Software or Resource</th> |
| <th class="tblheader" scope="col">Version Required</th> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="https://netbeans.org/downloads/index.html">NetBeans IDE</a></td> |
| <td class="tbltd1">version 7.1</td> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="http://java.sun.com/javase/downloads/index.jsp">Java Developer Kit (JDK)</a></td> |
| <td class="tbltd1">version 6 or above</td> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="http://repo1.maven.org/maven2/net/java/dev/rome/rome/1.0.0">Rss and atOM utilitiEs</a></td> |
| <td class="tbltd1"></td> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="http://repo1.maven.org/maven2/net/java/dev/rome/rome-fetcher/1.0.0">Rome Fetcher</a></td> |
| <td class="tbltd1"></td> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="http://jdom.org/downloads/index.html">JDom</a></td> |
| <td class="tbltd1"></td> |
| </tr> |
| <tr> |
| <td class="tbltd1"><a href="https://netbeans.org/files/documents/4/550/feedreader-images.zip">FeedReader icon and splash screen</a></td> |
| <td class="tbltd1"></td> |
| </tr> |
| </tbody> |
| </table> |
| |
| |
| |
| <h2><a name="knowledge"></a>Prerequisite Knowledge</h2> |
| <p>This is not a beginner's tutorial. You are assumed not only to |
| be familiar with Java and with Swing, but with the basic |
| concepts and processes of the NetBeans Platform. At the very |
| least, you should be aware of the following two documents |
| and, ideally, you will have worked through them and understood |
| the topics they address:</p> |
| |
| <ul> |
| <li><a href="http://refcardz.dzone.com/refcardz/netbeans-platform-70">NetBeans Platform 7 DZone Refcard</a>. |
| Anyone getting started with the NetBeans Platform must have this DZone RefCard, which explains |
| the benefits, scope, and features of the NetBeans Platform. It outlines the most important |
| APIs and provides many tips and tricks.</li> |
| <!-- <li><a href="https://platform.netbeans.org/tutorials/nbm-feedreader_background.html">Preparing to Create the FeedReader Application</a>. This |
| document provides the background of this tutorial. It walks |
| you through everything you will do in this tutorial, conceptually. |
| It also shows you |
| where you can find the source code of the sample that you |
| build in this tutorial.</li>--> |
| <li><a href="nbm-quick-start.html">NetBeans Platform Quick Start</a>. |
| This short tutorial guides you through a complete process |
| for building a rich-client application on top of the NetBeans |
| Platform. The major development stages and tools are covered, |
| and a modular application is the result of the tutorial. This tutorial |
| also introduces crucial classes used in the tutorial below, in particular |
| the classes <a href="http://bits.netbeans.org/dev/javadoc/org-openide-util-lookup/org/openide/util/Lookup.html">Lookup</a>, |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-util-lookup/org/openide/util/LookupListener.html">LookupListener</a>, |
| and <a href="http://bits.netbeans.org/dev/javadoc/org-openide-util-lookup/org/openide/util/lookup/InstanceContent.html">InstanceContent</a>.</li> |
| </ul> |
| |
| <h2><a name="setting"></a>Setting Up the Application</h2> |
| <p>In NetBeans IDE, building an application on top of the NetBeans Platform |
| starts with generating a number of files which will |
| serve as the foundation of your application. For example, |
| the IDE provides several project wizards that set up all |
| the basic files needed by modules and applications built |
| on the NetBeans Platform.</p> |
| <ul> |
| <li> |
| <b>NetBeans Platform Application.</b> A project that groups a set of |
| module projects and library wrapper module projects that have dependencies on |
| each other, and lets you deploy them together as a unit. Automatically included |
| are a subset of the modules that make up the NetBeans Platform.</li> |
| <li> |
| <b>Module Suite.</b> Same as above, except that the pre-included modules are |
| more than only those relating to the NetBeans Platform—in this case, |
| all the modules that make up NetBeans IDE are included as well.</li> |
| <li> |
| <b>Library Wrapper Module.</b> A project that puts a library JAR file on its classpath and exports some or all of the JAR file's packages from the module as public packages.</li> |
| <li> |
| <b>Module.</b> A project for implementing the functionality, business logic, and user interface of a module or application built on the NetBeans Platform.</li> |
| </ul> |
| <div class="indent"> |
| <h3> |
| Creating the Application Skeleton</h3> |
| <ol> |
| <li> |
| <p>Choose File > New Project (Ctrl-Shift-N). Under |
| Categories, select NetBeans Modules. Under Projects, select NetBeans Platform Application. |
| You should see the following:</p> |
| <p><img alt="project wizard" src="../../images/tutorials/feedreader/70/paintapp-proj-wiz.png" /></p> |
| <p class="tips">Depending on the modules installed in your IDE, different categories may be shown |
| in the Categories list above.</p> |
| <p>Click Next.</p></li> |
| <li> |
| <p>In the Name and Location panel, type <tt>FeedReader</tt> |
| in Project Name. Change the Project Location to any |
| directory on your computer.</p> |
| <p><img alt="project wizard" src="../../images/tutorials/feedreader/70/paintapp-proj-wiz2.png" /></p> |
| <p>Click Finish.</p></li> |
| </ol> |
| |
| <p>The IDE creates the <tt>FeedReader</tt> |
| project, which looks as follows in the |
| Projects window:</p> |
| <p><img alt="projects window" src="../../images/tutorials/feedreader/70/suite-project.png"/></p> |
| |
| <p>The project will contain the custom module projects that you |
| will create in the following sections. </p> |
| |
| <h3> |
| Wrapping the Libraries</h3> |
| |
| <p>You could bundle the entire Feed Reader application into a |
| single module. However, the application needs the Rome, Rome Fetcher, and JDom libraries:</p> |
| <ul> |
| <li> |
| <b>Rome.</b> Reads RSS and Atom feeds, using a very simple API. </li> |
| <li> |
| <b>Rome Fetcher.</b> Allows the retrieval of feeds via HTTP. </li> |
| <li> |
| <b>JDom.</b> Is an XML parsing API. The only reason the Feed Reader will need it is because the Rome library uses it.</li> |
| </ul> |
| |
| <p class="tips">Later, if you want to extend the Feed Reader |
| application with more modules that may use these libraries, |
| it would be better for them to depend on just the library modules, |
| rather than the entire Feed Reader. Also, library modules can |
| be "autoloading", which means that NetBeans will only load them |
| when needed. Until that happens, it won't take up any memory at runtime.</p> |
| <ol> |
| <li><p>Right-click the Modules node in the project in |
| the Projects window, as shown below, and click Add New Library:</p> |
| <p><img alt="add new library" src="../../images/tutorials/feedreader/70/add-lib0.png"/></p></li> |
| |
| <li> |
| In the Select Library panel, browse |
| to the folder where you downloaded JDom, and then select <tt>jdom.jar</tt> |
| and <tt>LICENSE.txt.</tt> |
| Click Next.</li> |
| <li> |
| <p>In the Name and Location panel, accept all the defaults.</p> |
| |
| |
| <p class="tips"> The library wrapper module project |
| will be stored within the application project. |
| You could also store it somewhere else, but for |
| versioning purposes it is a good idea to put it within |
| the application project. Therefore, the <tt>FeedReader</tt> |
| application project is selected in the |
| Add to Module Suite drop-down.</p> |
| <p>Click Next.</p></li> |
| <li> |
| <p>In the Basic Module Configuration panel, type <tt>org.jdom</tt> |
| as the code name base and leave all the other defaults unchanged. Click Finish.</p> |
| <p>The new library wrapper module project opens |
| in the IDE and displays in the Projects window. |
| You should now see the following in the Projects |
| window:</p> |
| |
| <p><img alt="projects window" src="../../images/tutorials/feedreader/70/lib-wiz2.png"/></p> |
| <p class="tips"> A frequent point of confusion is that you see two different "jdom" nodes above, |
| each accompanied by a purple icon. The first of these, above, shows the relationship of the |
| "jdom" module to the whole application. The second is the "jdom" project itself, containing |
| its sources and libraries. This pattern is used throughout the application, with each module |
| being shown in two different ways, as above.</p> |
| |
| </li> |
| <li> |
| Return to step 1 of this section and create a library wrapper module project for Rome. |
| Use code name base "org.rome" and accept all the other defaults.</li> |
| <li> |
| Return to step 1 of this section and create a library wrapper module project for Rome Fetcher. |
| Use code name base "org.fetcher" and accept all the other defaults.</li> |
| </ol> |
| |
| <p>You now have an application skeleton, |
| with three library wrapper module projects, |
| providing many useful Java classes that you will |
| be able to make use of throughout this tutorial:</p> |
| <p><img alt="projects window" src="../../images/tutorials/feedreader/70/lib-wiz3.png"/></p> |
| |
| <h3> |
| Creating the Module Project </h3> |
| <p>In this section, we create a project for the functionality |
| that our application will provide. The project will make use of |
| the classes made available by the library wrapper modules that |
| we created in the previous section.</p> |
| <ol> |
| <li> |
| <p>Right-click the Modules node in the application project in |
| the Projects window, as shown below, and click Add New:</p> |
| <p><img alt="" src="../../images/tutorials/feedreader/70/module-project1.png"/></p> |
| </li> |
| <li>In the Name and Location panel, type <tt>FeedReader</tt> |
| in Project Name. Accept all the other defaults. Click Next.</li> |
| <li>In the Basic Module Configuration panel, type <tt>org.myorg.feedreader</tt> |
| in Code Name Base.</li> |
| <li>Do not select "Generate OSGi Bundle". Click Finish.</li> |
| </ol> |
| <p> |
| The IDE creates the FeedReader project. |
| The project contains all of the module's sources and project |
| metadata, such as the project's Ant build script. The project |
| opens in the IDE. You can view its logical structure in the |
| Projects window (Ctrl-1) and its file structure in the Files |
| window (Ctrl-2). The Projects window should now show the |
| following:</p> |
| |
| <p><img alt="" src="../../images/tutorials/feedreader/70/module.png"/></p> |
| |
| </div> |
| <p>You have now created the source structure |
| of your new application. In the next section, |
| we will begin adding some code.</p> |
| |
| <h2><a name="creating"></a> |
| Creating the Feed Reader Window</h2> |
| <p> |
| In this section you use the Window wizard to generate files that create a |
| custom windowing component and an action to invoke it. Right after finishing |
| this section, you are shown how to try out the files that the Window wizard generates for you.</p> |
| <div class="indent"> |
| <ol> |
| <li> |
| <p>Right-click the <tt>FeedReader</tt> |
| module project node and choose New > Other. Under Categories, select Module Development. |
| Under File Types, select Window, as shown below:</p> |
| |
| |
| <img alt="" src="../../images/tutorials/feedreader/70/windowcomp-wiz.png"/> |
| |
| |
| <p>Click Next.</p></li> |
| <li> |
| <p>In the Basic Settings panel, select <tt>explorer</tt> |
| in the drop-down list and click Open on Application |
| Start. Click Next.</p></li> |
| <li> |
| <p>In the Name and Location panel, type Feed in the Class Name Prefix field. In the Icon field, browse to the location |
| where you saved <tt>rss16.gif (<img alt="" src="../../images/tutorials/feedreader/rss16.gif" />).</tt> |
| The GIF file will be shown in the menu item that |
| invokes the action. Click Finish.</p></li> |
| </ol> |
| <p>The new <tt>FeedTopComponent</tt> class is now shown in the Projects window, as well |
| as in the Design view of the Matisse GUI Builder. Open it into |
| the Source view and you should see the following:</p> |
| |
| <pre class="examplecode" style="overflow:auto;width:680px;">import org.openide.util.NbBundle; |
| import org.openide.windows.TopComponent; |
| import org.openide.windows.WindowManager; |
| import org.netbeans.api.settings.ConvertAsProperties; |
| import org.openide.awt.ActionID; |
| import org.openide.awt.ActionReference; |
| |
| <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-settings/org/netbeans/api/settings/ConvertAsProperties.html">@ConvertAsProperties</a>(dtd = "-//org.myorg.feedreader//Feed//EN", |
| autostore = false) |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.Description.html">@TopComponent.Description</a>(preferredID = "FeedTopComponent", |
| iconBase = "org/myorg/feedreader/rss16.gif", |
| persistenceType = TopComponent.PERSISTENCE_ALWAYS) |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.Registration.html">@TopComponent.Registration</a>(mode = "explorer", openAtStartup = true) |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/ActionID.html">@ActionID</a>(category = "Window", id = "org.myorg.feedreader.FeedTopComponent") |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/ActionReference.html">@ActionReference</a>(path = "Menu/Window" /*, position = 333 */) |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.OpenActionRegistration.html">@TopComponent.OpenActionRegistration</a>(displayName = "#CTL_FeedAction", |
| preferredID = "FeedTopComponent") |
| public final class FeedTopComponent extends <a href="http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/TopComponent.html">TopComponent</a> { |
| |
| private FeedTopComponent() { |
| setName(NbBundle.getMessage(FeedTopComponent.class, "CTL_FeedTopComponent")); |
| setToolTipText(NbBundle.getMessage(FeedTopComponent.class, "HINT_FeedTopComponent")); |
| } |
| |
| /** This method is called from within the constructor to |
| * initialize the form. |
| * WARNING: Do NOT modify this code. The content of this method is |
| * always regenerated by the Form Editor. |
| */ |
| // <editor-fold defaultstate="collapsed" desc="Generated Code"> |
| private void initComponents() { |
| javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); |
| this.setLayout(layout); |
| layout.setHorizontalGroup( |
| layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) |
| .addGap(0, 400, Short.MAX_VALUE) |
| ); |
| layout.setVerticalGroup( |
| layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) |
| .addGap(0, 300, Short.MAX_VALUE) |
| ); |
| }// </editor-fold> |
| // Variables declaration - do not modify |
| // End of variables declaration |
| |
| @Override |
| public void componentOpened() { |
| // TODO add custom code on component opening |
| } |
| |
| @Override |
| public void componentClosed() { |
| // TODO add custom code on component closing |
| } |
| |
| void writeProperties(java.util.Properties p) { |
| // better to version settings since initial version as advocated at |
| // http://wiki.apidesign.org/wiki/PropertyFiles |
| p.setProperty("version", "1.0"); |
| } |
| |
| void readProperties(java.util.Properties p) { |
| String version = p.getProperty("version"); |
| } |
| |
| }</pre> |
| <p>When you build the module, the class annotations that |
| you see at the top of the class above will be converted to XML tags in a file that will be |
| contributed to the central registry of the application. The XML file will |
| be named "generated-layer.xml" and will be found in the "build\classes\META-INF" |
| folder of your module, which you can see if the Files window (Ctrl-2) is open |
| in the IDE. This file is created at compile-time and contains XML entries generated |
| from the NetBeans annotations that you have defined in your Java classes. Together |
| with the "layer.xml" file that your module provides, the |
| "generated-layer.xml" file defines the contributions that the module makes to the central registry.</p> |
| <p class="tips">Valid values for the "mode" parameter, in <tt>@TopComponent.Registration</tt>, depend |
| on the modes registered in the central registry. Commonly, these include "editor", "explorer", "properties", |
| and "output", among others. See the section "Creating the RssFeeds Folder" below to learn about how |
| you can explore the central registry and inspect its folders and files. |
| if the <a href="http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/NbBundle.Messages.html">@Messages</a> annotation were to be used.</p> |
| <p class="tips">The code above could be even more idiomatic NetBeans Platform code |
| if the <a href="http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/NbBundle.Messages.html">@Messages</a> annotation were to be used.</p> |
| </div> |
| <h2><a name="running"></a>Trying Out the Application</h2> |
| <p> |
| Without having typed a single line of code, you can already |
| take your application for a spin. Trying it out means deploying |
| the modules to the NetBeans Platform and then checking to |
| see that the empty Feed |
| Window displays correctly.</p> |
| <div class="indent"> |
| <ol> |
| |
| <li><p>In the Projects window, right-click the <tt>FeedReader</tt> |
| project.</p></li> |
| <li><p>Choose Run.</p></li> |
| |
| </ol> |
| |
| <p>The application starts up. You see a splash screen. |
| Then the application opens and displays the |
| new Feed Window, as an explorer window, shown |
| below:</p> |
| |
| <p><img alt="" src="../../images/tutorials/feedreader/70/feedreader-1.png"/></p> |
| |
| <p><strong class="notes">Note: </strong> What you now have is an application consisting |
| of the following modules:</p> |
| <ul> |
| <li>The modules provided by the NetBeans Platform, for bootstrapping |
| the application, lifecycle management, window system, menu bars, toolbars, and other infrastructural |
| concerns.</li> |
| <li>The three library wrapper modules that you created in this tutorial.</li> |
| <li>The FeedReader functionality module that |
| you created in this tutorial, for providing the Feed window.</li> |
| </ul> |
| |
| |
| <p>In the application's Window menu, you should see the new menu item, |
| which you can use for opening the Feed window, if it is closed.</p> |
| </div> |
| <p>As you can see, without having done any coding, we have a |
| complete application. It doesn't do much yet, but the entire |
| infrastructure exists and works as one would expect. Next, we begin using some |
| of the NetBeans APIs, to add code to our application.</p> |
| |
| <h2><a name="adding"></a> |
| Adding Code to the Application</h2> |
| <p> |
| Now that you have laid the basis for your application, it's time to |
| begin adding your own code. Before doing so, you need to specify |
| the application's dependencies. Dependencies are modules that |
| provide the NetBeans APIs that you will extend or implement. Then, |
| you will use the New File wizard and the Source Editor to create |
| and code the classes that make up the Feed Reader application.</p> |
| <div class="indent"> |
| <h3><a name="dep1"></a> |
| Specifying the Application's Dependencies</h3> |
| |
| <p>You need to subclass several classes that belong to the NetBeans APIs. |
| The classes belong to modules that need to be declared as dependencies |
| of your Feed Reader application. Use the Project Properties dialog |
| box for this purpose, as explained in the steps below.</p> |
| <ol> |
| <li> |
| <p>In the Projects window, right-click the |
| <tt>FeedReader</tt> |
| module project and choose Properties. In the |
| Project Properties dialog box, click Libraries. |
| Notice that some APIs have already been declared as |
| Module Dependencies, thanks to the Window wizard you used |
| earlier.</p></li> |
| <li> |
| Click Add Dependency. |
| You will need the following APIs. Some have been |
| added by the Window wizard. Add the others yourself: |
| <pre class="examplecode"><a href="http://bits.netbeans.org/dev/javadoc/org-openide-actions/org/openide/actions/doc-files/api.html">Actions APIs</a> |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-loaders/org/openide/loaders/doc-files/api.html">Datasystems API</a> |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/package-summary.html">Dialogs API</a> |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/doc-files/api.html">Explorer and Property Sheet API</a> |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-filesystems/org/openide/filesystems/doc-files/api.html">File System API</a> |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-util-lookup/org/openide/util/lookup/doc-files/lookup-api.html">Lookup API</a> |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/doc-files/api.html">Nodes API</a> |
| rome |
| rome-fetcher |
| <a href="http://bits.netbeans.org/dev/javadoc/org-netbeans-modules-settings/overview-summary.html">Settings API</a> |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-awt/overview-summary.html">UI Utilities API</a> |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-util/overview-summary.html">Utilities API</a> |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-windows/org/openide/windows/package-summary.html">Window System API</a></pre> |
| |
| |
| <p>Click OK to exit the Project Properties dialog box.</p></li> |
| |
| <li><p>Expand the <tt>FeedReader</tt> project's Libraries |
| node and notice the list of modules that are now |
| available to this project:</p> |
| |
| |
| <img alt="" src="../../images/tutorials/feedreader/70/add-lib5.png"/> |
| |
| </li> |
| </ol> |
| |
| <h3><a name="dep2"></a> |
| Setting Dependencies Between Library Wrapper Modules</h3> |
| |
| <p>Now that we have set dependencies on the NetBeans API modules |
| that we will use, let's also set dependencies between our |
| library wrapper modules. For example, the Rome JAR makes use |
| of classes from the JDom JAR. Now that these are wrapped in |
| separate library wrapper modules, we need to specify the |
| relationship between the JARs via the library wrapper module's |
| Project Properties dialog box.</p> |
| |
| <ol> |
| <li> |
| <p>First, lets make Rome dependent on JDom. Right-click the Rome |
| library wrapper module project in the Projects window and choose |
| Properties. In the Project Properties dialog box, click Libraries |
| and then click Add Dependency. Add <tt>jdom</tt>. Click OK |
| to exit the Project Properties dialog box. When you expand |
| the Libraries node in the Rome project, you should now see the following:</p> |
| <p><img alt="" src="../../images/tutorials/feedreader/70/props-jdom.png"/></p> |
| </li> |
| <li> |
| <p>Finally, since Rome Fetcher depends on both |
| Rome and JDom, you need to make Rome Fetcher |
| dependent on Rome. Do so following the same instructions |
| as the above, so that Rome Fetcher depends on Rome, as shown below:</p> |
| |
| <p><img alt="" src="../../images/tutorials/feedreader/70/props-rome.png"/></p> |
| |
| Because Rome already |
| depends on JDom, you do not need to make |
| Rome Fetcher dependent on JDom.</li> |
| </ol> |
| <h3><a name="rssfeeds"></a> |
| Creating the RssFeeds Folder</h3> |
| |
| <p>You will use the IDE's user interface to add a folder |
| to the <tt>layer.xml</tt> file, which the module |
| will contribute to the application's central registry at runtime. The folder will contain |
| our RSS feed objects. Later, you will add code to <tt>FeedTopComponent.java</tt>, which |
| was created for you by |
| the Window wizard, to view the content of this folder.</p> |
| <ol> |
| <li><p>Right-click the <tt>FeedReader</tt> |
| module project node and choose New > Other > Module Development > XML Layer. |
| Click Finish. You now have an XML file, registered in the manifest of the module, |
| which the module will contribute to the central registry (the virtual filesystem) |
| of the application.</p></li> |
| <li> |
| <p>In the Projects window, expand the <tt>FeedReader</tt> |
| module project node and then expand the XML Layer |
| node. </p> |
| <p class="tips">When you expand the subnodes, you see <tt>"<this layer>"</tt>, containing |
| the folders and files registered in the current module's layer file, |
| as well as <tt>"<this layer in context>"</tt>, which merges all the folders |
| and files from all the modules in the whole application:</p> |
| <p><img alt="" src="../../images/tutorials/feedreader/70/expanded-layer.png"/></p></li> |
| <li> |
| <p>Right-click the <tt><this layer></tt> |
| node and choose New > Folder:</p> |
| <p><img alt="" src="../../images/tutorials/feedreader/70/expanded-layer2.png"/></p> |
| </li> |
| <li> |
| Type <tt>RssFeeds</tt> |
| in the New Folder dialog box. Click OK. |
| </li> |
| <li>Double-click the node for the <tt>layer.xml</tt> |
| file so that it opens in the Source Editor. Notice that this entry has been added: |
| <tt><folder name="RssFeeds"/></tt> |
| </li> |
| </ol> |
| <p>You have now created a new folder in the central registry. The central registry |
| is also known as the "System FileSystem". Read more about <a href="http://wiki.netbeans.org/DevFaqSystemFilesystem">the central |
| registry here</a>.</p> |
| <h3><a name="feedobject"></a> |
| Creating the Feed Object</h3> |
| |
| <p>Next you create a plain Java class that encapsulates a URL and its associated Rome feed.</p> |
| <ol> |
| <li> |
| Right-click the <tt>FeedReader</tt> |
| module project node, choose New > Java Class.</li> |
| <li> |
| Name the class <tt>Feed</tt> |
| and select <tt>org.myorg.feedreader</tt> |
| in the Package drop-down. Click Finish.</li> |
| <li> |
| In the Source Editor, replace the default <tt>Feed</tt> |
| class with the following: |
| <pre class="examplecode">public class Feed implements Serializable { |
| |
| private static final long serialVersionUID = 1L; |
| |
| private static final FeedFetcher FEED_FETCHER = |
| new HttpURLFeedFetcher(HashMapFeedInfoCache.getInstance()); |
| |
| private transient SyndFeed syndFeed; |
| private final URL url; |
| private String name; |
| |
| public Feed(URL url) { |
| this.url = url; |
| name = url.toExternalForm(); |
| } |
| |
| public URL getURL() { |
| return url; |
| } |
| |
| public SyndFeed getSyndFeed() throws IOException { |
| if (syndFeed == null) { |
| try { |
| syndFeed = FEED_FETCHER.retrieveFeed(url); |
| String title = syndFeed.getTitle(); |
| if (title != null) { |
| name = title; |
| } |
| } catch (Exception ex) { |
| throw (IOException) new IOException(ex.toString()).initCause(ex); |
| } |
| } |
| return syndFeed; |
| } |
| |
| @Override |
| public String toString() { |
| return name; |
| } |
| |
| public static Feed getSample() { |
| try { |
| return new Feed(new URL(NbBundle.getMessage(Feed.class, "URL_sample_feed"))); |
| } catch (MalformedURLException x) { |
| throw new AssertionError(x); |
| } |
| } |
| |
| }</pre> |
| </li> |
| </ol> |
| <p>A lot of code is underlined, because you have not declared their packages. You do this in the next steps. </p> |
| |
| <p>Take the following steps to reformat the file and declare its dependencies:</p> |
| <ol> |
| <li> |
| Press Alt-Shift-F to format the code. </li> |
| <li> |
| |
| <p>Press Ctrl-Shift-I and make sure |
| the following import statements are selected:</p> |
| |
| <p><img alt="" src="../../images/tutorials/feedreader/70/nb-imports.png"/></p> |
| |
| <p>Click OK, and the IDE adds the following import statements |
| to the class:</p> |
| |
| <pre class="examplecode">import com.sun.syndication.feed.synd.SyndFeed; |
| import com.sun.syndication.fetcher.FeedFetcher; |
| import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache; |
| import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import org.openide.util.NbBundle;</pre> |
| </li> |
| </ol> |
| |
| <p>All the red underlining should now have disappeared. If not, |
| do not continue with this tutorial until you have solved the problem.</p> |
| <h3><a name="feedwindow"></a> |
| Extending the Feed Window</h3> |
| |
| <p>In this section, we use a NetBeans Swing component called <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/view/BeanTreeView.html">BeanTreeView</a></tt> |
| to display a hierarchy of feeds in our <tt>TopComponent</tt>.</p> |
| |
| <ol> |
| <li> |
| Double-click <tt>FeedTopComponent.java</tt> |
| and then click the Source button, |
| so that the <tt>TopComponent</tt> opens in the Source Editor.</li> |
| <li> |
| Type <tt>implements <a href="http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/ExplorerManager.Provider.html">ExplorerManager.Provider</a></tt> |
| at the end of the class declaration.</li> |
| <li> |
| Press Alt-Enter in the line and click on the suggestion. The IDE adds an import statement for the required |
| package <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-explorer/org/openide/explorer/ExplorerManager.html">org.openide.explorer.ExplorerManager</a></tt> |
| .</li> |
| <li> |
| Press Alt-Enter again and click on the suggestion. The IDE implements the abstract |
| method <tt>getExplorerManager()</tt>. </li> |
| <li> |
| Type <tt>return manager;</tt> |
| in the body of the new <tt>getExplorerManager()</tt> |
| method. Press Alt-Enter in the line and let the IDE create a field called <tt>manager</tt> |
| for you. Replace the default definition with this one: |
| <pre class="examplecode">private final ExplorerManager manager = new ExplorerManager();</pre> |
| </li> |
| <li> |
| Right below the field declaration in the previous step, declare this one: |
| <pre class="examplecode">private final BeanTreeView view = new BeanTreeView();</pre> |
| </li> |
| <li> |
| Finally, add the following code to the end of the constructor: |
| <pre class="examplecode">setLayout(new BorderLayout()); |
| add(view, BorderLayout.CENTER); |
| manager.setRootContext(new RssNode.RootRssNode()); |
| ActionMap map = getActionMap(); |
| map.put("delete", ExplorerUtils.actionDelete(manager, true)); |
| associateLookup(ExplorerUtils.createLookup(manager, map));</pre> |
| </li> |
| </ol> |
| |
| <p> |
| Now a lot of code is underlined, because you have not declared their associated packages. You do this in the next steps. </p> |
| <p> |
| Take the following steps to reformat the file and declare its dependencies:</p> |
| <ol> |
| <li> |
| Press Alt-Shift-F to format the code. </li> |
| <li> |
| Press Ctrl-Shift-I and the IDE adds several import |
| statements below the package statement. The complete |
| list of import statements should be as follows: |
| |
| <pre class="examplecode">import java.awt.BorderLayout; |
| import java.util.logging.Logger; |
| import javax.swing.ActionMap; |
| import org.openide.util.NbBundle; |
| import org.openide.windows.TopComponent; |
| import org.openide.windows.WindowManager; |
| import org.openide.util.ImageUtilities; |
| import org.netbeans.api.settings.ConvertAsProperties; |
| import org.openide.explorer.ExplorerManager; |
| import org.openide.explorer.ExplorerUtils; |
| import org.openide.explorer.view.BeanTreeView;</pre> |
| |
| </li> |
| <li> |
| Note that the line <tt>manager.setRootContext(new RssNode.RootRssNode());</tt> |
| is still underlined in red, because you have not created <tt>RssNode.java</tt> |
| yet. This you will do in the next subsection. All other red underlining should now have disappeared. If not, do not continue with this tutorial until you have solved the problem.</li> |
| </ol> |
| <h3><a name="root"></a>Creating the Root Node</h3> |
| |
| <p>The top level node of our Feed Reader is provided |
| by the RssNode class. The class extends <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/AbstractNode.html">AbstractNode</a></tt>, |
| which is the generic convenience class for creating |
| your own Nodes. It creates its child Nodes |
| by using the 'RssFeeds' folder that you |
| created in the "Creating the RssFeeds Folder" section |
| earlier in this tutorial. In addition to child Nodes, |
| the RootNode has a display name and an Action for |
| creating new feeds.</p> |
| |
| <p>Take the following steps to create the RootNode class: </p> |
| |
| <ol> |
| <li> |
| Create <tt>RssNode.java</tt> |
| in the <tt>org.myorg.feedreader</tt> |
| package.</li> |
| <li> |
| Replace the default class with the following: |
| <pre class="examplecode">import javax.swing.Action; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.loaders.DataFolder; |
| import org.openide.loaders.DataObject; |
| import org.openide.loaders.DataObjectNotFoundException; |
| import org.openide.nodes.FilterNode; |
| import org.openide.nodes.Node; |
| import org.openide.util.NbBundle; |
| |
| public class RssNode extends <a href="http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/FilterNode.html">FilterNode</a> { |
| |
| /** Declaring the children of the root RSS node */ |
| public RssNode(Node folderNode) { |
| super(folderNode, new RssFolderChildren(folderNode)); |
| } |
| |
| /** Declaring the Add Feed action and Add Folder action */ |
| @Override |
| public Action[] getActions(boolean popup) { |
| DataFolder df = getLookup().lookup(DataFolder.class); |
| return new Action[] { |
| new AddRssAction(df), |
| new AddFolderAction(df) |
| }; |
| } |
| |
| /** Getting the root node */ |
| public static class RootRssNode extends RssNode { |
| public RootRssNode() throws DataObjectNotFoundException { |
| super(DataObject.find( |
| FileUtil.getConfigFile("RssFeeds")).getNodeDelegate()); |
| } |
| @Override |
| public String getDisplayName() { |
| return NbBundle.getMessage(RssNode.class, "FN_title"); |
| } |
| } |
| |
| }</pre> |
| </li> |
| </ol> |
| <p>Several red underline markings remain in the class, because |
| you have not yet created the RssFolderChildren class, the OneFeedNode class, the AddRssAction class, and the AddFolderAction class.</p> |
| |
| |
| <h3><a name="rssfolderchildren"></a>Creating the Root Node Children</h3> |
| |
| <p>In this section, we create the children of the root node. Each child |
| is a folder, containing RSS feeds. The RSS feeds, in turn, |
| contain the articles that are shown in a browser.</p> |
| |
| <p>To create this class, take the following steps:</p> |
| |
| <ol> |
| <li> |
| Create <tt>RssFolderChildren.java</tt> |
| in the <tt>org.myorg.feedreader</tt> |
| package.</li> |
| <li> |
| <p>Replace the default class with the following:</p> |
| <pre class="examplecode">import java.io.IOException; |
| import org.netbeans.feedreader.Feed; |
| import org.openide.filesystems.FileObject; |
| import org.openide.loaders.DataFolder; |
| import org.openide.nodes.FilterNode; |
| import org.openide.nodes.Node; |
| import org.openide.util.Exceptions; |
| |
| /** Getting the children of the root node */ |
| public class RssFolderChildren extends FilterNode.Children { |
| |
| RssFolderChildren(Node rssFolderNode) { |
| super(rssFolderNode); |
| } |
| |
| @Override |
| protected Node[] createNodes(Node n) { |
| if (n.getLookup().lookup(DataFolder.class) != null) { |
| return new Node[]{new RssNode(n)}; |
| } else { |
| Feed feed = getFeed(n); |
| if (feed != null) { |
| try { |
| return new Node[]{new OneFeedNode(n, feed.getSyndFeed())}; |
| } catch (IOException ioe) { |
| Exceptions.printStackTrace(ioe); |
| } |
| } |
| } |
| // best effort |
| return new Node[]{new FilterNode(n)}; |
| } |
| |
| /** Looking up a feed */ |
| private static Feed getFeed(Node node) { |
| Feed f = FileUtil.getConfigObject("RssFeeds/sample.instance", Feed.class); |
| if (f == null) { |
| throw new IllegalStateException("Bogus file in feeds folder: " + |
| node.getLookup().lookup(FileObject.class)); |
| } |
| return f; |
| } |
| |
| }</pre> |
| |
| </li> |
| |
| </ol> |
| |
| |
| <h3><a name="action1"></a>Creating the Add Feed Action</h3> |
| |
| <p>In this section, we create the menu item that adds new |
| feeds. As you can see in the previous section, the |
| Add Feed Action is bound to the context-menu of the |
| Root Node.</p> |
| |
| <p>To create this class, take the following steps:</p> |
| |
| <ol> |
| <li> |
| Create <tt>AddRssAction.java</tt> |
| in the <tt>org.myorg.feedreader</tt> |
| package.</li> |
| <li> |
| <p>Replace the default class with the following:</p> |
| <pre class="examplecode">import java.awt.event.ActionEvent; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.ObjectOutputStream; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import javax.swing.AbstractAction; |
| import org.netbeans.feedreader.Feed; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.filesystems.FileLock; |
| import org.openide.filesystems.FileObject; |
| import org.openide.loaders.DataFolder; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| |
| /** An action for adding a feed */ |
| public class AddRssAction extends AbstractAction { |
| |
| private final DataFolder folder; |
| |
| public AddRssAction(DataFolder df) { |
| super(NbBundle.getMessage(AddRssAction.class, "FN_addbutton")); |
| folder = df; |
| } |
| |
| public void actionPerformed(ActionEvent ae) { |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/NotifyDescriptor.html">NotifyDescriptor</a>.InputLine nd = new NotifyDescriptor.InputLine( |
| NbBundle.getMessage(AddRssAction.class, "FN_askurl_msg"), |
| NbBundle.getMessage(AddRssAction.class, "FN_askurl_title"), |
| NotifyDescriptor.OK_CANCEL_OPTION, |
| NotifyDescriptor.PLAIN_MESSAGE); |
| |
| Object result = <a href="http://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/DialogDisplayer.html">DialogDisplayer</a>.getDefault().notify(nd); |
| |
| if (result.equals(NotifyDescriptor.OK_OPTION)) { |
| String urlString = nd.getInputText(); |
| URL url; |
| try { |
| url = new URL(urlString); |
| } catch (MalformedURLException e) { |
| String message = NbBundle.getMessage(AddRssAction.class, "FN_askurl_err", urlString); |
| Exceptions.attachLocalizedMessage(e, message); |
| Exceptions.printStackTrace(e); |
| return; |
| } |
| try { |
| checkConnection(url); |
| } catch (IOException e) { |
| String message = NbBundle.getMessage(AddRssAction.class, "FN_cannotConnect_err", urlString); |
| Exceptions.attachLocalizedMessage(e, message); |
| Exceptions.printStackTrace(e); |
| return; |
| } |
| Feed f = new Feed(url); |
| FileObject fld = folder.getPrimaryFile(); |
| String baseName = "RssFeed"; |
| int ix = 1; |
| while (fld.getFileObject(baseName + ix, "ser") != null) { |
| ix++; |
| } |
| try { |
| FileObject writeTo = fld.createData(baseName + ix, "ser"); |
| FileLock lock = writeTo.lock(); |
| try { |
| ObjectOutputStream str = new ObjectOutputStream(writeTo.getOutputStream(lock)); |
| try { |
| str.writeObject(f); |
| } finally { |
| str.close(); |
| } |
| } finally { |
| lock.releaseLock(); |
| } |
| } catch (IOException ioe) { |
| Exceptions.printStackTrace(ioe); |
| } |
| } |
| } |
| |
| private static void checkConnection(final URL url) throws IOException { |
| InputStream is = url.openStream(); |
| is.close(); |
| } |
| |
| }</pre> |
| |
| </li> |
| |
| </ol> |
| |
| <h3><a name="action2"></a>Creating the Add Folder Action</h3> |
| |
| <p>In this section, we create the menu item that adds new |
| folders, in which new feeds can be created. As you can see in an earlier section, the |
| Add Folder Action is bound to the context-menu of the |
| Root Node.</p> |
| |
| <p>To create this class, take the following steps:</p> |
| |
| <ol> |
| <li> |
| Create <tt>AddFolderAction.java</tt> |
| in the <tt>org.myorg.feedreader</tt> |
| package.</li> |
| <li> |
| <p>Replace the default class with the following:</p> |
| <pre class="examplecode">import java.awt.event.ActionEvent; |
| import java.io.IOException; |
| import javax.swing.AbstractAction; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.loaders.DataFolder; |
| import org.openide.util.Exceptions; |
| import org.openide.util.NbBundle; |
| |
| /** An action for adding a folder to organize feeds into groups */ |
| public class AddFolderAction extends AbstractAction { |
| |
| private final DataFolder folder; |
| |
| public AddFolderAction(DataFolder df) { |
| super(NbBundle.getMessage(AddFolderAction.class, "FN_addfolderbutton")); |
| folder = df; |
| } |
| |
| public void actionPerformed(ActionEvent ae) { |
| <a href="http://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/NotifyDescriptor.html">NotifyDescriptor</a>.InputLine nd = new NotifyDescriptor.InputLine( |
| NbBundle.getMessage(AddFolderAction.class, "FN_askfolder_msg"), |
| NbBundle.getMessage(AddFolderAction.class, "FN_askfolder_title"), |
| NotifyDescriptor.OK_CANCEL_OPTION, |
| NotifyDescriptor.PLAIN_MESSAGE); |
| |
| Object result = <a href="http://bits.netbeans.org/dev/javadoc/org-openide-dialogs/org/openide/DialogDisplayer.html">DialogDisplayer</a>.getDefault().notify(nd); |
| |
| if (result.equals(NotifyDescriptor.OK_OPTION)) { |
| final String folderString = nd.getInputText(); |
| try { |
| DataFolder.create(folder, folderString); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| } |
| |
| }</pre> |
| |
| </li> |
| |
| </ol> |
| |
| <h3><a name="feed"></a>Creating the Feed Node</h3> |
| |
| <p>Here we are concerned with the container for the article nodes, as shown below |
| for the 'NetBeans Highlights' node:</p> |
| |
| <p> |
| <img alt="" src="../../images/tutorials/feedreader/60-actions2.png"/> |
| </p> |
| |
| <p>As can be seen, each of these nodes has |
| a display name, retrieved from the feed, an icon, and a Delete menu item.</p> |
| |
| <p>Take the following steps to create this class: </p> |
| |
| <ol> |
| <li> |
| Create <tt>OneFeedNode.java</tt> |
| in the <tt>org.myorg.feedreader</tt> |
| package.</li> |
| <li> |
| Replace the default class with the following: |
| |
| <pre class="examplecode">import com.sun.syndication.feed.synd.SyndFeed; |
| import java.awt.Image; |
| import javax.swing.Action; |
| import org.openide.actions.DeleteAction; |
| import org.openide.nodes.FilterNode; |
| import org.openide.nodes.Node; |
| import org.openide.util.ImageUtilities; |
| import org.openide.util.Lookup; |
| import org.openide.util.actions.SystemAction; |
| import org.openide.util.lookup.Lookups; |
| import org.openide.util.lookup.ProxyLookup; |
| |
| /** Getting the feed node and wrapping it in a FilterNode */ |
| public class OneFeedNode extends <a href="http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/FilterNode.html">FilterNode</a> { |
| |
| OneFeedNode(Node feedFileNode, SyndFeed feed) { |
| super(feedFileNode, |
| new FeedChildren(feed), |
| new ProxyLookup(new Lookup[]{ |
| Lookups.fixed(new Object[]{feed}), |
| feedFileNode.getLookup() |
| })); |
| } |
| |
| @Override |
| public String getDisplayName() { |
| SyndFeed feed = getLookup().lookup(SyndFeed.class); |
| return feed.getTitle(); |
| } |
| |
| @Override |
| public Image getIcon(int type) { |
| return <a href="http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/ImageUtilities.html">ImageUtilities</a>.loadImage("org/netbeans/feedreader/rss16.gif"); |
| } |
| |
| @Override |
| public Image getOpenedIcon(int type) { |
| return getIcon(type); |
| } |
| |
| @Override |
| public Action[] getActions(boolean context) { |
| return new Action[]{SystemAction.get(<a href="http://bits.netbeans.org/dev/javadoc/org-openide-actions/org/openide/actions/DeleteAction.html">DeleteAction</a>.class)}; |
| } |
| |
| }</pre> |
| |
| </li> |
| |
| </ol> |
| |
| <p>Several red underline markings remain in the class, because |
| we have not created our <tt>FeedChildren</tt> class yet. Here it is:</p> |
| |
| <pre class="examplecode">import com.sun.syndication.feed.synd.SyndEntry; |
| import com.sun.syndication.feed.synd.SyndFeed; |
| import java.beans.IntrospectionException; |
| import org.openide.nodes.Children; |
| import org.openide.nodes.Node; |
| import org.openide.util.NbCollections; |
| |
| /** Defining the children of a feed node */ |
| public class FeedChildren extends <a href="http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/Children.Keys.html">Children.Keys<SyndEntry></a> { |
| |
| private final SyndFeed feed; |
| |
| public FeedChildren(SyndFeed feed) { |
| this.feed = feed; |
| } |
| |
| @Override |
| protected void addNotify() { |
| setKeys(<a href="http://bits.netbeans.org/dev/javadoc/org-openide-util/org/openide/util/NbCollections.html#checkedListByCopy%28java.util.List,%20java.lang.Class,%20boolean%29">NbCollections.checkedListByCopy</a>(feed.getEntries(), SyndEntry.class, true)); |
| } |
| |
| @Override |
| public Node[] createNodes(SyndEntry entry) { |
| try { |
| return new Node[]{new EntryBeanNode(entry)}; |
| } catch (IntrospectionException ex) { |
| assert false : ex; |
| return new Node[0]; |
| } |
| } |
| |
| }</pre> |
| |
| |
| <h3><a name="entry"></a>Creating the Entry Node</h3> |
| |
| <p>Finally, we deal with the lowest level nodes, those |
| that represent articles provided by the feed.</p> |
| |
| <p>To create this class, take the following steps:</p> |
| |
| <ol> |
| <li> |
| Create <tt>OneEntryNode.java</tt> |
| in the <tt>org.myorg.feedreader</tt> |
| package.</li> |
| <li> |
| Replace the default class with the following: |
| <pre class="examplecode">import com.sun.syndication.feed.synd.SyndEntry; |
| import java.beans.IntrospectionException; |
| import javax.swing.Action; |
| import org.openide.actions.OpenAction; |
| import org.openide.nodes.BeanNode; |
| import org.openide.nodes.FilterNode; |
| import org.openide.util.actions.SystemAction; |
| import org.openide.util.lookup.Lookups; |
| |
| /** Wrapping the children in a FilterNode */ |
| public class OneEntryNode extends <a href="http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/nodes/FilterNode.html">FilterNode</a> { |
| |
| private final SyndEntry entry; |
| |
| public OneEntryNode(SyndEntry entry) throws IntrospectionException { |
| super(new BeanNode<SyndEntry>(entry), Children.LEAF, |
| Lookups.fixed(entry, new EntryOpenCookie(entry))); |
| this.entry = entry; |
| } |
| |
| /** Using HtmlDisplayName ensures any HTML in RSS entry titles are properly handled, escaped, entities resolved, etc. */ |
| @Override |
| public String getHtmlDisplayName() { |
| return entry.getTitle(); |
| } |
| |
| /** Making a tooltip out of the entry's description */ |
| @Override |
| public String getShortDescription() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("Author: ").append(entry.getAuthor()).append("; "); |
| if (entry.getPublishedDate() != null) { |
| sb.append("Published: ").append(entry.getPublishedDate().toString()); |
| } |
| return sb.toString(); |
| } |
| |
| /** Providing the Open action on a feed entry */ |
| @Override |
| public Action[] getActions(boolean popup) { |
| return new Action[]{SystemAction.get(<a href="http://bits.netbeans.org/dev/javadoc/org-openide-actions/org/openide/actions/OpenAction.html">OpenAction</a>.class)}; |
| } |
| |
| @Override |
| public Action getPreferredAction() { |
| return getActions(false)[0]; |
| } |
| |
| /** Specifying what should happen when the user invokes the Open action */ |
| private static class EntryOpenCookie implements <a href="http://bits.netbeans.org/dev/javadoc/org-openide-nodes/org/openide/cookies/OpenCookie.html">OpenCookie</a> { |
| |
| private final SyndEntry entry; |
| |
| EntryOpenCookie(SyndEntry entry) { |
| this.entry = entry; |
| } |
| |
| @Override |
| public void open() { |
| try { |
| URLDisplayer.getDefault().showURL(new URL(entry.getUri())); |
| } catch (MalformedURLException mue) { |
| Exceptions.printStackTrace(mue); |
| } |
| } |
| |
| } |
| |
| }</pre> |
| |
| <p class="tips">Above, you use the NetBeans <tt><a href="http://bits.netbeans.org/dev/javadoc/org-openide-awt/org/openide/awt/HtmlBrowser.URLDisplayer.html">URLDisplayer</a></tt> class to open an entry |
| in the Swing browser. See the completed sample (referred to in |
| the Troubleshooting section below) for code that lets you |
| create your own TopComponent, containing a JEditorPane for |
| displaying your entries.</p> |
| </li> |
| </ol> |
| <h3>Localizing the RssNode Class</h3> |
| |
| <ol> |
| <li> |
| Open the <tt>FeedReader</tt> |
| module's <tt>Bundle.properties</tt> |
| file.</li> |
| <li> |
| Add the following key-value pairs: |
| |
| <pre class="examplecode">FN_title=RSS/Atom Feeds |
| FN_addbutton=Add |
| FN_askurl_title=New Feed |
| FN_askurl_msg=Enter the URL of an RSS/Atom Feed |
| FN_askurl_err=Invalid URL: {0}| |
| FN_askfolder_msg=Enter the folder name |
| FN_askfolder_title=New Folder</pre> |
| |
| </li> |
| </ol> |
| |
| <p>Here is an explanation of the new key-value pairs, which localize strings defined |
| in <tt>RssNode.java</tt>:</p> |
| <ul> |
| <li> |
| <b> |
| FN_title.</b> |
| Localizes the label of the highest node in the Feed Window.</li> |
| </ul> |
| |
| <p>Localization of user interface for adding a feed:</p> |
| <ul> |
| <li> |
| <b> |
| FN_addbutton.</b> |
| Localizes the label of the Add menu item that appears in the highest node's pop-up.</li> |
| <li> |
| <b> |
| FN_askurl_title.</b> |
| Localizes the title of the New Feed dialog box.</li> |
| <li> |
| <b> |
| FN_askurl_msg.</b> |
| Localizes the message that appears in the New Feed dialog box.</li> |
| <li> |
| <b> |
| FN_askurl_err.</b> |
| Localizes the error string that is displayed if the URL is invalid.</li> |
| </ul> |
| </div> |
| <h2><a name="running"></a>Run the Application</h2> |
| <p> |
| Right-click the application and choose Run. You should see the application shown at the start of this tutorial:</p> |
| |
| <p><img alt="completed app" src="../../images/tutorials/feedreader/70/feedreader.png"/></p> |
| |
| <h2><a name="troubleshooting"></a>Troubleshooting</h2> |
| <p> |
| If you encounter problems during this tutorial, get the completed sample |
| here, in the New Project wizard (Ctrl-Shift-N):</p> |
| |
| <p><img alt="sample" src="../../images/tutorials/feedreader/70/sample.png"/></p> |
| |
| <!-- <p><b>Note:</b> The completed sample provided by the New Project wizard, above, |
| is slightly different to the code used in this tutorial. Though the end |
| result is the same to the user of the application, the source code |
| in the completed sample is different mainly in that its Nodes extend |
| the FilterNode class, |
| rather than the AbstractNode class used throughout this tutorial.</p> |
| --> |
| <div class="feedback-box"><a href="https://netbeans.org/about/contact_form.html?to=3&subject=Feedback: NetBeans Platform 7.1 Feed Reader Tutorial">Send Us Your Feedback</a> |
| <br style="clear:both;" /></div> |
| </body> |
| </html> |