All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.tidalwave.netbeans.explorer.ExplorerPanel Maven / Gradle / Ivy

/***********************************************************************************************************************
 *
 * OpenBlueSky - NetBeans Platform Enhancements
 * Copyright (C) 2006-2012 by Tidalwave s.a.s. (http://www.tidalwave.it)
 *
 ***********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 *
 ***********************************************************************************************************************
 *
 * WWW: http://openbluesky.java.net
 * SCM: https://bitbucket.org/tidalwave/openbluesky-src
 *
 **********************************************************************************************************************/
package it.tidalwave.netbeans.explorer;

import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.util.concurrent.ExecutionException;
import java.awt.BorderLayout;
import javax.swing.ActionMap;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.text.DefaultEditorKit;
import org.openide.util.Lookup;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils;
import org.openide.nodes.Node;
import org.openide.nodes.FilterNode;
import org.openide.nodes.NodeAdapter;
import org.openide.nodes.NodeListener;
import org.openide.nodes.NodeMemberEvent;
import it.tidalwave.util.logging.Logger;
import it.tidalwave.util.NotFoundException;
import it.tidalwave.netbeans.swing.OpenIDEWorker;
import it.tidalwave.netbeans.nodes.role.NodeManager;
import it.tidalwave.netbeans.nodes.role.RootNodeProvider;
import it.tidalwave.netbeans.windows.role.TopComponentRole;
import it.tidalwave.netbeans.explorer.view.EnhancedBeanTreeView;

/***********************************************************************************************************************
 *
 * This Swing component wraps an {@link ExplorerManager} and references a {@link RootNodeProvider}, providing a coherent
 * animation strategy for updating its contents. This strategy consists in blocking the component during long operations
 * and eventually performing a graphic transition when the new contents are available. The details of the animation are
 * delegated to an {@link TransitionController}.
 *
 * This class mixes a view responsibility (it's a renderable Swing component) with a controller responsibility (it
 * exposes a setRootNodeProvider() method which reloads data). The reason is because we support long lasting operations
 * (RootNodeProvider offers no timing guarantees) with the proper Swing management (blocking the UI and eventually
 * decorating it), thus we need to know when the operation starts and when completes. The {@code ExplorerManager} can
 * only fire an even when the operation has been completed).
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 **********************************************************************************************************************/
public class ExplorerPanel extends JPanel implements ExplorerManager.Provider, 
                                                     Lookup.Provider,
                                                     TopComponentRole, NodeManager
        // Could implement DataLoader, but this would confuse the resource injector
  {
    /*******************************************************************************************************************
     *
     * The delegate for the animation strategy of an {@link ExplorerPanel}.
     *
     ******************************************************************************************************************/
    public static interface TransitionController
      {
        /***************************************************************************************************************
         *
         * Sets the component responsible for the rendering of nodes.
         *
         * @param  view  the view
         *
         **************************************************************************************************************/
        public void setView (@Nonnull JComponent view);

        /***************************************************************************************************************
         *
         * Locks the component, preventing any mouse and keyboard interaction with it, and showing a waiting message.
         *
         * @param  message  the waiting message
         *
         **************************************************************************************************************/
        public void lock (@Nonnull String message);

        /***************************************************************************************************************
         *
         * Prepares the component for unlocking. This method is called just before {@link #unlock()} in order to allow
         * some preparation steps to be performed.
         *
         **************************************************************************************************************/
        public void prepareUnlock();

        /***************************************************************************************************************
         *
         * Unlocks the component, making it able again to interact with the mouse and the keyboard.
         *
         **************************************************************************************************************/
        public void unlock();

        /***************************************************************************************************************
         *
         * Returns the actual Swing componend used in this implementation.
         *
         * @param  component  the {@code Component}
         *
         **************************************************************************************************************/
        @Nonnull
        public JComponent getComponent();

        public static TransitionController EMPTY = new TransitionController()
          {
            private final JPanel component = new JPanel(new BorderLayout());

            @Override
            public void setView (final JComponent view)
              {
                component.add(view, BorderLayout.CENTER);
              }

            @Override
            public void lock (final String message)
              {
              }

            @Override
            public void prepareUnlock()
              {
              }

            @Override
            public void unlock()
              {
              }

            @Override
            @Nonnull
            public JComponent getComponent()
              {
                return component;
              }
          };
      }

    private static final String CLASS = ExplorerPanel.class.getName();
    private static final Logger logger = Logger.getLogger(CLASS);

    @Inject
    protected ExplorerManager explorerManager;

    @Inject
    protected TransitionController transitionController = TransitionController.EMPTY;

    @Inject
    private Lookup contextLookup;

    @CheckForNull
    private RootNodeProvider rootNodeProvider;

    @Nonnull
    private Lookup lookup;

    @Nonnull
    private JComponent view;
    
    protected int updateCounter; // for testing

    /*******************************************************************************************************************
     *
     * Creates a new {@code ExplorerPanel}.
     *
     ******************************************************************************************************************/
    public ExplorerPanel()
      {
        super(new BorderLayout());
      }

    /*******************************************************************************************************************
     *
     * Creates a new {@code ExplorerPanel} setting its name (for debugging purposes).
     *
     * @param  view  the view
     * @param  name  the name
     *
     ******************************************************************************************************************/
    public ExplorerPanel (final @Nonnull String name)
      {
        this();
        setName(name);
      }

    /*******************************************************************************************************************
     *
     * Activates the component (that is, make it able to react to events such as keyboard shortcuts).
     *
     ******************************************************************************************************************/
    @Override
    public void notifyActivated()
      {
        ExplorerUtils.activateActions(explorerManager, true);
      }

    /*******************************************************************************************************************
     *
     * Deactivates the component.
     *
     ******************************************************************************************************************/
    @Override
    public void notifyDeactivated()
      {
        ExplorerUtils.activateActions(explorerManager, false);
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public ExplorerManager getExplorerManager()
      {
        return explorerManager;
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override @Nonnull
    public Lookup getLookup()
      {
        return lookup;
      }

    @Override
    public void notifyOpened()
      {
        // nothing to do
      }

    @Override
    public void notifyClosed()
      {
        // nothing to do
      }

    @Override
    public void notifyShowing()
      {
        // nothing to do
      }

    @Override
    public void notifyHidden()
      {
        // nothing to do
      }

    /*******************************************************************************************************************
     *
     * {@inheritDoc}
     *
     ******************************************************************************************************************/
    @Override
    public void setRootNodeProvider (final @Nonnull RootNodeProvider rootNodeProvider)
      {
        this.rootNodeProvider = rootNodeProvider;
        loadData();
      }

    /*******************************************************************************************************************
     *
     * Loads data showing a waiting message in case the operation takes some time. The {@link RootNodeProvider} is
     * queried to provide a {@link Node} which is then set as the root node. The component is locked while the
     * operation is in progress. In case of no data or errors, an empty root node is rendered.
     *
     ******************************************************************************************************************/
    private void loadData()
      {
        logger.fine("loadData()");
        
        final String message = "Loading..."; // FIXME: use the bundle
        transitionController.lock(message);

        OpenIDEWorker.post(new OpenIDEWorker()
          {
            @Override @Nonnull
            protected Node doInBackground()
              throws NotFoundException
              {
                return NotFoundException.throwWhenNull(rootNodeProvider, "null rootNodeProvider").getRootNode();
              }

            @Override
            public void done2()
              throws InterruptedException
              {
                transitionController.prepareUnlock();
                logger.finer(">>>> setting root node...");
                Node root;

                try
                  {
                    root = get();
                    logger.finer(">>>> root node: %s", root);
                  }
                catch (ExecutionException e)
                  {
                    logger.warning("No root node: %s", e);
                    logger.throwing("done2()", CLASS, e);

                    final String message = (e.getCause() instanceof NotFoundException) ?
                                           "No available data" :
                                           "Error";
                    root = new FilterNode(Node.EMPTY);
                    root.setDisplayName(message);
                  }

                explorerManager.setRootContext(root);

                if (isWaitingNode(root))
                  {
                    root.addNodeListener(waitingNodeDetector);
                  }
                else
                  {
                    transitionController.unlock();
                  }

                updateCounter++;
              }
          });
      }

    /*******************************************************************************************************************
     *
     *
     ******************************************************************************************************************/
    @PostConstruct
    @SuppressWarnings("PMD.UnusedPrivateMethod") @edu.umd.cs.findbugs.annotations.SuppressWarnings("UPM_UNCALLED_PRIVATE_METHOD")
    private void initialize()
      {
        logger.fine("initialize()");

        if (transitionController == null)
          {
            transitionController = TransitionController.EMPTY;
          }

        logger.finer(">>>> transitionController: %s", transitionController);
        assert contextLookup != null : "contextLookup is null";

        for (final JComponent object : contextLookup.lookupAll(JComponent.class))
          {
            if (object instanceof EnhancedBeanTreeView) // FIXME: find a better way to guess the view
              {
                view = object;
                break;
              }
          }

        if (view == null)
          {
            throw new RuntimeException("Cannot find a view component");
          }

        transitionController.setView(view);
        add(transitionController.getComponent(), BorderLayout.CENTER);

        final ActionMap map = getActionMap();

        try
          {
            map.put(DefaultEditorKit.copyAction, ExplorerUtils.actionCopy(explorerManager));
            map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(explorerManager));
            map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(explorerManager));
            map.put("delete", ExplorerUtils.actionDelete(explorerManager, true));
          }
        catch (Throwable t)
          {
            logger.info("Cannot bind default actions - ok if it's not in NetBeans RCP");
          }

        lookup = ExplorerUtils.createLookup(explorerManager, map);
      }

    /*******************************************************************************************************************
     *
     * A {@code Node} listener that detects when the waiting node goes away.
     *
     ******************************************************************************************************************/
    private final NodeListener waitingNodeDetector = new NodeAdapter()
      {
        @Override
        public void childrenRemoved (final @Nonnull NodeMemberEvent event)
          {
            final Node[] nodes = event.getDelta();

            if ((nodes.length == 1) && (nodes[0].getClass().getName().indexOf("WaitFilterNode") >= 0))
              {
                logger.fine(">>>> waiting node removed");
                event.getNode().removeNodeListener(waitingNodeDetector);
                SwingUtilities.invokeLater(new Runnable()
                  {
                    @Override
                    public void run()
                      {
                        transitionController.unlock();
                      }
                  });
              }
          }
      };

    /*******************************************************************************************************************
     *
     * Returns {@code true} if the passed {@code Node} is a waiting node.
     *
     ******************************************************************************************************************/
    private boolean isWaitingNode (final @Nonnull Node node)
      {
        final Node[] nodes = node.getChildren().getNodes();
        return ((nodes.length == 1) && (nodes[0].getClass().getName().indexOf("WaitFilterNode") >= 0));
      }
  }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy