/*
* Copyright 2003-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 jetbrains.mps.ide.devkit.generator;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.ContentManagerAdapter;
import com.intellij.ui.content.ContentManagerEvent;
import jetbrains.mps.generator.GenerationTrace;
import jetbrains.mps.ide.generator.TransientModelsComponent;
import jetbrains.mps.ide.icons.IdeIcons;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.ide.tools.BaseProjectTool;
import jetbrains.mps.ide.tools.CloseAction;
import jetbrains.mps.util.ComputeRunnable;
import jetbrains.mps.workbench.action.ActionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JLabel;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.ArrayList;
import java.util.List;
public class GenerationTracerViewTool extends BaseProjectTool {
private NoTabsComponent myNoTabsComponent;
private List<GenerationTracerView> myTracerViews = new ArrayList<>();
private ContentManagerAdapter myContentListener;
private final TransientModelsComponent myTransientModelsOwner;
private boolean myAutoscroll;
public GenerationTracerViewTool(Project project, TransientModelsComponent transientModels) {
super(project, "Generation Tracer", null, IdeIcons.DEFAULT_ICON, ToolWindowAnchor.BOTTOM, false, true);
myTransientModelsOwner = transientModels;
myNoTabsComponent = new NoTabsComponent(this);
}
//////
public boolean hasTracingData() {
ComputeRunnable<Boolean> r = new ComputeRunnable<>(() -> {
// FIXME not quite nice code
return myTransientModelsOwner.getModules().iterator().hasNext();
});
ProjectHelper.getModelAccess(getProject()).runReadAction(r);
return r.getResult();
}
public boolean hasTraceInputData(SModelReference modelReference) {
return myTransientModelsOwner.getTrace(modelReference) != null;
}
public boolean hasTracebackData(SModelReference modelReference) {
return myTransientModelsOwner.getTrace(modelReference) != null;
}
public boolean showTraceInputData(@NotNull SNode node) {
int index = getTabIndex(GenerationTracerView.Kind.TraceForward, node.getReference());
if (index > -1) {
selectIndex(index);
openToolLater(true);
return true;
}
TraceNodeUI tracerNode = buildForwardTrace(node);
if (tracerNode == null) {
return false;
}
showTraceView(GenerationTracerView.Kind.TraceForward, tracerNode, node);
return true;
}
public boolean showTracebackData(SNode node) {
int index = getTabIndex(GenerationTracerView.Kind.TraceBackward, node.getReference());
if (index > -1) {
selectIndex(index);
openToolLater(true);
return true;
}
TraceNodeUI tracerNode = buildBackwardTrace(node);
if (tracerNode == null) {
return false;
}
showTraceView(GenerationTracerView.Kind.TraceBackward, tracerNode, node);
return true;
}
//////////////////
@Override
protected void createTool(boolean early) {
StartupManager.getInstance(getProject()).runWhenProjectIsInitialized(() -> {
showNoTabsComponent();
setTracingDataIsAvailable(hasTracingData());
setAvailable(false);
});
}
protected void doRegister() {
super.doRegister();
myContentListener = new ContentManagerAdapter() {
public void contentRemoved(ContentManagerEvent event) {
final boolean removedNoTabsTab = event.getContent().getComponent() == myNoTabsComponent;
//noTabs component could be removed
if (!removedNoTabsTab) {
myTracerViews.remove(event.getIndex());
}
if (getContentManager().getContentCount() == 0) {
showNoTabsComponent();
if (removedNoTabsTab) {
makeUnavailableLater();
}
}
}
};
getContentManager().addContentManagerListener(myContentListener);
}
@Override
protected void doUnregister() {
final ContentManager contentManager = getContentManager();
if (myContentListener != null && contentManager != null && !contentManager.isDisposed()) {
contentManager.removeContentManagerListener(myContentListener);
}
myContentListener = null;
super.doUnregister();
}
private void showNoTabsComponent() {
ContentManager manager = getContentManager();
manager.removeAllContents(true);
addContent(myNoTabsComponent, "", null, false);
}
private void closeTab(int index) {
//noinspection ConstantConditions
getContentManager().removeContent(getContentManager().getContent(index), true);
}
public void closeAll() {
getContentManager().removeAllContents(true);
}
void selectIndex(int index) {
ContentManager manager = getContentManager();
//noinspection ConstantConditions
manager.setSelectedContent(manager.getContent(index));
}
int getTabIndex(GenerationTracerView.Kind kind, SNodeReference node) {
int index = 0;
for (GenerationTracerView tracerView : myTracerViews) {
if (tracerView.isViewFor(kind, node)) {
return index;
}
index++;
}
return -1;
}
boolean isAutoscroll() {
return myAutoscroll;
}
void autoscrollsChanged(boolean b) {
if (myAutoscroll != b) {
myAutoscroll = b;
for (GenerationTracerView tracerView : myTracerViews) {
tracerView.setAutoscrollToSource(b);
}
}
}
void close(GenerationTracerView view) {
closeTab(myTracerViews.indexOf(view));
}
void showTraceView(GenerationTracerView.Kind viewToken, TraceNodeUI tracerNode, SNode node) {
GenerationTracerView tracerView = new GenerationTracerView(this, node.getReference(), viewToken, tracerNode);
myTracerViews.add(tracerView);
Icon i = Icons.getIcon(tracerView.isForwardTraceView() ? TraceNodeUI.Kind.INPUT : TraceNodeUI.Kind.OUTPUT, node);
Content content = addContent(tracerView.getComponent(), node.getPresentation(), i, true);
getContentManager().setSelectedContent(content);
Content noTabsContent = getContentManager().getContent(myNoTabsComponent);
if (noTabsContent != null) {
getContentManager().removeContent(noTabsContent, true);
}
openToolLater(true);
}
public void setTracingDataIsAvailable(final boolean dataPresent) {
ApplicationManager.getApplication().invokeLater(() -> myNoTabsComponent.setDataIsAvailable(dataPresent));
}
@Override
public Project getProject() {
return super.getProject(); // public for GenerationTracerView
}
TraceNodeUI buildForwardTrace(SNode node) {
final GenerationTrace ngt = myTransientModelsOwner.getTrace(node.getModel().getReference());
if (ngt == null) {
return null;
}
TraceNodeUI newTrace = new TraceNodeUI("New gen tracer", Icons.COLLECTION, node.getReference());
// XXX for now, we assume template source models reside in the same repository as the transient/origin node, this in
// not generally true. Likely shall be project repository (if different than that of transient modules) or the one with deployed languages
for (TraceNodeUI n : TraceBuilderUI.buildTrace(ngt, node, node.getModel().getRepository())) {
newTrace.addChild(n);
}
return newTrace;
}
TraceNodeUI buildBackwardTrace(SNode node) {
final GenerationTrace ngt = myTransientModelsOwner.getTrace(node.getModel().getReference());
if (ngt == null) {
return null;
}
TraceNodeUI newTrace = new TraceNodeUI("New gen tracer", Icons.COLLECTION, node.getReference());
for (TraceNodeUI n : TraceBuilderUI.buildBackTrace(ngt, node, node.getModel().getRepository())) {
newTrace.addChild(n);
}
return newTrace;
}
public static class NoTabsComponent extends JPanel {
JPanel myLabelsPanel = new JPanel();
public NoTabsComponent(final GenerationTracerViewTool tool) {
setLayout(new BorderLayout());
JPanel mainPanel = new JPanel(new GridBagLayout());
myLabelsPanel.setLayout(new BoxLayout(myLabelsPanel, BoxLayout.Y_AXIS));
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.CENTER;
mainPanel.add(myLabelsPanel, c);
add(mainPanel, BorderLayout.CENTER);
ApplicationManager.getApplication().invokeLater(() -> {
ActionGroup group = ActionUtils.groupFromActions(new CloseAction(tool));
ActionToolbar toolbar = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, false);
add(toolbar.getComponent(), BorderLayout.WEST);
});
}
public void setDataIsAvailable(boolean state) {
myLabelsPanel.removeAll();
String[] strings;
if (state) {
strings = new String[]{"Tracing data is available.", "To view trace/traceback data use generated node's popup menu."};
} else {
strings = new String[]{"No data available.", "To use the 'generation tracer' generate model with the 'save transient models' option."};
}
for (String string : strings) {
JLabel label = new JLabel(string);
label.setAlignmentX(Component.CENTER_ALIGNMENT);
myLabelsPanel.add(label);
}
}
}
}