/* * 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.icons.AllIcons.General; 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.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.actionSystem.PopupAction; import com.intellij.openapi.actionSystem.ToggleAction; import com.intellij.ui.ScrollPaneFactory; import jetbrains.mps.generator.GenerationSettingsProvider; import jetbrains.mps.generator.IGenerationSettings.GenTraceSettings; import jetbrains.mps.ide.project.ProjectHelper; import jetbrains.mps.project.Project; import jetbrains.mps.util.Computable; import jetbrains.mps.util.ModelComputeRunnable; import jetbrains.mps.workbench.action.ActionUtils; import jetbrains.mps.workbench.action.BaseAction; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import org.jetbrains.mps.openapi.module.SRepository; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.JPopupMenu; import java.awt.BorderLayout; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Map; /** * Tree of trace elements and an action bar */ final class GenerationTracerView { private final Project myProject; enum Kind { TraceForward, TraceBackward } private final JPanel myPanel; private final GenerationTracerTree myTree; private final GenerationTracerViewTool myTool; private final SNodeReference myInputNode; private final Kind myViewToken; public GenerationTracerView(GenerationTracerViewTool tool, SNodeReference inputNode, Kind viewToken, TraceNodeUI tracerNode) { myTool = tool; myInputNode = inputNode; myViewToken = viewToken; myPanel = new JPanel(new BorderLayout()); myProject = ProjectHelper.fromIdeaProject(tool.getProject()); myTree = new GenerationTracerTree(this, tracerNode, myProject, inputNode); myTree.setRootVisible(false); myPanel.add(ScrollPaneFactory.createScrollPane(myTree), BorderLayout.CENTER); myPanel.add(createActionsToolbar(), BorderLayout.WEST); myTree.rebuildLater(); } private Component createActionsToolbar() { ToggleAction autoscrollAction = new ToggleAction("", "Autoscroll to Source", Icons.AUTOSCROLL_TO_SOURCE) { public boolean isSelected(AnActionEvent e) { return myTool.isAutoscroll(); } public void setSelected(AnActionEvent e, boolean state) { myTool.autoscrollsChanged(state); } }; AnAction closeAction = new AnAction("", "Close", Icons.CLOSE) { @Override public void actionPerformed(AnActionEvent e) { myTool.close(GenerationTracerView.this); } }; class PresentationSettings extends AnAction implements PopupAction { PresentationSettings() { super("Settings", "Settings", General.Settings); } @Override public void actionPerformed(AnActionEvent e) { JPopupMenu menu = createViewSettingsMenu(); menu.show(e.getInputEvent().getComponent(), 10, 10); } } ActionGroup group = ActionUtils.groupFromActions(autoscrollAction, closeAction, new PresentationSettings()); ActionManager manager = ActionManager.getInstance(); ActionToolbar toolbar = manager.createActionToolbar(ActionPlaces.USAGE_VIEW_TOOLBAR, group, false); return toolbar.getComponent(); } boolean isViewFor(Kind kind, SNodeReference node) { return myViewToken == kind && myInputNode.equals(node); } @Nullable ActionGroup getTraceActionGroup(TraceNodeUI selected) { if (selected.getNavigateTarget() == null) { return null; } if (selected.hasPrevStep() || selected.hasNextStep()) { return new ModelComputeRunnable<ActionGroup>(new NodeActionGroup(selected, myProject.getRepository())).runRead(myProject.getModelAccess()); } return null; } boolean isForwardTraceView() { return myViewToken == Kind.TraceForward; } boolean isBackwardTraceView() { return myViewToken == Kind.TraceBackward; } GenerationTracerViewTool getTool() { return myTool; } public JComponent getComponent() { return myPanel; } void setAutoscrollToSource(boolean b) { myTree.setAutoOpen(b); } void viewSettingsChanged() { myProject.getModelAccess().runReadAction(new Runnable() { @Override public void run() { rebuildTree(); } }); } void rebuildTree() { SNode inputNode = myInputNode.resolve(myProject.getRepository()); if (inputNode == null) { return; } TraceNodeUI traceNode = null; if (isForwardTraceView()) { traceNode = myTool.buildForwardTrace(inputNode); } else if (isBackwardTraceView()) { traceNode = myTool.buildBackwardTrace(inputNode); } if (traceNode != null) { myTree.setRoot(traceNode); myTree.rebuildLater(); } } JPopupMenu createViewSettingsMenu() { final GenTraceSettings settings = GenerationSettingsProvider.getInstance().getGenerationSettings().getTraceSettings(); ActionListener l = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (false == e.getSource() instanceof JCheckBoxMenuItem) { return; } boolean value = ((JCheckBoxMenuItem) e.getSource()).isSelected(); switch (Integer.parseInt(e.getActionCommand())) { case 1 : settings.setGroupByStep(value); break; case 2 : settings.setCompactTemplates(value); break; case 3 : settings.setShowEmptySteps(value); break; case 4 : settings.setGroupByChange(value); } viewSettingsChanged(); } }; final JPopupMenu menu = new JPopupMenu(); JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem("Group changes by step"); menuItem.setToolTipText("Changes grouped by generation steps"); menuItem.setSelected(settings.isGroupByStep()); menuItem.setActionCommand(String.valueOf(1)); menuItem.addActionListener(l); menu.add(menuItem); menuItem = new JCheckBoxMenuItem("Show change-specific templates only"); menuItem.setToolTipText("Hides template elements except those closest to effective template node"); menuItem.setSelected(settings.isCompactTemplates()); menuItem.setActionCommand(String.valueOf(2)); menuItem.addActionListener(l); menu.add(menuItem); menuItem = new JCheckBoxMenuItem("Show steps without changes"); menuItem.setToolTipText("Reveal steps with no changes to selected node"); menuItem.setSelected(settings.isShowEmptySteps()); menuItem.setActionCommand(String.valueOf(3)); menuItem.addActionListener(l); menu.add(menuItem); menuItem = new JCheckBoxMenuItem("Show individual changes"); menuItem.setToolTipText("Each change entry (input-output pair) shown individually"); menuItem.setSelected(settings.isGroupByChange()); menuItem.setActionCommand(String.valueOf(4)); menuItem.addActionListener(l); menu.add(menuItem); return menu; } private class NodeActionGroup implements Computable<ActionGroup> { private final TraceNodeUI myTraceNode; private final SRepository myRepo; public NodeActionGroup(@NotNull TraceNodeUI nodeUI, @NotNull SRepository repository) { myTraceNode = nodeUI; myRepo = repository; } public ActionGroup compute() { assert myTraceNode.getNavigateTarget() != null; DefaultActionGroup group = new DefaultActionGroup(); if (myTraceNode.hasPrevStep()) { if (isBackwardTraceView()) { group.add(new ShowTraceAction("Show Trace", myTraceNode.getNavigateTarget(), myRepo)); } group.add(new ShowTracebackAction("Show Prev Step Traceback", myTraceNode.getNavigateTarget(), myRepo)); } else if (myTraceNode.hasNextStep()) { if (isForwardTraceView()) { group.add(new ShowTracebackAction("Show Traceback", myTraceNode.getNavigateTarget(), myRepo)); } group.add(new ShowTraceAction("Show Next Step Trace", myTraceNode.getNavigateTarget(), myRepo)); } // return group; } } private class ShowTraceAction extends BaseAction { private final SNodeReference myNode; private final SRepository myRepo; ShowTraceAction(@NotNull String title, @NotNull SNodeReference node, @NotNull SRepository repository) { super(title); myNode = node; myRepo = repository; } @Override protected void doExecute(AnActionEvent e, Map<String, Object> _params) { getTool().showTraceInputData(myNode.resolve(myRepo)); } @Override protected void doUpdate(AnActionEvent e, Map<String, Object> _params) { boolean enabled = myNode.resolve(myRepo) != null && getTool().hasTraceInputData(myNode.getModelReference()); setEnabledState(e.getPresentation(), enabled); } } private class ShowTracebackAction extends BaseAction { private final SNodeReference myNode; private final SRepository myRepo; ShowTracebackAction(@NotNull String title, @NotNull SNodeReference node, @NotNull SRepository repository) { super(title); myNode = node; myRepo = repository; } @Override protected void doExecute(AnActionEvent e, Map<String, Object> _params) { getTool().showTracebackData(myNode.resolve(myRepo)); } @Override protected void doUpdate(AnActionEvent e, Map<String, Object> _params) { boolean enabled = myNode.resolve(myRepo) != null && getTool().hasTracebackData(myNode.getModelReference()); setEnabledState(e.getPresentation(), enabled); } } }