How can I implement "Select all/Deselect all/Invert selection" features?

Apache NetBeans Wiki Index

Note: These pages are being reviewed.

Applications which manage sets of data items often offer to users the capability of selecting and deselecting all the items currently on the screen with a single menu (or key shortcut). In some cases even a "Invert selection" option could be useful which selects all unselected nodes an vice versa.

Implementing such a feature with the OpenIDE API is quite a simple task. We first define a subclass of SystemAction which listens for changes in the selection of the current TopComponent and tracks the currently active ExplorerManager:

public abstract class ExplorerManagerAction extends SystemAction
  {
    private ExplorerManager activeExplorerManager;

    public ExplorerManagerAction()
      {
        TopComponent.getRegistry().addPropertyChangeListener(new PropertyChangeListener()
          {
            public void propertyChange (PropertyChangeEvent event)
              {
                if (TopComponent.Registry.PROP_ACTIVATED.equals(event.getPropertyName()))
                  {
                    Object value = event.getNewValue();

                    if (value instanceof ExplorerManager.Provider)
                      {
                        activeExplorerManager = ((ExplorerManager.Provider)value).getExplorerManager();
                        setEnabled(true);
                      }

                    else
                      {
                        activeExplorerManager = null;
                        setEnabled(false);
                      }
                  }
              }
          });
      }

    final public void actionPerformed (ActionEvent actionEvent)
      {
        if (activeExplorerManager != null)
          {
            try
              {
                performAction(activeExplorerManager);
              }
            catch (PropertyVetoException e)
              {
                // ...
              }
          }
      }

    abstract protected void performAction (ExplorerManager explorerManager)
      throws PropertyVetoException;

    public HelpCtx getHelpCtx()
      {
        return HelpCtx.DEFAULT_HELP;
      }

    protected void initialize()
      {
        super.initialize();
        putValue("noIconInMenu", Boolean.TRUE);
      }

    protected boolean asynchronous()
      {
        return false;
      }
  }

Now in order to implement the specific node selection actions we just have to subclass and provide a concrete implementation of the performAction() method which takes an ExplorerManager as parameter.

For the "Select All" action we have:

public final class SelectAllAction extends ExplorerManagerAction
  {
    protected void performAction (ExplorerManager explorerManager)
      throws PropertyVetoException
      {
        explorerManager.setSelectedNodes(explorerManager.getRootContext().getChildren().getNodes());
      }
    public String getName()
      {
        return NbBundle.getMessage(SelectAllAction.class, "CTL_SelectAllAction");
      }
  }

For the "Deselect all" action we have:

public final class DeselectAllAction extends ExplorerManagerAction
  {
    protected void performAction (ExplorerManager explorerManager)
      throws PropertyVetoException
      {
        explorerManager.setSelectedNodes(new Node[0]);
      }
    public String getName()
      {
        return NbBundle.getMessage(DeselectAllAction.class, "CTL_DeselectAllAction");
      }

At last for the "Invert selection" action we have:

public final class InvertSelectionAction  extends ExplorerManagerAction
  {
    protected void performAction (ExplorerManager explorerManager)
      throws PropertyVetoException
      {
        List nodes = new ArrayList(Arrays.asList(explorerManager.getRootContext().getChildren().getNodes()));
        nodes.removeAll(Arrays.asList(explorerManager.getSelectedNodes()));
        explorerManager.setSelectedNodes((Node[[ | ]])nodes.toArray(new Node[0]));
      }
    public String getName()
      {
        return NbBundle.getMessage(InvertSelectionAction.class, "CTL_InvertSelectionAction");
      }
  }

The above code for "Select All" and "Invert selection" only works for "flat" node structures with a root and a single level of children. For more complex structures we just need to replace explorerManager.getRootContext().getChildren().getNodes() with a piece of code that recursively explores the node tree contents.

To complete our work, this is the XML code to put in the layer.xml in order to add actions in the menu, the toolbar and to define the proper key bindings:

<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
    <!-- Declares the relevant actions. -->
    <folder name="Actions">
        <folder name="Select">
            <file name="my-package-action-SelectAllAction.instance"/>
            <file name="my-package-action-DeselectAllAction.instance"/>
            <file name="my-package-action-InvertSelectionAction.instance"/>
        </folder>
    </folder>
    <!-- Adds the actions to the Select main menu. -->
    <folder name="Menu">
        <folder name="Select">
            <file name="my-package-action-SelectAllAction.shadow">
                <attr name="originalFile" stringvalue="Actions/Select/my-package-action-SelectAllAction.instance"/>
            </file>
            <attr name="my-package-action-SelectAllAction.shadow/my-package-action-DeselectAllAction.shadow" boolvalue="true"/>
            <file name="my-package-action-DeselectAllAction.shadow">
                <attr name="originalFile" stringvalue="Actions/Select/my-package-action-DeselectAllAction.instance"/>
            </file>
            <attr name="my-package-action-DeselectAllAction.shadow/my-package-action-InvertSelectionAction.shadow" boolvalue="true"/>
            <file name="my-package-action-InvertSelectionAction.shadow">
                <attr name="originalFile" stringvalue="Actions/Select/my-package-action-InvertSelectionAction.instance"/>
            </file>
            <attr name="my-package-action-InvertSelectionAction.instance/it-tidalwave-bluemarine-catalog-tagstamper-action-separatorBefore.instance" boolvalue="true"/>
        </folder>
    </folder>
    <!-- Declares the shortcuts. D- maps to "command" on Mac OS X and to "ctrl" on Linux and Windows. -->
    <folder name="Shortcuts">
        <file name="D-A.shadow">
            <attr name="originalFile" stringvalue="Actions/Select/my-package-action-SelectAllAction.instance"/>
        </file>
        <file name="D-D.shadow">
            <attr name="originalFile" stringvalue="Actions/Select/my-package-action-DeselectAllAction.instance"/>
        </file>
        <file name="D-I.shadow">
            <attr name="originalFile" stringvalue="Actions/Select/my-package-action-InvertSelectionAction.instance"/>
        </file>
    </folder>
    <!-- Adds the actions to the Select toolbar -->
    <folder name="Toolbars">
        <folder name="Select">
            <file name="my-package-action-InvertSelectionAction.shadow">
                <attr name="originalFile" stringvalue="Actions/Select/my-package-action-InvertSelectionAction.instance"/>
            </file>
            <attr name="my-package-action-InvertSelectionAction.shadow/my-package-action-DeselectAllAction.shadow" boolvalue="true"/>
            <file name="my-package-action-DeselectAllAction.shadow">
                <attr name="originalFile" stringvalue="Actions/Select/my-package-action-DeselectAllAction.instance"/>
            </file>
            <attr name="my-package-action-DeselectAllAction.shadow/my-package-action-SelectAllAction.shadow" boolvalue="true"/>
            <file name="my-package-action-SelectAllAction.shadow">
                <attr name="originalFile" stringvalue="Actions/Select/my-package-action-SelectAllAction.instance"/>
            </file>
        </folder>
    </folder>
</filesystem>

 — Main.fabriziogiudici - 06 Jul 2006