diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurable.kt b/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurable.kt index 9b352086e88d..04baaaa9bd6e 100644 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurable.kt +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunConfigurable.kt @@ -18,7 +18,6 @@ package com.intellij.execution.impl import com.intellij.execution.* import com.intellij.execution.configuration.ConfigurationFactoryEx import com.intellij.execution.configurations.* -import com.intellij.execution.dashboard.RunDashboardManager import com.intellij.execution.impl.RunConfigurable.Companion.collectNodesRecursively import com.intellij.execution.impl.RunConfigurableNodeKind.* import com.intellij.icons.AllIcons @@ -32,7 +31,6 @@ import com.intellij.openapi.options.* import com.intellij.openapi.project.Project import com.intellij.openapi.ui.LabeledComponent.create import com.intellij.openapi.ui.Messages -import com.intellij.openapi.ui.VerticalFlowLayout import com.intellij.openapi.util.Comparing import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Pair @@ -43,13 +41,11 @@ import com.intellij.openapi.wm.IdeFocusManager import com.intellij.openapi.wm.IdeFocusManager.getGlobalInstance import com.intellij.ui.* import com.intellij.ui.RowsDnDSupport.RefinedDropSupport.Position.* -import com.intellij.ui.components.JBLabel import com.intellij.ui.components.labels.ActionLink import com.intellij.ui.treeStructure.Tree import com.intellij.util.ArrayUtilRt import com.intellij.util.IconUtil import com.intellij.util.PlatformIcons -import com.intellij.util.containers.ContainerUtil import com.intellij.util.containers.HashMap import com.intellij.util.containers.nullize import com.intellij.util.ui.* @@ -57,11 +53,13 @@ import com.intellij.util.ui.tree.TreeUtil import gnu.trove.THashSet import gnu.trove.TObjectIntHashMap import net.miginfocom.swing.MigLayout -import java.awt.* +import java.awt.BorderLayout +import java.awt.FlowLayout +import java.awt.GridBagConstraints +import java.awt.GridBagLayout import java.awt.event.KeyEvent import java.util.* import java.util.function.ToIntFunction -import java.util.stream.Collectors import javax.swing.* import javax.swing.border.EmptyBorder import javax.swing.event.DocumentEvent @@ -99,9 +97,7 @@ open class RunConfigurable @JvmOverloads constructor(private val myProject: Proj private var toolbarDecorator: ToolbarDecorator? = null private var isFolderCreating: Boolean = false private val toolbarAddAction = MyToolbarAddAction() - private val runDashboardTypes = ContainerUtil.newHashSet() - private val hiddenRunDashboardTypesList = RunDashboardTypesList(myProject) - private val shownRunDashboardTypesList = RunDashboardTypesList(myProject) + private val runDashboardTypesPanel = RunDashboardTypesPanel(myProject) companion object { fun collectNodesRecursively(parentNode: DefaultMutableTreeNode, @@ -496,7 +492,8 @@ open class RunConfigurable @JvmOverloads constructor(private val myProject: Proj if (Registry.`is`("ide.run.dashboard.types.configuration") || ApplicationManager.getApplication().isInternal) { val wrapper = JPanel(BorderLayout()) - wrapper.add(createRunDashboardTypesPanel(), BorderLayout.CENTER) + wrapper.add(runDashboardTypesPanel, BorderLayout.CENTER) + runDashboardTypesPanel.addChangeListener(this::defaultsSettingsChanged) wrapper.add(settingsWrapper, BorderLayout.SOUTH) rightPanel.add(wrapper, BorderLayout.SOUTH) @@ -543,7 +540,7 @@ open class RunConfigurable @JvmOverloads constructor(private val myProject: Proj private fun defaultsSettingsChanged() { isModified = !Comparing.equal(recentsLimit.text, recentsLimit.getClientProperty(INITIAL_VALUE_KEY)) || !Comparing.equal(confirmation.isSelected, confirmation.getClientProperty(INITIAL_VALUE_KEY)) || - !Comparing.equal(runDashboardTypes, RunDashboardManager.getInstance(myProject).types) + runDashboardTypesPanel.isModified() } private fun createSettingsPanel(): JPanel { @@ -565,82 +562,6 @@ open class RunConfigurable @JvmOverloads constructor(private val myProject: Proj return bottomPanel } - private fun createRunDashboardTypesPanel(): JComponent { - updateRunDashboardTypes() - - val buttonsPanel = JPanel(VerticalFlowLayout()) - buttonsPanel.add(JButton(ExecutionBundle.message("run.dashboard.configurable.show")).apply { - addActionListener { changeRunDashboardTypes(false, true) } - }) - buttonsPanel.add(JButton(ExecutionBundle.message("run.dashboard.configurable.hide")).apply { - addActionListener { changeRunDashboardTypes(false, false) } - }) - buttonsPanel.add(JButton(ExecutionBundle.message("run.dashboard.configurable.show.all")).apply { - addActionListener { changeRunDashboardTypes(true, true) } - }) - buttonsPanel.add(JButton(ExecutionBundle.message("run.dashboard.configurable.hide.all")).apply { - addActionListener { changeRunDashboardTypes(true, false) } - }) - - val gridBag = GridBag().setDefaultWeightX(0, 0.5).setDefaultWeightX(1, 0.0).setDefaultWeightX(2, 0.5) - val treesPanel = JPanel(GridBagLayout()) - - val hiddenLabel = JBLabel(ExecutionBundle.message("run.dashboard.configurable.hidden.label")) - val shownLabel = JBLabel(ExecutionBundle.message("run.dashboard.configurable.shown.label")) - val hiddenSize = hiddenLabel.minimumSize - val shownSize = shownLabel.minimumSize - if (hiddenSize.width < shownSize.width) { - val size = Dimension(shownSize.width, hiddenSize.height) - hiddenLabel.minimumSize = size - hiddenLabel.preferredSize = size - hiddenLabel.maximumSize = size - } - else { - val size = Dimension(hiddenSize.width, shownSize.height) - shownLabel.minimumSize = size - shownLabel.preferredSize = size - shownLabel.maximumSize = size - } - treesPanel.add(hiddenLabel, gridBag.nextLine().next().anchor(GridBagConstraints.WEST)) - treesPanel.add(shownLabel, gridBag.next().next().anchor(GridBagConstraints.WEST)) - - treesPanel.add(hiddenRunDashboardTypesList.component, gridBag.nextLine().next().weighty(1.0).fillCell()) - treesPanel.add(buttonsPanel, gridBag.next().anchor(GridBagConstraints.CENTER)) - treesPanel.add(shownRunDashboardTypesList.component, gridBag.next().weighty(1.0).fillCell()) - - return treesPanel - } - - private fun updateRunDashboardTypes() { - hiddenRunDashboardTypesList.updateModel(runDashboardTypes, false) - shownRunDashboardTypesList.updateModel(runDashboardTypes, true) - - val hiddenSize = hiddenRunDashboardTypesList.component.preferredSize - val shownSize = shownRunDashboardTypesList.component.preferredSize - if (hiddenSize.width < shownSize.width) { - hiddenRunDashboardTypesList.component.preferredSize = Dimension(shownSize.width, hiddenSize.height) - } - else { - shownRunDashboardTypesList.component.preferredSize = Dimension(hiddenSize.width, shownSize.height) - } - } - - private fun changeRunDashboardTypes(all: Boolean, show: Boolean) { - val list = if (show) hiddenRunDashboardTypesList else shownRunDashboardTypesList - val types = if (all) list.allTypes else list.selectedTypes - val ids = types.stream().map(ConfigurationType::getId).collect(Collectors.toSet()) - if (show) { - runDashboardTypes.addAll(ids) - } - else { - runDashboardTypes.removeAll(ids) - } - - updateRunDashboardTypes() - - defaultsSettingsChanged() - } - private val selectedConfigurationType: ConfigurationType? get() { val configurationTypeNode = selectedConfigurationTypeNode @@ -684,8 +605,7 @@ open class RunConfigurable @JvmOverloads constructor(private val myProject: Proj confirmation.isSelected = config.isRestartRequiresConfirmation confirmation.putClientProperty(INITIAL_VALUE_KEY, confirmation.isSelected) - runDashboardTypes.clear() - runDashboardTypes.addAll(RunDashboardManager.getInstance(myProject).types) + runDashboardTypesPanel.reset() for (each in additionalSettings) { each.first.reset() @@ -725,10 +645,7 @@ open class RunConfigurable @JvmOverloads constructor(private val myProject: Proj manager.config.isRestartRequiresConfirmation = confirmation.isSelected confirmation.putClientProperty(INITIAL_VALUE_KEY, confirmation.isSelected) - val dashboardManager = RunDashboardManager.getInstance(myProject) - if (!Comparing.equal(runDashboardTypes, dashboardManager.types)) { - dashboardManager.types = runDashboardTypes - } + runDashboardTypesPanel.apply() for (configurable in storedComponents.values) { if (configurable.isModified) { @@ -1106,7 +1023,9 @@ open class RunConfigurable @JvmOverloads constructor(private val myProject: Proj configurationTypes.add(null) } - val popup = NewRunConfigurationPopup.createAddPopup(configurationTypes, "$hiddenCount items more (irrelevant)...", + val popup = NewRunConfigurationPopup.createAddPopup(configurationTypes, + ExecutionBundle.message("show.irrelevant.configurations.action.name", + hiddenCount), { factory -> createNewConfiguration(factory) }, selectedConfigurationType, { showAddPopup(false) }, true) //new TreeSpeedSearch(myTree); diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunDashboardTypesList.kt b/platform/lang-impl/src/com/intellij/execution/impl/RunDashboardTypesList.kt deleted file mode 100644 index c1fc740b4069..000000000000 --- a/platform/lang-impl/src/com/intellij/execution/impl/RunDashboardTypesList.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.impl - -import com.intellij.execution.configurations.ConfigurationType -import com.intellij.openapi.project.Project -import com.intellij.ui.CollectionListModel -import com.intellij.ui.ColoredListCellRenderer -import com.intellij.ui.ListSpeedSearch -import com.intellij.ui.SpeedSearchComparator -import com.intellij.ui.components.JBList -import com.intellij.ui.components.JBScrollPane -import com.intellij.ui.speedSearch.SpeedSearchUtil -import javax.swing.JComponent -import javax.swing.JList - -/** - * @author Konstantin Aleev - */ -internal class RunDashboardTypesList(private val myProject: Project) { - private val listModel = CollectionListModel() - private val list = JBList(listModel) - - val component: JComponent = JBScrollPane(list) - val selectedTypes: Collection - get() = list.selectedValuesList - val allTypes: Collection - get() = listModel.items - - init { - val search = ListSpeedSearch(list, { it.displayName }) - search.comparator = SpeedSearchComparator(false) - list.visibleRowCount = 20 - - list.cellRenderer = object : ColoredListCellRenderer() { - override fun customizeCellRenderer(list: JList, - value: ConfigurationType?, - index: Int, - selected: Boolean, - hasFocus: Boolean) { - if (value != null) { - append(value.displayName) - icon = value.icon - } - - SpeedSearchUtil.applySpeedSearchHighlighting(list, this, true, selected) - } - } - } - - fun updateModel(types: Set, include: Boolean) { - listModel.removeAll() - listModel.add( - RunManagerImpl.getInstanceImpl(myProject).configurationFactoriesWithoutUnknown.filterNot { types.contains(it.id) xor include }) - } -} \ No newline at end of file diff --git a/platform/lang-impl/src/com/intellij/execution/impl/RunDashboardTypesPanel.kt b/platform/lang-impl/src/com/intellij/execution/impl/RunDashboardTypesPanel.kt new file mode 100644 index 000000000000..5a00fe3c34db --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/impl/RunDashboardTypesPanel.kt @@ -0,0 +1,196 @@ +/* + * 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.impl + +import com.intellij.execution.ExecutionBundle +import com.intellij.execution.RunManager +import com.intellij.execution.configurations.ConfigurationType +import com.intellij.execution.dashboard.RunDashboardManager +import com.intellij.ide.util.PropertiesComponent +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.DefaultActionGroup +import com.intellij.openapi.actionSystem.impl.SimpleDataContext +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.popup.JBPopupFactory +import com.intellij.openapi.util.Comparing +import com.intellij.openapi.util.Conditions +import com.intellij.openapi.util.SystemInfo +import com.intellij.ui.* +import com.intellij.ui.components.JBLabel +import com.intellij.ui.components.JBList +import com.intellij.ui.speedSearch.SpeedSearchUtil +import com.intellij.util.containers.stream +import com.intellij.util.ui.UIUtil +import org.jetbrains.annotations.NonNls +import java.awt.BorderLayout +import javax.swing.BorderFactory +import javax.swing.JList +import javax.swing.JPanel +import javax.swing.event.ListDataEvent +import javax.swing.event.ListDataListener + +@NonNls private val EXPAND_PROPERTY_KEY = "ExpandRunDashboardTypesPanel" + +private val IGNORE_CASE_DISPLAY_NAME_COMPARATOR = Comparator { t1, t2 -> + t1.displayName.compareTo(t2.displayName, ignoreCase = true) +} + +/** + * @author Konstantin Aleev + */ +internal class RunDashboardTypesPanel(private val myProject: Project) : JPanel(BorderLayout()) { + private val listModel = CollectionListModel() + private val list = JBList(listModel) + + init { + val search = ListSpeedSearch(list, { it.displayName }) + search.comparator = SpeedSearchComparator(false) + list.visibleRowCount = 5 + + list.cellRenderer = object : ColoredListCellRenderer() { + override fun customizeCellRenderer(list: JList, + value: ConfigurationType?, + index: Int, + selected: Boolean, + hasFocus: Boolean) { + if (value != null) { + append(value.displayName) + icon = value.icon + } + + SpeedSearchUtil.applySpeedSearchHighlighting(list, this, true, selected) + } + } + + val toolbarDecorator = ToolbarDecorator.createDecorator(list) + if (!SystemInfo.isMac) { + toolbarDecorator.setAsUsualTopToolbar() + } + toolbarDecorator.setAddAction { showAddPopup(it, true) } + toolbarDecorator.setRemoveAction { list.selectedValuesList.forEach { listModel.remove(it) } } + toolbarDecorator.setMoveUpAction(null) + toolbarDecorator.setMoveDownAction(null) + + val listPanel = JPanel(BorderLayout()) + listPanel.add(toolbarDecorator.createPanel(), BorderLayout.CENTER) + val descriptionLabel = JBLabel(ExecutionBundle.message("run.dashboard.configurable.types.panel.description")).apply { + border = BorderFactory.createEmptyBorder(TitledSeparator.TOP_INSET, 20, 0, 0) + componentStyle = UIUtil.ComponentStyle.SMALL + } + listPanel.add(descriptionLabel, BorderLayout.SOUTH) + + val hideableDecorator = object : HideableDecorator(this, + ExecutionBundle.message("run.dashboard.configurable.types.panel.title"), + false) { + override fun on() { + super.on() + storeState() + } + + override fun off() { + super.off() + storeState() + } + + private fun storeState() { + PropertiesComponent.getInstance().setValue(EXPAND_PROPERTY_KEY, isExpanded.toString()) + } + } + hideableDecorator.setOn(PropertiesComponent.getInstance().getBoolean(EXPAND_PROPERTY_KEY, true)) + hideableDecorator.setContentComponent(listPanel) + } + + private fun showAddPopup(button: AnActionButton, showApplicableTypesOnly: Boolean) { + val allTypes = RunManager.getInstance(myProject).configurationFactoriesWithoutUnknown.filter { !listModel.contains(it) } + val configurationTypes = getTypesToShow(showApplicableTypesOnly, allTypes).toMutableList() + configurationTypes.sortWith(IGNORE_CASE_DISPLAY_NAME_COMPARATOR) + val hiddenCount = allTypes.size - configurationTypes.size + + val actionGroup = DefaultActionGroup(null, false) + configurationTypes.forEach({ + actionGroup.add(object : AnAction(it.displayName, null, it.icon) { + override fun actionPerformed(e: AnActionEvent?) { + listModel.add(it) + listModel.sort(IGNORE_CASE_DISPLAY_NAME_COMPARATOR) + list.selectedIndex = listModel.getElementIndex(it) + } + }) + }) + if (hiddenCount > 0) { + actionGroup.add(object : AnAction(ExecutionBundle.message("show.irrelevant.configurations.action.name", hiddenCount)) { + override fun actionPerformed(e: AnActionEvent?) { + showAddPopup(button, false) + } + }) + } + + val popup = JBPopupFactory.getInstance().createActionGroupPopup( + ExecutionBundle.message("run.dashboard.configurable.add.configuration.type"), + actionGroup, + SimpleDataContext.getProjectContext(myProject), + false, + false, + false, + null, + -1, + Conditions.alwaysTrue()) + popup.show(button.preferredPopupPoint!!) + } + + private fun getTypesToShow(showApplicableTypesOnly: Boolean, allTypes: List): List { + if (showApplicableTypesOnly) { + val applicableTypes = allTypes.filter { it.configurationFactories.stream().anyMatch { it.isApplicable(myProject) } } + if (applicableTypes.size < (allTypes.size - 3)) { + return applicableTypes + } + } + return allTypes + } + + fun addChangeListener(onChange: () -> Unit) { + listModel.addListDataListener(object : ListDataListener { + override fun contentsChanged(e: ListDataEvent?) { + onChange() + } + + override fun intervalRemoved(e: ListDataEvent?) { + onChange() + } + + override fun intervalAdded(e: ListDataEvent?) { + onChange() + } + }) + } + + fun isModified() = !Comparing.equal(listModel.items.mapTo(HashSet(), { it.id }), RunDashboardManager.getInstance(myProject).types) + + fun reset() { + listModel.removeAll() + val types = RunDashboardManager.getInstance(myProject).types + listModel.add(RunManagerImpl.getInstanceImpl(myProject).configurationFactoriesWithoutUnknown.filter { types.contains(it.id) }) + listModel.sort(IGNORE_CASE_DISPLAY_NAME_COMPARATOR) + } + + fun apply() { + val dashboardManager = RunDashboardManager.getInstance(myProject) + val types = listModel.items.mapTo(HashSet(), { it.id }) + if (!Comparing.equal(types, dashboardManager.types)) { + dashboardManager.types = types + } + } +} diff --git a/platform/platform-resources-en/src/messages/ExecutionBundle.properties b/platform/platform-resources-en/src/messages/ExecutionBundle.properties index 789319e70c0a..85cfa5626f37 100644 --- a/platform/platform-resources-en/src/messages/ExecutionBundle.properties +++ b/platform/platform-resources-en/src/messages/ExecutionBundle.properties @@ -58,6 +58,8 @@ run.configuration.startup.connection.rab.title=Startup/Connection add.new.run.configuration.action2.name=Add New Configuration add.new.run.configuration.action.name=Add New ''{0}'' Configuration +show.irrelevant.configurations.action.name={0} items more (irrelevant)... + remove.run.configuration.action.name=Remove Configuration no.parameters=No parameters @@ -410,9 +412,6 @@ run.dashboard.failed.group.name=Failed run.dashboard.group.configurations.title=Group Configurations run.dashboard.group.configurations.label=Group Name: -run.dashboard.configurable.show=Show > -run.dashboard.configurable.hide=< Hide -run.dashboard.configurable.show.all=Show All > -run.dashboard.configurable.hide.all=< Hide All -run.dashboard.configurable.shown.label=Run Dashboard -run.dashboard.configurable.hidden.label=Run/Debug Tool Window +run.dashboard.configurable.add.configuration.type=Add Configuration Type +run.dashboard.configurable.types.panel.title=Run Dashboard Types +run.dashboard.configurable.types.panel.description=To manage run configuration in Run Dashboard add its type.