Over a million developers have joined DZone.

Rewritten NetBeans Platform Paint Application in NetBeans IDE 6.9

· Java Zone

Discover how AppDynamics steps in to upgrade your performance game and prevent your enterprise from these top 10 Java performance problems, brought to you in partnership with AppDynamics.

Tim Boudreau has rewritten parts of the NetBeans Platform Paint Application (which is a NetBeans Platform sample distributed with NetBeans IDE). The changed Paint Application (together with updated tutorial) will be part of the release of NetBeans IDE 6.9.

Two parts have changed significantly, both relating to the "Save" feature of the Paint application, all of it within the PaintTopComponent, as addressed below.

  1. Integrating with the NetBeans Platform Save Functionality. Rather than creating your own action for saving the canvas, you'll now integrate with the standard NetBeans Platform save functionality. You do that by registering a context aware action that observes the current selection for instances of the "org.openide.cookies.SaveCookie" interface. When an instance of that interface is found in the current selection, the related menu item and toolbar button will become enabled. How that is achieved is by means of the following registrations in the layer file:
    <folder name="Actions">
        <folder name="File">
            <file name="org-openide-actions-SaveAction.instance">
                <attr name="instanceCreate" methodvalue="org.openide.awt.Actions.context"/>
                <attr name="delegate" newvalue="org.openide.actions.SaveAction"/>
                <attr name="selectionType" stringvalue="EXACTLY_ONE"/>
                <attr name="surviveFocusChange" boolvalue="false"/>
                <attr name="displayName" bundlevalue="org/openide/actions/Bundle#Save"/>
                <attr name="noIconInMenu" boolvalue="false"/>
                <attr name="iconBase" stringvalue="org/openide/resources/actions/save.png"/>
                <attr name="type" stringvalue="org.openide.cookies.SaveCookie"/>
            </file>
        </folder>
    </folder>
    <folder name="Menu">
        <folder name="File">
            <file name="SaveAction.shadow">
                <attr name="originalFile" stringvalue="Actions/File/org-openide-actions-SaveAction.instance"/>
                <attr name="position" intvalue="100"/>
            </file>
    </folder>
    </folder>
    <folder name="Toolbars">
        <folder name="File">
            <file name="SaveAction.shadow">
                <attr name="originalFile" stringvalue="Actions/File/org-openide-actions-SaveAction.instance"/>
                <attr name="position" intvalue="100"/>
            </file>
        </folder>
    </folder>

    Now that we have an action listening for instances of the "SaveCookie" interface, we need to introduce those instances, at the appropriate time, into the current selection. In the Paint application, this is done by adding an implementation of "SaveCookie" to the dynamic object "InstanceContent" (which we add to the TopComponent Lookup in the TopComponent constructor) whenever the canvas is modified:

    private void enableSaveAction(boolean canSave) {
    if (canSave) {
    //If the canvas is modified,
    //we add SaveCookie impl to Lookup:
    content.add(saver);
    } else {
    //Otherwise, we remove the SaveCookie impl from the lookup:
    content.remove(saver);
    canvas.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseReleased(MouseEvent e) {
    //Once we can save, we are done listening.
    //If enableSaveAction(false) is called, we will
    //start listening again.
    canvas.removeMouseListener(this);
    enableSaveAction(true);
    }
    });
    }
    }

    Above, "saver" is an implementation of the "SaveCookie" interface, which we will look at in the second part of this article. Note that it is removed from the "InstanceContent" whenever we specify that the canvas cannot be saved (i.e., when "canSave" is false). At that point, we add a "MouseListener" to the canvas, so that we can add the "SaveCookie" implementation to the "InstanceContent" next time that the mouse is released (which is when we assume a change has been made to the canvas).

    So, now the question is: "When do we call the above method?" Firstly, in the constructor of the TopComponent, so that the Save action is disabled by default, i.e., when the window is created:

    enableSaveAction(false);

    Secondly, in the "mouseReleased", as shown above, so that the save action can be invoked when the mouse is released:

    enableSaveAction(true);

    Thirdly, within the "SaveCookie" implementation, as shown in the next section of this article, whenever the save has been performed, so that the enabled action is disabled, so that the menu item and toolbar button are greyed out again:

    enableSaveAction(false);

    Fourthly, we implement an "ActionListener" in the "PaintTopComponent". Then, when we detect that a "JButton" is the source of the invoked action, we know that the "Clear" button has been clicked and pass "false" to the "enableSaveAction" again.

  2. Implementing the SaveCookie Interface. Above, we described how to integrate with the save functionality in the NetBeans Platform by making use of its "SaveCookie" interface. At the top of the class, we define "saver", which is what is added to the "InstanceContent" at the points described above:
    private Saver saver = new Saver();

    Then, here's the definition of "Saver", i.e., the implementation of the "SaveCookie" interface, where again we use the "InstanceContent" to distinguish between the situations where the file already exists and where it doesn't:

    private class Saver implements SaveCookie {

    @Override
    public void save() throws IOException {
    DataObject theFile = getLookup().lookup(DataObject.class);
    if (theFile != null) {
    File saveTo = FileUtil.toFile(theFile.getPrimaryFile());
    save(saveTo);
    } else {
    saveAs();
    }
    }

    public void saveAs() throws IOException {
    String title = NbBundle.getMessage(Saver.class, "TTL_SAVE_DIALOG");
    File f = new FileChooserBuilder(Saver.class).setTitle(title).showSaveDialog();
    if (f != null) {
    if (!f.getAbsolutePath().endsWith(".png")) {
    f = new File(f.getAbsolutePath() + ".png");
    }
    try {
    if (!f.exists()) {
    if (!f.createNewFile()) {
    String failMsg = NbBundle.getMessage(
    PaintTopComponent.class,
    "MSG_SaveFailed", f.getName());
    DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(failMsg));
    return;
    }
    } else {
    String overwriteMessage = NbBundle.getMessage(Saver.class, "MSG_Overwrite", f.getName());
    Object userChose = DialogDisplayer.getDefault().notify(new NotifyDescriptor.Confirmation(overwriteMessage));
    if (NotifyDescriptor.CANCEL_OPTION.equals(userChose)) {
    return;
    }
    }
    //Need getAbsoluteFile(), or X.png and x.png are different on windows
    save(f.getAbsoluteFile());
    } catch (IOException ioe) {
    Exceptions.printStackTrace(ioe);
    }
    }
    }

    private void save(File f) throws IOException {
    ImageIO.write(canvas.getImage(), "png", f);
    String savedMessage = NbBundle.getMessage(Saver.class, "MSG_Saved", f.getName());
    StatusDisplayer.getDefault().setStatusText(savedMessage);
    FileObject fob = FileUtil.toFileObject(FileUtil.normalizeFile(f));
    assert fob != null;
    //Store the file, so we don't show the Save dialog again
    content.add(DataObject.find(fob));
    setDisplayName(fob.getName());
    enableSaveAction(false);
    }

    }

That's it. An overview of the enhancements to this sample that will be implemented in NetBeans IDE 6.9. Note, though, that all the code above already works with NetBeans Platform 6.8.

The Java Zone is brought to you in partnership with AppDynamics. AppDynamics helps you gain the fundamentals behind application performance, and implement best practices so you can proactively analyze and act on performance problems as they arise, and more specifically with your Java applications. Start a Free Trial.

Topics:

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}