Friday Jun 28, 2013

XML Schema Inference on the NetBeans Platform

jInfer is a framework for XML schema inference, based on the NetBeans Platform. Using jInfer, you can create DTD or XSD schemas from your existing XML documents, schemas, and queries.

With jInfer modules, you can fine-tune the inference process and see it unfolding. With your own modules, you can do even moreexport to exotic languages, try cutting-edge algorithms, and more.

Thursday Jun 27, 2013

Tissue Specific Electrochemical Fingerprinting on the NetBeans Platform

Proteomics and metalloproteomics are rapidly developing interdisciplinary fields providing enormous amounts of data to be classified, evaluated, and interpreted. Approaches offered by bioinformatics and also by biostatistical data analysis and treatment are therefore becoming increasingly relevant.

A bioinformatics tool has been developed at universities in Prague and Brno, in the Czech Republic, for analysis and visualization in this domain, on the NetBeans Platform:

More info: 

http://www.plosone.org/article/info%3Adoi%2F10.1371%2Fjournal.pone.0049654

Wednesday Jun 26, 2013

Musical Movements on the NetBeans Platform

I came across VirtMus recently, the "modern music stand", on the NetBeans Platform:

Its intentions remind me a LOT of Mike Kelly's Chord Maestro, which is also on the NetBeans Platform. Maybe the two should integrate?

Speaking of music, I've been in touch with Winston Dehaney who is creating score notation software, named "Acapella Score", also on the NetBeans Platform:

That's an app that could be integrated with the JFugue Music NotePad at some stage!

Tuesday Jun 25, 2013

Squibbly Update: Multiple Document Support

An update on Squibbly, the recently announced free and open source integration framework for LibreOffice!

Now multiple documents can be opened at the same time, either from "File | Open File" or from the File Browser (i.e., the rebranded Favorites window). Click to enlarge the image below to get a fuller perspective on Squibbly:

Take note of the tabs at the bottom of the editor-area in the screenshot above, and especially when you click the image to enlarge it. Multiple tabs are available at the same time, each representing a different open document. Click a different tab and its document is activated and brought to the front of the application. That means multiple LibreOffice applications can be used simultaneously, each could be undocked from the frame of the application, and the user can work with multiple documents, from multiple LibreOffice applications, all at the same time.

Info from this forum entry was useful in getting to the above solution:

http://www.oooforum.org/forum/viewtopic.phtml?t=41955

Still several focus-related problems to solve for the application to be ready for general usage.

Monday Jun 24, 2013

How to Implement Project Type "Copy", "Move", "Rename", and "Delete"

You've followed the NetBeans Project Type Tutorial and now you'd like to let the user copy, move, rename, and delete the projects conforming to your project type. When they right-click a project, they should see the relevant menu items and those menu items should provide dialogs for user interaction, followed by event handling code to deal with the current operation.

Right now, at the end of the tutorial, the "Copy" and "Delete" menu items are present but disabled, while the "Move" and "Rename" menu items are absent:


The NetBeans Project API provides a built-in mechanism out of the box that you can leverage for project-level "Copy", "Move", "Rename", and "Delete" actions. All the functionality is there for you to use, while all that you need to do is a bit of enablement and configuration, which is described below.

To get started, read the following from the NetBeans Project API:

Now, let's do some work. For each of the menu items we're interested in, we need to do the following:

  • Provide enablement and invocation handling in an ActionProvider implementation.
  • Provide appropriate OperationImplementation classes.
  • Add the new classes to the Project Lookup.
  • Make the Actions visible on the Project Node.
  • Run the application and verify the Actions work as you'd like.

Here we go:

  1. Create an ActionProvider. Here you specify the Actions that should be supported, the conditions under which they should be enabled, and what should happen when they're invoked, using lots of default code that lets you reuse the functionality provided by the NetBeans Project API:
    class CustomerActionProvider implements ActionProvider {
        @Override
        public String[] getSupportedActions() {
            return new String[]{
                ActionProvider.COMMAND_RENAME,
                ActionProvider.COMMAND_MOVE,
                ActionProvider.COMMAND_COPY,
                ActionProvider.COMMAND_DELETE
            };
        }
        @Override
        public void invokeAction(String string, Lookup lkp) throws IllegalArgumentException {
            if (string.equalsIgnoreCase(ActionProvider.COMMAND_RENAME)) {
                DefaultProjectOperations.performDefaultRenameOperation(
                        CustomerProject.this,
                        "");
            }
            if (string.equalsIgnoreCase(ActionProvider.COMMAND_MOVE)) {
                DefaultProjectOperations.performDefaultMoveOperation(
                        CustomerProject.this);
            }
            if (string.equalsIgnoreCase(ActionProvider.COMMAND_COPY)) {
                DefaultProjectOperations.performDefaultCopyOperation(
                        CustomerProject.this);
            }
            if (string.equalsIgnoreCase(ActionProvider.COMMAND_DELETE)) {
                DefaultProjectOperations.performDefaultDeleteOperation(
                        CustomerProject.this);
            }
        }
        @Override
        public boolean isActionEnabled(String command, Lookup lookup) throws IllegalArgumentException {
            if ((command.equals(ActionProvider.COMMAND_RENAME))) {
                return true;
            } else if ((command.equals(ActionProvider.COMMAND_MOVE))) {
                return true;
            } else if ((command.equals(ActionProvider.COMMAND_COPY))) {
                return true;
            } else if ((command.equals(ActionProvider.COMMAND_DELETE))) {
                return true;
            }
            return false;
        }
    }

    Importantly, to round off this step, add "new CustomerActionProvider()" to the "getLookup" method of the project. If you were to run the application right now, all the Actions we're interested in would be enabled (if they are visible, as described in step 4 below) but when you invoke any of them you'd get an error message because each of the DefaultProjectOperations above looks in the Lookup of the Project for the presence of an implementation of a class for handling the operation. That's what we're going to do in the next step.

  2. Provide Implementations of Project Operations. For each of our operations, the NetBeans Project API lets you implement classes to handle the operation. The dialogs for interacting with the project are provided by the NetBeans project system, but what happens with the folders and files during the operation can be influenced via the operations. Below are the simplest possible implementations, i.e., here we assume we want nothing special to happen. Each of the below needs to be in the Lookup of the Project in order for the operation invocation to succeed.
    private final class CustomerProjectMoveOrRenameOperation implements MoveOrRenameOperationImplementation {
        @Override
        public List<FileObject> getMetadataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public List<FileObject> getDataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public void notifyRenaming() throws IOException {
        }
        @Override
        public void notifyRenamed(String nueName) throws IOException {
        }
        @Override
        public void notifyMoving() throws IOException {
        }
        @Override
        public void notifyMoved(Project original, File originalPath, String nueName) throws IOException {
        }
    }
    
    private final class CustomerProjectCopyOperation implements CopyOperationImplementation {
        @Override
        public List<FileObject> getMetadataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public List<FileObject> getDataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public void notifyCopying() throws IOException {
        }
        @Override
        public void notifyCopied(Project prjct, File file, String string) throws IOException {
        }
    }
    
    private final class CustomerProjectDeleteOperation implements DeleteOperationImplementation {
        @Override
        public List<FileObject> getMetadataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public List<FileObject> getDataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public void notifyDeleting() throws IOException {
        }
        @Override
        public void notifyDeleted() throws IOException {
        }
    }

    Also make sure to put the above methods into the Project Lookup.

  3. Check the Lookup of the Project. The "getLookup()" method of the project should now include the classes you created above, as shown in bold below:
    @Override
    public Lookup getLookup() {
        if (lkp == null) {
            lkp = Lookups.fixed(new Object[]{
                this,
                new Info(),
                new CustomerProjectLogicalView(this),
                new CustomerCustomizerProvider(this),
                new CustomerActionProvider(),
                new CustomerProjectMoveOrRenameOperation(),
                new CustomerProjectCopyOperation(),
                new CustomerProjectDeleteOperation(),
                new ReportsSubprojectProvider(this),
            });
        }
        return lkp;
    }
  4. Make Actions Visible on the Project Node. The NetBeans Project API gives you a number of CommonProjectActions, including for the actions we're dealing with. Make sure the items in bold below are in the "getActions" method of the project node:
    @Override
    public Action[] getActions(boolean arg0) {
        return new Action[]{
            CommonProjectActions.newFileAction(),
            CommonProjectActions.copyProjectAction(),
            CommonProjectActions.moveProjectAction(),
            CommonProjectActions.renameProjectAction(),
            CommonProjectActions.deleteProjectAction(),
            CommonProjectActions.customizeProjectAction(),
            CommonProjectActions.closeProjectAction()
        };
    }
  5. Run the Application. When you run the application, you should see this:

Let's now try out the various actions:

  • Copy. When you invoke the Copy action, you'll see the dialog below. Provide a new project name and location and then the copy action is performed when the Copy button is clicked below:

    The message you see above, in red, might not be relevant to your project type. When you right-click the application and choose Branding, you can find the string in the Resource Bundles tab, as shown below:

    However, note that the message will be shown in red, no matter what the text is, hence you can really only put something like a warning message there. If you have no text at all, it will also look odd.

    If the project has subprojects, the copy operation will not automatically copy the subprojects. Take a look here and here for similar more complex scenarios.

  • Move. When you invoke the Move action, the dialog below is shown:

  • Rename. The Rename Project dialog below is shown when you invoke the Rename action:

    I tried it and both the display name and the folder on disk are changed.

  • Delete. When you invoke the Delete action, you'll see this dialog:

    The checkbox is not checkable, in the default scenario, and when the dialog above is confirmed, the project is simply closed, i.e., the node hierarchy is removed from the application.

    However, if you truly want to let the user delete the project on disk, pass the Project to the DeleteOperationImplementation and then add the children of the Project you want to delete to the getDataFiles method:

    private final class CustomerProjectDeleteOperation implements DeleteOperationImplementation {
        private final CustomerProject project;
        private CustomerProjectDeleteOperation(CustomerProject project) {
            this.project = project;
        }
        @Override
        public List<FileObject> getDataFiles() {
           List<FileObject> files = new ArrayList<FileObject>();
           FileObject[] projectChildren = project.getProjectDirectory().getChildren();
           for (FileObject fileObject : projectChildren) {
              addFile(project.getProjectDirectory(), fileObject.getNameExt(), files);
           }
           return files;
        }
        private void addFile(FileObject projectDirectory, String fileName, List<FileObject> result) {
           FileObject file = projectDirectory.getFileObject(fileName);
           if (file != null) {
              result.add(file);
           }
        }
        @Override
        public List<FileObject> getMetadataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public void notifyDeleting() throws IOException {
        }
        @Override
        public void notifyDeleted() throws IOException {
        }
    }

    Now the user will be able to check the checkbox, causing the method above to be called in the DeleteOperationImplementation:

Hope this answers some questions or at least gets the discussion started. Before asking questions about this topic, please take the steps above and only then attempt to apply them to your own scenario.

Useful implementations to look at:

http://kickjava.com/src/org/netbeans/modules/j2ee/clientproject/AppClientProjectOperations.java.htm

https://kenai.com/projects/nbandroid/sources/mercurial/content/project/src/org/netbeans/modules/android/project/AndroidProjectOperations.java

Sunday Jun 23, 2013

Squibbly: LibreOffice Integration Framework for the Java Desktop

Squibbly is a new framework for Java desktop applications that need to integrate with LibreOffice, or more generally, need office features as part of a Java desktop solution that could include, for example, JavaFX components. Here's what it looks like, right now, on Ubuntu 13.04:

Why is the framework called Squibbly? Because I needed a unique-ish name, because "squibble" sounds a bit like "scribble" (which is what one does with text documents, etc), and because of the many absurd definitions in the Urban Dictionary for the apparently real word "squibble", e.g., "A name for someone who is squibblish in nature." And, another e.g., "A squibble is a small squabble. A squabble is a little skirmish." But the real reason is the first definition (and definitely not the fourth definition): "Taking a small portion of another persons something, such as a small hit off of a pipe, a bite of food, a sip of a drink, or drag of a cigarette."

In other words, I took (or "squibbled") a small portion of LibreOffice, i.e., OfficeBean, and integrated it into a NetBeans Platform application. Now anyone can add new features to it, to do anything they need, such as create a legislative software system as Propylon has done with their own solution on the NetBeans Platform:

For me, the starting point was Chuk Munn Lee's similar solution from some years ago. However, he uses reflection a lot in that solution, because he didn't want to bundle the related JARs with the application. I understand that benefit but I find it even more beneficial to not need to require the user to specify the location of the LibreOffice location, since all the necessary JARs and native libraries (currently 32-bit Linux only, by the way) are bundled with the application. Plus, hundreds of lines of reflection code, as in Chuk's solution, is not fun to work with at all.

Switching between applications is done like this:


It's a work in progress, a proof of concept only. Just the result of a few hours of work to get the basic integration to work. Several problems remain, some of them potentially unsolvable, starting with these, but others will be added here as I identify them:

  • Window management problems. I'd like to let the user have multiple LibreOffice applications and documents open at the same time, each in a new TopComponent. However, I haven't figured out how to do that. Right now, each application is opened into the same TopComponent, replacing the currently open application. I don't know the OfficeBean API well enough, e.g., should a single OfficeBean be shared among multiple TopComponents or should each of them have their own instance of it?

  • Focus problems. When putting the application behind other applications and then switching back to the application, typing text becomes impossible. When closing a TopComponent and reopening it, the content is lost completely. Somehow the loss of focus, and then the return of focus, disables something. No idea how to fix that.

The project is checked into this location, which isn't public yet, so you can't access it yet. Once it's publicly available, it would be great to get some code contributions and tweaks, etc.

https://java.net/projects/squibbly

Here's the source structure, showing especially how the OfficeBean JARs and native libraries (currently for Linux 32-bit only) fit in:


Ultimately, would be cool to integrate or share code with http://joeffice.com!

Saturday Jun 22, 2013

How to Open Any Folder as a Project in the NetBeans Platform

Typically, as described in the NetBeans Project Type Tutorial, you'll define a project type based on the presence of a file (e.g., "project.xml" or "customer.txt" or something like that) in a folder. I.e., if the file is there, then its parent, i.e., the folder that contains the file, is a project and should be opened in your application.

However, in some scenarios (as with the HTML5 project type introduced in NetBeans IDE 7.3), the user should be able to open absolutely any folder at all into the application. How to create a project type that is that liberal?

Here you go, the only condition that needs to be true is that the selected item in the "Open Project" dialog is a folder, as defined in the "isProject" method below. Nothing else. That's it. If you select a folder, it will be opened in your application, displaying absolutely everything as-is (since below there's no ProjectLogicalView defined):

import java.beans.PropertyChangeListener;
import java.io.IOException;
import javax.swing.Icon;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.spi.project.ProjectFactory;
import org.netbeans.spi.project.ProjectState;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.FilterNode;
import org.openide.util.Exceptions;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = ProjectFactory.class)
public class FolderProjectFactory implements ProjectFactory {
    @Override
    public boolean isProject(FileObject projectDirectory) {
        return DataFolder.findFolder(projectDirectory) != null;
    }
    @Override
    public Project loadProject(FileObject dir, ProjectState state) throws IOException {
        return isProject(dir) ? new FolderProject(dir) : null;
    }
    @Override
    public void saveProject(Project prjct) throws IOException, ClassCastException {
        // leave unimplemented for the moment
    }
    private class FolderProject implements Project {
        private final FileObject projectDir;
        private Lookup lkp;
        private FolderProject(FileObject dir) {
            this.projectDir = dir;
        }
        @Override
        public FileObject getProjectDirectory() {
            return projectDir;
        }
        @Override
        public Lookup getLookup() {
            if (lkp == null) {
                lkp = Lookups.fixed(new Object[]{
                    new Info(),
                });
            }
            return lkp;
        }
        private final class Info implements ProjectInformation {
            @Override
            public Icon getIcon() {
                Icon icon = null;
                try {
                    icon = ImageUtilities.image2Icon(
                            new FilterNode(DataFolder.find(
                            getProjectDirectory()).getNodeDelegate()).getIcon(1));
                } catch (DataObjectNotFoundException ex) {
                    Exceptions.printStackTrace(ex);
                }
                return icon;
            }
            @Override
            public String getName() {
                return getProjectDirectory().getName();
            }
            @Override
            public String getDisplayName() {
                return getName();
            }
            @Override
            public void addPropertyChangeListener(PropertyChangeListener pcl) {
                //do nothing, won't change
            }
            @Override
            public void removePropertyChangeListener(PropertyChangeListener pcl) {
                //do nothing, won't change
            }
            @Override
            public Project getProject() {
                return FolderProject.this;
            }
        }
    }
}

Even the ProjectInformation implementation really isn't needed at all, since it provides nothing more than the icon in the "Open Project" dialog, the rest (i.e., the display name in the "Open Project" dialog) is provided by default regardless of whether you have a ProjectInformation implementation or not.

Friday Jun 21, 2013

How to Close Objects in the NetBeans Platform

Just like in this blog entry yesterday, you've included the "User Utilities" module (by right-clicking your NetBeans Platform application, choosing Properties, clicking Libraries, and checking the box next to "User Utilities" in the "ide" cluster) and so now you magically have "File | Open File" in the menu bar:

However, you don't have a "Close File" menu item in the list above. Let's change that! If we look in the layer file of a module (i.e., create a new XML Layer file from the New File dialog, expand the "Important Files" node, and then expand the layer file), you'll see that a Close Action already exists at Actions/System/Close:

<file name="org-openide-actions-CloseAction.instance">
    <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
    <attr name="delegate" methodvalue="org.openide.awt.Actions.performer"/>
    <attr name="selectionType" stringvalue="ANY"/>
    <attr name="surviveFocusChange" boolvalue="false"/>
    <attr name="displayName" bundlevalue="org/openide/awt/Bundle#Close"/>
    <attr name="noIconInMenu" boolvalue="true"/>
    <attr name="type" stringvalue="org.netbeans.api.actions.Closable"/>
</file>

The above defines a CloseAction which (1) needs to be displayed somewhere and (2) is activated when any number of Closables are selected.

What are Closables? They are implementations of "org.netbeans.api.actions.Closable". That interface has a single method, close, which returns a boolean. What you do with that implementation is completely up to you. But multiple objects in your application could be publishing Closables, so it makes sense to actually do something like closing something when invoking the CloseAction, which is enabled when a Closable is in the currently selected item's Lookup. Then test for some condition to be true and do something like closing the item, e.g., removing the Node, closing the TopComponent, or something similar to that.

Right now, without doing anything, the above CloseAction is available on editor tabs. I.e., right-click the tab of an editor document and you'll see "Close". Now we'd also like to see that in the File menu. Therefore, add the following to one of your layer files:

<folder name="Menu">
    <folder name="File">
        <file name="org-openide-actions-CloseAction.shadow">
            <attr name="originalFile" stringvalue="Actions/System/org-openide-actions-CloseAction.instance"/>
            <attr intvalue="850" name="position"/>
        </file>
    </folder>
</folder>

Run the application again and now you see this:

In other words, you now have a "Close" menu item, defined by the layer entry shown above, which means that it will become enabled automatically when something that is currently selected has a Closable in its Lookup. But before we get to that stage, you might be reading this and asking "How can I change the display text 'Close' to be 'Close File' instead?" That's the wrong question because you have no idea which objects may have Closable in their Lookup, i.e., it could be a Node, it could be a TopComponent, it could be something else, hence "Close" is the most appropriate display text or maybe it could be "Close Object" or "Close Item" instead. If you don't like this, you can create your own CloseAction, which is not hard at all:

@ActionID(
        category = "File",
        id = "org.zip.file.CloseZIPFolderAction")
@ActionRegistration(
        displayName = "#CTL_CloseZIPFolderAction")
@ActionReferences({
    @ActionReference(path = "Menu/File", position = 850),
    @ActionReference(path = "Loaders/folder/any/Actions", position = 250)
})
@Messages("CTL_CloseZIPFolderAction=Close ZIP Folder")
public final class MyCloseAction implements ActionListener {

    private final List<ZIPFolderClosable> context;

    public MyCloseAction(List<ZIPFolderClosable> context) {
        this.context = context;
    }

    @Override
    public void actionPerformed(ActionEvent ev) {
        for (ZIPFolderClosable closable : context) {
            closable.close();
        }
    }
    
}

The above Action will be available from the File menu and also when right-clicking any folder. What happens when the Action is invoked? Well, it will only be enabled if one or more of your own special ZIPFolderClosables are in the Lookup of the currently selected object. When invoked, "close" will be called on all of those ZIPFolderClosables. What will that ZIPFolderClosable look like? It will be an interface with a method "close" that returns a boolean. The difference is that now you have a special Action for closing your own special object, i.e., ZIP folders.

Tip: If you want the text in the Close menu item to be updated dynamically, investigate the "menuText" attribute, as described here. And here's the result of that investigation:


@ActionID(
        category = "File",
        id = "org.zip.file.CloseZIPFolderAction")
@ActionRegistration(
        lazy = false,
        displayName = "NOT-USED")
@ActionReferences({
    @ActionReference(path = "Menu/File", position = 850),
    @ActionReference(path = "Loaders/folder/any/Actions", position = 250)
})
public final class MyCloseAction extends AbstractAction implements LookupListener {

    private Lookup.Result<Node> nodes;

    public MyCloseAction() {
        nodes = Utilities.actionsGlobalContext().lookupResult(Node.class);
        nodes.addLookupListener(
                WeakListeners.create(LookupListener.class, this, nodes));
        resultChanged(new LookupEvent(nodes));
    }

    @Override
    public void actionPerformed(ActionEvent ev) {
        for (Node node : nodes.allInstances()) {
            Closable closable = node.getLookup().lookup(Closable.class);
            if (closable != null) {
                closable.close();
            }
        }
    }

    @Override
    public void resultChanged(LookupEvent le) {
        Collection<? extends Node> p = nodes.allInstances();
        if (p.size() == 1) {
            Node currentNode = p.iterator().next();
            putValue("menuText", "Close " + currentNode.getDisplayName());
        } else {
            putValue("menuText", "Close");
        }
    }
    
}

Next, we need to publish a Closable into an object that needs to be closable when selected. Since all editor documents automatically have a Closable in their Lookup, you'll see the Close action above (i.e., the one defined at Actions/System/Close in the layer) will be enabled whenever you have an editor document selected.

But what about your own objects? How do you integrate your own objects into the above infrastructure? Well, for the ZIPDataObject discussed in recent blog entries, something like this is needed, i.e., add Closable to the Lookup of the ZIPNode, while (optionally) making sure all files currently opened from within the ZIP folder are also closed when the ZIP folder closes:

instanceContent.add(new Closable() {
    @Override
    public boolean close() {
        //Remove the FileObject from Central Lookup:
        CentralLookup.getDefault().remove(key);
        //Refresh the Node hierarchy, which will remove the Node:
        refresh(true);
        //But now we also want to close the related files
        //that are currently open, i.e., those from the 
        //currently closed ZIP folder.
        //Start by identifying the path to the ZIP folder:
        String fullPathToZIPFolder = key.getPath();
        //Iterate through open TopComponents, find matching FileObjects
        //and close those TopComponents
        Set opened = WindowManager.getDefault().getRegistry().getOpened();
        for (TopComponent topComponent : opened) {
            FileObject tcFileObject = topComponent.getLookup().lookup(FileObject.class);
            if (tcFileObject != null) {
                String fullPathToFile = tcFileObject.getPath();
                if(fullPathToFile.contains(fullPathToZIPFolder)){
                    if (topComponent.canClose()){
                       topComponent.close();
                    }
                }
            }
        }
        return true;
    }
});

And so now you have a centralized mechanism for closing all objects in a standardized way. Each object doesn't have its own Close action. Instead, all objects share the same Close action, they simply provide different implementations of what should happen when the Close action is invoked.

Thursday Jun 20, 2013

How to Extend the Open File Dialog

You've included the "User Utilities" module (by right-clicking your NetBeans Platform application, choosing Properties, clicking Libraries, and checking the box next to "User Utilities" in the "ide" cluster) and so now you magically have "File | Open File" in the menu bar. When you invoke the Action that has magically been provided, you see the Open dialog, as shown below:


Look at the "Files of Type" filter above. And now look at it again, after I've added the "Image" module to the application (also in the "ide" cluster, as described for the "User Utilities" module above):

The "Image" module adds a new "Files of Type" filter. Now let's say you're creating your own file types, as I did a few days ago for working with ZIP files. I tweaked the @MIMEResolver annotation slightly, which is automatically available at the top of a DataObject created via the New File Type dialog:

@MIMEResolver.ExtensionRegistration(
        displayName = "#LBL_ZIP_LOADER",
        mimeType = "application/x-zip",
        showInFileChooser = {"ZIP Files", "ZIP archives"},
        extension = {"zip", "ZIP"})

And now I see this, i.e., all thanks to the "showInFileChooser" annotation attribute above:


In other words, use "showInFileChooser" if you want a new filter added to the Open File dialog, to enable the user to filter for files conforming to your custom file type. The value is a String Array, which defines the text to be displayed to the left of the file extension, while the file extension is shown automatically when the "showInFileChooser" attribute is defined. Multiple Strings in the Array is useful, maybe as in the above, in cases where the user might have different understandings of what a ZIP is, i.e., a file or an archive. Fine, provide texts for both understandings. An empty String simply means that nothing will appear to the left of "[.zip]".

Wednesday Jun 19, 2013

Menubars and Toolbars are Optional in the NetBeans Platform

The question of the day comes from Ralf Eichinger, a few days ago in the comments in this blog:

Coming from Java EE and starting to refresh my Swing knowledge, I found your very valuable blog entries. As I am just starting to have a look at the NetBeans Platform, I am confused at seeing the NetBeans menubar and toolbar in all the example applications. This makes me think that all applications implemented with the NetBeans Platform must be heavier than they should be (carrying all the unnecessary menu and toolbar stuff). I (and probably all old school Swing developers) would like to see how easy it is to remove an unneccessary menubar or toolbar. And is the end result (JAR file) smaller when removing these stuff?

And the answer of the day, i.e., in response to the question from Ralf, above, comes from Sean Phillips, who works for NASA on NetBeans Platform applications and will most likely be at JavaOne this year:

What you will find is that the menu and toolbar get populated dynamically by the features you add to your application. It will only be as heavy as you want to make it, though if you want features you have to include the associated support for GUI components. If it is a matter of just not showing these things, you can hide them quite easily. Geertjan has written towards that several times, at https://blogs.oracle.com/geertjan/entry/simplified_usage_of_the_netbeans, for starters.

Great answer, Sean. Indeed, if you don't want a menubar or toolbar in a NetBeans Platform application, simply create a NetBeans module, add a layer file (both of these activities can be done via wizards in NetBeans IDE), and then hide the menubar and toolbar, like this:

<?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="Menu_hidden"/>
    <file name="Toolbars_hidden"/>
</filesystem>

Result:


But this will not make a difference in the size of the application. The code for the menubar and toolbar are still in the application, you've simply prevented them from being displayed. As Sean also points out in his response, you can take a different approach—remove the modules you don't need. If you remove the "org.netbeans.core.ui" module (by right-clicking the application, choosing Properties, expanding "platform", and unselected "Core - UI"), you will not have a toolbar anymore, for example. You can even remove all of the UI components from the NetBeans Platform, if that's what you want to do, as explained in this tutorial, which is the very first tutorial in the "NetBeans Platform Fundamentals" section of the NetBeans Platform Learning Trail:

https://platform.netbeans.org/tutorials/nbm-runtime-container.html

Also make sure to read the blog entry that Sean points to, which gives you a complete template for starting your own NetBeans Platform application without menubars and toolbars:

https://blogs.oracle.com/geertjan/entry/simplified_usage_of_the_netbeans

Tuesday Jun 18, 2013

Integrating ZIP Features in a NetBeans Platform Application

Let's say your requirement is that you need to open a ZIP file into an application, modify its content, and then ZIP it all up again.

And you're using the NetBeans Platform. Here's the general approach to take:

  1. Learn from Similar Code. The very first thing to do is to ask yourself: "Does NetBeans IDE or any other NetBeans Platform application do something similar?" The answer in this case is Yes because NetBeans IDE has "File | Import Project | From ZIP". So you can look in the NetBeans source code and get some nice code relating to java.util.zip.ZipInputStream, while FileUtil.copy is also great to use.

  2. Create a NetBeans Platform Application. When you create the application, go to the "ide" cluster in the Project Properties dialog of the application and include the "Image" module, as well as the "XML Text Editor" module, together with all the related modules (simply click the Resolve button) and then you'll have some cool editors immediately available when you open the content of the ZIP file in the application. Also include "User Utilities" so that you have "File | Open File" in your application.

  3. Use CentralLookup. We're going to end up creating a Node hierarchy, i.e., we'll be using classes such as Node, ChildFactory, BeanTreeView, and ExplorerManager. These integrate very well with the Lookup concept. That is, when objects of interest (e.g., a folder that contains the newly unzipped ZIP file) are registered in the Lookup, you will create new Nodes in the Node hierarchy.

    But which Lookup to use? Lookup.getDefault? No. That gives you access to META-INF/services, which is the standard Java 6 extension mechanism. Utilities.actionsGlobalContext? No. That gives you access to the currently selected object. We don't care about selection in this scenario. Whenever a ZIP file is unzipped into a new folder, we need to add that folder to some kind of observable list, i.e., Lookup, to which our Node hierarchy is listening. Therefore, copy CentralLookup into your application and use that. Here it is.

  4. Recognize ZIP Files. Create a new MIME type (i.e., use the New File Type wizard) to recognize files with a ZIP extension as "application/x-zip", remove all the editor related code in the DataObject (because you don't want to open the ZIP file into an editor, instead, you want to open the files within the ZIP file into an editor), and implement Openable.

    When the OpenAction is invoked, which happens automatically when you try to open a file via "File | Open File" (or from the Favorites window), your implementation of Openable will be called. In that Openable implementation, unzip the ZIP file into a new folder and then register that folder as a FileObject in the CentralLookup.

  5. Create a Node Hierarchy. Create a TopComponent in "explorer" mode. Create a ChildFactory that implements LookupListener, listening to CentralLookup for new FileObjects. When a new FileObject is present, wrap it in a FilterNode and refresh the Node hierarchy.

  6. Listen for Changes in the Folder. Use FileObject.addRecursiveListener() to listen for additions/deletions/changes in the folder. That gives you a way to mark the Node hierarchy as being "dirty", in some way, e.g., publish a Savable into the Lookup of the TopComponent or the Node and then the Save action will become enabled to save the Node hierarchy back into a ZIP file. And how to implement that? Well, look at the source code for "File | Export Project | To ZIP" in the NetBeans sources.

I've completed the first 5 steps above (and everything in step 6 is done, too, i.e., the Savable is added to the Node Lookup when appropriate and, when invoked, an "Export to ZIP" dialog is shown, which works as one might expect) and checked in the sources here:

https://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.3/misc/OpenZIP

Here's what the result looks like after opening a ZIP file, via "File | Open File", e.g., the images that you see will open into the NetBeans image editor automatically when they are double-clicked, because FilterNode is used to wrap the unzipped folder and its children:


Just for fun, I made the feature modular, i.e., the infrastructure for recognizing and parsing ZIP files is in one module, the explorer view is in another module, and a Utils module is included to provide the CentralLookup, which is the communication mechanism between the two modules:

Above you see everything I did to make this work, not very much when you consider the amount of features provided at the end, e.g., an application frame, window system, modular infrastructure, plugin system, menubar, toolbar, image editor, XML editor, ZIP file recognition, ZIP file parsing, rendering of ZIP file content in a Swing JTree (via ExplorerManager and BeanTreeView):

Finally, note that what you see above in the "ZIP Window" are not actually ZIP files. They're FilterNodes on top of folders created from ZIP files, with the display name of the FilterNode (and all its content, i.e., its children) derived from the original node, which is the folder created from unzipping the ZIP file. 

Monday Jun 17, 2013

Porting Wandora to the NetBeans Platform (Part 2)

A first step in porting an application, especially one you don't really know, is to run the application in the NetBeans debugger and then take a GUI snapshot of it. That lets you relate the GUI elements in the application to their source code, very easily, as shown below for Wandora:

It's kind of similar to the Chrome integration for HTML5 applications! There, you can click in the browser and then see where items are defined in the IDE. In the above screenshot, you're actually doing something very similar, but then for Java desktop applications, whether in Swing or JavaFX.

Sunday Jun 16, 2013

Porting Wandora to the NetBeans Platform (Part 1)

Wandora is a general purpose information extraction, management and publishing application based on Topic Maps and Java. Wandora has a graphical user interface, layered and merging information model, multiple visualization models, huge collection of information extraction, import and export options, embedded HTTP server with several output modules and open plug-in architecture. 

Here it is: 

The sources are in a handy NetBeans project:

Interestingly, attempts have been made to port Wandora to the NetBeans Platform, for very good reasons:

http://www.wandora.org/forum/viewtopic.php?f=11&t=50

But success has been elusive: 

http://www.wandora.net/wandora/forum/viewtopic.php?f=11&t=95

The application is clearly large and understandably a massive undertaking to port it. But I'm going to try it nonetheless and report on my progress here. 

Saturday Jun 15, 2013

Sublime Text Look & Feel for NetBeans IDE

The highest ranked NetBeans theme on the truly brilliant NetBeans Themes site replicates Sublime Text and looks really great. Click to enlarge the screenshot below to see it in context in NetBeans IDE 7.3.1:

I have no idea who is behind that NetBeans Themes site, but it's an amazing contribution to the NetBeans community. Just take a look at the themes on the site, choose one, download its ZIP file, go to Tools | Options in the IDE, click Import and then browse to the ZIP you downloaded. You can also upload your own themes to the site, i.e., configure NetBeans IDE the way you want it to be, export the settings by clicking Export in Tools | Options, click Upload Yours on the site and you're done. Wonderfully simple and a wonderful site!

In other news. My internet discovery of the day is Comedians In Cars Getting Coffee! Excellent.

Friday Jun 14, 2013

Ubuntu 13.04

I've been actively and naturally using Ubuntu since Ubuntu 7.04 "Feisty Fawn", which means, of course, April 2007, as described here. Now let's flash forward six years or so to yesterday. Yesterday I was on 12.04 "Precise Pangolin", today on the latest version 13.04 "Raring Ringtail", which means I skipped 12.10 "Quantal Quetzal". In general, looking back in this blog over the years, I seem to have installed more "04" releases than "10" releases, which is interesting. I tend to skip one Ubuntu release per year, more or less. Not sure why, exactly. Part of it is that I'm too lazy to backup my data prior to an upgrade and can probably only handle the occasional frantic moments of despair (e.g., the sudden pause in the upgrade garbles that could either be an internal crash or the tiny technicians in my computer simply needing a coffee break) in the middle of upgrades on an annual basis.

The process which I followed to get from 12.04 to 13.04 is completely illustrative of how software updates happen and is interesting from a NetBeans IDE point of view, too. Often, on Twitter and on mailing lists, at least 60% of NetBeans-related questions I see immediately cause a small fuse to blow in my mind while I think, almost out loud: "Well, why don't you just go to the NetBeans Wiki and you'll find the answer there, very easily." But, of course, that's not how we gather info nowadays, we simply go to Google even though we know that each and every software product has its own dedicated site with highly detailed information. Then, when we don't find the answer we're looking for (or we think we're looking for) within 30 seconds, we start yelling "Help this product sucks" on Twitter.

And that's also why I can't explain exactly how I got from 12.04 to 13.04. I basically googled around and somewhere found a command line that I assumed would bring me directly to 13.04 but got me to 12.10. Then I went to the Update Manager, which (several hours later, zillions of new files later, while leaving everything else miraculously untouched) brought me to 13.04.

The other interesting thing is why I wanted to move to 13.04 in the first place. I would have been perfectly happy to have stayed at 12.04 if it wasn't for the fact that the VPN Cisco AnyConnect Client doesn't (or at least not without various low level tweaks, from a variety of random unreliable and contradictory online searches) work on 12.04. It worked before that, but from 12.04 onwards, I couldn't VPN into internal company sites that I need to do my work and had to use a Windows laptop purely for that reason only. That's why I upgraded. However, many many hours later, when I was finally on 13.04, I had exactly the same problems as before! Then I moved to "openconnect", which solved all my VPN problems immediately. But this aspect is interesting from a NetBeans point of view, where I often find myself thinking in response to some question or other: "Come on, you're asking this question, and then it turns out you're on NetBeans IDE 6.9 while the latest release is NetBeans IDE 7.3! Never thought of upgrading, in all these years..?" Etc. Then again, on the other hand, switching from one release of NetBeans IDE to another is a LOT simpler (takes a lot less time, is a lot less risky and a lot less irreversible) than switching from one version of Ubuntu to another.

Finally, a missing piece in my software toolbox on Ubuntu was Cisco IP Communicator, a soft phone, which only has Windows and Mac distributions. I never had it (nor tried to have it) on Ubuntu before but thought I'd give it a try since it would be the final missing piece on my Ubuntu from my Windows world. And, lo and behold, it works perfectly (well, aside from the Audio bit at the beginning, but that's a known problem) on Ubuntu too, if you install it via Wine. (Maybe the biggest plus from the process of updating to Ubuntu 13.04 is the side effect that I now know a lot more about Wine, i.e., the ".wine" folder is a fascinating replication of a Windows operating system.)

And now I'm on 13.04, the world isn't a very different place (not noticed anything new, so far, maybe performance improvements, but maybe mostly because I always assume each release of anything must have performance enhancements somehow). I used to have absolute nightmares setting up Wifi after an Ubuntu upgrade, but everything worked immediately out of the box. So far I don't have anything to mention in terms of differences, but certainly the smoothest upgrade (especially since I upgraded from 12.04 to 12.10 and then 13.04, all in the same set of hours) I have ever experienced. And good to know I'm on the latest release of Ubuntu again!

Thursday Jun 13, 2013

Ruby on Rails in NetBeans IDE 7.3.1

NetBeans IDE 7.3.1 is released, hurray, etc. Now let's look at using Ruby on Rails in NetBeans IDE 7.3.1.

Go here, to the NetBeans plugin for JRuby maintained by Tom Enebo and the JRuby community (since there really isn't anyone better in the world to be responsible for a tooling plugin than the community itself) and click the Download button:

http://plugins.netbeans.org/plugin/38549/ruby-and-rails

Now you have a ZIP file. Unzip it. Go to Tools | Plugins in NetBeans IDE 7.3.1. Select all the NBMs from the unzipped file. Make very sure to change the "Files of Type" filter to "All", so that you can include the JRuby JAR, which you must have, otherwise the NBMs will not be able to be installed. I.e., you can include the JAR, as shown below, which is in the ZIP file, but, again, make sure to change the filter from the default, so that not only NBM files are selectable.

When you click Install above, you'll see this dialog, just accept what it tells you:

Once the installation is complete, go to File | New Project and there you'll see the Depot Application, which is a nice sample to get you started:

Now you have a full-blown and very fast/smooth editor for your code (click to enlarge the image below):

You can configure a bunch of things in the Project Properties dialog, i.e., per project:

Also, there are IDE-wide configuration settings you can configure in Tools | Options:

And, aside from the sample shown above, there are various starting points for your own applications:

So dive in today, the water is good!

Wednesday Jun 12, 2013

Adding a Palette to a MultiViewElement

The question of the day comes from Michele Vigilante from Italy who wants to combine a Palette with a MultiViewElement.

The two key references to solving this are the following:

Here's step by step how to put the pieces together, starting from the application described here:

https://blogs.oracle.com/geertjan/entry/how_to_create_a_satellite

  1. Create a Node Hierarchy. Typically you already have a hierarchy of nodes, but for the sake of argument, here's the one I'll be using, as simple as it gets:
    Node nodeHierarchy = new AbstractNode(Children.create(new ChildFactory<String>() {
        @Override
        protected boolean createKeys(List toPopulate) {
            for (int i = 0; i < 10; i++) {
                toPopulate.add("Category " + i);
            }
            return true;
        }
        @Override
        protected Node createNodeForKey(String key) {
            AbstractNode an = new AbstractNode(Children.create(new ChildFactory<String>() {
                @Override
                protected boolean createKeys(List<String> toPopulate) {
                    for (int i = 0; i < 10; i++) {
                        toPopulate.add("Item " + i);
                    }
                    return true;
                }
                @Override
                protected Node createNodeForKey(final String key) {
                    AbstractNode an = new AbstractNode(Children.LEAF, Lookups.singleton(key));
                    an.setDisplayName(key);
                    return an;
                }
            }, true));
            an.setDisplayName(key);
            return an;
        }
    }, true));
  2. Create a Palette from the Node Hierarchy. For the below, your module will need to depend on "Common Palette", which it won't be able to do unless it has been set as part of the application. Also, make sure to add the Palette to the Lookup of the MultiViewElement, so that the Palette will open/close with the MultiViewElement, together with the Lookup that has already been defined for it, by adding the following to the end of the MultiViewElement constructor:
    InstanceContent ic = new InstanceContent();
    Lookup dynamicLookup = new AbstractLookup(ic);
    lookup = new ProxyLookup(dynamicLookup, getLookup());
    ic.add(PaletteFactory.createPalette(nodeHierarchy, new PaletteActions() {
        @Override
        public Action[] getImportActions() {
            return null;
        }
        @Override
        public Action[] getCustomPaletteActions() {
            return null;
        }
        @Override
        public Action[] getCustomCategoryActions(Lookup category) {
            return null;
        }
        @Override
        public Action[] getCustomItemActions(Lookup item) {
            return null;
        }
        @Override
        public Action getPreferredAction(Lookup item) {
            return null;
        }
    }));
  3. Accept the Drop. Now, when the Visual tab opens, the user sees a Palette with items in it. Let's do something useful with the Palette, again in the MultiViewElement, somewhere in the constructor where the Scene is retrieved from the Lookup:
    scene.getActions().addAction(ActionFactory.createAcceptAction(new AcceptProvider() {
        @Override
        public ConnectorState isAcceptable(Widget widget, Point point, Transferable transferable) {
            return ConnectorState.ACCEPT;
        }
        @Override
        public void accept(Widget widget, Point point, Transferable transferable) {
            Node transferredNode = NodeTransfer.node(transferable, NodeTransfer.DND_COPY_OR_MOVE);
            String label = transferredNode.getLookup().lookup(String.class);
            if (label != null) {
                LabelWidget lw = new LabelWidget(scene, label);
                lw.setPreferredLocation(point);
                lkp.lookup(LayerWidget.class).addChild(lw);
                try {
                    String existingContent = obj.getPrimaryFile().asText();
                    String newContent = existingContent + "\n" + label;
                    OutputStream os = obj.getPrimaryFile().getOutputStream();
                    OutputStreamWriter writer = new OutputStreamWriter(os);
                    writer.write(newContent);
                    writer.flush();
                    writer.close();
                } catch (FileAlreadyLockedException ex) {
                    Exceptions.printStackTrace(ex);
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        }
    }));

    Now, when an item is dropped, if the Node has a String in its Lookup, a LabelWidget is added to the LayerWidget and the String is also added to the underlying file.

That's all you need to do to get started with a simple scenario that can be extended however you like.

All the source code discussed above is integrated into the sample:

https://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.3/misc/ABCFileTypeNavigator

Are you trying out the code discussed in this blog entry and something doesn't work for you? Go to the location above and look at the source code and/or check it out from its repo.

Tuesday Jun 11, 2013

JavaFX WebView & JavaFX HTMLEditor: A Tale of Two Cities

Tough times in the world of Fosfor, the EPUB Open Toolbox. Turns out that while the JavaFX WebView has perfect rendering but no editing capabilities, the JavaFX HTMLEditor has perfect editing capabilities but crap rendering.

Exhibit one is right here, with the JavaFX HTMLEditor on the left and the JavaFX WebView on the right, rendering the same XHTML file:

As can be seen, the images, CSS, and entities defined in the XHTML file above are perfectly rendered in WebView, but not at all in the HTMLEditor. It seems to me that the HTMLEditor is meant for rendering text only, nothing else.

I then hacked my own HTMLEditor on top of the JavaFX HTMLEditor, which searches through all the text prior to rendering and uses the file protocol to locally locate images and CSS:

https://java.net/projects/epubopentoolbox/sources/epub/content/jepub-browser-external/src/main/java/com/eirik/jepub/browser/external/HTMLEditorControl.java

The result is as follows, i.e., I've fixed the images and CSS problem, but not the entities, yet:

In short, I'm going to try and create a set of service providers for a "fixHTMLEditorRendering" service which will pre-process images, CSS, and HTML entities prior to them being rendered in the JavaFX HTMLEditor. Then, when saving the file back to EPUB, I'm going to have to reverse the process.

Not ideal, but I think it's workable.

Issue: https://javafx-jira.kenai.com/browse/RT-30487

Monday Jun 10, 2013

Tips for Teachers in NetBeans IDE

So you're using NetBeans IDE to teach programming to your class. What kind of features would be specifically relevant to you, during teaching?

  • Increase/decrease font size. Hold down the Alt key and scroll the mouse up/down and you'll see the font size magically increase/decrease. Same thing in the Output window, to really show your class the errors of their ways.

  • Quickly switch between open editor documents. Ctrl-Tab, just like all/most other applications, just like when you switch between applications on your computer.

  • Maximize/minimize NetBeans IDE. Alt-Shift-Enter, this toggles between maximized/minimized mode of the entire window frame of the IDE.

  • Distraction-free mode. Wouldn't it be cool if you could remove all the windows around the editor, showing nothing other than the editor document? That's coming up in NetBeans IDE 7.4, distraction-free mode, Ctrl-Shift-Enter, mentioned in NetBeans IDE 7.4 New & Noteworthy, already available in daily builds (also via "View | Show Only Editor").
Now, you'll be able to increase the font size, remove everything from the screen except the editor document, maximize the editor document to cover the entire screen, and switch between multiple open documents. Excellent. Now all that's left for you to do is... teach.

Sunday Jun 09, 2013

Pharmacy Terminal Application on the NetBeans Platform

Speaking of terminal emulators, which I did yesterday, here's another terminal application on top of the NetBeans Platform, one that's been in production for several years, by and for Thrifty White Pharmacy. It is named JTerm. 

After Windows 7 came out, the company needed a Telnet/Terminal emulator that would replace the existing emulators that only worked up to Windows XP. Their main pharmacy application is written in COBOL and requires a Console/Terminal emulator to access it.

Instead of the NetBeans Terminal library, which was used in the blog entry yesterday, the open source JTA - Telnet/SSH library is used in a wrapper library in JTerm. Here's a screenshot of JTerm:

JTerm allows the user to open a new session via an XML file to connect to any of the company's pharmacy servers across their WAN. It has print functionality, with a wizard to allow users to create new XML session files.

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
« June 2013 »
SunMonTueWedThuFriSat
      
29
30
      
Today