Download NetBeans!

20090831 Monday August 31, 2009

Get Notified When A Window Opens/Closes (Part 1)

public class Installer extends ModuleInstall {

    private Result<DataObject> res;

    @Override
    public void restored() {
        WindowManager.getDefault().invokeWhenUIReady(new DemoRunnable());
    }

    private class DemoRunnable implements Runnable {
        @Override
        public void run() {
            final TopComponent.Registry registry = TopComponent.getRegistry();
            registry.addPropertyChangeListener(new PropertyChangeListener() {
                @Override
                public void propertyChange(PropertyChangeEvent evt) {
                    TopComponent tc = registry.getActivated();
                    if (tc != null) {
                        res = tc.getLookup().lookupResult(DataObject.class);
                        EditorCookie ec = tc.getLookup().lookup(EditorCookie.class);
                        if (ec != null && ec.getDocument() != null && res.allInstances().iterator().hasNext()) {
                            FileObject fo = res.allInstances().iterator().next().getPrimaryFile();
                            StatusDisplayer.getDefault().setStatusText("Open: " + fo.getPath());
                        } else {
                            StatusDisplayer.getDefault().setStatusText("Closed...");
                        }
                    }
                }
            });
        }
    }

}

Aug 31 2009, 03:32:53 PM PDT Permalink

Download NetBeans!

20090830 Sunday August 30, 2009

Bye Blinking Cursor

Life without a blinking cursor is much better, more peaceful, surprisingly calming. I never considered such a thing before reading the comments here. I found an old thread on the dev alias, by Emilian Bold (here), and now my cursor doesn't blink at all anymore.

Here's the whole module, with all the code (i.e., set blink rate per editor type) in its simplest implementation (i.e., without Options window extension):

Hoping to see this functionality (within the Options window) as a standard part of NetBeans IDE.

Aug 30 2009, 02:30:47 AM PDT Permalink

Download NetBeans!

20090829 Saturday August 29, 2009

Intersystems Caché Management on the NetBeans Platform

In the last few days, I found out about a number of different applications making use of the NetBeans Platform that I'd never heard of before.

Here's one of them: Caché Monitor. It is a standalone SQL development tool for working with Intersystems Caché. To see some of the details, click the pics below to enlarge them:

What does this application gain from the NetBeans Platform? According to its home page:

  • A window system that greatly simplifies the manipulation of multiple components within a single frame.
  • An Actions system that makes it easy to declaratively install and uninstall menu items, toolbar items, keyboard shortcuts, etc.
  • The Auto Update mechanism, which provides users a way to dynamically update their installation of your application.
  • ... and more ;-)

Interestingly, this isn't the first database-related tool on the NetBeans Platform. Read about a related project in Egypt here and one in Russia here.

I will write about the other applications I found out about, and hopefully interview the developers behind them, in the coming days. Did you also develop (or are you also developing) an application on the NetBeans Platform? Drop me an e-mail and we'll add a screenshot to our cool screenshots page and publicize the cool work you're doing!

Aug 29 2009, 02:44:13 PM PDT Permalink

Download NetBeans!

20090828 Friday August 28, 2009

Turn PS into PDF

Made a small plugin that lets me generate PDF from PS, via the "ps2pdf" utility on Ubuntu (and probably other Linuxes and other os'es):

Aside from the NetBeans API classes relating to files, I used the following for the above integration:

  • org.netbeans.api.progress.ProgressHandle
  • org.netbeans.api.progress.ProgressHandleFactory
  • org.openide.execution.NbProcessDescriptor
  • org.openide.util.RequestProcessor
  • org.openide.windows.IOProvider
  • org.openide.windows.InputOutput
  • org.openide.windows.OutputWriter

As a result, I can write the output from the external process to the Output window in the IDE, I prevent the GUI from hanging while the processing occurs, and I integrate with the IDE's progress bar.

Aug 28 2009, 11:50:33 AM PDT Permalink

Download NetBeans!

20090825 Tuesday August 25, 2009

How to Serialize Visual Library Scenes

The many Visual Library samples in "contrib" include one called "serialization". I used that sample today to serialize one of my own scenes.

Before showing the code, some screenshots. First, I drag and drop one or two items from the palette onto my scene:

Next, after resizing them (code for which you can find via the NetBeans Platform Tutorials page, bearing in mind this issue), I save them, using the Save menu item:

Then, simply as a result of using the above menu item, this XML file is created, containing the state of my objects, which I can then load into the scene via the Load menu item above:

Here's how to do it (but, again, have a look at the sample pointed out above, in the first sentence of this blog entry). Before looking at the code below, you need to be aware that I have a business object called "AWidget", which has a name, an icon, a width, a height, and a location. I also have a scene called "AWidgetGraphScene", which extends "GraphScene", using my business object for the node of the scene. The scene has no edges.

  1. In my scene constructor:
    final JPopupMenu menu = new JPopupMenu();
    
    JMenuItem load = new JMenuItem("Load scene...");
    load.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            load ();
        }
    });
    menu.add(load);
    
    JMenuItem save = new JMenuItem("Save scene...");
    save.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            save();
        }
    });
    menu.add(save);
    
    getActions().addAction(ActionFactory.createPopupMenuAction(new PopupMenuProvider() {
        @Override
        public JPopupMenu getPopupMenu(Widget widget, Point localLocation) {
            return menu;
        }
    }));

  2. Here are the definitions of "load()" and "save()":
    private void load () {
        JFileChooser chooser = new JFileChooser ();
        chooser.setDialogTitle ("Load Scene ...");
        chooser.setMultiSelectionEnabled (false);
        chooser.setFileSelectionMode (JFileChooser.FILES_ONLY);
        if (chooser.showOpenDialog (getView ()) == JFileChooser.APPROVE_OPTION) {
            for (String edge : new ArrayList (getEdges ()))
                removeEdge (edge);
            for (AWidget node : new ArrayList (getNodes ()))
                removeNode (node);
            SceneSerializer.deserialize (this, chooser.getSelectedFile ());
            validate ();
        }
    }
    
    private void save() {
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("Save Scene ...");
        chooser.setMultiSelectionEnabled(false);
        chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
        if (chooser.showSaveDialog(getView()) == JFileChooser.APPROVE_OPTION) {
            SceneSerializer.serialize(this, chooser.getSelectedFile());
        }
    }

  3. And here, almost identical to the original referred to above, is the "SceneSerializer" class:
    import org.netbeans.api.visual.widget.Widget;
    import org.openide.util.Exceptions;
    import org.openide.xml.XMLUtil;
    import org.w3c.dom.*;
    import org.xml.sax.ErrorHandler;
    import org.xml.sax.InputSource;
    import org.xml.sax.SAXException;
    import org.xml.sax.SAXParseException;
    
    import java.awt.*;
    import java.io.*;
    
    public class SceneSerializer {
    
        private static final String SCENE_ELEMENT = "Scene"; // NOI18N
        private static final String VERSION_ATTR = "version"; // NOI18N
    
        private static final String SCENE_NODE_COUNTER_ATTR = "nodeIDcounter"; // NOI18N
    
        private static final String NODE_ELEMENT = "Node"; // NOI18N
        private static final String NODE_ID_ATTR = "id"; // NOI18N
        private static final String NODE_X_ATTR = "x"; // NOI18N
        private static final String NODE_Y_ATTR = "y"; // NOI18N
        private static final String NODE_WIDTH = "w"; // NOI18N
        private static final String NODE_HEIGHT = "h"; // NOI18N
        private static final String NODE_ICON = "icon"; // NOI18N
    
        private static final String VERSION_VALUE_1 = "1"; // NOI18N
    
        // call in AWT to serialize scene
        public static void serialize (AWidgetGraphScene scene, File file) {
            Document document = XMLUtil.createDocument (SCENE_ELEMENT, null, null, null);
    
            Node sceneElement = document.getFirstChild ();
            setAttribute (document, sceneElement, VERSION_ATTR, VERSION_VALUE_1);
            setAttribute (document, sceneElement, SCENE_NODE_COUNTER_ATTR, Long.toString (scene.nodeIDcounter));
    
            for (AWidget node : scene.getNodes ()) {
                Element nodeElement = document.createElement (NODE_ELEMENT);
                setAttribute (document, nodeElement, NODE_ID_ATTR, node.getType());
                Widget widget = scene.findWidget (node);
                Point location = widget.getPreferredLocation ();
                setAttribute (document, nodeElement, NODE_WIDTH, Integer.toString (widget.getClientArea().width));
                setAttribute (document, nodeElement, NODE_HEIGHT, Integer.toString (widget.getClientArea().height));
                setAttribute (document, nodeElement, NODE_X_ATTR, Integer.toString (location.x));
                setAttribute (document, nodeElement, NODE_Y_ATTR, Integer.toString (location.y));
                setAttribute (document, nodeElement, NODE_ICON, node.getIcon());
                sceneElement.appendChild (nodeElement);
            }
    
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream (file);
                XMLUtil.write (document, fos, "UTF-8"); // NOI18N
            } catch (Exception e) {
                Exceptions.printStackTrace (e);
            } finally {
                try {
                    if (fos != null) {
                        fos.close ();
                    }
                } catch (Exception e) {
                    Exceptions.printStackTrace (e);
                }
            }
        }
    
        // call in AWT to deserialize scene
        public static void deserialize (AWidgetGraphScene scene, File file) {
            Node sceneElement = getRootNode (file);
            if (! VERSION_VALUE_1.equals (getAttributeValue (sceneElement, VERSION_ATTR)))
                return;
            scene.nodeIDcounter = Long.parseLong (getAttributeValue (sceneElement, SCENE_NODE_COUNTER_ATTR));
            for (Node element : getChildNode (sceneElement)) {
                if (NODE_ELEMENT.equals (element.getNodeName ())) {
                    String type = getAttributeValue (element, NODE_ID_ATTR);
                    int x = Integer.parseInt (getAttributeValue (element, NODE_X_ATTR));
                    int y = Integer.parseInt (getAttributeValue (element, NODE_Y_ATTR));
                    int width = Integer.parseInt (getAttributeValue (element, NODE_WIDTH));
                    int height = Integer.parseInt (getAttributeValue (element, NODE_HEIGHT));
                    String icon = getAttributeValue (element, NODE_ICON);
                    AWidget aWidget = new AWidget();
                    aWidget.setType(type);
                    aWidget.setIcon(icon);
                    Widget nodeWidget = scene.addNode(aWidget);
                    nodeWidget.setPreferredLocation (new Point (x, y));
                    nodeWidget.setPreferredSize (new Dimension (width, height));
                } 
            }
        }
    
        private static void setAttribute (Document xml, Node node, String name, String value) {
            NamedNodeMap map = node.getAttributes ();
            Attr attribute = xml.createAttribute (name);
            attribute.setValue (value);
            map.setNamedItem (attribute);
        }
    
        private static Node getRootNode (File file) {
            FileInputStream is = null;
            try {
                is = new FileInputStream (file);
                Document doc = XMLUtil.parse (new InputSource (is), false, false, new ErrorHandler() {
                    public void error (SAXParseException e) throws SAXException {
                        throw new SAXException (e);
                    }
    
                    public void fatalError (SAXParseException e) throws SAXException {
                        throw new SAXException (e);
                    }
    
                    public void warning (SAXParseException e) {
                        Exceptions.printStackTrace (e);
                    }
                }, null);
                return doc.getFirstChild ();
            } catch (Exception e) {
                Exceptions.printStackTrace (e);
            } finally {
                try {
                    if (is != null)
                        is.close ();
                } catch (IOException e) {
                    Exceptions.printStackTrace (e);
                }
            }
            return null;
        }
    
        private static String getAttributeValue (Node node, String attr) {
            try {
                if (node != null) {
                    NamedNodeMap map = node.getAttributes ();
                    if (map != null) {
                        node = map.getNamedItem (attr);
                        if (node != null)
                            return node.getNodeValue ();
                    }
                }
            } catch (DOMException e) {
                Exceptions.printStackTrace (e);
            }
            return null;
        }
    
        private static Node[] getChildNode (Node node) {
            NodeList childNodes = node.getChildNodes ();
            Node[] nodes = new Node[childNodes != null ? childNodes.getLength () : 0];
            for (int i = 0; i < nodes.length; i++)
                nodes[i] = childNodes.item (i);
            return nodes;
        }
    
    }

That's it. Put the generated XML page into a MultiViewElement and then you have the makings of a GUI editor.

Aug 25 2009, 05:44:22 AM PDT Permalink

Download NetBeans!

20090823 Sunday August 23, 2009

Wicket Components in NetBeans HTML Palette

I've started re-adding the palette with Wicket components to the Wicket plugin for NetBeans IDE. The old approach, done by Petr Pisl using MDR, no longer worked with the Retouche APIs and so we simply disabled that palette with the introduction of the 6.0 Java editor. Now, however, I've used the Retouche APIs so that when an item is dropped into the HTML editor, some corresponding code is dropped into the constructor on the Java side.

Here you can see the palette, i.e., bottom right of this screenshot:

Below I show you two examples. First, I drag the "Label" item from the palette into the HTML editor:

Then, when I open the matching Java file, I see that a label with a matching ID has been added to the constructor:

Next, I drag the "DropDown List" item into the HTML editor, with this result:

As a result, on the Java side, the following lines have magically been added:

I'm now working on the AJAX autocomplete textfield (which will use this code). To this end, I've exposed the Wicket Extensions JAR to the Library Manager, via the plugin, so that the related AJAX class from that JAR can be used.

If anyone would like specific Wicket components to be prioritized, please let me know. Otherwise, I'll add whatever Wicket components I happen to like to the palette. What you see above is already part of the "nb_67" branch of the Wicket plugin and will be available as part of the next binary distribution.

Aug 23 2009, 07:38:04 AM PDT Permalink

Download NetBeans!

20090821 Friday August 21, 2009

Navigation for Related Wicket Artifacts in NetBeans IDE

I reused some of Tim Boudreau's cool utility methods in the Wicket plugin and wired them up to actions in the Java editor and HTML editor. Here's a new action in the Java editor, if we're dealing with a Wicket project and there's a matching markup artifact:

However, if these conditions aren't true, the action isn't enabled:

Same thing in the HTML editor:

These actions (which resolve issue 12 in nbwicketsupport Issuezilla) are in the "nb_67" branch of the nbwicketsupport project on java.net and will be part of the next distribution of the Wicket plugin binaries in the NetBeans Plugin Portal.

Any suggestions what keyboard shortcuts I should attach to these actions? If you're using Wicket in NetBeans IDE, you're invited to leave suggestions for keyboard shortcuts for these actions in the comments to this blog entry. In the absence of consensus, the feuding parties will need to pinky wrestle, and the winner's choice will abide.

Aug 21 2009, 04:38:10 AM PDT Permalink

Download NetBeans!

20090820 Thursday August 20, 2009

Updates for Wicket Plugin in NetBeans IDE 6.7

Several updates to the Wicket plugin for NetBeans IDE 6.7 (or above):

http://plugins.netbeans.org/PluginPortal/faces/PluginDetailPage.jsp?pluginid=3586

When you click the Download button, you should get a ZIP with a name ending in "Wicket4NB67-3.zip". That's the right one, the previous one ended "Wicket4NB67-2.zip".

The changes are as follows:

  • Libraries from the final release of 1.4 Wicket are bundled and the 1.4 RC-5 libraries are removed.

  • Enhancements from Hantsy Bai: The "Frameworks" panel in the Project Properties dialog should now correctly display whether the current application supports Wicket. In addition, the Login sample and the Pizza sample now both use the Wicket filter instead of the Wicket servlet. Both samples now correctly reflect in the "Frameworks" panel that they support Wicket.

  • Enhancements from Daniel Toffetti: The Login sample now works correctly because the sample has been changed so that Wicket's built in support for https is used instead of the original hand-coded support in the sample. I believe Daniel wins a prize for this fix, I recall promising a prize for whoever provided this fix.

  • Enhancements from Milos Kleint: Maven projects now work with Wicket, thanks to patches provided by Milos, (issue 6 and issue 14). Read about this enhancement here.

  • Enhancements from Tim Boudreau: Code templates for Wicket. Try typing the word "form" (i.e., without the quotation marks) in a Wicket page constructor and then press the TAB key. Then do the same with "lbl".

I haven't tried the plugin at all in 6.7.1. Hopefully someone else will do so and create an issue if it doesn't work there, with as much info as possible. Feedback on the above is very welcome, as issues in the plugin's Issuezilla.

That's it for this time. Great to have several contributions! Many more welcome, of course (all in "nb_67" branch, please).

Aug 20 2009, 02:58:54 PM PDT Permalink

Download NetBeans!

20090819 Wednesday August 19, 2009

Employee Attendance & Task Management on the NetBeans Platform

A Java application by a company offering NetBeans Platform trainings, in combination with Maven & Spring:

Click to enlarge the images. Read more about it here.

Aug 19 2009, 12:44:54 PM PDT Permalink

Download NetBeans!

20090818 Tuesday August 18, 2009

Bonn on the NetBeans Platform!

I spent a weekend in Bonn, Germany, with fellow trainers Anton (Toni) Epple from Eppleton and Aljoscha (Josh) Rittner from Sepix, giving a training organized by AureliusConsult, a student consultancy:

As always, there was a group picture near the end of the course, (this one taken by Toni which is why he isn't in it):

We covered the basic principles of the NetBeans Platform. Below you see Josh explaining the Nodes API:

And here's Toni talking about the Visual Library API:

AureliusConsult took us on a tour of Bonn and we had a fun dinner together:

The program for the two days was as follows:

Saturday

Sunday

The course was yet another edition of the Community-Based NetBeans Platform Certified Training, which is offered for free to educational institutions and non-profit organizations. (Get at least 10 or so enthusiastic Java Swing programmers together and then write to users AT edu DOT netbeans DOT org and we'll figure out a way to get the course delivered to you.) The commercial version of the course is offered through NetBeans Platform for Desktop Java Development (DTJ-2601) by Sun Learning Services.

Aug 18 2009, 12:52:02 AM PDT Permalink

Download NetBeans!

20090817 Monday August 17, 2009

How to Create an Action Annotation for NetBeans Platform Applications

Let's use org.openide.filesystems.annotations.LayerGeneratingProcessor for the first time. We will use it to process an @Action annotation which we will be able to set on ActionListeners in NetBeans Platform applications, as shown yesterday.

Here's the definition of my @Action annotation, with Javadoc for "path()", so that you can see how, in yesterday's blog entry, the second screenshot was able to show Javadoc.

package org.demo.action.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Action {

    int position() default Integer.MAX_VALUE;

    String displayName();

    /**
     * The path to the folder where this action will be registered.
     * The menu item and/or toolbar button will be registered
     * in the same folder. For example, if "Edit" is returned,
     * the action will be registered in (at least) "Actions/Edit",
     * as well as, optionally, "Menu/Edit" and "Toolbars/Edit".
     * @return String (default is "File")
     */
    String path() default "File";

    String iconBase() default "";

    boolean menuBar() default false;

    boolean toolBar() default false;
    
}

Take note that the @Target is set to ElementType.TYPE. That means that the @Action annotation will be settable on the class level, i.e., as opposed to method level or field level or something else. Also note that @Retention is set to RetentionPolicy.SOURCE, which means that the the @Action annotation will be processed at compile time, i.e., as opposed to runtime.

So, now, I can annotate classes like this:

package org.demo.action;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.demo.action.annotation.Action;

@Action(position = 1, displayName = "#key",
menuBar = true, toolBar = true, iconBase = "org/demo/action/icon.png")
public class DemoActionListener implements ActionListener {
    public void actionPerformed(ActionEvent e) {
        System.out.println("hello world");
    }
}

When I build the module that contains the above ActionListener... guess what? In build/classes/META-INF, I see an XML file named "generated-layer.xml", which has all of the following content:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.2//EN" "http://www.netbeans.org/dtds/filesystem-1_2.dtd">
<filesystem>
    <folder name="Actions">
        <folder name="File">
            <file name="org-demo-action-DemoActionListener.instance">
                <attr name="delegate" newvalue="org.demo.action.DemoActionListener"/>
                <attr bundlevalue="org.demo.action.Bundle#key" name="displayName"/>
                <attr name="iconBase" stringvalue="org/demo/action/icon.png"/>
                <attr methodvalue="org.openide.awt.Actions.alwaysEnabled" name="instanceCreate"/>
            </file>
        </folder>
    </folder>
    <folder name="Menu">
        <folder name="File">
            <file name="org-demo-action-DemoActionListener.shadow">
                <attr name="originalFile" stringvalue="Actions/File/org-demo-action-DemoActionListener.instance"/>
                <attr intvalue="1" name="position"/>
            </file>
        </folder>
    </folder>
    <folder name="Toolbars">
        <folder name="File">
            <file name="org-demo-action-DemoActionListener.shadow">
                <attr name="originalFile" stringvalue="Actions/File/org-demo-action-DemoActionListener.instance"/>
                <attr intvalue="1" name="position"/>
            </file>
        </folder>
    </folder>
</filesystem>

So... ALL of the XML above, i.e., the file as well as the content, was generated when I built the module containing the ActionListener shown earlier. I.e., the annotations on that specific ActionListener resulted in the above specific XML content in build/classes/META-INF. (And if I had 10 ActionListeners or 100 ActionListeners or any other number, they'd all be processed in the same way, if I add an @Action annotation to the class declaration, as shown above.)

All of this is made possible by... the annotation processor that processes the @Action annotation. This particular annotation processor uses several NetBeans API annotation processor classes, which enables the XML layer file to be created, with the content shown above.

And here it is:

package org.demo.action.annotation.impl;

import java.util.Set;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import org.demo.action.annotation.Action;
import org.openide.filesystems.annotations.LayerBuilder.File;
import org.openide.filesystems.annotations.LayerGeneratingProcessor;
import org.openide.filesystems.annotations.LayerGenerationException;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = Processor.class)
@SupportedAnnotationTypes("org.demo.action.annotation.Action")
@SupportedSourceVersion(SourceVersion.RELEASE_5)
public class ActionProcessor extends LayerGeneratingProcessor {

    @Override
    protected boolean handleProcess(
            Set set, RoundEnvironment env) 
            throws LayerGenerationException {
        Elements elements = processingEnv.getElementUtils();

        for (Element e : env.getElementsAnnotatedWith(Action.class)) {

            TypeElement clazz = (TypeElement) e;
            Action action = clazz.getAnnotation(Action.class);
            String teName = elements.getBinaryName(clazz).toString();

            File f = layer(e).file(
                    "Actions/" + action.path() + teName.replace('.', '-') + ".instance");
            f.newvalue(
                    "delegate",
                    teName);
            f.bundlevalue(
                    "displayName",
                    action.displayName());
            f.stringvalue(
                    "iconBase",
                    action.iconBase());
            f.methodvalue(
                    "instanceCreate",
                    "org.openide.awt.Actions",
                    "alwaysEnabled");
            f.write();

            if (action.menuBar() == true && action.toolBar() == true) {
                writeDisplayLocation(e, teName, action, "Menu/");
                writeDisplayLocation(e, teName, action, "Toolbars/");
            } else if (action.menuBar() == true && action.toolBar() == false) {
                writeDisplayLocation(e, teName, action, "Menu/");
            } else if (action.menuBar() == false && action.toolBar() == true) {
                writeDisplayLocation(e, teName, action, "Toolbars/");
            }

        }

        return true;
    }

    private void writeDisplayLocation(Element e, String teName, Action action, String loc) {
        File f1 = layer(e).shadowFile(teName, loc + action.path(), teName.replace('.', '-'));
        f1.stringvalue(
                "originalFile",
                "Actions/" + action.path() + teName.replace('.', '-') + ".instance");
        f1.intvalue(
                "position",
                action.position());
        f1.write();
    }

}

By putting the @Action annotation class and the ActionProcessor class into a separate module, I am able to reuse the @Action annotation in any module I like... by setting a dependency from the module/s containing my ActionListeners onto the module providing the @Action annotation. So long as I have exposed the package containing the @Action annotation, I can use it, like any other annotation.

And the processor, which is in the same module as the @Action annotation, will process any class that makes use of the @Action annotation... at compile time, hence creating the XML layer file which is then available at runtime.

Go here for Jaroslav Tulach's more detailed version of the above. (I wrote the above before looking at his version, I promise! In fact, I wrote it in the train from Bonn to Amsterdam yesterday.)

Aug 17 2009, 03:01:23 AM PDT Permalink

Download NetBeans!

20090816 Sunday August 16, 2009

Farewell New Action wizard!

A picture speaks 1000 words:

This great blog entry by Jaroslav Tulach provides most of the info you need for something like the above, coupled with a trip through the NetBeans sources to see how other annotations, such as @ServiceProvider are defined. Together, that should be all you need.

It is by no means finished. For example, the display name should be referenced in the bundle, rather than hardcoded in the annotation. But there are already SO many benefits over the current situation. How about the code completion and Javadoc support for example:

It works exactly as shown, i.e., the above gets me a new menu item and toolbar button, i.e., without writing a single line in my layer file (i.e., I don't need the New Action wizard at all anymore). I plan to write a blog entry describing how I created the above annotation, in the next few days.

Aug 16 2009, 02:19:18 PM PDT Permalink

Download NetBeans!

20090814 Friday August 14, 2009

JRadioButtonMenuItem & JCheckBoxMenuItem on the NetBeans Platform

Creating a toggle button on the NetBeans Platform can be done in different ways, here's one of them. We'll create two actions that let us toggle between two JRadioButtonMenuItems:

We'll begin by creating a utility class that returns a javax.swing.ButtonGroup, to which we'll add the current javax.swing.JRadioButtonMenuItem that we return from the NetBeans Platform Presenter.Menu. All along, we'll be using an ActionListener to define our action, since we're using NetBeans Platform 6.7 or above.

  1. Create a helper class that returns a ButtonGroup:
    import javax.swing.ButtonGroup;
    
    public class ButtonGroupHelper {
        
        public static ButtonGroup bg = new ButtonGroup();
        
        /** Creates a new instance of Group */
        public ButtonGroupHelper() {
        }
        
        /** Returns a ButtonGroup */
        public static ButtonGroup returnGroup() {
            return bg;
        }
        
    }

  2. Create an action for Marge:
    public final class MargeAction implements Presenter.Menu, ActionListener {
    
        private ImageIcon ICON = new ImageIcon(ImageUtilities.loadImage("org/myorg/googletoolbar/marge.png", true));
    
        @Override
        public void actionPerformed(ActionEvent e) {
            // nothing needs to happen here
        }
    
        @Override
        public JMenuItem getMenuPresenter() {
            JRadioButtonMenuItem abc = new JRadioButtonMenuItem("Marge", null);
            ButtonGroup local = ButtonGroupHelper.returnGroup();
            local.add(abc);
            abc.setSelected(false);
            abc.setIcon(ICON);
            abc.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    StatusDisplayer.getDefault().setStatusText("Marge chosen");
                }
            });
            return abc;
        }
    
    }

  3. Create an action for Homer:
    public final class HomerAction implements Presenter.Menu, ActionListener {
    
        private ImageIcon ICON = new ImageIcon(ImageUtilities.loadImage("org/myorg/googletoolbar/homer.png", true));
    
        @Override
        public void actionPerformed(ActionEvent e) {
            // nothing needs to happen here
        }
    
        @Override
        public JMenuItem getMenuPresenter() {
            JRadioButtonMenuItem abc = new JRadioButtonMenuItem("Homer", null);
            ButtonGroup local = ButtonGroupHelper.returnGroup();
            local.add(abc);
            abc.setSelected(false);
            abc.setIcon(ICON);
            abc.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    StatusDisplayer.getDefault().setStatusText("Homer chosen");
                }
            });
            return abc;
        }
    
    }

  4. Make very sure that you've registered the above actions correctly:
    <folder name="Actions">
        <folder name="File">
            <file name="org-myorg-googletoolbar-MargeAction.instance">
                <attr name="delegate" newvalue="org.myorg.googletoolbar.MargeAction"/>
            </file>
            <file name="org-myorg-googletoolbar-HomerAction.instance">
                <attr name="delegate" newvalue="org.myorg.googletoolbar.HomerAction"/>
            </file>
        </folder>
    </folder>
    <folder name="Menu">
        <folder name="File">
            <file name="org-myorg-googletoolbar-MargeAction.shadow">
                <attr name="originalFile" stringvalue="Actions/File/org-myorg-googletoolbar-MargeAction.instance"/>
                <attr name="position" intvalue="0"/>
            </file>
            <file name="org-myorg-googletoolbar-HomerAction.shadow">
                <attr name="originalFile" stringvalue="Actions/File/org-myorg-googletoolbar-HomerAction.instance"/>
                <attr name="position" intvalue="10"/>
            </file>
        </folder>
    </folder>

    As pointed out in the NetBeans Plugin Quick Start, if you're using the New Action wizard to create your actions, you will (at the very least) need to delete the "instanceCreate" attribute because you do not want to create an instance of "org.openide.awt.Actions.alwaysEnabled", in this case. None of the other attributes are relevant in this scenario.

  5. Let's now change the radiobutton menu items to checkbox menu items:

    Firstly, simply change the declaration of the JRadioButtonMenuItem to JCheckBoxMenuItem, in both the action classes:

    JCheckBoxMenuItem abc = new JCheckBoxMenuItem("Homer", null);

    Also, add the following line after the call to "local.add(abc)":

    abc.setState(true);

  6. Finally, we'll present those checkbox menu items as toolbar buttons instead:

    To achieve the above effect, implement Presenter.Toolbar instead of Presenter.Menu in each action. Then change the implementing method to getToolbarPresenter() instead of getMenuPresenter() in each action. Finally, change the folder named "Menu" in the layer to "Toolbars" so that the actions are registered as toolbar buttons instead of menu items. That's all! Now you'll have toolbar buttons instead of menu items.

Now go back and look at how few NetBeans Platform classes you've used. Very few, right? Apart from Presenter.*, and the ImageUtilities class from the Utilities API, everything is standard JDK code. And that's going to increase even further in the coming releases of the NetBeans Platform. Pretty cool!

Aug 14 2009, 01:46:20 AM PDT Permalink

Download NetBeans!

20090813 Thursday August 13, 2009

Vote for AccuRev in NetBeans IDE!

Mark Claassen, one of several NetBeans Platform developers I met for the first time at JavaOne 2009, is working on AccuRev support in NetBeans IDE:

Plugin sources on Kenai

Issue 159825: Support AccuRev CVS

"It has some issues," writes Mark above, "but works pretty well for what it was designed for. The most important things for me were the quick-diff and seeing which files were modified."

I installed the plugin above and then saw AccuRev represented in the place I would expect:

If you'd like to have AccuRev CVS support in NetBeans IDE, you need to support the issue by voting for it here! Or check out the sources above and contribute to them yourself!

Aug 13 2009, 03:14:47 PM PDT Permalink

Download NetBeans!

20090812 Wednesday August 12, 2009

Two Tips for OutlineViews

Let's now change our BeanTreeView from yesterday to an OutlineView. As you can see, below, you can use an OutlineView as a simple table, with the difference that it is a NetBeans Platform component, meaning that it can benefit from other NetBeans Platform components, such as Nodes.

Let's answer two very frequently asked questions about OutlineViews:

  • Question: How do I change the column name from "Nodes" to something else (such as "Persons") as shown above?

    Answer: Read the related Javadoc and you'll see you can pass in a string when creating the OutlineView. In other words, I added "Persons" instead of "Nodes", like this:

    OutlineView personsOutlineView = new OutlineView("Persons");

  • Question: How do I remove the small "..." buttons next to the properties in the OutlineView?

    Answer: Read the source and you will find a property "suppressCustomEditor". You use it like this, when defining your properties (look at the line in bold below):

    class CityProperty extends PropertySupport.ReadOnly<String> {
    
        private String city;
    
        public CityProperty(String city) {
            super("City", String.class, "City", "City Details");
            setValue("suppressCustomEditor", Boolean.TRUE);
            if (city != null) {
                this.city = city;
            } else {
                this.city = "";
            }
        }
    
        @Override
        public String getValue() throws IllegalAccessException, InvocationTargetException {
            return city;
        }
    
    }

    Only one other property seems to be relevant: "SortableColumn"

Note: Read the related Javadoc and you will see that the first value passed to the superclass of PropertySupport is the name of the property. This name MUST be unique, i.e., if you have two properties with the same first value passed to super, you will have a serious problem that will be hard to debug.

Aug 12 2009, 07:47:38 AM PDT Permalink