/*
* 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.icons.AllIcons;
import com.intellij.icons.AllIcons.Actions;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.ui.Splitter;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ui.UIUtil;
import jetbrains.mps.icons.MPSIcons.Nodes;
import jetbrains.mps.ide.tools.BaseTool;
import jetbrains.mps.ide.ui.tree.MPSTree;
import jetbrains.mps.newTypesystem.state.State;
import jetbrains.mps.nodeEditor.EditorComponent;
import jetbrains.mps.project.Project;
import jetbrains.mps.workbench.action.ActionUtils;
import jetbrains.mps.workbench.action.BaseAction;
import org.jetbrains.mps.openapi.model.SNode;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.util.Map;
public class TypeSystemTracePanel extends JPanel implements Disposable {
private TypeSystemTraceTree myTraceTree;
private TypeSystemStateTree myStateTree;
private JPanel myButtons;
private EditorComponent myEditorComponent;
private BaseTool myTool;
private MPSTree myDetailsTree;
private TypecheckingContextTracker myContextTracker;
private Project myProject;
public TypeSystemTracePanel(BaseTool tool) {
this.setLayout(new BorderLayout());
myButtons = new JPanel(new BorderLayout());
this.setMinimumSize(new Dimension(700, 700));
ApplicationManager.getApplication().invokeLater(() -> {
JComponent buttonsPanel = ActionManager.getInstance().createActionToolbar(ActionPlaces.TYPE_HIERARCHY_VIEW_TOOLBAR, createButtonsGroup(), true).getComponent();
myButtons.add(buttonsPanel, BorderLayout.WEST);
});
myTool = tool;
}
@Override
public Color getBackground() {
return Color.WHITE;
}
public void showTraceForNode(Project mpsProject, SNode node, EditorComponent editorComponent) {
cleanUp();
myProject = mpsProject;
myEditorComponent = editorComponent;
myTraceTree = new TypeSystemTraceTree(mpsProject, node, this, editorComponent);
// TODO: refactor into a separate class
this.myDetailsTree = myTraceTree.getDetailsTree();
myStateTree = new TypeSystemStateTree(mpsProject, myTraceTree.getState(), editorComponent);
JBScrollPane traceScrollPane = new JBScrollPane(myTraceTree);
traceScrollPane.setPreferredSize(new Dimension(400, 700));
JBScrollPane detailsScrollPane = new JBScrollPane(myDetailsTree);
detailsScrollPane.setPreferredSize(new Dimension(400, 200));
JBScrollPane stateScrollPane = new JBScrollPane(myStateTree);
stateScrollPane.setPreferredSize(new Dimension(400, 900));
Splitter leftHSplitPane = new Splitter(true, 0.8f);
leftHSplitPane.setFirstComponent(traceScrollPane);
leftHSplitPane.setSecondComponent(detailsScrollPane);
leftHSplitPane.getDivider().setBackground(UIUtil.getPanelBackground());
leftHSplitPane.getDivider().setOpaque(true);
Splitter vSplitPane = new Splitter(false, 0.65f);
vSplitPane.setFirstComponent(leftHSplitPane);
vSplitPane.setSecondComponent(stateScrollPane);
vSplitPane.getDivider().setBackground(UIUtil.getPanelBackground());
vSplitPane.getDivider().setOpaque(true);
this.removeAll();
this.add(vSplitPane);
this.add(myButtons, BorderLayout.NORTH);
this.setVisible(true);
}
public void resetState(State state) {
myStateTree.resetState(state);
}
public void updateState(State state) {
myStateTree.updateState(state);
}
@Override
public void dispose() {
cleanUp();
}
public void cleanUp() {
if (myTraceTree != null) {
Disposer.dispose(myTraceTree);
this.myTraceTree = null;
}
if (myStateTree != null) {
Disposer.dispose(myStateTree);
this.myStateTree = null;
}
if (myDetailsTree != null) {
Disposer.dispose(myDetailsTree);
this.myDetailsTree = null;
}
}
protected DefaultActionGroup createButtonsGroup() {
return ActionUtils.groupFromActions(
new BaseAction("Close", "Close type system trace tool", Actions.Cancel) {
@Override
protected void doExecute(AnActionEvent e, Map<String, Object> _params) {
myTool.setAvailable(false);
}
},
/* Let's always show applied rules
new MyToggleAction("Show Apply Rule", "Show apply rule operations in trace", Nodes.Rule) {
@Override
boolean initialState() {
return TraceSettings.isShowApplyRuleOperations();
}
@Override
void stateChanged(boolean newState) {
TraceSettings.setShowApplyRuleOperations(newState);
}
},
*/
/* Generation mode is not currently supported
new ToggleAction("Generation Mode", "Show trace in generation mode", Nodes.Generator) {
@Override
public boolean isSelected(AnActionEvent e) {
return TraceSettings.isGenerationMode();
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
TraceSettings.setGenerationMode(state);
}
},
*/
new MyToggleAction("Trace for Selected Node", "Show trace for selected node", Nodes.Node) {
@Override
boolean initialState() {
return TraceSettings.isTraceForSelectedNode();
}
@Override
void stateChanged(boolean newState) {
TraceSettings.setTraceForSelectedNode(newState);
}
},
/* Always show type expansion
new MyToggleAction("Show types expansion", "Show types expansion", AllIcons.Nodes.Folder) {
@Override
boolean initialState() {
return TraceSettings.isShowTypesExpansion();
}
@Override
void stateChanged(boolean newState) {
TraceSettings.setShowTypesExpansion(newState);
}
},
*/
/* Never show block dependencies
new MyToggleAction("Show block dependencies", "Show block dependencies in trace", MPSIcons.General.Block) {
@Override
boolean initialState() {
return TraceSettings.isShowBlockDependencies();
}
@Override
void stateChanged(boolean newState) {
TraceSettings.setShowBlockDependencies(newState);
}
},
*/
new BaseAction("Refresh", "Refresh", Actions.Refresh) {
@Override
protected void doExecute(AnActionEvent e, Map<String, Object> _params) {
myProject.getModelAccess().runReadInEDT(() -> refresh());
}
},
new BaseAction("Next error", "Navigate to next error in trace", AllIcons.General.Error) {
@Override
protected void doExecute(AnActionEvent e, Map<String, Object> _params) {
myTraceTree.goToNextError();
}
});
}
public void refresh() {
if (myEditorComponent == null || myProject == null) {
return;
}
final SNode selectedNode = myEditorComponent.getSelectedNode();
if (selectedNode == null) {
return;
}
showTraceForNode(myProject, selectedNode, myEditorComponent);
this.validate();
}
protected abstract class MyToggleAction extends ToggleAction {
public MyToggleAction(String text, String description, Icon icon) {
super(text, description, icon);
mySelected = initialState();
}
@Override
public boolean isSelected(AnActionEvent e) {
return mySelected;
}
@Override
public void setSelected(AnActionEvent e, boolean state) {
mySelected = state;
stateChanged(state);
if (myTraceTree != null) {
myTraceTree.rebuildNow();
myTraceTree.expandAll();
}
}
abstract boolean initialState();
abstract void stateChanged(boolean newState);
private boolean mySelected;
}
}