// Copyright 2012 Google Inc. All Rights Reserved.
//
// 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.
package com.google.collide.client.code.debugging;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnEvaluateExpressionResponse;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnRemoteObjectPropertiesResponse;
import com.google.collide.client.code.debugging.DebuggerApiTypes.OnRemoteObjectPropertyChanged;
import com.google.collide.client.code.debugging.DebuggerApiTypes.PropertyDescriptor;
import com.google.collide.client.code.debugging.DebuggerApiTypes.RemoteObject;
import com.google.collide.client.ui.dropdown.DropdownWidgets;
import com.google.collide.client.ui.tree.Tree;
import com.google.collide.client.ui.tree.TreeNodeElement;
import com.google.collide.client.ui.tree.TreeNodeLabelRenamer;
import com.google.collide.client.ui.tree.TreeNodeMutator;
import com.google.collide.client.util.AnimationUtils;
import com.google.collide.client.util.Elements;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.json.shared.JsonStringMap;
import com.google.collide.mvp.CompositeView;
import com.google.collide.mvp.UiComponent;
import com.google.collide.shared.util.JsonCollections;
import com.google.collide.shared.util.ListenerRegistrar;
import com.google.collide.shared.util.StringUtils;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.client.Timer;
import elemental.css.CSSStyleDeclaration;
import elemental.events.Event;
import elemental.events.EventListener;
import elemental.html.DragEvent;
import elemental.html.Element;
import javax.annotation.Nullable;
/**
* Renders a {@link RemoteObject} in a tree-like UI.
*
*/
public class RemoteObjectTree extends UiComponent<RemoteObjectTree.View> {
public interface Css extends CssResource, TreeNodeMutator.Css {
String root();
@Override
String nodeNameInput();
}
interface Resources
extends
DropdownWidgets.Resources,
ClientBundle,
Tree.Resources,
RemoteObjectNodeRenderer.Resources {
@Source("RemoteObjectTree.css")
Css remoteObjectTreeCss();
}
/**
* The view for the remote object tree.
*/
static class View extends CompositeView<ViewEvents> {
private final Css css;
private final Tree.View<RemoteObjectNode> treeView;
private final EventListener dblClickListener = new EventListener() {
@Override
public void handleEvent(Event evt) {
Element target = (Element) evt.getTarget();
if (getDelegate().onDblClick(target)) {
evt.stopPropagation();
}
}
};
private final EventListener scrollListener = new EventListener() {
@Override
public void handleEvent(Event evt) {
// Ensure scrollLeft is always zero (we do not support horizontal scrolling).
getElement().setScrollLeft(0);
}
};
View(Resources resources) {
css = resources.remoteObjectTreeCss();
treeView = new Tree.View<RemoteObjectNode>(resources);
Element rootElement = Elements.createDivElement(css.root());
rootElement.appendChild(treeView.getElement());
rootElement.addEventListener(Event.DBLCLICK, dblClickListener, false);
rootElement.addEventListener(Event.SCROLL, scrollListener, false);
setElement(rootElement);
}
}
/**
* User actions on the debugger.
*/
public interface Listener {
void onRootChildrenChanged();
}
/**
* The view events.
*/
private interface ViewEvents {
boolean onDblClick(Element target);
}
static RemoteObjectTree create(View view, Resources resources, DebuggerState debuggerState) {
RemoteObjectNodeDataAdapter nodeDataAdapter = new RemoteObjectNodeDataAdapter();
RemoteObjectNodeRenderer nodeRenderer = new RemoteObjectNodeRenderer(resources);
Tree<RemoteObjectNode> tree = Tree.create(view.treeView, nodeDataAdapter, nodeRenderer,
resources);
TreeNodeLabelRenamer<RemoteObjectNode> nodeLabelMutator =
new TreeNodeLabelRenamer<RemoteObjectNode>(
nodeRenderer, nodeDataAdapter, resources.remoteObjectTreeCss());
RemoteObjectTreeContextMenuController contextMenuController =
RemoteObjectTreeContextMenuController.create(
resources, debuggerState, nodeRenderer, nodeLabelMutator);
return new RemoteObjectTree(
view, tree, nodeRenderer, nodeLabelMutator, debuggerState, contextMenuController);
}
private static final int MAX_NUMBER_OF_CACHED_PATHS = 50;
private final Tree<RemoteObjectNode> tree;
private final TreeNodeLabelRenamer<RemoteObjectNode> nodeLabelMutator;
private final RemoteObjectNodeRenderer nodeRenderer;
private final DebuggerState debuggerState;
private final RemoteObjectTreeContextMenuController contextMenuController;
private Listener listener;
private RemoteObjectNodeCache remoteObjectNodes = new RemoteObjectNodeCache();
private JsonArray<JsonArray<String>> pathsToExpand = JsonCollections.createArray();
private JsonStringMap<String> deferredEvaluations = JsonCollections.createMap();
private RemoteObjectNode recentEditedNode;
private final Tree.Listener<RemoteObjectNode> treeListener =
new Tree.Listener<RemoteObjectNode>() {
@Override
public void onNodeAction(TreeNodeElement<RemoteObjectNode> node) {
}
@Override
public void onNodeClosed(TreeNodeElement<RemoteObjectNode> node) {
}
@Override
public void onNodeContextMenu(int mouseX, int mouseY,
TreeNodeElement<RemoteObjectNode> node) {
contextMenuController.show(mouseX, mouseY, node);
}
@Override
public void onNodeDragDrop(TreeNodeElement<RemoteObjectNode> node, DragEvent event) {
}
@Override
public void onRootDragDrop(DragEvent event) {
}
@Override
public void onNodeExpanded(TreeNodeElement<RemoteObjectNode> node) {
RemoteObjectNode remoteObjectNode = node.getData();
if (remoteObjectNode.shouldRequestChildren()) {
debuggerState.requestRemoteObjectProperties(
remoteObjectNode.getRemoteObject().getObjectId());
}
}
@Override
public void onRootContextMenu(int mouseX, int mouseY) {
}
@Override
public void onNodeDragStart(TreeNodeElement<RemoteObjectNode> node, DragEvent event) {
}
};
private final TreeNodeLabelRenamer.LabelRenamerCallback<RemoteObjectNode> addNewNodeCallback =
new TreeNodeLabelRenamer.LabelRenamerCallback<RemoteObjectNode>() {
@Override
public void onCommit(String oldLabel, TreeNodeElement<RemoteObjectNode> node) {
handleOnAddNewNode(node);
}
@Override
public boolean passValidation(TreeNodeElement<RemoteObjectNode> node, String newLabel) {
return true;
}
};
private class DebuggerListenerImpl implements DebuggerState.RemoteObjectListener,
DebuggerState.DebuggerStateListener,
DebuggerState.EvaluateExpressionListener {
@Override
public void onDebuggerStateChange() {
if (!debuggerState.isActive()) {
// Prevent memory leaks.
pathsToExpand.clear();
deferredEvaluations = JsonCollections.createMap();
}
}
@Override
public void onRemoteObjectPropertiesResponse(OnRemoteObjectPropertiesResponse response) {
handleOnRemoteObjectPropertiesResponse(response);
}
@Override
public void onRemoteObjectPropertyChanged(OnRemoteObjectPropertyChanged response) {
handleOnRemoteObjectPropertyChanged(response);
}
@Override
public void onEvaluateExpressionResponse(OnEvaluateExpressionResponse response) {
handleOnEvaluateExpressionResponse(response);
}
@Override
public void onGlobalObjectChanged() {
reevaluateRootChildren();
}
}
private final DebuggerListenerImpl debuggerListener = new DebuggerListenerImpl();
private final Timer treeHeightRestorer = new Timer() {
@Override
public void run() {
AnimationUtils.animatePropertySet(getView().getElement(), "min-height", "0px",
AnimationUtils.SHORT_TRANSITION_DURATION);
}
};
private final ListenerRegistrar.RemoverManager removerManager =
new ListenerRegistrar.RemoverManager();
private RemoteObjectTree(View view, Tree<RemoteObjectNode> tree,
RemoteObjectNodeRenderer nodeRenderer,
TreeNodeLabelRenamer<RemoteObjectNode> nodeLabelMutator, DebuggerState debuggerState,
RemoteObjectTreeContextMenuController contextMenuController) {
super(view);
this.tree = tree;
this.nodeRenderer = nodeRenderer;
this.nodeLabelMutator = nodeLabelMutator;
this.debuggerState = debuggerState;
this.contextMenuController = contextMenuController;
removerManager
.track(debuggerState.getRemoteObjectListenerRegistrar().add(debuggerListener))
.track(debuggerState.getDebuggerStateListenerRegistrar().add(debuggerListener))
.track(debuggerState.getEvaluateExpressionListenerRegistrar().add(debuggerListener));
tree.setTreeEventHandler(treeListener);
setContextMenuEventListener();
view.setDelegate(new ViewEvents() {
@Override
public boolean onDblClick(Element target) {
return handleOnDblClick(target);
}
});
}
private void setContextMenuEventListener() {
contextMenuController.setListener(new RemoteObjectTreeContextMenuController.Listener() {
@Override
public void onAddNewChild(TreeNodeElement<RemoteObjectNode> nodeElement) {
// Expand the node if collapsed.
tree.expandNode(nodeElement);
treeListener.onNodeExpanded(nodeElement);
addMutableChild(nodeElement.getData());
}
@Override
public void onNodeEdited(TreeNodeElement<RemoteObjectNode> nodeElement, String newLabel) {
// Collapse the node if expanded.
tree.closeNode(nodeElement);
RemoteObjectNode node = nodeElement.getData();
RemoteObjectNode parent = node.getParent();
if (node.isRootChild()) {
String expression = "(" + node.getName() + ") = (" + newLabel + ")";
// After this is executed we need to re-evaluate the original watch expression.
deferredEvaluations.put(expression, node.getName());
debuggerState.evaluateExpression(expression);
} else if (parent != null && parent.getRemoteObject() != null) {
// We do not know the new token type, so just remove the old one for the
// time being (i.e. until we get the answer from the debugger).
nodeRenderer.removeTokenClassName(node, nodeElement.getNodeLabel());
recentEditedNode = node;
debuggerState.setRemoteObjectProperty(
parent.getRemoteObject().getObjectId(), node.getName(), newLabel);
}
}
@Override
public void onNodeDeleted(TreeNodeElement<RemoteObjectNode> nodeElement) {
RemoteObjectNode node = nodeElement.getData();
RemoteObjectNode parent = node.getParent();
JsonArray<RemoteObjectNode> selectedNodes = tree.getSelectionModel().getSelectedNodes();
if (node.isRootChild()) {
for (int i = 0, n = selectedNodes.size(); i < n; ++i) {
RemoteObjectNode selectedNode = selectedNodes.get(i);
if (selectedNode.isRootChild() && selectedNode.getParent() == parent) {
parent.removeChild(selectedNode);
}
}
// Repaint the sub-tree.
replaceRemoteObjectNode(parent, parent);
} else if (parent != null && parent.getRemoteObject() != null) {
for (int i = 0, n = selectedNodes.size(); i < n; ++i) {
RemoteObjectNode selectedNode = selectedNodes.get(i);
if (selectedNode.getParent() == parent) {
recentEditedNode = null;
debuggerState.removeRemoteObjectProperty(
parent.getRemoteObject().getObjectId(), selectedNode.getName());
}
}
}
}
@Override
public void onNodeRenamed(TreeNodeElement<RemoteObjectNode> nodeElement, String oldLabel) {
RemoteObjectNode node = nodeElement.getData();
RemoteObjectNode parent = node.getParent();
if (node.isRootChild()) {
debuggerState.evaluateExpression(node.getName());
} else if (parent != null && parent.getRemoteObject() != null) {
String newLabel = node.getName();
// Undo renaming until we get the answer from the debugger.
nodeLabelMutator.mutateNodeKey(nodeElement, oldLabel);
recentEditedNode = node;
debuggerState.renameRemoteObjectProperty(
parent.getRemoteObject().getObjectId(), oldLabel, newLabel);
}
}
});
}
void teardown() {
removerManager.remove();
tree.setTreeEventHandler(null);
contextMenuController.setListener(null);
setRoot(null); // Also clears the RemoteObjectNodeCache.
setListener(null);
pathsToExpand.clear();
deferredEvaluations = JsonCollections.createMap();
recentEditedNode = null;
}
void setListener(Listener listener) {
this.listener = listener;
}
/**
* Returns the root of the tree. Also commits all active mutations, so that
* the tree should not be in an inconsistent state while exposing it's root
* outside.
*
* @return tree root
*/
RemoteObjectNode getRoot() {
nodeLabelMutator.forceCommit();
return tree.getModel().getRoot();
}
int getRootChildrenCount() {
RemoteObjectNode root = tree.getModel().getRoot();
if (root == null) {
return 0;
}
return root.getChildren().size();
}
void setRoot(@Nullable RemoteObjectNode newRoot) {
RemoteObjectNode root = tree.getModel().getRoot();
if (root != newRoot) {
recentEditedNode = null;
}
replaceRemoteObjectNode(root, newRoot);
}
Element getContextMenuElement() {
return contextMenuController.getContextMenuElement();
}
/**
* Replaces an existing {@link RemoteObjectNode} with a new one, and refreshes
* the tree UI. This will also save the changes to the {@link RemoteObjectNode}
* model.
*
* @param oldNode old node to be removed
* @param newNode new node to replace the old one
*/
void replaceRemoteObjectNode(RemoteObjectNode oldNode, RemoteObjectNode newNode) {
contextMenuController.hide();
// Save the changes to the model.
if (oldNode != newNode && oldNode != null) {
RemoteObjectNode parent = oldNode.getParent();
if (parent != null) {
parent.removeChild(oldNode);
if (newNode != null) {
parent.addChild(newNode);
}
}
}
saveTreeMinHeight();
// NOTE: Update the UI before tearing down the cache.
pathsToExpand.addAll(tree.replaceSubtree(oldNode, newNode, false));
replaceRemoteObjectNodesCache(collectAllChildren());
expandCachedPaths();
if (listener != null && newNode == tree.getModel().getRoot()) {
listener.onRootChildrenChanged();
}
}
private void expandCachedPaths() {
pathsToExpand = tree.expandPaths(pathsToExpand, true);
// Ensure maximum size of the cache.
if (pathsToExpand.size() > MAX_NUMBER_OF_CACHED_PATHS) {
pathsToExpand.splice(0, pathsToExpand.size() - MAX_NUMBER_OF_CACHED_PATHS);
}
}
private void removeRemoteObjectNode(RemoteObjectNode node) {
contextMenuController.hide();
// Save the changes to the model.
RemoteObjectNode parent = node.getParent();
if (parent != null) {
parent.removeChild(node);
}
// Update the tree UI.
tree.removeNode(node.getRenderedTreeNode());
remoteObjectNodes.remove(node);
node.teardown();
if (listener != null && parent == tree.getModel().getRoot()) {
listener.onRootChildrenChanged();
}
}
private void saveTreeMinHeight() {
Element element = getView().getElement();
AnimationUtils.removeTransitions(element.getStyle());
element.getStyle().setProperty("min-height",
element.getOffsetHeight() + CSSStyleDeclaration.Unit.PX);
treeHeightRestorer.schedule(100);
}
void collapseRootChildren() {
RemoteObjectNode root = tree.getModel().getRoot();
if (root == null) {
return;
}
JsonArray<RemoteObjectNode> children = root.getChildren();
for (int i = 0, n = children.size(); i < n; ++i) {
TreeNodeElement<RemoteObjectNode> nodeElement =
tree.getModel().getDataAdapter().getRenderedTreeNode(children.get(i));
if (nodeElement != null) {
tree.closeNode(nodeElement);
}
}
}
void addMutableRootChild() {
if (nodeLabelMutator.isMutating()) {
return;
}
RemoteObjectNode root = tree.getModel().getRoot();
if (root == null) {
root = RemoteObjectNode.createRoot();
setRoot(root);
}
addMutableChild(root);
}
private void addMutableChild(RemoteObjectNode parent) {
RemoteObjectNode child = RemoteObjectNode.createBeingEdited();
if (hasNoRealRemoteObjectChildren(parent)) {
// Handle the case when there is only a dummy "No Properties" node.
replaceRemoteObjectNode(parent.getChildren().get(0), child);
} else {
parent.addChild(child);
replaceRemoteObjectNode(parent, parent);
}
nodeLabelMutator.cancel();
nodeLabelMutator.enterMutation(tree.getModel().getDataAdapter().getRenderedTreeNode(child),
addNewNodeCallback);
}
private void handleOnAddNewNode(TreeNodeElement<RemoteObjectNode> nodeElement) {
RemoteObjectNode node = nodeElement.getData();
RemoteObjectNode parent = node.getParent();
String name = node.getName();
boolean isRootChild = node.isRootChild();
recentEditedNode = parent;
removeRemoteObjectNode(node);
if (isRootChild) {
appendRootChild(name);
} else if (parent != null && parent.getRemoteObject() != null
&& !StringUtils.isNullOrWhitespace(name)) {
RemoteObjectNode newChild = parent.getFirstChildByName(name);
if (newChild == null) {
// Tell the debugger to add the new property to the remote object and wait for response.
debuggerState.setRemoteObjectProperty(parent.getRemoteObject().getObjectId(),
name, DebuggerApiTypes.UNDEFINED_REMOTE_OBJECT.getDescription());
} else if (!nodeLabelMutator.isMutating() && newChild.getRenderedTreeNode() != null) {
contextMenuController.enterEditPropertyValue(newChild.getRenderedTreeNode());
}
} else if (hasNoRealRemoteObjectChildren(parent)) {
// We should probably render the dummy "No Properties" element again.
replaceRemoteObjectNode(parent, parent);
}
}
/**
* @return true if the given node has only the dummy "No Properties" child
*/
private static boolean hasNoRealRemoteObjectChildren(RemoteObjectNode node) {
JsonArray<RemoteObjectNode> children = node.getChildren();
return (children.size() == 1 && children.get(0).getRemoteObject() == null);
}
private void handleOnRemoteObjectPropertiesResponse(OnRemoteObjectPropertiesResponse response) {
JsonArray<RemoteObjectNode> parentNodes = remoteObjectNodes.get(response.getObjectId());
if (parentNodes == null) {
return;
}
for (int i = 0, n = parentNodes.size(); i < n; ++i) {
handleOnRemoteObjectPropertiesResponse(response, parentNodes.get(i));
}
expandCachedPaths();
// Re-schedule the treeHeightRestorer timer.
saveTreeMinHeight();
if (recentEditedNode != null) {
// A user may have been in the process of adding a new property, but was unable to do so
// until we fetch all properties of the remote object from the debugger.
for (int i = 0, n = parentNodes.size(); i < n; ++i) {
RemoteObjectNode parentNode = parentNodes.get(i);
if (parentNode == recentEditedNode) {
recentEditedNode = null;
if (!nodeLabelMutator.isMutating()) {
addMutableChild(parentNode);
}
break;
}
}
}
}
private void handleOnRemoteObjectPropertiesResponse(OnRemoteObjectPropertiesResponse response,
RemoteObjectNode parentNode) {
if (!parentNode.shouldRequestChildren()) {
// We already processed a request for this node.
return;
}
JsonArray<PropertyDescriptor> properties = response.getProperties();
for (int i = 0, n = properties.size(); i < n; ++i) {
PropertyDescriptor property = properties.get(i);
boolean isGetterOrSetter =
(property.getGetterFunction() != null || property.getSetterFunction() != null);
if (isGetterOrSetter) {
if (property.getGetterFunction() != null) {
RemoteObjectNode child = RemoteObjectNode.createGetterProperty(
property.getName(), property.getGetterFunction());
appendNewNode(parentNode, child);
}
if (property.getSetterFunction() != null) {
RemoteObjectNode child = RemoteObjectNode.createSetterProperty(
property.getName(), property.getSetterFunction());
appendNewNode(parentNode, child);
}
} else if (property.getValue() != null) {
RemoteObjectNode child =
new RemoteObjectNode.Builder(property.getName(), property.getValue())
.setWasThrown(property.wasThrown())
.setDeletable(property.isConfigurable())
.setWritable(property.isWritable())
.setEnumerable(property.isEnumerable())
.build();
appendNewNode(parentNode, child);
}
}
parentNode.setAllChildrenRequested();
// Repaint the node.
pathsToExpand.addAll(tree.replaceSubtree(parentNode, parentNode, false));
}
private void appendNewNode(RemoteObjectNode parent, RemoteObjectNode child) {
remoteObjectNodes.put(child);
parent.addChild(child);
}
private void handleOnRemoteObjectPropertyChanged(OnRemoteObjectPropertyChanged response) {
JsonArray<RemoteObjectNode> parentNodes = remoteObjectNodes.get(response.getObjectId());
if (parentNodes == null) {
return;
}
boolean shouldRefreshProperties =
(response.wasThrown() || (response.isValueChanged() && response.getValue() == null));
for (int i = 0, n = parentNodes.size(); i < n; ++i) {
RemoteObjectNode parent = parentNodes.get(i);
if (shouldRefreshProperties) {
// Just refresh all properties of this object.
if (parent == recentEditedNode) {
recentEditedNode = null;
}
RemoteObjectNode newParent =
new RemoteObjectNode.Builder(parent.getName(), parent.getRemoteObject(), parent)
.build();
replaceRemoteObjectNode(parent, newParent);
continue;
}
RemoteObjectNode child = parent.getFirstChildByName(response.getOldName());
if (child == null) {
// A new property was added.
if (response.getNewName() != null) {
RemoteObjectNode newChild =
new RemoteObjectNode.Builder(response.getNewName(), response.getValue()).build();
parent.addChild(newChild);
replaceRemoteObjectNode(parent, parent);
// Continue adding a new property with editing it's value.
if (parent == recentEditedNode) {
recentEditedNode = null;
if (!nodeLabelMutator.isMutating() && newChild.getRenderedTreeNode() != null) {
contextMenuController.enterEditPropertyValue(newChild.getRenderedTreeNode());
}
}
}
} else if (response.getNewName() == null) {
// The property was removed.
removeRemoteObjectNode(child);
} else {
RemoteObject newValue =
response.isValueChanged() ? response.getValue() : child.getRemoteObject();
RemoteObjectNode newChild =
new RemoteObjectNode.Builder(response.getNewName(), newValue, child).build();
// We could be replacing onto an existing child. If so, delete it first.
if (!StringUtils.equalNonEmptyStrings(response.getOldName(), response.getNewName())) {
RemoteObjectNode oldNewChild = parent.getFirstChildByName(response.getNewName());
if (oldNewChild != null) {
removeRemoteObjectNode(oldNewChild);
}
}
replaceRemoteObjectNode(child, newChild);
// Collapse the selection to the recently edited node.
if (child == recentEditedNode) {
recentEditedNode = null;
tree.getSelectionModel().selectSingleNode(newChild);
}
}
}
}
private void replaceRemoteObjectNodesCache(final RemoteObjectNodeCache newCache) {
// Tear down objects from the old cache first.
remoteObjectNodes.iterate(new RemoteObjectNodeCache.IterationCallback() {
@Override
public void onIteration(String key, JsonArray<RemoteObjectNode> values) {
for (int i = 0, n = values.size(); i < n; ++i) {
RemoteObjectNode node = values.get(i);
if (!newCache.contains(node)) {
node.teardown();
}
}
}
});
remoteObjectNodes = newCache;
}
private RemoteObjectNodeCache collectAllChildren() {
final RemoteObjectNodeCache result = new RemoteObjectNodeCache();
RemoteObjectNode root = tree.getModel().getRoot();
if (root != null) {
Tree.iterateDfs(root, tree.getModel().getDataAdapter(), new Tree.Visitor<RemoteObjectNode>() {
@Override
public boolean shouldVisit(RemoteObjectNode node) {
return true; // Visit all nodes.
}
@Override
public void visit(RemoteObjectNode node, boolean willVisitChildren) {
result.put(node);
}
});
}
return result;
}
private boolean handleOnDblClick(Element target) {
TreeNodeElement<RemoteObjectNode> nodeElement = tree.getNodeFromElement(target);
if (nodeElement == null) {
return false;
}
RemoteObjectNode node = nodeElement.getData();
if (node.hasChildren()) {
// The default DblClick will expand/collapse the node.
return false;
}
if (nodeRenderer.getAncestorPropertyNameElement(target) != null) {
return contextMenuController.enterRenameProperty(nodeElement);
} else if (nodeRenderer.getAncestorPropertyValueElement(target) != null) {
return contextMenuController.enterEditPropertyValue(nodeElement);
}
return false;
}
private void handleOnEvaluateExpressionResponse(OnEvaluateExpressionResponse response) {
RemoteObjectNode root = tree.getModel().getRoot();
if (root == null) {
return;
}
String expression = response.getExpression();
RemoteObject result = response.getResult();
JsonArray<RemoteObjectNode> children = root.getChildren();
for (int i = 0, n = children.size(); i < n; ++i) {
RemoteObjectNode child = children.get(i);
if (!child.isTransient() && child.getName().equals(expression)) {
RemoteObjectNode newChild = new RemoteObjectNode.Builder(expression, result, child)
.setWasThrown(response.wasThrown())
.build();
// Repaint the subtree.
replaceRemoteObjectNode(child, newChild);
}
}
String deferredExpression = deferredEvaluations.remove(expression);
if (!StringUtils.isNullOrEmpty(deferredExpression)) {
debuggerState.evaluateExpression(deferredExpression);
}
}
void reevaluateRootChildren() {
// Clear cached data (we are about to refresh everything anyway).
deferredEvaluations = JsonCollections.createMap();
RemoteObjectNode root = tree.getModel().getRoot();
if (root == null) {
// Nothing to do.
return;
}
JsonArray<RemoteObjectNode> children = root.getChildren();
if (debuggerState.isActive()) {
for (int i = 0, n = children.size(); i < n; ++i) {
RemoteObjectNode child = children.get(i);
if (!child.isTransient()) {
debuggerState.evaluateExpression(child.getName());
}
}
} else {
// Set all expressions' values undefined.
RemoteObjectNode newRoot = RemoteObjectNode.createRoot();
for (int i = 0, n = children.size(); i < n; ++i) {
RemoteObjectNode oldChild = children.get(i);
RemoteObjectNode newChild = new RemoteObjectNode.Builder(
oldChild.getName(), DebuggerApiTypes.UNDEFINED_REMOTE_OBJECT, oldChild).build();
newRoot.addChild(newChild);
}
setRoot(newRoot);
}
}
private void appendRootChild(String expression) {
String trimmedExpression = StringUtils.trimNullToEmpty(expression);
if (StringUtils.isNullOrEmpty(trimmedExpression)) {
return;
}
RemoteObjectNode root = tree.getModel().getRoot();
if (root == null) {
root = RemoteObjectNode.createRoot();
}
RemoteObjectNode lastChild = root.getLastChild();
RemoteObjectNode newChild =
new RemoteObjectNode.Builder(trimmedExpression, DebuggerApiTypes.UNDEFINED_REMOTE_OBJECT)
.setOrderIndex(lastChild == null ? 0 : lastChild.getOrderIndex() + 1)
.build();
root.addChild(newChild);
// Repaint the root.
setRoot(root);
debuggerState.evaluateExpression(trimmedExpression);
}
}