/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* 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.intellij.xdebugger.impl.ui.tree;
import com.intellij.openapi.util.Comparing;
import com.intellij.xdebugger.XNamedTreeNode;
import com.intellij.xdebugger.frame.presentation.XValuePresentation;
import com.intellij.xdebugger.impl.ui.tree.nodes.RestorableStateNode;
import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode;
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueContainerNode;
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
import org.jetbrains.annotations.NotNull;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author nik
*/
public class XDebuggerTreeRestorer implements XDebuggerTreeListener, TreeSelectionListener {
public static final String SELECTION_PATH_PROPERTY = "selection.path";
private final XDebuggerTree myTree;
private final Rectangle myLastVisibleNodeRect;
private final Map<XDebuggerTreeNode, XDebuggerTreeState.NodeInfo> myNode2State = new HashMap<>();
private final Map<RestorableStateNode, XDebuggerTreeState.NodeInfo> myNode2ParentState = new HashMap<>();
private boolean myStopRestoringSelection;
private boolean myInsideRestoring;
private TreePath mySelectionPath;
private boolean myFinished;
public XDebuggerTreeRestorer(final XDebuggerTree tree, Rectangle lastVisibleNodeRect) {
myTree = tree;
myLastVisibleNodeRect = lastVisibleNodeRect;
mySelectionPath = (TreePath)myTree.getClientProperty(SELECTION_PATH_PROPERTY);
myTree.putClientProperty(SELECTION_PATH_PROPERTY, null);
tree.addTreeListener(this);
tree.addTreeSelectionListener(this);
}
private void restoreChildren(final XDebuggerTreeNode treeNode, final XDebuggerTreeState.NodeInfo nodeInfo) {
if (nodeInfo.isExpanded()) {
myTree.expandPath(treeNode.getPath());
treeNode.getLoadedChildren().forEach(child -> restoreNode(child, nodeInfo));
myNode2State.put(treeNode, nodeInfo);
}
}
void restore(final XDebuggerTreeNode treeNode, final XDebuggerTreeState.NodeInfo parentInfo) {
if (treeNode instanceof RestorableStateNode) {
doRestoreNode((RestorableStateNode)treeNode, parentInfo);
}
else {
restoreChildren(treeNode, parentInfo);
}
}
private void restoreNode(final XDebuggerTreeNode treeNode, final XDebuggerTreeState.NodeInfo parentInfo) {
if (treeNode instanceof RestorableStateNode) {
RestorableStateNode node = (RestorableStateNode)treeNode;
if (node.isComputed()) {
doRestoreNode(node, parentInfo.getChild(node));
}
else {
myNode2ParentState.put(node, parentInfo);
}
}
}
private void doRestoreNode(final RestorableStateNode treeNode, final XDebuggerTreeState.NodeInfo nodeInfo) {
if (nodeInfo != null) {
if (!checkExtendedModified(treeNode) && !(Comparing.equal(nodeInfo.getValue(), treeNode.getRawValue()))) {
treeNode.markChanged();
}
if (!myStopRestoringSelection && nodeInfo.isSelected() && mySelectionPath == null) {
try {
myInsideRestoring = true;
myTree.addSelectionPath(treeNode.getPath());
}
finally {
myInsideRestoring = false;
}
}
if (!(treeNode.isComputed() && treeNode.isLeaf())) { // do not restore computed leafs children
restoreChildren((XDebuggerTreeNode)treeNode, nodeInfo);
}
}
else {
if (!checkExtendedModified(treeNode)) {
treeNode.markChanged();
}
if (mySelectionPath != null && !myStopRestoringSelection && pathsEqual(mySelectionPath, treeNode.getPath())) {
myTree.addSelectionPath(treeNode.getPath());
}
}
}
// comparing only named nodes
private static boolean pathsEqual(@NotNull TreePath path1, @NotNull TreePath path2) {
if (path1.getPathCount() != path2.getPathCount()) {
return false;
}
do {
Object component1 = path1.getLastPathComponent();
Object component2 = path2.getLastPathComponent();
if (component1 instanceof XNamedTreeNode && component2 instanceof XNamedTreeNode) {
if (!Comparing.equal(((XNamedTreeNode)component1).getName(), ((XNamedTreeNode)component2).getName())) {
return false;
}
}
path1 = path1.getParentPath();
path2 = path2.getParentPath();
} while (path1 != null && path2 != null);
return true;
}
private static boolean checkExtendedModified(RestorableStateNode treeNode) {
if (treeNode instanceof XValueNodeImpl) {
XValuePresentation presentation = ((XValueNodeImpl)treeNode).getValuePresentation();
if (presentation instanceof XValueExtendedPresentation) {
if (((XValueExtendedPresentation)presentation).isModified()) {
treeNode.markChanged();
}
return true;
}
}
return false;
}
@Override
public void nodeLoaded(@NotNull final RestorableStateNode node, final String name) {
XDebuggerTreeState.NodeInfo parentInfo = myNode2ParentState.remove(node);
if (parentInfo != null) {
doRestoreNode(node, parentInfo.getChild(node));
}
disposeIfFinished();
}
private void disposeIfFinished() {
if (myNode2ParentState.isEmpty() && myNode2State.isEmpty()) {
myFinished = true;
if (myLastVisibleNodeRect != null) {
myTree.scrollRectToVisible(myLastVisibleNodeRect);
}
dispose();
}
}
@Override
public void childrenLoaded(@NotNull final XDebuggerTreeNode node, @NotNull final List<XValueContainerNode<?>> children, final boolean last) {
XDebuggerTreeState.NodeInfo nodeInfo = myNode2State.get(node);
if (nodeInfo != null) {
for (XDebuggerTreeNode child : children) {
restoreNode(child, nodeInfo);
}
}
if (last) {
myNode2State.remove(node);
disposeIfFinished();
}
}
public void dispose() {
myNode2ParentState.clear();
myNode2State.clear();
myTree.removeTreeListener(this);
myTree.removeTreeSelectionListener(this);
}
public boolean isFinished() {
return myFinished;
}
@Override
public void valueChanged(TreeSelectionEvent e) {
if (!myInsideRestoring) {
myStopRestoringSelection = true;
}
}
}