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