IDEA-155345 Add run/stop/edit and grouping actions for runtime dashboard tool window

This commit is contained in:
Konstantin Aleev
2017-01-16 15:26:35 +03:00
parent e9f36bb8ee
commit bc64549879
36 changed files with 1444 additions and 162 deletions
@@ -15,6 +15,8 @@
*/
package com.intellij.execution.dashboard;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.openapi.project.Project;
import com.intellij.ui.content.Content;
import org.jetbrains.annotations.Nullable;
@@ -23,5 +25,14 @@ import org.jetbrains.annotations.Nullable;
*/
public interface DashboardNode {
@Nullable
Content getContent();
default RunContentDescriptor getDescriptor() {
return null;
}
@Nullable
default Content getContent() {
return getDescriptor() == null ? null : getDescriptor().getAttachedContent();
}
Project getProject();
}
@@ -0,0 +1,29 @@
/*
* Copyright 2000-2017 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.RunnerAndConfigurationSettings;
import org.jetbrains.annotations.NotNull;
/**
* @author konstantin.aleev
*/
public interface DashboardRunConfigurationNode extends DashboardNode {
@NotNull
RunnerAndConfigurationSettings getConfigurationSettings();
boolean isTerminated();
}
@@ -0,0 +1,150 @@
/*
* Copyright 2000-2017 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.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author konstantin.aleev
*/
public abstract class DashboardTreeAction<T, C extends TreeContent> extends AnAction {
protected DashboardTreeAction(String text, String description, Icon icon) {
super(text, description, icon);
}
@Override
public void update(@NotNull AnActionEvent e) {
Presentation presentation = e.getPresentation();
List<T> targetNodes = getTargetNodes(e);
boolean visible;
boolean enabled;
if (targetNodes == null) {
visible = false;
enabled = false;
}
else {
visible = true;
enabled = true;
for (T targetNode : targetNodes) {
visible &= isVisible4(targetNode);
enabled &= visible && isEnabled4(targetNode);
}
}
presentation.setVisible(visible);
presentation.setEnabled(enabled);
updatePresentation(presentation, ContainerUtil.getFirstItem(targetNodes));
}
/**
* Invokes {@link #collectNodes(AbstractTreeBuilder) collectNodes()} to collect nodes.
* If each collected node could be casted to tree action node class,
* returns a list of collected nodes casted to tree action node class, otherwise returns {@code null}.
*
* @param e Action event.
* @return List of target nodes for this action.
*/
@Nullable
protected List<T> getTargetNodes(AnActionEvent e) {
C content = getTreeContent(e);
if (content == null) {
return null;
}
Set<?> selectedElements = collectNodes(content.getBuilder());
int selectionCount = selectedElements.size();
if (selectionCount == 0 || selectionCount > 1 && !isMultiSelectionAllowed()) {
return null;
}
Class<T> targetNodeClass = getTargetNodeClass();
List<T> result = new ArrayList<>();
for (Object selectedElement : selectedElements) {
if (!targetNodeClass.isInstance(selectedElement)) {
return null;
}
result.add(targetNodeClass.cast(selectedElement));
}
return result;
}
/**
* This implementation returns a set of selected nodes.
* Subclasses may override this method to return modified nodes set.
*
* @param treeBuilder Tree builder.
* @return Set of tree nodes for which action should be performed.
*/
@NotNull
protected Set<?> collectNodes(@NotNull AbstractTreeBuilder treeBuilder) {
return treeBuilder.getSelectedElements();
}
protected abstract C getTreeContent(AnActionEvent e);
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
List<T> targetNodes = getTargetNodes(e);
if (targetNodes == null) {
return;
}
List<T> verifiedTargetNodes = ContainerUtil.filter(targetNodes, targetNode -> isVisible4(targetNode) && isEnabled4(targetNode));
doActionPerformed(getTreeContent(e), e, verifiedTargetNodes);
}
protected boolean isMultiSelectionAllowed() {
return false;
}
protected boolean isVisible4(T node) {
return true;
}
protected boolean isEnabled4(T node) {
return true;
}
protected void updatePresentation(@NotNull Presentation presentation, @Nullable T node) {
}
protected void doActionPerformed(@NotNull C content, AnActionEvent e, List<T> nodes) {
for (T node : nodes) {
doActionPerformed(content, e, node);
}
}
protected void doActionPerformed(@NotNull C content, AnActionEvent e, T node) {
doActionPerformed(node);
}
protected void doActionPerformed(T node) {
throw new UnsupportedOperationException();
}
protected abstract Class<T> getTargetNodeClass();
}
@@ -0,0 +1,27 @@
/*
* Copyright 2000-2017 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 javax.swing.*;
/**
* @author konstantin.aleev
*/
public interface Group {
String getName();
Icon getIcon();
}
@@ -0,0 +1,46 @@
/*
* Copyright 2000-2017 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.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.smartTree.TreeAction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
/**
* Action for grouping items in a runtime dashboard tree.
*
* @author konstantin.aleev
*/
public interface GroupingRule extends TreeAction {
/**
* @return A list of groups which should be shown in the tree even if they do not contain any nodes.
*/
@NotNull
default List<Group> getPermanentGroups() {
return Collections.emptyList();
}
/**
* @param node Node which should be grouped by this grouping rule.
* @return A group which node belongs to or null if node could not be grouped by this rule.
*/
@Nullable
Group getGroup(AbstractTreeNode<?> node);
}
@@ -44,10 +44,11 @@ class PanelContentUI implements ContentUI {
manager.addContentManagerListener(new ContentManagerAdapter() {
@Override
public void selectionChanged(final ContentManagerEvent event) {
if (ContentManagerEvent.ContentOperation.add != event.getOperation()) {
return;
if (ContentManagerEvent.ContentOperation.add == event.getOperation()) {
showContent(event.getContent());
} else if (ContentManagerEvent.ContentOperation.remove == event.getOperation()) {
hideContent();
}
showContent(event.getContent());
}
});
}
@@ -63,6 +64,12 @@ class PanelContentUI implements ContentUI {
}
}
private void hideContent() {
myPanel.removeAll();
myPanel.revalidate();
myPanel.repaint();
}
@Override
public boolean isSingleSelection() {
return true;
@@ -0,0 +1,27 @@
/*
* Copyright 2000-2017 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.ide.util.treeView.AbstractTreeBuilder;
import org.jetbrains.annotations.NotNull;
/**
* @author konstantin.aleev
*/
public interface TreeContent {
@NotNull
AbstractTreeBuilder getBuilder();
}
@@ -19,6 +19,7 @@ import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.ExecutionManager;
import com.intellij.execution.KillableProcess;
import com.intellij.execution.configurations.RunProfile;
import com.intellij.execution.impl.ExecutionManagerImpl;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.icons.AllIcons;
@@ -124,7 +125,7 @@ class StopAction extends DumbAwareAction implements AnAction.TransparentUpdate {
int todoSize = cancellableProcesses.size() + stoppableDescriptors.size();
if (todoSize == 1) {
if (!stoppableDescriptors.isEmpty()) {
stopProcess(stoppableDescriptors.get(0));
ExecutionManagerImpl.stopProcess(stoppableDescriptors.get(0));
} else {
cancellableProcesses.get(0).second.cancel();
}
@@ -182,12 +183,12 @@ class StopAction extends DumbAwareAction implements AnAction.TransparentUpdate {
}
}
else {
stopProcess(getRecentlyStartedContentDescriptor(dataContext));
ExecutionManagerImpl.stopProcess(getRecentlyStartedContentDescriptor(dataContext));
}
}
@NotNull
private static List<Pair<TaskInfo, ProgressIndicator>> getCancellableProcesses(@Nullable Project project) {
private static List<Pair<TaskInfo, ProgressIndicator>> getCancellableProcesses(@Nullable Project project) {
IdeFrame frame = ((WindowManagerEx)WindowManager.getInstance()).findFrameFor(project);
StatusBarEx statusBar = frame == null ? null : (StatusBarEx)frame.getStatusBar();
if (statusBar == null) return Collections.emptyList();
@@ -212,7 +213,7 @@ class StopAction extends DumbAwareAction implements AnAction.TransparentUpdate {
HandlerItem item = new HandlerItem(descriptor.getDisplayName(), descriptor.getIcon(), false) {
@Override
void stop() {
stopProcess(descriptor);
ExecutionManagerImpl.stopProcess(descriptor);
}
};
items.add(item);
@@ -235,22 +236,6 @@ class StopAction extends DumbAwareAction implements AnAction.TransparentUpdate {
return Pair.create(items, selected);
}
private static void stopProcess(@Nullable RunContentDescriptor descriptor) {
ProcessHandler processHandler = descriptor != null ? descriptor.getProcessHandler() : null;
if (processHandler == null) return;
if (processHandler instanceof KillableProcess && processHandler.isProcessTerminating()) {
((KillableProcess)processHandler).killProcess();
return;
}
if (processHandler.detachIsDefault()) {
processHandler.detachProcess();
}
else {
processHandler.destroyProcess();
}
}
@Nullable
static RunContentDescriptor getRecentlyStartedContentDescriptor(@NotNull DataContext dataContext) {
final RunContentDescriptor contentDescriptor = LangDataKeys.RUN_CONTENT_DESCRIPTOR.getData(dataContext);
@@ -16,24 +16,30 @@
package com.intellij.execution.dashboard;
import com.intellij.execution.*;
import com.intellij.execution.dashboard.tree.Grouper;
import com.intellij.execution.dashboard.tree.RuntimeDashboardTreeStructure;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
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.OnePixelSplitter;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.SideBorder;
import com.intellij.ui.TreeSpeedSearch;
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 org.jetbrains.annotations.Nullable;
@@ -44,12 +50,18 @@ 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
*/
class RuntimeDashboardContent extends JPanel implements Disposable {
public class RuntimeDashboardContent extends JPanel implements TreeContent, Disposable {
public static final DataKey<RuntimeDashboardContent> KEY = DataKey.create("runtimeDashboardContent");
@NonNls private static final String PLACE_TOOLBAR = "RuntimeDashboardContent#Toolbar";
@NonNls private static final String RUNTIME_DASHBOARD_TOOLBAR = "RuntimeDashboardToolbar";
@NonNls private static final String RUNTIME_DASHBOARD_POPUP = "RuntimeDashboardPopup";
private static final String MESSAGE_CARD = "message";
private static final String CONTENT_CARD = "content";
@@ -62,15 +74,17 @@ class RuntimeDashboardContent extends JPanel implements Disposable {
private AbstractTreeBuilder myBuilder;
private AbstractTreeNode<?> myLastSelection;
private Set<Object> myCollapsedTreeNodeValues = new HashSet<>();
private List<Grouper> myGroupers;
@NotNull private final ContentManager myContentManager;
@NotNull private final ContentManagerListener myContentManagerListener;
@NotNull private final Project myProject;
public RuntimeDashboardContent(@NotNull Project project, @NotNull ContentManager contentManager) {
public RuntimeDashboardContent(@NotNull Project project, @NotNull ContentManager contentManager, @NotNull List<Grouper> groupers) {
super(new BorderLayout());
myProject = project;
myGroupers = groupers;
myTreeModel = new DefaultTreeModel(new DefaultMutableTreeNode());
myTree = new Tree(myTreeModel);
@@ -80,7 +94,7 @@ class RuntimeDashboardContent extends JPanel implements Disposable {
myTree.setCellRenderer(new NodeRenderer());
myTree.setLineStyleAngled();
//TODO [konstantin.aleev] Create toolbar.
add(createToolbar(), BorderLayout.WEST);
Splitter splitter = new OnePixelSplitter(false, 0.3f);
splitter.setFirstComponent(ScrollPaneFactory.createScrollPane(myTree, SideBorder.LEFT));
@@ -146,7 +160,10 @@ class RuntimeDashboardContent extends JPanel implements Disposable {
}
});
//TODO [konstantin.aleev] setup popup actions.
DefaultActionGroup popupActionGroup = new DefaultActionGroup();
popupActionGroup.add(ActionManager.getInstance().getAction(RUNTIME_DASHBOARD_TOOLBAR));
popupActionGroup.add(ActionManager.getInstance().getAction(RUNTIME_DASHBOARD_POPUP));
PopupHandler.installPopupHandler(myTree, popupActionGroup, ActionPlaces.UNKNOWN, ActionManager.getInstance());
new TreeSpeedSearch(myTree, TreeSpeedSearch.NODE_DESCRIPTOR_TOSTRING, true);
}
@@ -198,7 +215,7 @@ class RuntimeDashboardContent extends JPanel implements Disposable {
}
private void setupBuilder() {
RuntimeDashboardTreeStructure structure = new RuntimeDashboardTreeStructure(myProject);
RuntimeDashboardTreeStructure structure = new RuntimeDashboardTreeStructure(myProject, myGroupers);
myBuilder = new AbstractTreeBuilder(myTree, myTreeModel, structure, IndexComparator.INSTANCE) {
@Override
protected boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) {
@@ -231,7 +248,10 @@ class RuntimeDashboardContent extends JPanel implements Disposable {
}
@Override
public void processTerminated(@NotNull String executorId, @NotNull ExecutionEnvironment env, @NotNull ProcessHandler handler, int exitCode) {
public void processTerminated(@NotNull String executorId,
@NotNull ExecutionEnvironment env,
@NotNull ProcessHandler handler,
int exitCode) {
updateTreeIfNeeded(env.getRunnerAndConfigurationSettings());
}
});
@@ -243,11 +263,98 @@ class RuntimeDashboardContent extends JPanel implements Disposable {
}
}
private JComponent createToolbar() {
JPanel toolBarPanel = new JPanel(new GridLayout());
DefaultActionGroup leftGroup = new DefaultActionGroup();
leftGroup.add(ActionManager.getInstance().getAction(RUNTIME_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 RuntimeDashboardContent.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 Separator());
myGroupers.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 updateTree() {
ApplicationManager.getApplication().invokeLater(myBuilder::queueUpdate, myProject.getDisposed());
ApplicationManager.getApplication().invokeLater(() -> myBuilder.queueUpdate().doWhenDone(() -> {
// 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());
}
@NotNull
public AbstractTreeBuilder getBuilder() {
return myBuilder;
}
private class GroupAction extends ToggleAction implements DumbAware {
private Grouper myGrouper;
public GroupAction(Grouper 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);
updateTree();
}
}
}
@@ -15,8 +15,16 @@
*/
package com.intellij.execution.dashboard;
import com.intellij.execution.dashboard.tree.ConfigurationTypeGroupingRule;
import com.intellij.execution.dashboard.tree.FolderGroupingRule;
import com.intellij.execution.dashboard.tree.Grouper;
import com.intellij.execution.dashboard.tree.StatusGroupingRule;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.components.StoragePathMacros;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.registry.Registry;
@@ -28,16 +36,30 @@ import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.ContentUI;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author konstantin.aleev
*/
public class RuntimeDashboardManagerImpl implements RuntimeDashboardManager {
@State(
name = "RuntimeDashboard",
storages = @Storage(StoragePathMacros.WORKSPACE_FILE)
)
public class RuntimeDashboardManagerImpl implements RuntimeDashboardManager, PersistentStateComponent<Element> {
@NonNls private static final String GROUPERS_TAG = "groupers";
@NonNls private static final String GROUPER_TAG = "grouper";
@NonNls private static final String NAME_ATTR = "name";
@NonNls private static final String ENABLED_ATTR = "enabled";
@NotNull private final ContentManager myContentManager;
private List<Grouper> myGroupers = new ArrayList<>();
public RuntimeDashboardManagerImpl(@NotNull final Project project) {
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
@@ -49,7 +71,11 @@ public class RuntimeDashboardManagerImpl implements RuntimeDashboardManager {
project, true);
toolWindow.setIcon(getToolWindowIcon());
if (!ApplicationManager.getApplication().isUnitTestMode()) {
RuntimeDashboardContent dashboardContent = new RuntimeDashboardContent(project, myContentManager);
myGroupers.add(new Grouper(new ConfigurationTypeGroupingRule()));
myGroupers.add(new Grouper(new StatusGroupingRule()));
myGroupers.add(new Grouper(new FolderGroupingRule()));
RuntimeDashboardContent dashboardContent = new RuntimeDashboardContent(project, myContentManager, myGroupers);
Content content = contentFactory.createContent(dashboardContent, null, false);
Disposer.register(content, dashboardContent);
toolWindow.getContentManager().addContent(content);
@@ -76,4 +102,40 @@ public class RuntimeDashboardManagerImpl implements RuntimeDashboardManager {
public Icon getToolWindowIcon() {
return AllIcons.Toolwindows.ToolWindowRun; // TODO [konstantin.aleev] provide new icon
}
@Nullable
@Override
public Element getState() {
final Element element = new Element("state");
final Element groupers = new Element(GROUPERS_TAG);
element.addContent(groupers);
myGroupers.forEach(grouper -> groupers.addContent(writeGrouperState(grouper)));
return element;
}
private static Element writeGrouperState(Grouper grouper) {
Element element = new Element(GROUPER_TAG);
element.setAttribute(NAME_ATTR, grouper.getRule().getName());
element.setAttribute(ENABLED_ATTR, Boolean.toString(grouper.isEnabled()));
return element;
}
@Override
public void loadState(Element element) {
Element groupersElement = element.getChild(GROUPERS_TAG);
if (groupersElement != null) {
List<Element> groupers = groupersElement.getChildren(GROUPER_TAG);
groupers.forEach(this::readGrouperState);
}
}
private void readGrouperState(Element grouperElement) {
String id = grouperElement.getAttributeValue(NAME_ATTR);
for (Grouper grouper : myGroupers) {
if (grouper.getRule().getName().equals(id)) {
grouper.setEnabled(Boolean.valueOf(grouperElement.getAttributeValue(ENABLED_ATTR, "true")));
return;
}
}
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.RunManagerEx;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configuration.ConfigurationFactoryEx;
import com.intellij.execution.configurations.ConfigurationFactory;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import com.intellij.execution.impl.RunnerAndConfigurationSettingsImpl;
import com.intellij.util.PlatformIcons;
/**
* @author konstantin.aleev
*/
public class CopyConfigurationAction extends RunConfigurationTreeAction {
public CopyConfigurationAction() {
super(ExecutionBundle.message("copy.configuration.action.name"),
ExecutionBundle.message("copy.configuration.action.name"),
PlatformIcons.COPY_ICON);
}
@Override
@SuppressWarnings("unchecked")
protected void doActionPerformed(DashboardRunConfigurationNode node) {
RunManagerEx runManager = RunManagerEx.getInstanceEx(node.getProject());
RunnerAndConfigurationSettings settings = node.getConfigurationSettings();
RunnerAndConfigurationSettings copiedSettings = ((RunnerAndConfigurationSettingsImpl)settings).clone();
runManager.setUniqueNameIfNeed(copiedSettings);
copiedSettings.setFolderName(settings.getFolderName());
final ConfigurationFactory factory = settings.getFactory();
if (factory instanceof ConfigurationFactoryEx) {
((ConfigurationFactoryEx)factory).onConfigurationCopied(settings.getConfiguration());
}
runManager.addConfiguration(copiedSettings, runManager.isConfigurationShared(settings),
runManager.getBeforeRunTasks(settings.getConfiguration()), false);
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.Executor;
import com.intellij.execution.ExecutorRegistry;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.wm.ToolWindowId;
/**
* @author konstantin.aleev
*/
public class DebugAction extends ExecutorAction {
public DebugAction() {
super(ExecutionBundle.message("runtime.dashboard.debug.action.name"),
ExecutionBundle.message("runtime.dashboard.debug.action.name"),
AllIcons.Toolwindows.ToolWindowDebugger);
}
@Override
protected Executor getExecutor() {
return ExecutorRegistry.getInstance().getExecutorById(ToolWindowId.DEBUG);
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import com.intellij.execution.impl.RunDialog;
import com.intellij.icons.AllIcons;
public class EditConfigurationAction extends RunConfigurationTreeAction {
public EditConfigurationAction() {
super(ExecutionBundle.message("runtime.dashboard.edit.configuration.action.name"),
ExecutionBundle.message("runtime.dashboard.edit.configuration.action.name"),
AllIcons.Actions.EditSource);
}
@Override
protected void doActionPerformed(DashboardRunConfigurationNode node) {
RunDialog.editConfiguration(node.getProject(), node.getConfigurationSettings(),
ExecutionBundle.message("runtime.dashboard.edit.configuration.dialog.title"));
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.ExecutionManager;
import com.intellij.execution.ExecutionTargetManager;
import com.intellij.execution.Executor;
import com.intellij.execution.ProgramRunnerUtil;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import com.intellij.execution.runners.ProgramRunner;
import com.intellij.execution.ui.RunContentDescriptor;
import javax.swing.*;
/**
* @author konstantin.aleev
*/
public abstract class ExecutorAction extends RuntimeDashboardTreeLeafAction<DashboardRunConfigurationNode> {
protected ExecutorAction(String text, String description, Icon icon) {
super(text, description, icon);
}
@Override
protected boolean isEnabled4(DashboardRunConfigurationNode node) {
String executorId = getExecutor().getId();
ProgramRunner runner = ProgramRunnerUtil.getRunner(executorId, node.getConfigurationSettings());
return runner != null && runner.canRun(executorId, node.getConfigurationSettings().getConfiguration());
}
@Override
protected void doActionPerformed(DashboardRunConfigurationNode node) {
RunContentDescriptor descriptor = node.getDescriptor();
ExecutionManager.getInstance(node.getProject()).restartRunProfile(node.getProject(),
getExecutor(),
ExecutionTargetManager.getActiveTarget(node.getProject()),
node.getConfigurationSettings(),
descriptor == null ? null : descriptor.getProcessHandler());
}
@Override
protected Class<DashboardRunConfigurationNode> getTargetNodeClass() {
return DashboardRunConfigurationNode.class;
}
protected abstract Executor getExecutor();
}
@@ -0,0 +1,66 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.RunManagerEx;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import com.intellij.execution.dashboard.RuntimeDashboardContent;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author konstantin.aleev
*/
public class RemoveConfigurationAction extends RunConfigurationTreeAction {
public RemoveConfigurationAction() {
super(ExecutionBundle.message("runtime.dashboard.remove.configuration.action.name"),
ExecutionBundle.message("runtime.dashboard.remove.configuration.action.name"),
AllIcons.General.Remove);
}
@Override
protected boolean isEnabled4(DashboardRunConfigurationNode node) {
return node.isTerminated();
}
@Override
protected boolean isMultiSelectionAllowed() {
return true;
}
@Override
protected void doActionPerformed(@NotNull RuntimeDashboardContent content, AnActionEvent e, List<DashboardRunConfigurationNode> nodes) {
if (Messages.showYesNoDialog((Project)null,
ExecutionBundle.message("runtime.dashboard.remove.configuration.dialog.message"),
ExecutionBundle.message("runtime.dashboard.remove.configuration.dialog.title"),
Messages.getWarningIcon())
!= Messages.YES) {
return;
}
super.doActionPerformed(content, e, nodes);
}
@Override
protected void doActionPerformed(DashboardRunConfigurationNode node) {
RunManagerEx.getInstanceEx(node.getProject()).removeConfiguration(node.getConfigurationSettings());
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.Executor;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.icons.AllIcons;
/**
* @author konstantin.aleev
*/
public class RunAction extends ExecutorAction {
public RunAction() {
super(ExecutionBundle.message("runtime.dashboard.run.action.name"),
ExecutionBundle.message("runtime.dashboard.run.action.name"),
AllIcons.Toolwindows.ToolWindowRun);
}
@Override
protected Executor getExecutor() {
return DefaultRunExecutor.getRunExecutorInstance();
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import javax.swing.*;
/**
* @author konstantin.aleev
*/
public abstract class RunConfigurationTreeAction extends RuntimeDashboardTreeAction<DashboardRunConfigurationNode> {
protected RunConfigurationTreeAction(String text, String description, Icon icon) {
super(text, description, icon);
}
@Override
protected Class<DashboardRunConfigurationNode> getTargetNodeClass() {
return DashboardRunConfigurationNode.class;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.dashboard.*;
import com.intellij.openapi.actionSystem.AnActionEvent;
import javax.swing.*;
/**
* @author konstantin.aleev
*/
public abstract class RuntimeDashboardTreeAction<T extends DashboardNode> extends DashboardTreeAction<T, RuntimeDashboardContent> {
protected RuntimeDashboardTreeAction(String text, String description, Icon icon) {
super(text, description, icon);
}
@Override
protected final RuntimeDashboardContent getTreeContent(AnActionEvent e) {
return e.getData(RuntimeDashboardContent.KEY);
}
}
@@ -0,0 +1,74 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.dashboard.DashboardNode;
import com.intellij.execution.dashboard.tree.GroupingNode;
import com.intellij.ide.util.treeView.AbstractTreeBuilder;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import java.util.*;
/**
* @author konstantin.aleev
*/
public abstract class RuntimeDashboardTreeLeafAction<T extends DashboardNode> extends RuntimeDashboardTreeAction<T> {
protected RuntimeDashboardTreeLeafAction(String text, String description, Icon icon) {
super(text, description, icon);
}
@Override
protected final boolean isMultiSelectionAllowed() {
return true;
}
@Override
@NotNull
protected Set<?> collectNodes(@NotNull AbstractTreeBuilder treeBuilder) {
Set<Object> selectedElement = treeBuilder.getSelectedElements();
List<AbstractTreeNode> nodes = new ArrayList<>();
for (Object o : selectedElement) {
if (!(o instanceof AbstractTreeNode)) {
return Collections.emptySet();
}
AbstractTreeNode node = (AbstractTreeNode)o;
if (node instanceof GroupingNode && node.getChildren().isEmpty()) {
// Action could not be performed if current selection contains empty grouping nodes
return Collections.emptySet();
}
nodes.add(node);
}
return getLeaves(nodes);
}
private static Set<AbstractTreeNode> getLeaves(Collection<? extends AbstractTreeNode> nodes) {
Set<AbstractTreeNode> result = new HashSet<>();
for (AbstractTreeNode<?> node : nodes) {
Collection<? extends AbstractTreeNode> children = node.getChildren();
if (children.isEmpty()) {
if (!(node instanceof GroupingNode)) {
// Do not add grouping nodes to the target set
result.add(node);
}
} else {
result.addAll(getLeaves(children));
}
}
return result;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2000-2017 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.actions;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import com.intellij.execution.impl.ExecutionManagerImpl;
import com.intellij.icons.AllIcons;
/**
* @author konstantin.aleev
*/
public class StopAction extends RuntimeDashboardTreeLeafAction<DashboardRunConfigurationNode> {
public StopAction() {
super(ExecutionBundle.message("runtime.dashboard.stop.action.name"),
ExecutionBundle.message("runtime.dashboard.stop.action.name"),
AllIcons.Actions.Suspend);
}
@Override
protected void doActionPerformed(DashboardRunConfigurationNode node) {
ExecutionManagerImpl.stopProcess(node.getDescriptor());
}
@Override
protected Class<DashboardRunConfigurationNode> getTargetNodeClass() {
return DashboardRunConfigurationNode.class;
}
}
@@ -15,15 +15,14 @@
*/
package com.intellij.execution.dashboard.tree;
import com.intellij.execution.RunManagerEx;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.dashboard.DashboardNode;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import com.intellij.execution.dashboard.RuntimeDashboardContributor;
import com.intellij.execution.ui.RunContentDescriptor;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.project.Project;
import com.intellij.ui.RowIcon;
import com.intellij.ui.content.Content;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,7 +31,7 @@ import javax.swing.*;
/**
* @author konstantin.aleev
*/
abstract class AbstractRunConfigurationNode<T> extends AbstractTreeNode<T> implements DashboardNode {
abstract class AbstractRunConfigurationNode<T> extends AbstractTreeNode<T> implements DashboardRunConfigurationNode {
@NotNull private final RunnerAndConfigurationSettings myConfigurationSettings;
protected AbstractRunConfigurationNode(Project project, T value, @NotNull RunnerAndConfigurationSettings configurationSettings) {
@@ -43,7 +42,7 @@ abstract class AbstractRunConfigurationNode<T> extends AbstractTreeNode<T> imple
@Override
protected void update(PresentationData presentation) {
presentation.setPresentableText(myConfigurationSettings.getName());
Icon icon = myConfigurationSettings.getConfiguration().getIcon();
Icon icon = RunManagerEx.getInstanceEx(getProject()).getConfigurationIcon(myConfigurationSettings);
Icon decorator = getIconDecorator();
if (decorator != null) {
icon = new RowIcon(icon, decorator);
@@ -56,14 +55,11 @@ abstract class AbstractRunConfigurationNode<T> extends AbstractTreeNode<T> imple
}
@Override
@Nullable
public Content getContent() {
return getDescriptor() == null ? null : getDescriptor().getAttachedContent();
@NotNull
public RunnerAndConfigurationSettings getConfigurationSettings() {
return myConfigurationSettings;
}
@Nullable
protected abstract Icon getIconDecorator();
@Nullable
protected abstract RunContentDescriptor getDescriptor();
}
@@ -0,0 +1,64 @@
/*
* Copyright 2000-2017 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.tree;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.configurations.ConfigurationType;
import com.intellij.execution.dashboard.Group;
import com.intellij.execution.dashboard.GroupingRule;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import com.intellij.icons.AllIcons;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.smartTree.ActionPresentation;
import com.intellij.ide.util.treeView.smartTree.ActionPresentationData;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author konstantin.aleev
*/
public class ConfigurationTypeGroupingRule implements GroupingRule {
@NonNls private static final String NAME = "TypeGroupingRule";
@Override
@NotNull
public String getName() {
return NAME;
}
@NotNull
@Override
public ActionPresentation getPresentation() {
return new ActionPresentationData(ExecutionBundle.message("runtime.dashboard.group.by.type.action.name"),
ExecutionBundle.message("runtime.dashboard.group.by.type.action.name"),
AllIcons.Actions.GroupByFile);
}
@Nullable
@Override
public Group getGroup(AbstractTreeNode<?> node) {
if (node instanceof DashboardRunConfigurationNode) {
RunnerAndConfigurationSettings configurationSettings = ((DashboardRunConfigurationNode)node).getConfigurationSettings();
ConfigurationType type = configurationSettings.getType();
if (type != null) {
return new GroupImpl<>(type, type.getDisplayName(), type.getIcon());
}
}
return null;
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2000-2017 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.tree;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.RunnerAndConfigurationSettings;
import com.intellij.execution.dashboard.Group;
import com.intellij.execution.dashboard.GroupingRule;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import com.intellij.icons.AllIcons;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.smartTree.ActionPresentation;
import com.intellij.ide.util.treeView.smartTree.ActionPresentationData;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author konstantin.aleev
*/
public class FolderGroupingRule implements GroupingRule {
@NonNls private static final String NAME = "FolderGroupingRule";
@Override
@NotNull
public String getName() {
return NAME;
}
@NotNull
@Override
public ActionPresentation getPresentation() {
return new ActionPresentationData(ExecutionBundle.message("runtime.dashboard.group.by.folder.action.name"),
ExecutionBundle.message("runtime.dashboard.group.by.folder.action.name"),
AllIcons.Actions.GroupByPackage);
}
@Nullable
@Override
public Group getGroup(AbstractTreeNode<?> node) {
if (node instanceof DashboardRunConfigurationNode) {
RunnerAndConfigurationSettings configurationSettings = ((DashboardRunConfigurationNode)node).getConfigurationSettings();
String folderName = configurationSettings.getFolderName();
if (folderName != null) {
return new GroupImpl<>(folderName, folderName, AllIcons.Nodes.Folder);
}
}
return null;
}
}
@@ -0,0 +1,58 @@
/*
* Copyright 2000-2017 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.tree;
import com.intellij.execution.dashboard.Group;
import javax.swing.*;
/**
* @author konstantin.aleev
*/
public class GroupImpl<T> implements Group {
private final T myValue;
private final String myName;
private final Icon myIcon;
public GroupImpl(T value, String name, Icon icon) {
myValue = value;
myName = name;
myIcon = icon;
}
@Override
public String getName() {
return myName;
}
@Override
public Icon getIcon() {
return myIcon;
}
@Override
public int hashCode() {
return myValue.hashCode();
}
@Override
public final boolean equals(Object obj) {
if (obj instanceof GroupImpl) {
return myValue.equals(((GroupImpl)obj).myValue);
}
return false;
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2000-2017 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.tree;
import com.intellij.execution.dashboard.GroupingRule;
import org.jetbrains.annotations.NotNull;
/**
* @author konstantin.aleev
*/
public class Grouper {
@NotNull private final GroupingRule myRule;
private boolean myEnabled = true;
public Grouper(@NotNull GroupingRule rule) {
myRule = rule;
}
@NotNull
public GroupingRule getRule() {
return myRule;
}
public boolean isEnabled() {
return myEnabled;
}
public void setEnabled(boolean enabled) {
myEnabled = enabled;
}
}
@@ -0,0 +1,60 @@
/*
* Copyright 2000-2017 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.tree;
import com.intellij.execution.dashboard.DashboardNode;
import com.intellij.execution.dashboard.Group;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author konstantin.aleev
*/
public class GroupingNode extends AbstractTreeNode<Pair<Object, Group>> implements DashboardNode {
private final List<AbstractTreeNode> myChildren = new ArrayList<>();
public GroupingNode(Project project, Object parent, Group group) {
super(project, Pair.create(parent, group));
}
public Group getGroup() {
//noinspection ConstantConditions ???
return getValue().getSecond();
}
@NotNull
@Override
public Collection<? extends AbstractTreeNode> getChildren() {
return myChildren;
}
public void addChildren(Collection<? extends AbstractTreeNode> children) {
myChildren.addAll(children);
}
@Override
protected void update(PresentationData presentation) {
presentation.setPresentableText(getGroup().getName());
presentation.setIcon(getGroup().getIcon());
}
}
@@ -77,10 +77,20 @@ class RunConfigurationNode extends AbstractRunConfigurationNode<RunnerAndConfigu
@Nullable
@Override
protected RunContentDescriptor getDescriptor() {
public RunContentDescriptor getDescriptor() {
if (myChildren.size() == 1) {
return myChildren.get(0).getDescriptor();
}
return null;
}
@Override
public boolean isTerminated() {
for (RunDescriptorNode node : myChildren) {
if (!node.isTerminated()) {
return false;
}
}
return true;
}
}
@@ -44,8 +44,9 @@ class RunDescriptorNode extends AbstractRunConfigurationNode<RunContentDescripto
return Collections.emptyList();
}
@Nullable
@Override
protected RunContentDescriptor getDescriptor() {
public RunContentDescriptor getDescriptor() {
return getValue();
}
@@ -62,4 +63,9 @@ class RunDescriptorNode extends AbstractRunConfigurationNode<RunContentDescripto
}
return null;
}
@Override
public boolean isTerminated() {
return getContent() == null || RunContentManagerImpl.isTerminated(getContent());
}
}
@@ -16,6 +16,8 @@
package com.intellij.execution.dashboard.tree;
import com.intellij.execution.RunManager;
import com.intellij.execution.dashboard.Group;
import com.intellij.execution.dashboard.GroupingRule;
import com.intellij.execution.dashboard.RuntimeDashboardContributor;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.projectView.TreeStructureProvider;
@@ -25,9 +27,7 @@ import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -35,11 +35,13 @@ import java.util.stream.Collectors;
*/
public class RuntimeDashboardTreeStructure extends AbstractTreeStructureBase {
private final Project myProject;
private final List<Grouper> myGroupers;
private final RunConfigurationsTreeRootNode myRootElement;
public RuntimeDashboardTreeStructure(@NotNull Project project) {
public RuntimeDashboardTreeStructure(@NotNull Project project, @NotNull List<Grouper> groupers) {
super(project);
myProject = project;
myGroupers = groupers;
myRootElement = new RunConfigurationsTreeRootNode();
}
@@ -71,10 +73,13 @@ public class RuntimeDashboardTreeStructure extends AbstractTreeStructureBase {
@NotNull
@Override
public Collection<? extends AbstractTreeNode> getChildren() {
return RunManager.getInstance(myProject).getAllSettings().stream()
.filter(runConfiguration -> RuntimeDashboardContributor.isShowInDashboard(runConfiguration.getType()))
.map(runConfiguration -> new RunConfigurationNode(myProject, runConfiguration))
.collect(Collectors.toList());
return group(myProject,
this,
myGroupers.stream().filter(Grouper::isEnabled).map(Grouper::getRule).collect(Collectors.toList()),
RunManager.getInstance(myProject).getAllSettings().stream()
.filter(runConfiguration -> RuntimeDashboardContributor.isShowInDashboard(runConfiguration.getType()))
.map(runConfiguration -> new RunConfigurationNode(myProject, runConfiguration))
.collect(Collectors.toList()));
}
@Override
@@ -82,4 +87,33 @@ public class RuntimeDashboardTreeStructure extends AbstractTreeStructureBase {
}
}
private static Collection<? extends AbstractTreeNode> group(final Project project, final AbstractTreeNode parent,
List<GroupingRule> rules, List<AbstractTreeNode> nodes) {
if (rules.isEmpty()) {
return nodes;
}
final List<GroupingRule> remaining = new ArrayList<>(rules);
GroupingRule rule = remaining.remove(0);
Map<Group, List<AbstractTreeNode>> groups = nodes.stream().collect(
HashMap::new,
(map, node) -> map.computeIfAbsent(rule.getGroup(node), key -> new ArrayList<>()).add(node),
(firstMap, secondMap) -> firstMap.forEach((key, value) -> value.addAll(secondMap.get(key)))
);
rule.getPermanentGroups().forEach(group -> groups.computeIfAbsent(group, key -> new ArrayList<>()));
final List<AbstractTreeNode> result = new ArrayList<>();
final List<AbstractTreeNode> ungroupedNodes = new ArrayList<>();
groups.forEach((group, groupedNodes) -> {
if (group == null) {
ungroupedNodes.addAll(group(project, parent, remaining, groupedNodes));
} else {
GroupingNode node = new GroupingNode(project, parent.getValue(), group);
node.addChildren(group(project, node, remaining, groupedNodes));
result.add(node);
}
});
Collections.sort(result, Comparator.comparing(node -> ((GroupingNode)node).getGroup().getName()));
result.addAll(ungroupedNodes);
return result;
}
}
@@ -0,0 +1,90 @@
/*
* Copyright 2000-2017 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.tree;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.dashboard.Group;
import com.intellij.execution.dashboard.GroupingRule;
import com.intellij.execution.dashboard.DashboardRunConfigurationNode;
import com.intellij.icons.AllIcons;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.smartTree.ActionPresentation;
import com.intellij.ide.util.treeView.smartTree.ActionPresentationData;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author konstantin.aleev
*/
public class StatusGroupingRule implements GroupingRule {
@NonNls private static final String NAME = "StatusGroupingRule";
@Override
@NotNull
public String getName() {
return NAME;
}
@NotNull
@Override
public ActionPresentation getPresentation() {
return new ActionPresentationData(ExecutionBundle.message("runtime.dashboard.group.by.status.action.name"),
ExecutionBundle.message("runtime.dashboard.group.by.status.action.name"),
AllIcons.Actions.GroupByPrefix); // TODO [konstantin.aleev] provide new icon
}
@NotNull
@Override
public List<Group> getPermanentGroups() {
return Arrays.stream(Status.values()).map(Status::getGroup).collect(Collectors.toList());
}
@Nullable
@Override
public Group getGroup(AbstractTreeNode<?> node) {
if (node instanceof DashboardRunConfigurationNode) {
if (((DashboardRunConfigurationNode)node).isTerminated()) {
return Status.STOPPED.getGroup();
} else {
return Status.STARTED.getGroup();
}
}
return null;
}
public enum Status {
STARTED(ExecutionBundle.message("runtime.dashboard.started.group.name"), AllIcons.Toolwindows.ToolWindowRun),
STOPPED(ExecutionBundle.message("runtime.dashboard.stopped.group.name"), AllIcons.Actions.Suspend);
private final String myLabel;
private final Icon myIcon;
Status(String label, Icon icon) {
myLabel = label;
myIcon = icon;
}
public Group getGroup() {
return new GroupImpl<>(this, myLabel, myIcon);
}
}
}
@@ -218,7 +218,7 @@ public class ExecutionManagerImpl extends ExecutionManager implements Disposable
Messages.getQuestionIcon(), option) == Messages.OK;
}
private static void stop(@Nullable RunContentDescriptor descriptor) {
public static void stopProcess(@Nullable RunContentDescriptor descriptor) {
ProcessHandler processHandler = descriptor != null ? descriptor.getProcessHandler() : null;
if (processHandler == null) {
return;
@@ -502,7 +502,7 @@ public class ExecutionManagerImpl extends ExecutionManager implements Disposable
}
for (RunContentDescriptor descriptor : runningToStop) {
stop(descriptor);
stopProcess(descriptor);
}
}
@@ -376,3 +376,16 @@ sm.test.runner.magnitude.assertion.failed.title=Assertion failed
sm.test.runner.magnitude.testerror.title=Error
runtime.dashboard.empty.selection.message=Select a node in the tree to view details
runtime.dashboard.group.by.type.action.name=Group by Type
runtime.dashboard.group.by.status.action.name=Group by Status
runtime.dashboard.group.by.folder.action.name=Group by Folder
runtime.dashboard.run.action.name=(Re)run
runtime.dashboard.debug.action.name=(Re)run in Debug Mode
runtime.dashboard.stop.action.name=Stop
runtime.dashboard.edit.configuration.action.name=Edit Configuration
runtime.dashboard.edit.configuration.dialog.title=Edit Run Configuration
runtime.dashboard.remove.configuration.action.name=Remove Run Configuration
runtime.dashboard.remove.configuration.dialog.title=Remove Run Configuration
runtime.dashboard.remove.configuration.dialog.message=Are you sure to remove selected run configuration(s)?
runtime.dashboard.started.group.name=Started
runtime.dashboard.stopped.group.name=Stopped
@@ -58,7 +58,7 @@ tool.window.name.version.control=Version Control
tool.window.name.module.dependencies=Module Dependencies
tool.window.name.tasks=Time Tracking
tool.window.name.database=Database
tool.window.name.runtime.dashboard=Runtime Dashboard
tool.window.name.runtime.dashboard=Run Dashboard
tool.window.move.to.action.group.name=Move to
tool.window.move.to.top.action.name=Top
tool.window.move.to.left.action.name=Left
@@ -1016,5 +1016,15 @@
<action id="DisableInspection" class="com.intellij.codeInspection.ui.actions.KeyAwareInspectionViewAction$DisableInspection"/>
<action id="RunInspectionOn" class="com.intellij.codeInspection.ui.actions.KeyAwareInspectionViewAction$RunInspectionOn"/>
</group>
<group id="RuntimeDashboardToolbar">
<action id="RuntimeDashboard.Run" class="com.intellij.execution.dashboard.actions.RunAction"/>
<action id="RuntimeDashboard.Debug" class="com.intellij.execution.dashboard.actions.DebugAction"/>
<action id="RuntimeDashboard.Stop" class="com.intellij.execution.dashboard.actions.StopAction"/>
<action id="RuntimeDashboard.EditConfiguration" class="com.intellij.execution.dashboard.actions.EditConfigurationAction"/>
<action id="RuntimeDashboard.CopyConfiguration" class="com.intellij.execution.dashboard.actions.CopyConfigurationAction"/>
<action id="RuntimeDashboard.RemoveConfiguration" class="com.intellij.execution.dashboard.actions.RemoveConfigurationAction"/>
</group>
<group id="RuntimeDashboardPopup"/>
</actions>
</idea-plugin>
@@ -1,5 +1,6 @@
package com.intellij.remoteServer.impl.runtime.ui;
import com.intellij.execution.dashboard.TreeContent;
import com.intellij.ide.DataManager;
import com.intellij.ide.actions.ContextHelpAction;
import com.intellij.ide.util.treeView.AbstractTreeNode;
@@ -45,7 +46,7 @@ import java.util.Set;
/**
* @author michael.golubev
*/
public class ServersToolWindowContent extends JPanel implements Disposable, ServersTreeNodeSelector {
public class ServersToolWindowContent extends JPanel implements Disposable, ServersTreeNodeSelector, TreeContent {
public static final DataKey<ServersToolWindowContent> KEY = DataKey.create("serversToolWindowContent");
@NonNls private static final String PLACE_TOOLBAR = "ServersToolWindowContent#Toolbar";
@NonNls private static final String SERVERS_TOOL_WINDOW_TOOLBAR = "RemoteServersViewToolbar";
@@ -289,6 +290,8 @@ public class ServersToolWindowContent extends JPanel implements Disposable, Serv
public void dispose() {
}
@Override
@NotNull
public TreeBuilderBase getBuilder() {
return myBuilder;
}
@@ -1,117 +1,21 @@
package com.intellij.remoteServer.impl.runtime.ui.tree.actions;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.execution.dashboard.DashboardTreeAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.project.DumbAware;
import com.intellij.remoteServer.impl.runtime.ui.ServersToolWindowContent;
import com.intellij.remoteServer.impl.runtime.ui.tree.ServersTreeNode;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public abstract class ServersTreeAction<T extends ServersTreeNode> extends AnAction implements DumbAware {
public abstract class ServersTreeAction<T extends ServersTreeNode> extends DashboardTreeAction<T, ServersToolWindowContent>
implements DumbAware {
protected ServersTreeAction(String text, String description, Icon icon) {
super(text, description, icon);
}
@Override
public void update(@NotNull AnActionEvent e) {
Presentation presentation = e.getPresentation();
List<T> targetNodes = getTargetNodes(e);
boolean visible;
boolean enabled;
if (targetNodes == null) {
visible = false;
enabled = false;
}
else {
visible = true;
enabled = true;
for (T targetNode : targetNodes) {
visible &= isVisible4(targetNode);
enabled &= visible && isEnabled4(targetNode);
}
}
presentation.setVisible(visible);
presentation.setEnabled(enabled);
updatePresentation(presentation, ContainerUtil.getFirstItem(targetNodes));
}
private List<T> getTargetNodes(AnActionEvent e) {
ServersToolWindowContent content = getContent(e);
if (content == null) {
return null;
}
Set<Object> selectedElements = content.getBuilder().getSelectedElements();
int selectionCount = selectedElements.size();
if (selectionCount == 0 || selectionCount > 1 && !isMultiSelectionAllowed()) {
return null;
}
Class<T> targetNodeClass = getTargetNodeClass();
List<T> result = new ArrayList<>();
for (Object selectedElement : selectedElements) {
ServersTreeNode node = (ServersTreeNode)selectedElement;
if (!targetNodeClass.isInstance(node)) {
return null;
}
result.add(targetNodeClass.cast(node));
}
return result;
}
private static ServersToolWindowContent getContent(AnActionEvent e) {
protected final ServersToolWindowContent getTreeContent(AnActionEvent e) {
return e.getData(ServersToolWindowContent.KEY);
}
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
List<T> targetNodes = getTargetNodes(e);
if (targetNodes == null) {
return;
}
List<T> verifiedTargetNodes = ContainerUtil.filter(targetNodes, targetNode -> isVisible4(targetNode) && isEnabled4(targetNode));
doActionPerformed(getContent(e), e, verifiedTargetNodes);
}
protected boolean isMultiSelectionAllowed() {
return false;
}
protected boolean isVisible4(T node) {
return true;
}
protected boolean isEnabled4(T node) {
return true;
}
protected void updatePresentation(@NotNull Presentation presentation, @Nullable T node) {
}
protected void doActionPerformed(@NotNull ServersToolWindowContent content, AnActionEvent e, List<T> nodes) {
for (T node : nodes) {
doActionPerformed(content, e, node);
}
}
protected void doActionPerformed(@NotNull ServersToolWindowContent content, AnActionEvent e, T node) {
doActionPerformed(node);
}
protected void doActionPerformed(T node) {
throw new UnsupportedOperationException();
}
protected abstract Class<T> getTargetNodeClass();
}