/*
* Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC
* All rights reserved.
*
* The source code of this document is proprietary work, and is not licensed for
* distribution. For information about licensing, contact Sam Harwell at:
* sam@tunnelvisionlabs.com
*/
package org.antlr.netbeans.editor.navigation;
import java.awt.Image;
import java.awt.datatransfer.Transferable;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.Action;
import org.antlr.netbeans.editor.navigation.actions.OpenAction;
import org.netbeans.api.annotations.common.NonNull;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.datatransfer.PasteType;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.InstanceContent;
/**
*
* @author Sam Harwell
*/
@NbBundle.Messages({
"LBL_WaitNode=Please wait..."
})
public abstract class NavigatorNode extends AbstractNode {
private static Node WAIT_NODE;
private final NavigatorPanelUI ui;
private final Factory factory;
private Description description;
private OpenAction openAction;
public NavigatorNode(@NonNull NavigatorPanelUI ui, @NonNull Description description, Factory nodeFactory) {
super(description.getChildren().isEmpty() ? Children.LEAF : new ElementChildren(ui, description.getChildren(), nodeFactory), prepareLookup(description));
this.ui = ui;
this.description = description;
this.factory = nodeFactory;
setDisplayName(description.getName());
}
public NavigatorPanelUI getUI() {
return ui;
}
@Override
public Image getOpenedIcon(int type) {
return getIcon(type);
}
@Override
public String getDisplayName() {
if (description.getName() != null) {
return description.getName();
}
if (description.getFileObject() != null) {
return description.getFileObject().getNameExt();
}
return null;
}
@Override
public String getHtmlDisplayName() {
return description.getHtmlHeader();
}
@Override
public Action[] getActions(boolean context) {
if (context || description.getName() == null || description.getFileObject() == null || !description.getChildren().isEmpty()) {
return getUI().getActions();
} else {
Action[] panelActions = getUI().getActions();
int extraActionCount = 2;
Action[] actions = new Action[extraActionCount + panelActions.length];
actions[0] = getOpenAction();
actions[1] = null;
System.arraycopy(panelActions, 0, actions, extraActionCount, panelActions.length);
return actions;
}
}
@Override
public Action getPreferredAction() {
return getOpenAction();
}
@Override
public boolean canCopy() {
return false;
}
@Override
public boolean canCut() {
return false;
}
@Override
public boolean canDestroy() {
return false;
}
@Override
public boolean canRename() {
return false;
}
@Override
public PasteType getDropType(Transferable t, int action, int index) {
return null;
}
@Override
public Transferable drag() throws IOException {
return null;
}
public Description getDescription() {
return description;
}
@Override
protected void createPasteTypes(Transferable t, List<PasteType> s) {
// do nothing
}
public void refreshRecursively() {
Children ch = getChildren();
if (!description.getChildren().isEmpty() && !(ch instanceof ElementChildren)) {
ch = new ElementChildren(ui, description.getChildren(), factory);
setChildren(ch);
}
if (ch instanceof ElementChildren) {
boolean scrollOnExpand = getUI().getScrollOnExpand();
getUI().setScrollOnExpand(false);
((ElementChildren)ch).resetKeys(description.getChildren(), getUI().getFilters());
for (Node sub : ch.getNodes()) {
getUI().expandNode(sub);
((NavigatorNode)sub).refreshRecursively();
}
getUI().setScrollOnExpand(scrollOnExpand);
}
}
public void updateRecursively(Description newDescription) {
Children children = getChildren();
if (!newDescription.getChildren().isEmpty() && !(children instanceof ElementChildren)) {
children = new ElementChildren(ui, newDescription.getChildren(), factory);
setChildren(children);
}
if (children instanceof ElementChildren) {
Set<Description> oldChildren = new HashSet<>(description.getChildren());
// Create a hashtable which maps Description to node.
// We will then identify the nodes by the description. The trick is
// that the new and old description are equal and have the same hashcode
Node[] nodes = children.getNodes(true);
HashMap<Description, NavigatorNode> oldD2node = new HashMap<>();
for (Node node : nodes) {
oldD2node.put(((NavigatorNode)node).description, (NavigatorNode)node);
}
Collection<Description> newChildren = newDescription.getChildren();
// Now refresh keys
((ElementChildren)children).resetKeys(newChildren, getUI().getFilters());
// Reread nodes
nodes = children.getNodes(true);
for (Description newSub : newChildren) {
NavigatorNode node = oldD2node.get(newSub);
if (node != null) { // filtered out
if (!oldChildren.contains(newSub) && node.getChildren() != Children.LEAF) {
getUI().expandNode(node); // Make sure new nodes get expanded
}
node.updateRecursively(newSub); // update the node recursively
}
}
}
Description oldDescription = description; // Remember old description
description = newDescription; // set new descrioption to the new node
if (oldDescription.getHtmlHeader() != null && !oldDescription.getHtmlHeader().equals(description.getHtmlHeader())) {
// Different headers => we need to fire displayname change
fireDisplayNameChange(oldDescription.getHtmlHeader(), description.getHtmlHeader());
}
/*if (oldDescription.modifiers != null && !oldDescription.modifiers.equals(newDescription.modifiers)) {
fireIconChange();
fireOpenedIconChange();
}*/
}
private synchronized Action getOpenAction() {
if (openAction == null) {
openAction = new OpenAction(description);
}
return openAction;
}
private static Lookup prepareLookup(Description d) {
InstanceContent instanceContent = new InstanceContent();
instanceContent.add(d, ConvertDescriptionToFileObject);
instanceContent.add(d, ConvertDescriptionToDataObject);
return new AbstractLookup(instanceContent);
}
private static final InstanceContent.Convertor<Description, FileObject> ConvertDescriptionToFileObject =
new InstanceContent.Convertor<Description, FileObject>() {
@Override
public FileObject convert(Description obj) {
return obj.getFileObject();
}
@Override
public Class<? extends FileObject> type(Description obj) {
return FileObject.class;
}
@Override
public String id(Description obj) {
return "IL[" + obj.toString();
}
@Override
public String displayName(Description obj) {
return id(obj);
}
};
private static final InstanceContent.Convertor<Description, DataObject> ConvertDescriptionToDataObject =
new InstanceContent.Convertor<Description, DataObject>() {
@Override
public DataObject convert(Description obj) {
try {
FileObject fileObject = obj.getFileObject();
return fileObject != null ? DataObject.find(fileObject) : null;
} catch (DataObjectNotFoundException ex) {
return null;
}
}
@Override
public Class<? extends DataObject> type(Description obj) {
return DataObject.class;
}
@Override
public String id(Description obj) {
return "IL[" + obj.toString();
}
@Override
public String displayName(Description obj) {
return id(obj);
}
};
public static synchronized Node getWaitNode() {
if (WAIT_NODE == null) {
WAIT_NODE = new WaitNode();
}
return WAIT_NODE;
}
private static class WaitNode extends AbstractNode {
private final Image waitIcon = ImageUtilities.loadImage("org/antlr/netbeans/editor/navigation/resources/wait.gif");
public WaitNode() {
super(Children.LEAF);
}
@Override
public Image getIcon(int type) {
return waitIcon;
}
@Override
public Image getOpenedIcon(int type) {
return getIcon(type);
}
@Override
public String getDisplayName() {
return Bundle.LBL_WaitNode();
}
@Override
public String getHtmlDisplayName() {
return getDisplayName();
}
}
public static final class ElementChildren extends Children.Keys<Description> {
private final NavigatorPanelUI ui;
private final Factory nodeFactory;
public ElementChildren(NavigatorPanelUI ui, Collection<Description> descriptions, Factory nodeFactory) {
this.ui = ui;
this.nodeFactory = nodeFactory;
resetKeys(descriptions, ui.getFilters());
}
@Override
protected Node[] createNodes(Description key) {
return new Node[] { nodeFactory.createNode(ui, key) };
}
private void resetKeys(Collection<Description> descriptions, Filters filters) {
setKeys(filters.filter(descriptions));
}
}
public interface Factory {
public NavigatorNode createNode(NavigatorPanelUI ui, Description key);
}
}