februari 14, 2011

Dynamic node edit property menu

Hi everybody.

I'm sorry for the last months of no posts here but there hasn't been any time for me to write any blog posts since I have been really busy at work (working with Netbeans RCP offcourse)

But here is a post I started to wrote some time ago but haven't been able to finish until today.

So, in my application I wanted to have my users to be able to access the property editor for each of my Nodes properties without having to open up the normal Properties popup panel (which action you can access with SystemAction.get(PropertiesAction.class)). My goal was that when the user did right click on a node the menu would show me a bunch of Edit 'Propertyname' menu entries and when it was selected the editor for that property was showing up (the same editor that you get when clicking the "..." button in the properties panel). The screenshot below shows what I'm trying to describe in words.

So how can we achieve this?  I couldn't find any clear examples so I did one of my favourite things..   look at the netbeans source code..  And this might not be the only way to do this and most certain not the nicest way but it works.

To start with we need to do this in our Node.java file and we need to override the following method:

public Action[] getActions(boolean context){ }

[source language="java"][/source]

This method is called each time the user right clicks on a node in your application. And as we can see it expects to get back an Array of Actions so lets create an array with our dynamic actions:

public Action[] getActions(boolean context){
    ArrayList<Action> actionsToShow = new ArrayList<Action>();
        for (Node.PropertySet propertySet : getPropertySets()) {
            for (final Node.Property<> property : propertySet.getProperties()) {
                if (property.canWrite() &amp;&amp; property.getPropertyEditor().supportsCustomEditor()) {
                    actionsToShow.add(new EditPropertyAction(property, this));
                 }
           }
    }
    return (Action[]) actionsToShow.toArray(new Action[actionsToShow.size()]);
}

Ok. What is this code doing? First I'm just creating an ArrayList that I can add my Actions into. Then I'm asking my node to get the the propertyset and for each property I'm asking for the Node.Property. If Node.Property is writeable (we don't want to show read-only properties in the menu) and that it has a customeditor (Integers doesnt have one for example) then I'm creating a EditPropertyAction that takes two arguments, the property and the node itself. Finally I'm returning the ArrayList as an Array.

Are we done yet? No we aren't. We need to implement the EditPropertyAction.

private static class EditPropertyAction extends AbstractAction {
        private final Node.Property<> property;
        private final Node node;

        public EditPropertyAction(Node.Property<> property, Node node) {
            this.property = property;
            this.node = node;
            putValue(Action.NAME, "Edit "; + property.getDisplayName());
        }

        @Override
        public void actionPerformed(ActionEvent ae) {
            final PropertyPanel propertyPanel = new PropertyPanel(property, PropertyPanel.PREF_CUSTOM_EDITOR);
            propertyPanel.setChangeImmediate(true);
            propertyPanel.setProperty(property);

            final DialogDescriptor dlg = new DialogDescriptor(propertyPanel, node.getDisplayName() + " " + property.getDisplayName());

            dlg.setButtonListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    if (dlg.getValue().equals(DialogDescriptor.OK_OPTION)) {
                        try {
                            propertyPanel.updateValue();
                        } catch (Exception e1) {
                        }
                        dlg.setClosingOptions(new Object[]{dlg.getValue()});
                    } else {
                        dlg.setClosingOptions(null);
                    }
                }
            });
            Dialog createDialog = DialogDisplayer.getDefault().createDialog(dlg);
            createDialog.setVisible(true);
        }
    }

Pheew.. that was alot of code! A quick explanation of this code: An action that extends AbstractAction. It has the node itself and the property as fields which are set by the constructor. And then the Action name is set to "Edit " and the displayname of the property.

Lets move on to the actionperformed. This is what is called by the application when the user selects the entry in the menu. What we do here is creating a new PropertyPanel which is a Netbeans class that extends JComponent. So what we get back is a Swing component. And to show this component I'm using another nice  builtin Netbeans thing called DialogDescriptor. What it does is showing a popup jframe with the component inside and an Ok and Cancel button. And as you can see I'm creating a very basic ActionListner and connect it to the dialogdescriptor and when the user press Ok all we have to do is calling propertyPanel.updateValue() and the property is updated with the information the user entered, if cancel was pressed the property is not updated.

That's all needed. And as you can see there is no connections to the node data in here so it's possible to create a NodeUtilities class with a static method that generates this for every node you want. And if you have customized and created your own CustomEditor and registred with your Node that editor is what you gonna see. This is also true for any editors you have registered globally using PropertyEditorManager.registerEditor() off course.

If you know a nicer way to do this let me know!