X

Geertjan's Blog

  • November 19, 2007

SaveCookie (Part 2)

Geertjan Wielenga
Product Manager
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!

Join the discussion

Comments ( 14 )
  • Aisha Khairat Wednesday, December 5, 2007

    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


  • Geertjan Wednesday, December 5, 2007

    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.


  • Geertjan Wednesday, December 5, 2007

    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.


  • Aisha Khairat Thursday, December 6, 2007

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


  • David Wednesday, February 20, 2008

    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.


  • Puce Monday, October 6, 2008

    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?)


  • Glenn Saturday, May 9, 2009

    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


  • Tim Tuesday, June 9, 2009

    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.


  • guest Thursday, October 8, 2009

    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?


  • Geertjan Thursday, October 8, 2009

    Use Proxylookup.


  • Bernd Ruehlicke Friday, October 9, 2009

    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.


  • Tatarize Thursday, March 18, 2010

    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()});

    }


  • metator Wednesday, August 25, 2010

    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.


  • metator Wednesday, August 25, 2010

    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.


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.