/* * 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.execution.dashboard; import com.intellij.execution.ExecutionBundle; import com.intellij.execution.dashboard.tree.DashboardGrouper; import com.intellij.execution.dashboard.tree.RunDashboardTreeStructure; import com.intellij.ide.CommonActionsManager; import com.intellij.ide.DataManager; import com.intellij.ide.DefaultTreeExpander; import com.intellij.ide.TreeExpander; import com.intellij.ide.util.treeView.*; import com.intellij.ide.util.treeView.smartTree.ActionPresentation; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Disposer; import com.intellij.ui.*; import com.intellij.ui.components.JBPanelWithEmptyText; import com.intellij.ui.content.*; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.ObjectUtils; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import java.awt.*; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author konstantin.aleev */ public class RunDashboardContent extends JPanel implements TreeContent, Disposable { public static final DataKey<RunDashboardContent> KEY = DataKey.create("runDashboardContent"); @NonNls private static final String PLACE_TOOLBAR = "RunDashboardContent#Toolbar"; @NonNls private static final String RUN_DASHBOARD_TOOLBAR = "RunDashboardToolbar"; @NonNls private static final String RUN_DASHBOARD_POPUP = "RunDashboardPopup"; private static final String MESSAGE_CARD = "message"; private static final String CONTENT_CARD = "content"; private final Tree myTree; private final CardLayout myDetailsPanelLayout; private final JPanel myDetailsPanel; private final JBPanelWithEmptyText myMessagePanel; private final DefaultTreeModel myTreeModel; private AbstractTreeBuilder myBuilder; private AbstractTreeNode<?> myLastSelection; private Set<Object> myCollapsedTreeNodeValues = new HashSet<>(); private List<DashboardGrouper> myGroupers; @NotNull private final ContentManager myContentManager; @NotNull private final ContentManagerListener myContentManagerListener; @NotNull private final Project myProject; public RunDashboardContent(@NotNull Project project, @NotNull ContentManager contentManager, @NotNull List<DashboardGrouper> groupers) { super(new BorderLayout()); myProject = project; myGroupers = groupers; myTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode()); myTree = new Tree(myTreeModel); myTree.setRootVisible(false); myTree.setShowsRootHandles(true); myTree.setCellRenderer(new NodeRenderer()); myTree.setLineStyleAngled(); add(createToolbar(), BorderLayout.WEST); Splitter splitter = new OnePixelSplitter(false, 0.3f); splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT)); myDetailsPanelLayout = new CardLayout(); myDetailsPanel = new JPanel(myDetailsPanelLayout); myMessagePanel = new JBPanelWithEmptyText().withEmptyText(ExecutionBundle.message("run.dashboard.empty.selection.message")); myDetailsPanel.add(MESSAGE_CARD, myMessagePanel); splitter.setSecondComponent(myDetailsPanel); add(splitter, BorderLayout.CENTER); myContentManager = contentManager; myContentManagerListener = new ContentManagerAdapter() { @Override public void selectionChanged(final ContentManagerEvent event) { if (ContentManagerEvent.ContentOperation.add != event.getOperation()) { return; } myBuilder.queueUpdate().doWhenDone(() -> myBuilder.accept(DashboardNode.class, new TreeVisitor<DashboardNode>() { @Override public boolean visit(@NotNull DashboardNode node) { if (node.getContent() == event.getContent()) { myBuilder.select(node); } return false; } })); showContentPanel(); } }; myContentManager.addContentManagerListener(myContentManagerListener); myDetailsPanel.add(CONTENT_CARD, myContentManager.getComponent()); setupBuilder(); myTree.addTreeSelectionListener(e -> onSelectionChanged()); myTree.addTreeExpansionListener(new TreeExpansionListener() { @Override public void treeExpanded(TreeExpansionEvent event) { Object value = getNodeValue(event); if (value != null) { myCollapsedTreeNodeValues.remove(value); } } @Override public void treeCollapsed(TreeExpansionEvent event) { Object value = getNodeValue(event); if (value != null) { myCollapsedTreeNodeValues.add(value); } } private Object getNodeValue(TreeExpansionEvent event) { DefaultMutableTreeNode treeNode = ObjectUtils.tryCast(event.getPath().getLastPathComponent(), DefaultMutableTreeNode.class); if (treeNode == null) { return null; } AbstractTreeNode nodeDescriptor = ObjectUtils.tryCast(treeNode.getUserObject(), AbstractTreeNode.class); if (nodeDescriptor == null) { return null; } return nodeDescriptor.getValue(); } }); DefaultActionGroup popupActionGroup = new DefaultActionGroup(); popupActionGroup.add(ActionManager.getInstance().getAction(RUN_DASHBOARD_TOOLBAR)); popupActionGroup.add(ActionManager.getInstance().getAction(RUN_DASHBOARD_POPUP)); PopupHandler.installPopupHandler(myTree, popupActionGroup, ActionPlaces.RUN_DASHBOARD_POPUP, ActionManager.getInstance()); new TreeSpeedSearch(myTree, TreeSpeedSearch.NODE_DESCRIPTOR_TOSTRING, true); } private void onSelectionChanged() { Set<AbstractTreeNode> nodes = myBuilder.getSelectedElements(AbstractTreeNode.class); if (nodes.size() != 1) { showMessagePanel(ExecutionBundle.message("run.dashboard.empty.selection.message")); myLastSelection = null; return; } AbstractTreeNode<?> node = nodes.iterator().next(); if (Comparing.equal(node, myLastSelection)) { return; } myLastSelection = node; if (node instanceof DashboardNode) { Content content = ((DashboardNode)node).getContent(); if (content != null) { if (content != myContentManager.getSelectedContent()) { myContentManager.removeContentManagerListener(myContentManagerListener); myContentManager.setSelectedContent(content); myContentManager.addContentManagerListener(myContentManagerListener); } showContentPanel(); return; } if (node instanceof DashboardRunConfigurationNode) { showMessagePanel(ExecutionBundle.message("run.dashboard.not.started.configuration.message")); return; } } showMessagePanel(ExecutionBundle.message("run.dashboard.empty.selection.message")); } private void showMessagePanel(String text) { Content selectedContent = myContentManager.getSelectedContent(); if (selectedContent != null) { myContentManager.removeContentManagerListener(myContentManagerListener); myContentManager.removeFromSelection(selectedContent); myContentManager.addContentManagerListener(myContentManagerListener); } myMessagePanel.getEmptyText().setText(text); myDetailsPanelLayout.show(myDetailsPanel, MESSAGE_CARD); } private void showContentPanel() { myDetailsPanelLayout.show(myDetailsPanel, CONTENT_CARD); } private void setupBuilder() { RunDashboardTreeStructure structure = new RunDashboardTreeStructure(myProject, myGroupers); myBuilder = new AbstractTreeBuilder(myTree, myTreeModel, structure, IndexComparator.INSTANCE) { @Override protected boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) { return super.isAutoExpandNode(nodeDescriptor) || !myCollapsedTreeNodeValues.contains(((AbstractTreeNode)nodeDescriptor).getValue()); } }; myBuilder.initRootNode(); Disposer.register(this, myBuilder); } private JComponent createToolbar() { JPanel toolBarPanel = new JPanel(new GridLayout()); DefaultActionGroup leftGroup = new DefaultActionGroup(); leftGroup.add(ActionManager.getInstance().getAction(RUN_DASHBOARD_TOOLBAR)); // TODO [konstantin.aleev] provide context help ID //leftGroup.add(new Separator()); //leftGroup.add(new ContextHelpAction(HELP_ID)); ActionToolbar leftActionToolBar = ActionManager.getInstance().createActionToolbar(PLACE_TOOLBAR, leftGroup, false); toolBarPanel.add(leftActionToolBar.getComponent()); myTree.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, new DataProvider() { @Override public Object getData(@NonNls String dataId) { if (KEY.getName().equals(dataId)) { return RunDashboardContent.this; } return null; } }); leftActionToolBar.setTargetComponent(myTree); DefaultActionGroup rightGroup = new DefaultActionGroup(); TreeExpander treeExpander = new DefaultTreeExpander(myTree); AnAction expandAllAction = CommonActionsManager.getInstance().createExpandAllAction(treeExpander, this); rightGroup.add(expandAllAction); AnAction collapseAllAction = CommonActionsManager.getInstance().createCollapseAllAction(treeExpander, this); rightGroup.add(collapseAllAction); rightGroup.add(new AnSeparator()); myGroupers.stream().filter(grouper -> !grouper.getRule().isAlwaysEnabled()).forEach(grouper -> rightGroup.add(new GroupAction(grouper))); ActionToolbar rightActionToolBar = ActionManager.getInstance().createActionToolbar(PLACE_TOOLBAR, rightGroup, false); toolBarPanel.add(rightActionToolBar.getComponent()); rightActionToolBar.setTargetComponent(myTree); return toolBarPanel; } @Override public void dispose() { } public void updateContent(boolean withStructure) { ApplicationManager.getApplication().invokeLater(() -> myBuilder.queueUpdate(withStructure).doWhenDone(() -> { if (!withStructure) { return; } // Remove nodes not presented in the tree from collapsed node values set. // Children retrieving is quick since grouping and run configuration nodes are already constructed. Set<Object> nodes = new HashSet<>(); myBuilder.accept(AbstractTreeNode.class, new TreeVisitor<AbstractTreeNode>() { @Override public boolean visit(@NotNull AbstractTreeNode node) { nodes.add(node.getValue()); return false; } }); myCollapsedTreeNodeValues.retainAll(nodes); }), myProject.getDisposed()); } @Override @NotNull public AbstractTreeBuilder getBuilder() { return myBuilder; } private class GroupAction extends ToggleAction implements DumbAware { private DashboardGrouper myGrouper; public GroupAction(DashboardGrouper grouper) { super(); myGrouper = grouper; } @Override public void update(@NotNull AnActionEvent e) { super.update(e); Presentation presentation = e.getPresentation(); ActionPresentation actionPresentation = myGrouper.getRule().getPresentation(); presentation.setText(actionPresentation.getText()); presentation.setDescription(actionPresentation.getDescription()); presentation.setIcon(actionPresentation.getIcon()); } @Override public boolean isSelected(AnActionEvent e) { return myGrouper.isEnabled(); } @Override public void setSelected(AnActionEvent e, boolean state) { myGrouper.setEnabled(state); updateContent(true); } } }