Monday Dec 31, 2012

How to Provide HTML Attribute Code Completion via an Annotation!

I reduced all the code shown yesterday, by Olivier Schmitt, who works on NetBeans at the French Ministry of Agriculture, to this:

Wait a minute, what happened to all of Olivier's code? How can the code completion for the "lang" attribute in text/xhtml work if there's no code in the module at all?

Well, the answer is found in the "package-info.java" class above:

@HTMLAttributeCompletionRegistration(
        id="jee-architect-cookbook-netbeans-iso6391-LanguageAttributeCompletionProvider", 
        attribute="lang",
        iconBase="jee/architect/cookbook/netbeans/iso6391/bubble.png",
        content="jee/architect/cookbook/netbeans/iso6391/ISO6391.csv")
package jee.architect.cookbook.netbeans.iso6391;

import org.netbeans.cra.api.HTMLAttributeCompletionRegistration;

That's the only code in that Java class. And, also, that's the only code in the entire module.

When the module is compiled, the annotations shown above are converted to this in the generated-layer.xml file:

<folder name="Editors">
    <folder name="text">
        <folder name="xhtml">
            <folder name="CompletionProviders">
                <file name="jee-architect-cookbook-netbeans-iso6391-LanguageAttributeCompletionProvider.instance">
                    <attr name="iconBase" stringvalue="jee/architect/cookbook/netbeans/iso6391/bubble.png"/>
                    <attr name="attribute" stringvalue="lang"/>
                    <attr name="content" stringvalue="jee/architect/cookbook/netbeans/iso6391/ISO6391.csv"/>
                    <attr methodvalue="org.netbeans.cra.spi.HTMLAttributeCompletionProvider.create" name="instanceCreate"/>
                </file>
            </folder>
        </folder>
    </folder>
</folder>

The most important  part of the above is the "methodvalue" attribute, which is where the map of attributes is magically passed. The "HTMLAttributeCompletionProvider" is taken directly from Oliver's code yesterday, as well as all the other classes from his module, which I've simply made more generic so that any attribute can be passed in:

This is only a proof of concept. The next steps would be to let you provide multiple different attributes, so that the provided code completion words can be made available to multiple attributes simultaneously, as well as multiple different annotations within the same package-info.java class:

@HTMLAttributeCompletionRegistrations({
    @HTMLAttributeCompletionRegistration(
        id = "jee-architect-cookbook-netbeans-iso6391-LanguageAttributeCompletionProvider",
        attribute = "lang",
        iconBase = "jee/architect/cookbook/netbeans/iso6391/bubble.png",
        content = "jee/architect/cookbook/netbeans/iso6391/ISO6391.csv",
        contentType="csv"),
    @HTMLAttributeCompletionRegistration(
        id = "jee-architect-cookbook-netbeans-iso6391-BlaAttributeCompletionProvider",
        attribute = "bla",
        iconBase = "jee/architect/cookbook/netbeans/iso6391/bla.png",
        content = "tom,dick,harry",
        contentType="basic")
})
package jee.architect.cookbook.netbeans.iso6391;

import org.netbeans.cra.api.HTMLAttributeCompletionRegistration;
import org.netbeans.cra.api.HTMLAttributeCompletionRegistrations;

In the annotations above, I have defined that the "lang" attribute and the "bla" attribute will have code completion defined by the "content" attribute (notice that there are now different types of content, distinguished via the "contentType" attribute, e.g., imagine another one one for "json", for example, and then you have a starting point for accessing code completion words online, maybe exposed via a RESTful web service) and the "iconBase" attribute.


Ultimately this could be made even more generic, e.g., instead of assuming text/xhtml, require the user of the annotation to provide a MIME type and then process the content accordingly. Also, consider the possibilities in other areas, e.g., you could create annotations for the NetBeans HyperlinkProvider and register your implementations in the package-info class, consisting of annotation attributes for MIME type, a matcher pattern, and instructions for what should happen when the hyperlink is selected and clicked.

All the code you see above is freely available here:

http://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.3/misc/CompletionRegistrationSupport

You would need to install both modules, i.e., the module providing the annotation and the module that makes use of it, into NetBeans IDE and then the code completion will be available as defined by the @HTMLAttributeCompletionRegistration annotation. 

Note: If you're not amazed yet, consider how easily this solution lets you create multiple HTML attribute code completions. The process is now a trivial question of registering lists of words, attributes, and the related icons!

Another interesting thing to explore would be to let the CSV files be loaded externally, i.e., the user would create on disk a CSV file, somehow map that CSV file to an attribute, e.g., in the netbeans.conf file there'd be a "key/value" pairing of "attribute/file.csv", which a NetBeans module would parse and then map the content of the file to the associated attribute. That way, the NetBeans IDE user, without needing to have available or create a new NetBeans module that uses the annotations above, would have control over the code completion for whichever attribute is of interest to them.

Happy new year everyone and have fun with NetBeans IDE in 2013!

Friday Dec 28, 2012

Perl Code Folding

I took some first steps in working on Perl code folding for the Perl/NetBeans project that Sudeep Hazra is working on.

You can see the first signs of code folding above, though not completely correct yet.

The main class I changed was this one, as follows, giving you a lot of default functionality out of the box:

import org.netbeans.api.lexer.Language;
import org.netbeans.modules.csl.spi.DefaultLanguageConfig;
import org.netbeans.modules.csl.spi.LanguageRegistration;
import org.netbeans.perl.lexer.PerlTokenId;

//@org.openide.util.lookup.ServiceProvider(service=org.netbeans.spi.lexer.LanguageProvider.class)
@LanguageRegistration(mimeType = "text/x-perl")
public class PerlLanguageProvider extends DefaultLanguageConfig {

    @Override
    public Language getLexerLanguage() {
        return PerlTokenId.getLanguage();
    }

    @Override
    public String getDisplayName() {
        return "Perl";
    }
//    @Override
//    public Language findLanguage(String mimeType) {
//        if ("text/x-perl".equals(mimeType)){
//            return new PerlLanguageHierarchy().language();
//        }
//
//        return null;
//    }
//
//    @Override
//    public LanguageEmbedding findLanguageEmbedding(Token token, LanguagePath lp, InputAttributes ia) {
//         return null;
//    }
} 

Next, unrelated but helpful, the brace matcher can be made to work quite simply by doing the following, which means that two classes can be deleted:

import org.netbeans.spi.editor.bracesmatching.BracesMatcher;
import org.netbeans.spi.editor.bracesmatching.BracesMatcherFactory;
import org.netbeans.spi.editor.bracesmatching.MatcherContext;
import org.netbeans.spi.editor.bracesmatching.support.BracesMatcherSupport;

@MimeRegistration(mimeType="text/x-perl", service=BracesMatcherFactory.class)
public class PerlBracesMatcherFactory implements BracesMatcherFactory {
    
    @Override
    public BracesMatcher createMatcher(MatcherContext context) {
        return BracesMatcherSupport.defaultMatcher(context, -1, -1);
    }
 
}

The Editors/text/x-perl section of the layer needs to be rewritten to the following:

 <folder name="Editors">
    <folder name="text">
        <folder name="x-perl">
            <attr name="displayName" bundlevalue="org.netbeans.perl.file.Bundle#Editors/text/x-perl"/>
            <folder name="FoldManager">
                <file name="org-netbeans-editor-CustomFoldManager$Factory.instance"/>
            </folder>
            <file name="org-netbeans-perl-parser-PerlParserFactory.instance"/>
            <file name="org-netbeans-perl-parser-SyntaxErrorsHighlightingTaskFactory.instance"/>
            <folder name="FontsColors">
                <folder name="NetBeans">
                    <folder name="Defaults">
                        <file name="org-netbeans-perl-file-FontAndColors.xml" url="FontAndColors.xml">
                            <attr name="SystemFileSystem.localizingBundle" bundlevalue="org.netbeans.perl.file.Bundle"/>
                        </file>
                    </folder>
                </folder>
            </folder>
        </folder>
    </folder>
</folder>

Surprised by any of the above information? Ha. That means you haven't followed the related tutorials:

http://platform.netbeans.org/tutorials/nbm-javacc-lexer.html
http://platform.netbeans.org/tutorials/nbm-javacc-parser.html

Do yourself a favor and follow the above two tutorials before even thinking about creating your own editor on top of the NetBeans IDE APIs. 

Next, I'm working on the FoldManager and its factory, which I so far have based on the following:

http://svn.debux.org/webmotion-ext/webmotion-plugin-netbeans/trunk/src/org/debux/webmotion/netbeans/WebMotionFoldManager.java

More soon. I'm aiming to have quite some fun documentation relating to code folding, which is one of the NetBeans IDE APIs about which sporadically (since, after all, how many people in the world are creating editors?) questions arise, soon!

Thursday Dec 27, 2012

Programmatically Expanding & Collapsing Nodes

Let's say you have two toolbar buttons, one for expanding and the other for collapsing the currently selected node, as shown below, within the black box drawn on the screenshot:

How to, from the two toolbar buttons above, expand/collapse the currently selected node?

The answer is very similar to "Add Widget from Action in Toolbar". Start by creating two interfaces, one for expanding and the other for collapsing:

public interface Expandable {
    void expand();
}
public interface Collapsible {
    void collapse();
}

Next, put implementations of the above into the Lookup of the TopComponent that displays the Node hierarchy. So, start by going to the TopComponent and, within the TopComponent (since that's where you have access to the BeanTreeView and to the ExplorerManager that controls the Node hierarchy) implement your two interfaces:

private class CollapsibleMovieNodes implements Collapsible {
    @Override
    public void collapse() {
        myBeanTreeView.collapseNode(myExplorerManager.getSelectedNodes()[0]);
    }
}

private class ExpandableMovieNodes implements Expandable {
    @Override
    public void expand() {
        myBeanTreeView.expandNode(myExplorerManager.getSelectedNodes()[0]);
    }
}

Then, put those two implementations into the Lookup of the TopComponent:

associateLookup(new ProxyLookup(
        Lookups.fixed(new CollapsibleMovieNodes(), new ExpandableMovieNodes()),
        ExplorerUtils.createLookup(manager, map)));

Finally, now, you can get the two implementations from the Lookup, anywhere in your application, and just call the method on it. For example, here is an Action that can be invoked from a toolbar button to expand the currently selected Node:

@ActionID(
    category = "Edit",
    id = "com.mit.moview.controler.ExpandNodeAction")
@ActionRegistration(
    iconBase = "com/mit/moview/controler/expandIcon.png",
    displayName = "#CTL_ExpandNodeAction")
@ActionReference(
    path = "Toolbars/ExpCol", 
    position = 3330)
@Messages("CTL_ExpandNodeAction=Expand Node")
public final class ExpandNodeAction implements ActionListener {

    private final Expandable context;

    public ExpandNodeAction(Expandable context) {
        this.context = context;
    }

    @Override
    public void actionPerformed(ActionEvent ev) {
        context.expand();
    }
 
}

What's cool about the above is that if there's no Expandable in the Lookup, e.g., when the Properties window or Output window are selected, the toolbar button will automatically be disabled and greyed out.

And here's the solution in the form of a screenshot, showing that the Actions can be in different modules to where the View is found, and are loosely coupled from each other, since the module providing the Actions does not need a dependency on the module providing the View, since they are loosely coupled because they interact via the two capabilities:

But what about if the toolbar button (i.e., the underlying Action) should only be enabled if a Node has children? In other words, if a Node cannot be expanded because it does not have child Nodes, then the toolbar button should not be enabled. In that case, you need to do a bit more work because you want to have access in your Action to two different objects: the Node, as well as the Expandable. That's where you need to use a CookieAction and let the Action be enabled eagerly, i.e., not via the Action*. factories. Here's the solution:

@ActionID(
    category = "Edit",
    id = "com.mit.moview.controler.ExpandNodeAction")
@ActionRegistration(
    lazy = false,
    displayName = "not-used")
@ActionReference(
    path = "Toolbars/ExpCol",
    position = 3330)
public final class ExpandNodeAction extends CookieAction {
    private final Lookup context;
    private Expandable expandable;
    public ExpandNodeAction() {
        context = Utilities.actionsGlobalContext();
    }
    @Override
    protected boolean enable(Node[] activatedNodes) {
        if (context.lookup(Expandable.class) != null
                && context.lookup(Node.class) != null
                && !context.lookup(Node.class).isLeaf()) {
            this.expandable = context.lookup(Expandable.class);
            return true;
        }
        return false;
    }
    @Override
    protected int mode() {
        return CookieAction.MODE_ONE;
    }
    @Override
    protected Class[] cookieClasses() {
        return new Class[]{Node.class, Expandable.class};
    }
    @Override
    protected void performAction(Node[] nodes) {
        if (expandable != null) {
            expandable.expand();
        }
    }
    @Override
    public String getName() {
        return null;
    }
    @Override
    protected String iconResource() {
        return "com/mit/moview/controler/expandIcon.png";
    }
    @Override
    public HelpCtx getHelpCtx() {
        return HelpCtx.DEFAULT_HELP;
    }   
}

Finally, what if the toolbar button for expanding should be disabled when a Node has already been expanded? Create a new capability, NodeExpandable, introduce it into the Lookup of the Node whenever it is expanded, remove it again when it is collapsed. Then, in the Action, check for the presence/absence of that capability and enable/disable accordingly.

Wednesday Dec 26, 2012

File Browser

import java.awt.BorderLayout;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ActionMap;
import javax.swing.text.DefaultEditorKit;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.explorer.view.BeanTreeView;
import org.openide.filesystems.FileObject;
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;
import org.openide.windows.TopComponent;

@TopComponent.Description(
        preferredID = "FileBrowserTopComponent",
        persistenceType = TopComponent.PERSISTENCE_ALWAYS)
@TopComponent.Registration(
        mode = "explorer", 
        openAtStartup = true)
@ActionID(
        category = "Window", 
        id = "com.mycompany.FileBrowserTopComponent")
@ActionReference(
        path = "Menu/Window")
@TopComponent.OpenActionRegistration(
        displayName = "#CTL_FileBrowserTopComponentAction",
        preferredID = "FileBrowserTopComponent")
@NbBundle.Messages({
    "CTL_FileBrowserTopComponentAction=Open File Browser",
    "CTL_FileBrowserTopComponent=File Browser",
})
public class FileBrowserTopComponent extends TopComponent implements ExplorerManager.Provider  {
 
    private ExplorerManager mgr = new ExplorerManager();

    public FileBrowserTopComponent() throws DataObjectNotFoundException {
        setDisplayName(Bundle.CTL_FileBrowserTopComponent());
        setLayout(new BorderLayout());
        add(new BeanTreeView(), BorderLayout.CENTER);
        File file = new File(System.getProperty("user.home"));
        FileObject fo = FileUtil.toFileObject(FileUtil.normalizeFile(file));
        DataObject dob = DataObject.find(fo);
        Node rootNode = dob.getNodeDelegate();
        mgr.setRootContext(new FileFilterNode(rootNode));
        ActionMap map = this.getActionMap();
        map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(mgr));
        map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(mgr));
        map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(mgr));
        map.put("delete", ExplorerUtils.actionDelete(mgr, true)); // or false
        associateLookup(ExplorerUtils.createLookup(mgr, map));
    }

    @Override
    public ExplorerManager getExplorerManager() {
        return mgr;
    }
 
    public class FileFilterNode extends FilterNode {
        public FileFilterNode(Node original) {
            super(original, new FileFilterNodeChildren(original));
        }
        //Override e.g., icons here...
    }

    public class FileFilterNodeChildren extends FilterNode.Children {
        public FileFilterNodeChildren(Node original) {
            super(original);
        }
        @Override
        protected Node[] createNodes(Node object) {
            List<Node> result = new ArrayList<Node>();
            for (Node node : super.createNodes(object)) {
                if (accept(node)) {
                    result.add(node);
                }
            }
            return result.toArray(new Node[0]);
        }
        //Example filter for children, e.g., don't include if folder:
        private boolean accept(Node node) {
            return node.getLookup().lookup(DataFolder.class) == null;
        }
    }
 
}

Thanks to Tim Boudreau for help with the above.

Saturday Dec 22, 2012

Maltego Radium CE

The free Community Edition of forensics software Maltego Radium was released the day before yesterday:

Details: http://maltego.blogspot.nl/2012/12/maltego-radium-community-edition.html

Those guys make by far the coolest YouTube movies on their software releases, e.g., this recent one:

http://www.youtube.com/watch?v=OSipLyYZQks

Friday Dec 21, 2012

Add Widget via Action in Toolbar (Part 2)

Updated the "Add Widget via Action in Toolbar" sample so that, when a new object is added to the canvas (via the red dot toolbar button below), a new customer is automatically added to the explorer view, below, after which any node selected in the explorer is automatically highlighted in the canvas, while any object selected in the canvas causes the related node in the explorer to be highlighted:

This is a cool example illustrating communication between multiple modules, with the Visual Library included too.

Also, Lookup is used in different ways:

  • Customer Action Loosely Coupled from Canvas. The canvas publishes a Droppable object, which causes the button in the toolbar to be enabled because its Action is sensitive to instances of Droppable. When the user clicks the button, i.e., invokes the Action, a small dialog is shown where the user can specify a text. When OK is pressed in the dialog, a new Customer object is created, with its title set to the text, and passed by the Action to the Droppable implementation published by the canvas, which creates a widget in the canvas. This mechanism is similar to how Savable works.

  • Node Hierarchy Displays Customer Objects in Canvas. When a new widget is created by the Droppable in the canvas, the customer object is added to the CentralLookup (thanks Wade), which is what the ChildFactory in the explorer is listening to for new Customer objects. When a new Customer object appears in the CentralLookup, the explorer is updated with new nodes representing the new customers found in the CentralLookup. CentralLookup is used because, in this scenario, the explorer doesn't care about what is currently selected, it simply wants to show all the objects created in the canvas.

  • Visual Library Listens for Selected Node. Each node in the explorer publishes its underlying Customer object, when the Node is selected. The canvas is defined by an ObjectScene from the Visual Library, which implements LookupListener, listening for Customer objects in the global Lookup. So, when a different Node is selected, a different Customer object is available in the global Lookup (i.e., Utilities.actionsGlobalContext). The title of that object is compared to the titles of all the objects in the ObjectScene and the widget of whichever object matches the Customer object's title has its foreground turned to red, with all other widgets turned to black. This happens whenever a new Node is selected in the explorer.

  • Node Hierarchy in Explorer Window Listens for Selected Widget. When a widget is double-clicked, a SelectProvider in the Visual Library Scene publishes the underlying Customer object into an InstanceContent published into the TopComponent Lookup, i.e., different to the InstanceContent for the Droppable described in point 1 above. The explorer listens to the global Lookup (as well as to the CentralLookup as described in point 2 above) for Customer objects and then finds the matching Node in the ExplorerManager and selects it.

Source code:

http://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.3/misc/WidgetCreationFromAction

Thursday Dec 20, 2012

Opening Foreign Windows

I discovered, perhaps unsurprisingly, that it's possible to create a module that defines window groups consisting of windows defined in other modules, even (and that's the most fun part) windows over which you normally have no control, e.g., the windows defined in the NetBeans Platform itself, such as the Properties window, the Favorites window, and the Output window. Once you have defined a window group consisting of some of your own windows, plus some windows from the NetBeans Platform itself, or from other modules over which you have no control, you can open and close those windows together.

Above, I have two Actions, the open action is like this:

@Override
public void actionPerformed(ActionEvent e) {
    TopComponentGroup group = WindowManager.getDefault().findTopComponentGroup("testGroup");
    if (group != null) {
        group.open();
    }
}

And the close action is the same as the above, except that "close" is called on the group.

Now, what does the group consist of? You can see "testGroup" as the name of the group, above. Here's where that name comes from, in the file "testGroupWsgrp.xml" shown in the screenshot above:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE group PUBLIC
          "-//NetBeans//DTD Group Properties 2.0//EN"
          "http://www.netbeans.org/dtds/group-properties2_0.dtd">
<group version="2.0">
    <module name="org.fwc" spec="1.0" />
    <name unique="testGroup" />
    <state opened="false" />
</group>

And the above is registered in the layer.xml file (which, in turn, is registered in the manifest file), as follows:

<folder name="Windows2">
    <folder name="Groups">
        <file name="testGroup.wsgrp" url="testGroupWsgrp.xml"/>
        <folder name="testGroup">
            <file name="output.wstcgrp" url="groups/output.xml"/>
            <file name="properties.wstcgrp" url="groups/properties.xml"/>
            <file name="favorites.wstcgrp" url="groups/favorites.xml"/>
        </folder>
    </folder>
</folder>

Each of the window system component group files above has content like this, here you see it for the "output.wstcgrp":

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tc-group PUBLIC
  "-//NetBeans//DTD Top Component in Group Properties 2.0//EN"
  "http://www.netbeans.org/dtds/tc-group2_0.dtd">
<tc-group version="2.0">
    <module name="org.netbeans.core.io.ui" spec="1.0"/>
    <tc-id id="output" />
    <open-close-behavior open="true" close="true" />
</tc-group>

And here it is for the "properties.wstcgrp":

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tc-group PUBLIC
  "-//NetBeans//DTD Top Component in Group Properties 2.0//EN"
  "http://www.netbeans.org/dtds/tc-group2_0.dtd">
<tc-group version="2.0">
    <module name="org.netbeans.core.ui" spec="1.0"/>
    <tc-id id="properties" />
    <open-close-behavior open="true" close="true" />
</tc-group>

And, finally, here it is for the "favorites.wstcgrp":

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tc-group PUBLIC
  "-//NetBeans//DTD Top Component in Group Properties 2.0//EN"
  "http://www.netbeans.org/dtds/tc-group2_0.dtd">
<tc-group version="2.0">
    <module name="org.netbeans.modules.favorites" spec="1.0"/>
    <tc-id id="favorites" />
    <open-close-behavior open="true" close="true" />
</tc-group> 

Note: It is very important that you get the "module name" and "tc-id name" to be 100% correct. And the value of "tc-id name" must be the same as the name of the file. That's the only information needed for the TopComponent to be found when the "open" and "close" are called on the group you have defined, since each of the above XML files is registered within the group in the layer file.

And that's how you can open (and close) foreign windows. Now that these windows are part of a group, whenever you open the group and then close one of the windows within the group, next time you open the group that window you closed will still be closed. I.e., this is the way you can provide workspaces for your users, that is, workspaces consisting of windows that adapt themselves automatically to the needs of the user.

PS: I've noticed that if your TopComponent is an "editor" TopComponent, it doesn't close when the "close" is called on the group of which it is a part. Seems like a bug.

Wednesday Dec 19, 2012

On Golf Tournaments & Installers

I've been in touch recently with Ann Maybury, who is creating a golf tournament roundrobin manager for senior citizens in Palm Desert, California. The application is created on the NetBeans Platform and looks as follows, very neat and professional:

Ann has been working on wrapping up the application for distribution and needs to include the JRE, since end users of the application don't necessarily have the JRE installed when they install the application.

Several blogs and articles are available for creating and customizing installers for NetBeans Platform applications, as well as for bundling the JRE and other resources, though there are some gaps and inaccuracies in those documents. However, now there's a new official tutorial, for the first time:

http://platform.netbeans.org/tutorials/nbm-nbi.html

The above is focused on Ant builds and Windows, specifically, and doesn't cover Maven scenarios, for which there'll be a separate tutorial soon. Feedback on the above new tutorial is very welcome, as always.

Tuesday Dec 18, 2012

Project Time Tracker

Based on yesterday's blog entry, let's do something semi useful and display, in the project popup, which is available when you right-click a project in the Projects window, the time since the last change was made anywhere in the project, i.e., we can listen recursively to any changes done within a project and then update the popup with the newly acquired information, dynamically:

import java.awt.event.ActionEvent;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.swing.AbstractAction;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.awt.StatusDisplayer;
import org.openide.filesystems.FileAttributeEvent;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileRenameEvent;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;

@ActionID(
        category = "Demo",
        id = "org.ptt.TrackProjectSelectionAction")
@ActionRegistration(
        lazy = false,
        displayName = "NOT-USED")
@ActionReference(
        path = "Projects/Actions",
        position = 0)
public final class TrackProjectSelectionAction extends AbstractAction
        implements LookupListener, FileChangeListener {

    private Lookup.Result<Project> projects;
    private Project context;
    private Long startTime;
    private Long changedTime;
    private DateFormat formatter;
    private List<Project> timedProjects;

    public TrackProjectSelectionAction() {
        putValue("popupText", "Timer");
        formatter = new SimpleDateFormat("HH:mm:ss");
        timedProjects = new ArrayList<Project>();
        projects = Utilities.actionsGlobalContext().lookupResult(Project.class);
        projects.addLookupListener(
                WeakListeners.create(LookupListener.class, this, projects));
        resultChanged(new LookupEvent(projects));
    }

    @Override
    public void resultChanged(LookupEvent le) {
        Collection<? extends Project> allProjects = projects.allInstances();
        if (allProjects.size() == 1) {
            Project currentProject = allProjects.iterator().next();
            if (!timedProjects.contains(currentProject)) {
                String currentProjectName =
                        ProjectUtils.getInformation(currentProject).getDisplayName();
                putValue("popupText", "Start Timer for Project: " + currentProjectName);
                StatusDisplayer.getDefault().setStatusText(
                        "Current Project: " + currentProjectName);
                timedProjects.add(currentProject);
                context = currentProject;
            } 
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        refresh();
    }

    protected void refresh() {
        startTime = System.currentTimeMillis();
        String formattedStartTime = formatter.format(startTime);
        putValue("popupText", "Timer started: " + formattedStartTime + " ("
                + ProjectUtils.getInformation(context).getDisplayName() + ")");
    }

    @Override
    public void fileChanged(FileEvent fe) {
        changedTime = System.currentTimeMillis();
        formatter = new SimpleDateFormat("mm:ss");
        String formattedLapse = formatter.format(changedTime - startTime);
        putValue("popupText", "Time since last change: " + formattedLapse + " ("
                + ProjectUtils.getInformation(context).getDisplayName() + ")");
        startTime = changedTime;
    }

    @Override
    public void fileFolderCreated(FileEvent fe) {}
    @Override
    public void fileDataCreated(FileEvent fe) {}
    @Override
    public void fileDeleted(FileEvent fe) {}
    @Override
    public void fileRenamed(FileRenameEvent fre) {}
    @Override
    public void fileAttributeChanged(FileAttributeEvent fae) {}
    
}

Some more work needs to be done to complete the above, i.e., for each project you somehow need to maintain the start time and last change and redisplay that whenever the user right-clicks the project.

Monday Dec 17, 2012

Dynamically Changing the Display Names of Menus and Popups

Very interesting thing and handy to know when needed is the fact that "menuText" and "popupText" (from org.openide.awt.ActionRegistration) can be changed dynamically, via "putValue" as shown below for "popupText". The Action class, in this case, needs to be eager, hence you won't receive the object of interest via the constructor, but you can easily use the global Lookup for that purpose instead, as also shown below.

import java.awt.event.ActionEvent;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collection;
import javax.swing.AbstractAction;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionRegistration;
import org.openide.awt.StatusDisplayer;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.Utilities;
import org.openide.util.WeakListeners;

@ActionID(
        category = "Demo",
        id = "org.ptt.TrackProjectSelectionAction")
@ActionRegistration(
        lazy = false,
        displayName = "NOT-USED")
@ActionReference(
        path = "Projects/Actions",
        position = 0)
public final class TrackProjectSelectionAction extends AbstractAction 
    implements LookupListener {

    private Lookup.Result<Project> projects;
    private Project context;

    public TrackProjectSelectionAction() {
        projects = Utilities.actionsGlobalContext().lookupResult(Project.class);
        projects.addLookupListener(
                WeakListeners.create(LookupListener.class, this, projects));
        resultChanged(new LookupEvent(projects));
    }

    @Override
    public void resultChanged(LookupEvent le) {
        Collection<? extends Project> p = projects.allInstances();
        if (p.size() == 1) {
            Project currentProject = p.iterator().next();
            String currentProjectName = 
                    ProjectUtils.getInformation(currentProject).getDisplayName();
            putValue("popupText", "Current Project: " + currentProjectName);
            StatusDisplayer.getDefault().setStatusText(
                    "Current Project: " + currentProjectName);
            context = currentProject;
        }
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        refresh();
    }

    protected void refresh() {
        DateFormat formatter = new SimpleDateFormat("HH:mm:ss");
        String formatted = formatter.format(System.currentTimeMillis());
        putValue("popupText", "Last Click At: " + formatted + 
                " (" + ProjectUtils.getInformation(context) + ")");
    }
    
}

Sunday Dec 16, 2012

Add Widget via Action in Toolbar (Part 1)

The question of the day comes from Vadim, who asks on the NetBeans Platform mailing list: "Looking for example showing how to add Widget to Scene, e.g. by toolbar button click."

Well, the solution is very similar to this blog entry, where you see a solution provided by Jesse Glick for VisiTrend in Boston: https://blogs.oracle.com/geertjan/entry/zoom_capability

Other relevant articles to read are as follows:

Let's go through it step by step, with this result in the end, a solution involving 4 classes split (optionally, since a central feature of the NetBeans Platform is modularity) across multiple modules:

The Customer object has a "name" field, with getters, setters, and a constructor, while the Droppable capability has a method "doDrop" which takes a Customer object:

public interface Droppable {
    void doDrop(Customer c);
}

In the TopComponent, we use "TopComponent.associateLookup" to publish an instance of "Droppable", which creates a new LabelWidget and adds it to the Scene in the TopComponent. Here's the TopComponent constructor:

public CustomerCanvasTopComponent() {
    initComponents();
    setName(Bundle.CTL_CustomerCanvasTopComponent());
    setToolTipText(Bundle.HINT_CustomerCanvasTopComponent());
    final Scene scene = new Scene();
    final LayerWidget layerWidget = new LayerWidget(scene);
    Droppable d = new Droppable(){
        @Override
        public void doDrop(Customer c) {
            LabelWidget customerWidget = new LabelWidget(scene, c.getTitle());
            customerWidget.getActions().addAction(ActionFactory.createMoveAction());
            layerWidget.addChild(customerWidget);
            scene.validate();
        }
    };
    scene.addChild(layerWidget);
    jScrollPane1.setViewportView(scene.createView());
    associateLookup(Lookups.singleton(d));
}

The Action is displayed in the toolbar and is enabled only if a Droppable is currently in the Lookup:

@ActionID(
        category = "Tools",
        id = "org.customer.controler.AddCustomerAction")
@ActionRegistration(
        iconBase = "org/customer/controler/icon.png",
        displayName = "#AddCustomerAction")
@ActionReferences({
    @ActionReference(path = "Toolbars/File", position = 300)
})
@NbBundle.Messages("AddCustomerAction=Add Customer")
public final class AddCustomerAction implements ActionListener {
    private final Droppable context;
    public AddCustomerAction(Droppable droppable) {
        this.context = droppable;
    }
    @Override
    public void actionPerformed(ActionEvent ev) {
        NotifyDescriptor.InputLine inputLine = new NotifyDescriptor.InputLine("Name:", "Data Entry");
        Object result = DialogDisplayer.getDefault().notify(inputLine);
        if (result == NotifyDescriptor.OK_OPTION) {
            Customer customer = new Customer(inputLine.getInputText());
            context.doDrop(customer);
        }
    }
}

Therefore, when the Properties window, for example, is selected, the Action will be disabled because the Properties window does not publish an instance of the Droppable capability. (See the Zoomable example referred to in the link above for another example of this.) As you can see above, when the Action is invoked, a Droppable must be available (otherwise the Action would not have been enabled). The Droppable is obtained in the Action and a new Customer object is passed to its "doDrop" method.

The above in pictures, take note of the enablement of the toolbar button with the red dot, on the extreme left of the toolbar in the screenshots below:

The above shows the JButton is only enabled if the relevant TopComponent is active and, when the Action is invoked, the user can enter a name, after which a new LabelWidget is created in the Scene.

Now imagine that we have a different TopComponent (or a Node or anything that implements Lookup.Provider) that also should let the user drop something on itself. All we need to do is publish a new instance of Droppable from that TopComponent... and then the Action defined above will automatically also be enabled when that TopComponent is selected, but this time the result will be different, depending on however we've implemented the Droppable. In other words, one single Action can now do multiple different things, depending on the Droppable currently available in the Lookup.

The source code of the above is here, note that I used a recent 7.3 development build, so you may need to change the dependencies after you download the code, otherwise compilation will fail because of incorrect dependency version numbers:

http://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.3/misc/WidgetCreationFromAction

Note: Showing this as an MVC example is slightly misleading because, depending on which model object ("Customer" or "Droppable") you're looking at, the V and the C are different. From the point of view of "Customer", the TopComponent is the View, while the Action is the Controler, since it determines when the M is displayed. However, from the point of view of "Droppable", the TopComponent is the Controler, since it determines when the Action, i.e., which is in this case the View, displays the presence of the M.

Continue to part 2...

Friday Dec 14, 2012

JavaFX Makeover for JFugue Music NotePad

Bengt-Erik Fröberg from Sweden, one of the developers working on ProSang, the leading Scandinavian blood bank system (and based on the NetBeans Platform), is reworking the user interface of the JFugue Music NotePad.

In particular, the Score window (named ScoreFX window below) contains components that are now quite clearly JavaFX, instead of Swing. Looks a lot better and also performs better.

The sliders in the Keyboard window are candidates for being similarly redone to use JavaFX instead of Swing.

Want to do something similar? Here's all the info you need:

http://platform.netbeans.org/tutorials/nbm-javafx.html

Thursday Dec 13, 2012

HTML5 Samples in NetBeans IDE 7.3


Wednesday Dec 12, 2012

NetBeans IDE 7.3 Knows Null

What's the difference between these two methods, "test1" and "test2"?

public int test1(String str) {
    return str.length();
}
  

public int test2(String str) {     if (str == null) {         System.err.println("Passed null!.");         //forgotten return;     }     return str.length(); }

The difference, or at least, the difference that is relevant for this blog entry, is that whoever wrote "test2" apparently thinks that the variable "str" may be null, though did not provide a null check. In NetBeans IDE 7.3, you see this hint for "test2", but no hint for "test1", since in that case we don't know anything about the developer's intention for the variable and providing a hint in that case would flood the source code with too many false positives:

 Annotations are supported in understanding how a piece of code is intended to be used. If method return types use @Nullable, @NullAllowed, @CheckForNull, the value is considered to be "strongly possible to be null", as well as if the variable is tested to be null, as shown above. When using @NotNull, @NonNull, @Nonnull, the value is considered to be non-null. (The exact FQNs of the annotations are ignored, only simple names are checked.)

Here are examples showing where the hints are displayed for the non-null hints (the "strongly possible to be null" hints are not shown below, though you can see one of them in the screenshot above), together with a comment showing what is shown when you hover over the hint:

There isn't a "one size fits all" refactoring for these various instances relating to null checks, hence you can't do an automated refactoring across your code base via tools in NetBeans IDE, as shown yesterday for class member reordering across code bases. However, you can, instead, go to Source | Inspect and then do a scan throughout a scope (e.g., current file/package/project or combinations of these or all open projects) for class elements that the IDE identifies as potentially having a problem in this area:

Thanks to Jan Lahoda, who reports that this currently also works in NetBeans IDE 7.3 dev builds for fields but that may need to be disabled since right now too many false positives are returned, for help with the info above and any misunderstandings are my own fault!

Tuesday Dec 11, 2012

Handy Tool for Code Cleanup: Automated Class Element Reordering

You're working on an application and this thought occurs to you: "Wouldn't it be cool if I could define rules specifying that all static members, initializers, and fields should always be at the top of the class? And then, whenever I wanted to, I'd start off a process that would actually do the reordering for me, moving class elements around, based on the rules I had defined, automatically, across one or more classes or packages or even complete code bases, all at the same time?"

Well, here you go:

That's where you can set rules for the ordering of your class members. A new hint (i.e., new in NetBeans IDE 7.3), which you need to enable yourself because by default it is disabled, let's the IDE show a hint in the Java Editor whenever there's code that isn't ordered according to the rules you defined:

The first element in a file that the Java Editor identifies as not matching your rules gets a lightbulb hint shown in the left sidebar:

Then, when you click the lightbulb, automatically the file is reordered according to your defined rules.

However, it's not much fun going through each file individually to fix class elements as shown above. For that reason, you can go to "Refactor | Inspect and Transform". There, in the "Inspect and Transform" dialog, you can choose the hint shown above and then specify that you'd like it to be applied to a scope of your choice, which could be a file, a package, a project, combinations of these, or all of the open projects, as shown below:

Then, when Inspect is clicked, the Refactoring window shows all the members that are ordered in ways that don't conform to your rules:

Click "Do Refactoring" above and, in one fell swoop, all the class elements within the selected scope are ordered according to your rules.

Monday Dec 10, 2012

How to Integrate Backbone.js with RESTful Web Services in 5 Minutes!

In NetBeans IDE 7.3, a Backbone.js file can be generated from a Java RESTful web service. The Backbone.js file contains complete CRUD functionality and your HTML5 application can immediately be deployed to make use of those features. Coupled with the NetBeans IDE two-way editing support for HTML5, via interaction with WebKit in Chrome, Backbone.js users have a completely new and powerful tool for coding their HTML5 applications.

The above is illustrated via the brand new YouTube movie below:


This makes NetBeans IDE 7.3 well suited as a learning tool for new Backbone.js users, as well as a productivity tool for those who are comfortable with Backbone.js already.

Sunday Dec 09, 2012

Rendering Flickr Cats Via Backbone.js

Create a JavaScript file and refer to it inside an HTML file. Then put this into the JavaScript file:

(function($) {

    var CatCollection = Backbone.Collection.extend({
        url: 'http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?',
        parse: function(response) {
            return response.items;
        }
    });

    var CatView = Backbone.View.extend({
        el: $('body'),
        initialize: function() {
            _.bindAll(this, 'render');
            carCollectionInstance.fetch({
                success: function(response, xhr) {
                    catView.render();
                }
            });
        },
        render: function() {
            $(this.el).append("<ul></ul>");
            for (var i = 0; i < carCollectionInstance.length; i++) {
                $('ul', this.el).append("<li>" + i + carCollectionInstance.models[i].get("description") + "</li>");
            }
        }
    });

    var carCollectionInstance = new CatCollection();
    var catView = new CatView();

})(jQuery);

Apologies for any errors or misused idioms. It's my second day with Backbone.js, in fact, my second day with JavaScript. I haven't seen anywhere online so far where an example such as the above is found, though plenty that do kind of or pieces of the above, or explain in text, without an actual full example.

The next step, and the only reason for the above experiment, is to create some JPA entities and expose them via RESTful webservices created on EJB methods, for consumption into an HTML5 application via a Backbone.js script very similar to the above. 

Saturday Dec 08, 2012

Savable in Widget Lookup on Move Action

Possible from 7.3 onwards, since Widget now implements Lookup.Provider for the first time:
import java.awt.Point;
import java.io.IOException;
import org.netbeans.api.visual.action.ActionFactory;
import org.netbeans.api.visual.action.MoveProvider;
import org.netbeans.api.visual.widget.LabelWidget;
import org.netbeans.api.visual.widget.Scene;
import org.netbeans.api.visual.widget.Widget;
import org.netbeans.spi.actions.AbstractSavable;
import org.openide.util.Lookup;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
import org.openide.windows.TopComponent;

public class MyWidget extends LabelWidget {
    
    private MySavable mySavable;
    private Lookup lookup;
    private TopComponent tc;
    private InstanceContent ic;
    
    public MyWidget(Scene scene, String label, TopComponent tc) {
        super(scene, label);
        this.tc = tc;
        ic = new InstanceContent();
        getActions().addAction(ActionFactory.createMoveAction(null, new MoveStrategyProvider()));
    }
    
    @Override
    public Lookup getLookup() {
        if (lookup == null) {
            lookup = new AbstractLookup(ic);
        }
        return lookup;
    }
    
    private class MoveStrategyProvider implements MoveProvider {
        @Override
        public void movementStarted(Widget widget) {
        }
        @Override
        public void movementFinished(Widget widget) {
            modify();
        }
        @Override
        public Point getOriginalLocation(Widget widget) {
            return ActionFactory.createDefaultMoveProvider().getOriginalLocation(widget);
        }
        @Override
        public void setNewLocation(Widget widget, Point point) {
            ActionFactory.createDefaultMoveProvider().setNewLocation(widget, point);
        }
    }
    
    private void modify() {
        if (getLookup().lookup(MySavable.class) == null) {
            ic.add(mySavable = new MySavable());
        }
    }
    
    private class MySavable extends AbstractSavable {
        public MySavable() {
            register();
        }
        TopComponent tc() {
            return tc;
        }
        @Override
        protected String findDisplayName() {
            return getLabel();
        }
        @Override
        protected void handleSave() throws IOException {
            ic.remove(mySavable);
            unregister();
        }
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof MySavable) {
                MySavable m = (MySavable) obj;
                return tc() == m.tc();
            }
            return false;
        }
        @Override
        public int hashCode() {
            return tc().hashCode();
        }
    }

}

Friday Dec 07, 2012

java.net poll: What best describes your current feeling about Gradle

Check out the latest interesting java.net poll: What best describes your current feeling about Gradle?

Thursday Dec 06, 2012

Customizable Method Bodies in NetBeans IDE 7.3

In NetBeans IDE 7.3, bodies of newly created methods can now be customized in Tools/Templates/Java/Code Snippets, see below:

The content of the first of the two above, "Generated Method Body", is like this:

<#--
A built-in Freemarker template (see http://freemarker.sourceforge.net) used for
filling the body of methods generated by the IDE. When editing the template,
the following predefined variables, that will be then expanded into
the corresponding values, could be used together with Java expressions and
comments:
${method_return_type}       a return type of a created method
${default_return_value}     a value returned by the method by default
${method_name}              name of the created method
${class_name}               qualified name of the enclosing class
${simple_class_name}        simple name of the enclosing class
-->
throw new java.lang.UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.

The second one, "Overriden Methody Body", is as follows:

<#--
A built-in Freemarker template (see http://freemarker.sourceforge.net) used for
filling the body of overridden methods generated by the IDE. When editing
the template, the following predefined variables, that will be then expanded
into the corresponding values, could be used together with Java expressions and
comments:
${super_method_call}        a super method call
${method_return_type}       a return type of a created method
${default_return_value}     a value returned by the method by default
${method_name}              name of the created method
${class_name}               qualified name of the enclosing class
${simple_class_name}        simple name of the enclosing class
-->
<#if method_return_type?? && method_return_type != "void">
return ${super_method_call}; //To change body of generated methods, choose Tools | Templates.
<#else>
${super_method_call}; //To change body of generated methods, choose Tools | Templates.
</#if>
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« December 2012 »
SunMonTueWedThuFriSat
      
1
15
23
24
25
29
     
Today