/*
* Copyright 2003-2015 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 jetbrains.mps.ide.typesystem.trace;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.ui.JBColor;
import jetbrains.mps.ide.ui.tree.MPSTree;
import jetbrains.mps.ide.ui.tree.MPSTreeNode;
import jetbrains.mps.ide.util.ColorAndGraphicsUtil;
import jetbrains.mps.newTypesystem.state.NodeMaps;
import jetbrains.mps.newTypesystem.state.State;
import jetbrains.mps.newTypesystem.state.blocks.Block;
import jetbrains.mps.newTypesystem.state.blocks.BlockKind;
import jetbrains.mps.newTypesystem.state.blocks.InequalityBlock;
import jetbrains.mps.nodeEditor.DefaultEditorMessage;
import jetbrains.mps.nodeEditor.EditorComponent;
import jetbrains.mps.nodeEditor.EditorMessage;
import jetbrains.mps.nodeEditor.NodeHighlightManager;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.message.EditorMessageOwner;
import jetbrains.mps.openapi.editor.message.SimpleEditorMessage;
import jetbrains.mps.openapi.editor.style.StyleRegistry;
import jetbrains.mps.openapi.navigation.EditorNavigator;
import jetbrains.mps.project.Project;
import jetbrains.mps.util.Pair;
import jetbrains.mps.workbench.MPSDataKeys;
import jetbrains.mps.workbench.action.ActionUtils;
import jetbrains.mps.workbench.action.BaseAction;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class TypeSystemStateTree extends MPSTree implements DataProvider {
private final Project myProject;
private State myState;
private EditorComponent myEditorComponent;
private NodeHighlightManager myHighlightManager;
private EditorMessageOwner myMessageOwner;
public TypeSystemStateTree(Project mpsProject, State state, EditorComponent editorComponent) {
myProject = mpsProject;
myState = state;
myEditorComponent = editorComponent;
this.myHighlightManager = editorComponent.getHighlightManager();
this.myMessageOwner = new EditorMessageOwner() {
};
this.getSelectionModel().addTreeSelectionListener(new TypeSystemStateTree.EditorMessageUpdater());
this.rebuildNow();
expandAll();
}
public void resetState(State state) {
myState = state;
rebuildNow();
expandAll();
clearSelection();
}
public void updateState(State state) {
HashSet<String> existing = null;
if (myState == state) {
existing = new HashSet<String>();
collectExisting(getRootNode(), existing);
}
myState = state;
rebuildNow();
expandAll();
if (existing != null) {
List<TreePath> newNodes = new ArrayList<TreePath>();
collectNew(new TreePath(getRootNode()), existing, newNodes);
setSelectionPaths(newNodes.toArray(new TreePath[newNodes.size()]));
}
}
@Override
public void dispose() {
clearHighlighting();
super.dispose();
}
@Override
protected MPSTreeNode rebuild() {
clearHighlighting();
return createNode();
}
private void collectExisting(MPSTreeNode node, Collection<String> existing) {
for (int idx = 0; idx < node.getChildCount(); idx++) {
TreeNode child = node.getChildAt(idx);
if (child instanceof MPSTreeNode) {
existing.add(child.toString());
collectExisting(((MPSTreeNode) child), existing);
}
}
}
private void collectNew(TreePath path, Collection<String> existing, Collection<TreePath> newNodes) {
Object lastPathComponent = path.getLastPathComponent();
if (lastPathComponent instanceof MPSTreeNode) {
MPSTreeNode node = ((MPSTreeNode) lastPathComponent);
for (int idx = 0; idx < node.getChildCount(); idx++) {
TreeNode child = node.getChildAt(idx);
if (child instanceof MPSTreeNode) {
TreePath childPath = path.pathByAddingChild(child);
if (!(existing.contains(child.toString()))) {
newNodes.add(childPath);
}
collectNew(childPath, existing, newNodes);
}
}
}
}
private TypeSystemStateTreeNode createNode() {
TypeSystemStateTreeNode result = new TypeSystemStateTreeNode("Type system state");
result.add(new TypeSystemStateTreeNode("Solving inequalities in process: " + myState.getInequalities().isSolvingInProcess()));
TypeSystemStateTreeNode[] nodes = {createInequalitiesNode(), createNode("Comparable", myState.getBlocks(BlockKind.COMPARABLE), null), createNode(
"When concrete", myState.getBlocks(BlockKind.WHEN_CONCRETE), null), createNode("Errors", myState.getNodeMaps().getErrorListPresentation(),
JBColor.RED), createNode("Check-only equations", myState.getBlocks(BlockKind.CHECK_EQUATION), null), createEquationsNode()};
for (TypeSystemStateTreeNode node : nodes) {
if (node.children().hasMoreElements()) {
result.add(node);
}
}
return result;
}
private TypeSystemStateTreeNode createNode(String category, List<String> entries, Color color) {
TypeSystemStateTreeNode result = new TypeSystemStateTreeNode(category);
if (color != null) {
result.setColor(color);
}
for (String string : entries) {
result.add(new TypeSystemStateTreeNode(string));
}
return result;
}
private TypeSystemStateTreeNode createNode(String category, Set<Block> entries, Color color) {
TypeSystemStateTreeNode result = new TypeSystemStateTreeNode(category + " (" + entries.size() + ")");
if (color == null) {
color = Color.LIGHT_GRAY;
}
result.setColor(color);
for (Block block : entries) {
result.add(new BlockTreeNode(block, myState, myEditorComponent));
}
return result;
}
private TypeSystemStateTreeNode createInequalitiesNode() {
TypeSystemStateTreeNode result = new TypeSystemStateTreeNode("Inequalities by groups");
Set<String> nodePresentations = new HashSet<String>();
for (Map.Entry<Set<SNode>, Set<InequalityBlock>> entry : myState.getInequalities().getInequalityGroups(
myState.getBlocks(BlockKind.INEQUALITY)).entrySet()) {
Set<SNode> key = entry.getKey();
TypeSystemStateTreeNode current;
if (key.isEmpty() || entry.getValue().size() <= 1) {
current = result;
} else {
current = new TypeSystemStateTreeNode(key.toString());
}
nodePresentations.clear();
for (InequalityBlock block : entry.getValue()) {
BlockTreeNode node = new BlockTreeNode(block, myState, myEditorComponent);
String presentation = node.toString();
if (!(nodePresentations.contains(presentation))) {
current.add(node);
nodePresentations.add(presentation);
}
}
if (result != current) {
result.add(current);
}
}
return result;
}
private TypeSystemStateTreeNode createEquationsNode() {
TypeSystemStateTreeNode result = new TypeSystemStateTreeNode("Equations");
for (Map.Entry<SNode, Set<SNode>> equationGroup : myState.getEquations().getEquationGroups()) {
result.add(new EquationTreeNode(equationGroup.getKey(), equationGroup.getValue(), myState, myEditorComponent));
}
return result;
}
private void clearHighlighting() {
myHighlightManager.clearForOwner(myMessageOwner);
}
private void highlightNodesWithTypes(final Collection<? extends MPSTreeNode> treeNodes) {
clearHighlighting();
myProject.getModelAccess().runReadAction(new Runnable() {
@Override
public void run() {
NodeMaps maps = myState.getNodeMaps();
List<EditorMessage> messages = new ArrayList<EditorMessage>();
for (MPSTreeNode treeNode : treeNodes) {
TypeSystemStateTreeNode stateNode = (TypeSystemStateTreeNode) treeNode;
List<SNode> vars = stateNode.getVariables();
if (null == vars) {
continue;
}
for (SNode var : vars) {
SNode node = check_x8yvv7_a0a0d0c0a0a0a0b0t(maps, var);
if (node != null && node.getModel() != null) {
EditorCell nodeCell = myEditorComponent.findNodeCell(node);
if (nodeCell != null) {
messages.add(new TypeSystemStateTree.TypeEditorMessage(nodeCell, String.valueOf(var)));
}
}
}
if (messages.size() > 0) {
myHighlightManager.mark(messages);
}
}
}
});
}
@Override
protected ActionGroup createPopupActionGroup(final MPSTreeNode treeNode) {
final TypeSystemStateTreeNode stateNode = (TypeSystemStateTreeNode) treeNode;
final DefaultActionGroup group = ActionUtils.groupFromActions();
myProject.getModelAccess().runReadAction(new Runnable() {
@Override
public void run() {
NodeMaps maps = myState.getNodeMaps();
List<SNode> vars = stateNode.getVariables();
if (null == vars) {
return;
}
for (SNode var : vars) {
SNode node = check_x8yvv7_a0a0d0a0a0a0d0u(maps, var);
if (node != null && node.getModel() != null) {
final SNodeReference pointer = new jetbrains.mps.smodel.SNodePointer(node);
group.add(new BaseAction("Go to node with type " + var) {
@Override
public void doExecute(AnActionEvent e, Map<String, Object> _params) {
new EditorNavigator(myProject).shallFocus(true).shallSelect(true).open(pointer);
}
});
}
}
}
});
return group;
}
@Override
@Nullable
public Object getData(@NonNls String id) {
TypeSystemStateTreeNode currentNode = (TypeSystemStateTreeNode) this.getCurrentNode();
if (id.equals(MPSDataKeys.RULE_MODEL_AND_ID.getName())) {
String ruleModel = currentNode.getRuleModel();
String ruleId = currentNode.getRuleId();
if (ruleModel == null || ruleId == null) {
return null;
}
return new Pair<String, String>(ruleModel, ruleId);
}
return null;
}
private class TypeEditorMessage extends DefaultEditorMessage {
private EditorCell myCell;
public TypeEditorMessage(EditorCell cell, String message) {
super(cell.getSNode(), StyleRegistry.getInstance().getSimpleColor(Color.blue), message, myMessageOwner);
this.myCell = cell;
}
@Override
public EditorCell getCell(EditorComponent component) {
return myCell;
}
@Override
public boolean acceptCell(EditorCell cell, EditorComponent component) {
return myCell == cell;
}
@Override
protected void paintWithColor(Graphics graphics, EditorCell cell, Color color) {
int x = cell.getX() + cell.getLeftInset();
int y = cell.getY() + cell.getTopInset();
int width = cell.getWidth() - cell.getLeftInset() - cell.getRightInset() - 1;
int height = cell.getHeight() - cell.getTopInset() - cell.getBottomInset() - 1;
graphics.setColor(color);
ColorAndGraphicsUtil.drawDashedRect(graphics, x, y, width, height);
}
@Override
public boolean isBackground() {
return true;
}
@Override
public boolean sameAs(SimpleEditorMessage that) {
return super.sameAs(that) && this.equals(that);
}
@Override
public boolean equals(Object that) {
if (that == null) {
return false;
}
if (this == that) {
return true;
}
if (that.getClass() != TypeSystemStateTree.TypeEditorMessage.class) {
return false;
}
return this.myCell.equals(((TypeSystemStateTree.TypeEditorMessage) that).myCell);
}
@Override
public int hashCode() {
return myCell.hashCode() * 37;
}
}
private class EditorMessageUpdater implements TreeSelectionListener {
public EditorMessageUpdater() {
}
@Override
public void valueChanged(TreeSelectionEvent event) {
List<MPSTreeNode> selection = new ArrayList<MPSTreeNode>();
TreePath[] selectionPaths = getSelectionPaths();
if (selectionPaths == null) {
clearHighlighting();
return;
}
for (TreePath path : selectionPaths) {
if (((TreeSelectionModel) event.getSource()).isPathSelected(path)) {
MPSTreeNode selected = (MPSTreeNode) path.getLastPathComponent();
selection.add(selected);
}
}
highlightNodesWithTypes(selection);
}
}
private static SNode check_x8yvv7_a0a0d0c0a0a0a0b0t(NodeMaps checkedDotOperand, SNode var) {
if (null != checkedDotOperand) {
return checkedDotOperand.getNode(var);
}
return null;
}
private static SNode check_x8yvv7_a0a0d0a0a0a0d0u(NodeMaps checkedDotOperand, SNode var) {
if (null != checkedDotOperand) {
return checkedDotOperand.getNode(var);
}
return null;
}
}