Friday May 31, 2013

Integrating with the NetBeans Platform Recent File Infrastructure

If a CloneableTopComponent has a DataObject in its Lookup, the DataObject will be added to "File | Open Recent File", after the CloneableTopComponent has closed.

The relevant classes are in the "org.netbeans.modules.openfile" package in the NetBeans sources. To include the package in your application, put a checkmark next to "User Utilities" in the "ide" cluster, in the Project Properties dialog of your application.

The RecentFiles class, in the "org.netbeans.modules.openfile" package, maintains a list of recently closed files. In the Installer class of the module, a call is made to RecentFiles.init(), which adds a WindowListener to the TopComponent.Registry. Whenever a TopComponent closes, its DataObject is added to the recent files list, while it is removed when the TopComponent opens.

However, here's the catch:

private static void addFile(TopComponent tc) {
    if (tc instanceof CloneableTopComponent) {

Let's say that you're creating a GUI editor (i.e., not a text editor). You have a TopComponent, within which you have GUI components, on top of a DataObject. I.e., there's an actual file beneath the TopComponent but you don't want a text editor to be available to the user. The above code shows that if you want your DataObject to be included in "File | Open Recent File", you need to extend CloneableTopComponent, rather than simply TopComponent.

Next, make very sure that there's a DataObject in the Lookup of the TopComponent. Otherwise the code below, again in the RecentFiles class, will fail:

private static String obtainPath(TopComponent tc) {
    DataObject dObj = tc.getLookup().lookup(DataObject.class);
    if (dObj != null) {
        FileObject fo = dObj.getPrimaryFile();
        if (fo != null) {
            return convertFile2Path(fo);
    return null;

So, now you have a CloneableTopComponent with a DataObject in its Lookup and, when the CloneableTopComponent closes, its DataObject is added to "File | Open Recent File". All seems to be well and good at this point. However, there's still a catch. Because you're extending CloneableTopComponent, the Clone action in the tab of your CloneableTopComponent is automatically enabled. When the user invokes that action, you need to be sure to have a no-arg constructor in your CloneableTopComponent, because that's what the Clone action invokes. The solution to this is simply to get the current DataObject from the Lookup and pass that into the constructor that receives the DataObject.

At the same time, though, another problem is that you need to make sure that when the user opens your CloneableTopComponent a subsequent time for the same file, the same CloneableTopComponent should be opened, rather than creating a new one. And possibly the reopening is done from the Favorites window, which automatically has the DataObject in its Lookup when you select the file. Hence, you need to put a new Object, I've named it Reopenable, into the Lookup of the CloneableTopComponent, so that when a new attempt is made to open it, the correct one is found.

In short, I end up with a file editor as follows:

        preferredID = "MyFileEditor",
        iconBase = "com/example/singleview/filetype/myImageIcon.png",
        persistenceType = TopComponent.PERSISTENCE_NEVER)
        mode = "editor",
        openAtStartup = false)
        category = "Window",
        id = "org.example.singleview.filetype.MyFileEditor")
            path = "Menu/Window",
            position = 0),
            path = "Toolbars/File",
            position = 0)
    "CTL_OpenMyImageAction=My Image Data"})
public class MyFileEditor extends CloneableTopComponent {

    public MyFileEditor() {

    public MyFileEditor(final MyCustomDataObject mcdo) {
        associateLookup(Lookups.fixed(mcdo, new Reopenable() {
            public MyCustomDataObject reopen() {
                return mcdo;


And here's the related DataObject (minus all the annotations at the top):

public class MyCustomDataObject extends MultiDataObject implements Openable {

    public MyCustomDataObject(FileObject pf, MultiFileLoader loader) throws DataObjectExistsException, IOException {
        super(pf, loader);

    public void open() {
        TopComponent tc = findTopComponent();
        if (tc == null) {
            tc = new MyFileEditor(this);

    private TopComponent findTopComponent() {
        Set<TopComponent> openTopComponents = WindowManager.getDefault().getRegistry().getOpened();
        for (TopComponent tc : openTopComponents) {
            if (tc.getLookup().lookup(Reopenable.class) != null
                    && tc.getLookup().lookup(Reopenable.class).reopen()
                    == this.getLookup().lookup(MyCustomDataObject.class)) {
                return tc;
        return null;


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.


« May 2013 »