SaveCookie (Part 2)

SaveCookie (Part 1) worked but wasn't very efficient, because we were passing a field around, i.e., a boolean that was set whenever a text field was modified. Plus, we could have used getCookieSet().assign to assign our implementation of SaveCookie to the cookie set. So, here's the new and improved save functionality for a text field in a TopComponent, with help from Jarda to create it. As before, we begin by creating a node in the TopComponent.

private class DummyNode extends AbstractNode {

    SaveCookieImpl impl;

    public DummyNode() {
        super(Children.LEAF);
        impl = new SaveCookieImpl();
    }

    public void fire(boolean modified) {
        if (modified) {
            //If the text is modified,
            //we implement SaveCookie,
            //and add the implementation to the cookieset:
            getCookieSet().assign(SaveCookie.class, impl);
        } else {
            //Otherwise, we make no assignment
            //and the SaveCookie is not made available:
            getCookieSet().assign(SaveCookie.class);
        }
    }

    private class SaveCookieImpl implements SaveCookie {

        public void save() throws IOException {

            Confirmation msg = new NotifyDescriptor.Confirmation("Do you want to save \\"" + 
                guessedWord.getText() + "\\"?", NotifyDescriptor.OK_CANCEL_OPTION, 
                NotifyDescriptor.QUESTION_MESSAGE);

            Object result = DialogDisplayer.getDefault().notify(msg);

            //When user clicks "Yes", indicating they really want to save,
            //we need to disable the Save button and Save menu item,
            //so that it will only be usable when the next change is made
            //to the text field:
            if (NotifyDescriptor.YES_OPTION.equals(result)) {
                fire(false);
                //Implement your save functionality here.
            }

        }
    }
}

We'll need to refer to this node in other places, so let's create a class variable:

private DummyNode dummyNode;

Here's how the node is added to the TopComponent, i.e., in the constructor:

setActivatedNodes(new Node[]{dummyNode = new DummyNode()});

And then we notify the node whenever changes are made to the text field:

myTextField.getDocument().addDocumentListener(new DocumentListener() {

    public void insertUpdate(DocumentEvent arg0) {
        dummyNode.fire(true);
    }

    public void removeUpdate(DocumentEvent arg0) {
        dummyNode.fire(true);
    }

    public void changedUpdate(DocumentEvent arg0) {
        dummyNode.fire(true);
    }
});

Because we pass true to the node, our implementation of SaveCookie is added to the cookie set, which enables the Save button (if you have one, which you don't by default), as well as the Save menu item under the File menu. If you want a Save button in the toolbar, add this to the layer:

<folder name="Toolbars">
    <folder name="File">
        <file name="org-openide-actions-SaveAction.instance"/>
    </folder>
</folder>

In other news. Have a look at this pretty brilliant clip on YouTube: The future is in the past!

Comments:

Hi Geertjan,
Although I have the modified variable set as 'true' and I added the tag you specified in the layer.xml, I still have the save icon frozen from both the toolbar and the menu item. Why could this be the case?

I also had another inquiry. The save() method is not called unless I explicitly call impl.save() after the line getCookieSet().assign(SaveCookie.class, impl);

is that normal?

Thanks!
Aisha

Posted by Aisha Khairat on December 05, 2007 at 06:18 AM PST #

I suggest you create a whole new module. From scratch. Forget your own module(s) for the moment. Create a whole new one. Then follow the steps above. Create a new module, create a TopComponent via the Window Component wizard, and then take the steps described above. Does that work as expected, i.e., separate from your existing code? If the answer is "yes", then there is something wrong with your existing code.

Posted by Geertjan on December 05, 2007 at 06:27 AM PST #

Here is a sample especially for you:

http://blogs.sun.com/geertjan/resource/org-netbeans-aishasample.nbm

Install it. Then go to New Project wizard, then Samples | NetBeans Modules | Aisha Sample. Complete the wizard and you will have a module providing a TopComponent with a text field. Install that module. Open the window if it doesn't open automatically. Type something in the text field. Notice that the Sve button is now enabled.

Posted by Geertjan on December 05, 2007 at 06:47 AM PST #

yup, it works perfectly.. I'll have to figure out why it behaves differently when plugged into my code.. Thanks a LOT Geertjan!

Posted by Aisha Khairat on December 06, 2007 at 03:51 AM PST #

If I want to save a file that's done with XML Multiview were can I make the setActivatedNodes(new Node[]{dummyNode = new DummyNode()}); call because calling this on the TopComponent when using XML Multiview doesn't work.

Posted by David on February 20, 2008 at 06:19 AM PST #

Tip: If you're using the Beans Binding framework you can replace the DocumentListener code in this sample with the following code to get notified when ANY bound component changes:

bindingGroup.addBindingListener(new AbstractBindingListener() {

@Override
public void targetChanged(Binding binding, PropertyStateEvent event) {
if (event.getValueChanged()) {
<... do your stuff here >
}
}
});

Note: the TopComponent subclass needs to be public to work with the Beans Binding framework! (Bug in the Beans Binding framework?)

Posted by Puce on October 06, 2008 at 05:22 AM PDT #

I also had the problem where my "Save" action would not enable in the Menu or Toolbar. In my case, I followed the example and added to my TopComponent constructor:

setActivatedNodes( new Node[]{dummyNode} );

but a few lines later I was putting another object into the TopComponent's lookup:

associateLookup( Lookups.fixed( new Object[] { satelliteView } ) );

which clobbered the existing lookup containing the dummyNode. Replacing both lines with this put both objects in the lookup and things worked:

associateLookup( Lookups.fixed( new Object[] { satelliteView, dummyNode } ) );

Not quite sure what the purpose of setActivatedNodes is, though.

Cheers
-Glenn

Posted by Glenn on May 09, 2009 at 11:23 AM PDT #

Thanks Geertjan, I have used this and it's great.

@Glenn, I had the exact same problem, following your advice it now works:

lookupContent = new InstanceContent();
lookup = new AbstractLookup(lookupContent);
saveNode = new MySaveNode();
reloadNode = new MyReloadNode();

// this.setActivatedNodes(new Node[] {saveNode, reloadNode});
// this.associateLookup(lookup);

this.associateLookup(Lookups.fixed( new Object[] {lookup, reloadNode, saveNode}));

Regardless of whether I set the activated nodes first or did the associateLookup first, either way the actions were disabled.

Posted by Tim on June 09, 2009 at 04:48 PM PDT #

My TopComponent has a BeanTreeView and it also has some actions associated the selecting tree nodes. These actions are subclasses of ContextAwareAction, exactly the same as the one described in the following link:

http://wiki.netbeans.org/DevFaqActionContextSensitive

These actions work perfectly if I do the following,

associateLookup(ExplorerUtils.createLookup(treeView.getExplorerManager(), getActionMap()));

where treeView is a JPanel and it implements ExplorerManager.Provider, it is basically used to hold the tree.

If I do what Puce suggested above, e.g.

associateLookup( Lookups.fixed( new Object[] { treeView, dummyNode } ) );

it works for Save action, but all the associated node actions are disabled. I tried the following

associateLookup( Lookups.fixed( new Object[] { reeView.getExplorerManager(), getActionMap()), dummyNode } ) );

but it makes no difference, does anybody have any idea?

Posted by guest on October 07, 2009 at 09:27 PM PDT #

Use Proxylookup.

Posted by Geertjan on October 07, 2009 at 09:29 PM PDT #

Hmm very interesting. I had a similar issue but got it working just by changing the syntax a little. I wonder if any of you can explain why

NOT WORKING: Lookup l = new ProxyLookup(new Lookup[]{Lookups.fixed(_paletteController), Lookups.fixed(dummyNode), new AbstractLookup(_content)});

NOT WORKING: Lookup l = new ProxyLookup(new Lookup[]{Lookups.singleton(_paletteController), Lookups.singleton(dummyNode), new AbstractLookup(_content)});

WORKING : Lookup l = new ProxyLookup(Lookups.fixed( new Object[] {_paletteController, dummyNode, new AbstractLookup(_content)}) );

After this I call of course: associateLookup(l);

For me all three ways are doing the same thing - but obviously they are not. I wonder why.

Posted by Bernd Ruehlicke on October 09, 2009 at 05:21 AM PDT #

My guess is that setActivatedNodes() creates a proxy lookup associates with that. Then when you call associateLookup() it associates the lookup with that particular lookup with priority.

if (associatedLookup != null) return associatedLookup;
if (activatedNodes != null) return ProxyLookup(of the active nodes);
else return super.getLookup()

So rather than do what we'd think it should do and create a ProxyLookup of the associatedLookup and the activatedNodes lookup it just kills the activated nodes when you associate a lookup.

The best fix I ran into was to simply overload the getLookup and create a proxylookup of DummyNode.getLookup() and lookup. As I created my DummyNode later and couldn't associateLookup or set it active.

public Lookup getLookup() {
if (root == null) { return lookup; }
return new ProxyLookup(new Lookup[] {lookup, root.getLookup()});
}

Posted by Tatarize on March 17, 2010 at 08:56 PM PDT #

Hi,

This is a bit off topic but how can one show an unsaved TopComponent with tab text in bold? I've managed to activate/deactivate Save action based on DataObject modified state, but tab text is not set in bold.

Thanks in advance.

Posted by metator on August 25, 2010 at 02:48 AM PDT #

So, i worked around to get the tab text in bold by using setHtmlDisplayName() method whenever DataObject's modified state changes. Doesn't seem to be the ideal solution, but it works...

Thanks anyway.

Posted by metator on August 25, 2010 at 03:35 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
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
« July 2015
SunMonTueWedThuFriSat
   
4
7
11
12
20
30
31
 
       
Today