diff --git a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/JdkListConfigurable.java b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/JdkListConfigurable.java index 149dd1212668..d917baf643f9 100644 --- a/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/JdkListConfigurable.java +++ b/java/idea-ui/src/com/intellij/openapi/roots/ui/configuration/projectRoot/JdkListConfigurable.java @@ -12,9 +12,7 @@ import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.projectRoots.SdkModel; import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl; import com.intellij.openapi.roots.ProjectRootManager; -import com.intellij.openapi.roots.ui.configuration.SdkListModelBuilder; -import com.intellij.openapi.roots.ui.configuration.SdkPopupFactory; -import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable; +import com.intellij.openapi.roots.ui.configuration.*; import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.SdkProjectStructureElement; import com.intellij.openapi.ui.MasterDetailsComponent; @@ -238,7 +236,8 @@ public class JdkListConfigurable extends BaseStructureConfigurable { notSimpleJavaSdkType(), null, sdk -> false) - ).showPopup(e, () -> {}); + ).createPopup(new SdkPopup.SdkPopupListener() { }) + .showPopup(e); } } } diff --git a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkTracker.java b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkTracker.java index 49abd7d2fca7..6e03f90d16cb 100644 --- a/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkTracker.java +++ b/platform/lang-impl/src/com/intellij/openapi/projectRoots/impl/UnknownSdkTracker.java @@ -14,12 +14,17 @@ import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.progress.util.ProgressIndicatorBase; import com.intellij.openapi.project.Project; -import com.intellij.openapi.projectRoots.*; +import com.intellij.openapi.projectRoots.ProjectJdkTable; +import com.intellij.openapi.projectRoots.Sdk; +import com.intellij.openapi.projectRoots.SdkModificator; +import com.intellij.openapi.projectRoots.SdkType; import com.intellij.openapi.projectRoots.impl.SdkUsagesCollector.SdkUsage; import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.DownloadSdkFix; import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.LocalSdkFix; import com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.UnknownSdkLookup; +import com.intellij.openapi.roots.ui.configuration.SdkListItem; import com.intellij.openapi.roots.ui.configuration.SdkListModelBuilder; +import com.intellij.openapi.roots.ui.configuration.SdkPopup; import com.intellij.openapi.roots.ui.configuration.SdkPopupFactory; import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; import com.intellij.openapi.roots.ui.configuration.projectRoot.SdkDownloadTask; @@ -36,7 +41,6 @@ import org.jetbrains.annotations.Nullable; import java.awt.*; import java.util.List; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; import static com.intellij.openapi.progress.PerformInBackgroundOption.ALWAYS_BACKGROUND; import static com.intellij.openapi.projectRoots.impl.UnknownSdkResolver.UnknownSdk; @@ -212,25 +216,22 @@ public class UnknownSdkTracker { modelBuilder ); - AtomicReference wasSdkCreated = new AtomicReference<>(null); - model.addListener(new SdkModel.Listener() { - @Override - public void sdkAdded(@NotNull Sdk sdk) { - //TODO: handle if an existing item is selected! - //it is easier and safer than committing the ProjectSdksModel instance - wasSdkCreated.set(sdk); - } - }); - - popup.showUnderneathToTheRightOf( - underneathRightOfComponent, - () -> { - Sdk sdk = wasSdkCreated.get(); - if (sdk != null) { - onSelectionMade.consume(sdk); + popup.createPopup(new SdkPopup.SdkPopupListener() { + private void handleNewItem(@NotNull SdkListItem item) { + if (item instanceof SdkListItem.SdkItem) { + onSelectionMade.consume(((SdkListItem.SdkItem)item).getSdk()); } } - ); + @Override + public void onNewItemAdded(@NotNull SdkListItem item) { + handleNewItem(item); + } + + @Override + public void onExistingItemSelected(@NotNull SdkListItem item) { + handleNewItem(item); + } + }).showUnderneathToTheRightOf(underneathRightOfComponent); } private static void configureLocalSdks(@NotNull Map localFixes) { diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkPopup.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkPopup.java new file mode 100644 index 000000000000..93ca8dfb9c6b --- /dev/null +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkPopup.java @@ -0,0 +1,32 @@ +// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package com.intellij.openapi.roots.ui.configuration; + +import com.intellij.openapi.actionSystem.AnActionEvent; +import org.jetbrains.annotations.NotNull; + +import java.awt.*; + +public interface SdkPopup { + void showPopup(@NotNull AnActionEvent e); + void showUnderneathToTheRightOf(@NotNull Component component); + + interface SdkPopupListener { + /** + * Executed on popup is closed, independently from the result + */ + default void onClosed() {} + + /** + * Executed when a new item was created via a user action + * and added to the model, called after model is refreshed + */ + default void onNewItemAdded(@NotNull SdkListItem item) {} + + /** + * Executed when an existing selectable item was selected + * in the popup, it does mean no new items were created + * by a user + */ + default void onExistingItemSelected(@NotNull SdkListItem item) {} + } +} diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkPopupFactory.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkPopupFactory.java index bf043a48f03b..d5b43431ccdc 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkPopupFactory.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/SdkPopupFactory.java @@ -4,6 +4,7 @@ package com.intellij.openapi.roots.ui.configuration; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ui.configuration.SdkListModelBuilder.ModelListener; +import com.intellij.openapi.roots.ui.configuration.SdkPopup.SdkPopupListener; import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel; import com.intellij.openapi.ui.popup.JBPopupListener; import com.intellij.openapi.ui.popup.LightweightWindowEvent; @@ -16,9 +17,10 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.util.Collections; +import java.util.function.Consumer; public class SdkPopupFactory { - private final SdkListModelBuilder myModel; + private final SdkListModelBuilder myModelBuilder; @Nullable private final Project myProject; @NotNull private final ProjectSdksModel mySdkModel; @@ -27,13 +29,21 @@ public class SdkPopupFactory { @NotNull SdkListModelBuilder modelBuilder) { myProject = project; mySdkModel = sdkModel; - myModel = modelBuilder; + myModelBuilder = modelBuilder; } @NotNull - private ComboBoxPopup createPopup(@NotNull Runnable onClosed) { + public SdkPopup createPopup(@NotNull SdkPopupListener listener) { SdkListItemContext context = new SdkListItemContext(); - ComboBoxPopup popup = new ComboBoxPopup<>(context, null); + + SdkPopupImpl popup = new SdkPopupImpl(context, value -> { + if (value instanceof SdkListItem.ActionableItem) { + ((SdkListItem.ActionableItem)value).executeAction(); + //TODO: handle the outcome of the action execution here + return; + } + listener.onExistingItemSelected(value); + }); ModelListener modelListener = new ModelListener() { @Override @@ -42,46 +52,45 @@ public class SdkPopupFactory { popup.syncWithModelChange(); } }; - myModel.addModelListener(modelListener); + + myModelBuilder.addModelListener(modelListener); popup.addListener(new JBPopupListener() { @Override public void beforeShown(@NotNull LightweightWindowEvent event) { - myModel.reloadActions(popup.getList(), null); - myModel.detectItems(popup.getList(), popup); + myModelBuilder.reloadActions(popup.getList(), null); + myModelBuilder.detectItems(popup.getList(), popup); } @Override public void onClosed(@NotNull LightweightWindowEvent event) { - myModel.removeListener(modelListener); - onClosed.run(); + myModelBuilder.removeListener(modelListener); + listener.onClosed(); } }); return popup; } - public void showPopup(@NotNull AnActionEvent e, - @NotNull Runnable onClosed) { - ComboBoxPopup popup = createPopup(onClosed); - - if (e instanceof AnActionButton.AnActionEventWrapper) { - ((AnActionButton.AnActionEventWrapper)e).showPopup(popup); - } else { - popup.showInBestPositionFor(e.getDataContext()); + private class SdkPopupImpl extends ComboBoxPopup implements SdkPopup { + SdkPopupImpl(SdkListItemContext context, Consumer onItemSelected) { + super(context, null, onItemSelected); } - } - public void showPopup(@NotNull RelativePoint aPoint, - @NotNull Runnable onClosed) { - createPopup(onClosed).show(aPoint); - } + @Override + public void showPopup(@NotNull AnActionEvent e) { + if (e instanceof AnActionButton.AnActionEventWrapper) { + ((AnActionButton.AnActionEventWrapper)e).showPopup(this); + } else { + showInBestPositionFor(e.getDataContext()); + } + } - public void showUnderneathToTheRightOf(@NotNull Component component, - @NotNull Runnable onClosed) { - ComboBoxPopup popup = createPopup(onClosed); - int popupWidth = popup.getList().getPreferredSize().width; - popup.show(new RelativePoint(component, new Point(component.getWidth() - popupWidth, component.getHeight()))); + @Override + public void showUnderneathToTheRightOf(@NotNull Component component) { + int popupWidth = getList().getPreferredSize().width; + show(new RelativePoint(component, new Point(component.getWidth() - popupWidth, component.getHeight()))); + } } private class SdkListItemContext implements ComboBoxPopup.Context { @@ -120,14 +129,5 @@ public class SdkPopupFactory { public ListCellRenderer getRenderer() { return myRenderer; } - - @Override - public void setSelectedItem(SdkListItem value) { - if (value != null) { - if (value instanceof SdkListItem.ActionableItem) { - ((SdkListItem.ActionableItem)value).executeAction(); - } - } - } } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaJBPopupComboPopup.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaJBPopupComboPopup.java index 1d3ca99cf9c2..59d901911eb8 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaJBPopupComboPopup.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaJBPopupComboPopup.java @@ -66,14 +66,6 @@ public class DarculaJBPopupComboPopup implements ComboPopup, ComboBoxPopup.Co return myComboBox.getRenderer(); } - @Override - public void setSelectedItem(T selectedValue) { - // this call could show some more controls... - ApplicationManager.getApplication().invokeLater(() -> { - myComboBox.setSelectedItem(selectedValue); - }, ModalityState.stateForComponent(myComboBox)); - } - @Override public int getMaximumRowCount() { return Math.max(10, myComboBox.getMaximumRowCount()); @@ -93,7 +85,9 @@ public class DarculaJBPopupComboPopup implements ComboPopup, ComboBoxPopup.Co myPopup.cancel(); } - myPopup = new ComboBoxPopup(this, myComboBox.getSelectedItem()) { + //noinspection unchecked + T selectedItem = (T)myComboBox.getSelectedItem(); + myPopup = new ComboBoxPopup(this, selectedItem, value -> myComboBox.setSelectedItem(value)) { @Override public void cancel(InputEvent e) { if (e instanceof MouseEvent) { diff --git a/platform/platform-impl/src/com/intellij/ui/popup/list/ComboBoxPopup.java b/platform/platform-impl/src/com/intellij/ui/popup/list/ComboBoxPopup.java index a77256fdfafb..07de717017bd 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/list/ComboBoxPopup.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/list/ComboBoxPopup.java @@ -1,6 +1,7 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.ui.popup.list; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.ComboBoxPopupState; import com.intellij.openapi.ui.popup.PopupStep; @@ -9,6 +10,7 @@ import com.intellij.ui.SimpleColoredComponent; import com.intellij.ui.TitledSeparator; import com.intellij.ui.components.JBList; import com.intellij.ui.popup.WizardPopup; +import java.util.function.Consumer; import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -17,14 +19,17 @@ import javax.swing.*; import javax.swing.border.Border; import java.awt.*; import java.util.ArrayList; +import java.util.EventListener; import java.util.List; import java.util.function.Supplier; public class ComboBoxPopup extends ListPopupImpl { private final Context myContext; - public ComboBoxPopup(@NotNull Context context, @Nullable Object selectedItem) { - this(context, null, popupStateFromContext(context, selectedItem), null); + public ComboBoxPopup(@NotNull Context context, + @Nullable T selectedItem, + @NotNull Consumer onItemSelected) { + this(context, null, popupStateFromContext(context, onItemSelected, selectedItem), null); } private ComboBoxPopup(@NotNull Context context, @@ -38,8 +43,11 @@ public class ComboBoxPopup extends ListPopupImpl { @NotNull private static MyBasePopupState popupStateFromContext(@NotNull Context context, + @NotNull Consumer onItemSelected, @Nullable Object selectedItem) { - MyBasePopupState step = new MyBasePopupState(context, () -> context.getModel()) { + MyBasePopupState step = new MyBasePopupState(onItemSelected, + () -> context.getModel(), + () -> context.getRenderer()) { @Override public void canceled() { context.onPopupStepCancelled(); @@ -63,14 +71,16 @@ public class ComboBoxPopup extends ListPopupImpl { @NotNull ListCellRenderer getRenderer(); - void setSelectedItem(T value); - default int getMaximumRowCount() { return 10; } default void onPopupStepCancelled() {} default void configureList(@NotNull JList list) {} default void customizeListRendererComponent(JComponent component) {} } + public interface SelectionListener extends EventListener { + void setSelectedItem(@NotNull T value); + } + public void syncWithModelChange() { //mouse may be under the expandable item, sub-popup would be shown, //this popup could grow/shrink making the sub-popup mispositioned, @@ -163,14 +173,17 @@ public class ComboBoxPopup extends ListPopupImpl { private static class MyBasePopupState extends BaseListPopupStep { private final JBList myProxyList = new JBList<>(); - private final Context myContext; + private final Consumer myOnItemSelected; private final Supplier> myGetComboboxModel; + private final Supplier> myGetRenderer; - private MyBasePopupState(@NotNull Context context, - @NotNull Supplier> getComboboxModel) { + private MyBasePopupState(@NotNull Consumer onItemSelected, + @NotNull Supplier> getComboboxModel, + @NotNull Supplier> getRenderer) { super(null, copyItemsFromModel(getComboboxModel.get())); + myOnItemSelected = onItemSelected; myGetComboboxModel = getComboboxModel; - myContext = context; + myGetRenderer = getRenderer; } @Nullable @@ -182,11 +195,14 @@ public class ComboBoxPopup extends ListPopupImpl { //noinspection unchecked ListModel nextModel = ((ComboBoxPopupState)model).onChosen(selectedValue); if (nextModel != null) { - return new MyBasePopupState<>(myContext, () -> nextModel); + return new MyBasePopupState<>(myOnItemSelected, () -> nextModel, myGetRenderer); } } - myContext.setSelectedItem(selectedValue); + if (selectedValue != null) { + ApplicationManager.getApplication().invokeLater(() -> myOnItemSelected.accept(selectedValue)); + } + return FINAL_CHOICE; } @@ -208,7 +224,7 @@ public class ComboBoxPopup extends ListPopupImpl { @NotNull @Override public String getTextFor(T value) { - Component component = myContext.getRenderer().getListCellRendererComponent(myProxyList, value, -1, false, false); + Component component = myGetRenderer.get().getListCellRendererComponent(myProxyList, value, -1, false, false); return component instanceof TitledSeparator || component instanceof JSeparator ? "" : component instanceof JLabel ? ((JLabel)component).getText() : component instanceof SimpleColoredComponent @@ -218,7 +234,7 @@ public class ComboBoxPopup extends ListPopupImpl { @Override public boolean isSelectable(T value) { - Component component = myContext.getRenderer().getListCellRendererComponent(myProxyList, value, -1, false, false); + Component component = myGetRenderer.get().getListCellRendererComponent(myProxyList, value, -1, false, false); return !(component instanceof TitledSeparator || component instanceof JSeparator); } }