sdk-popup - correctly extract selected element from the popup

GitOrigin-RevId: a9b2310b2e44561fed7311158ff1544911a1f32e
This commit is contained in:
Eugene Petrenko
2019-12-18 22:37:16 +00:00
committed by intellij-monorepo-bot
parent 2c34ddf4c9
commit 98b1e28cc4
6 changed files with 123 additions and 81 deletions
@@ -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);
}
}
}
@@ -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<Sdk> 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<UnknownSdk, LocalSdkFix> localFixes) {
@@ -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) {}
}
}
@@ -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<SdkListItem> createPopup(@NotNull Runnable onClosed) {
public SdkPopup createPopup(@NotNull SdkPopupListener listener) {
SdkListItemContext context = new SdkListItemContext();
ComboBoxPopup<SdkListItem> 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<SdkListItem> popup = createPopup(onClosed);
if (e instanceof AnActionButton.AnActionEventWrapper) {
((AnActionButton.AnActionEventWrapper)e).showPopup(popup);
} else {
popup.showInBestPositionFor(e.getDataContext());
private class SdkPopupImpl extends ComboBoxPopup<SdkListItem> implements SdkPopup {
SdkPopupImpl(SdkListItemContext context, Consumer<SdkListItem> 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<SdkListItem> 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<SdkListItem> {
@@ -120,14 +129,5 @@ public class SdkPopupFactory {
public ListCellRenderer<? super SdkListItem> getRenderer() {
return myRenderer;
}
@Override
public void setSelectedItem(SdkListItem value) {
if (value != null) {
if (value instanceof SdkListItem.ActionableItem) {
((SdkListItem.ActionableItem)value).executeAction();
}
}
}
}
}
@@ -66,14 +66,6 @@ public class DarculaJBPopupComboPopup<T> 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<T> implements ComboPopup, ComboBoxPopup.Co
myPopup.cancel();
}
myPopup = new ComboBoxPopup<T>(this, myComboBox.getSelectedItem()) {
//noinspection unchecked
T selectedItem = (T)myComboBox.getSelectedItem();
myPopup = new ComboBoxPopup<T>(this, selectedItem, value -> myComboBox.setSelectedItem(value)) {
@Override
public void cancel(InputEvent e) {
if (e instanceof MouseEvent) {
@@ -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<T> extends ListPopupImpl {
private final Context<T> myContext;
public ComboBoxPopup(@NotNull Context<T> context, @Nullable Object selectedItem) {
this(context, null, popupStateFromContext(context, selectedItem), null);
public ComboBoxPopup(@NotNull Context<T> context,
@Nullable T selectedItem,
@NotNull Consumer<T> onItemSelected) {
this(context, null, popupStateFromContext(context, onItemSelected, selectedItem), null);
}
private ComboBoxPopup(@NotNull Context<T> context,
@@ -38,8 +43,11 @@ public class ComboBoxPopup<T> extends ListPopupImpl {
@NotNull
private static <T> MyBasePopupState<T> popupStateFromContext(@NotNull Context<T> context,
@NotNull Consumer<T> onItemSelected,
@Nullable Object selectedItem) {
MyBasePopupState<T> step = new MyBasePopupState<T>(context, () -> context.getModel()) {
MyBasePopupState<T> step = new MyBasePopupState<T>(onItemSelected,
() -> context.getModel(),
() -> context.getRenderer()) {
@Override
public void canceled() {
context.onPopupStepCancelled();
@@ -63,14 +71,16 @@ public class ComboBoxPopup<T> extends ListPopupImpl {
@NotNull
ListCellRenderer<? super T> getRenderer();
void setSelectedItem(T value);
default int getMaximumRowCount() { return 10; }
default void onPopupStepCancelled() {}
default void configureList(@NotNull JList<T> list) {}
default void customizeListRendererComponent(JComponent component) {}
}
public interface SelectionListener<T> 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<T> extends ListPopupImpl {
private static class MyBasePopupState<T> extends BaseListPopupStep<T> {
private final JBList<T> myProxyList = new JBList<>();
private final Context<T> myContext;
private final Consumer<T> myOnItemSelected;
private final Supplier<ListModel<T>> myGetComboboxModel;
private final Supplier<ListCellRenderer<? super T>> myGetRenderer;
private MyBasePopupState(@NotNull Context<T> context,
@NotNull Supplier<ListModel<T>> getComboboxModel) {
private MyBasePopupState(@NotNull Consumer<T> onItemSelected,
@NotNull Supplier<ListModel<T>> getComboboxModel,
@NotNull Supplier<ListCellRenderer<? super T>> getRenderer) {
super(null, copyItemsFromModel(getComboboxModel.get()));
myOnItemSelected = onItemSelected;
myGetComboboxModel = getComboboxModel;
myContext = context;
myGetRenderer = getRenderer;
}
@Nullable
@@ -182,11 +195,14 @@ public class ComboBoxPopup<T> extends ListPopupImpl {
//noinspection unchecked
ListModel<T> nextModel = ((ComboBoxPopupState<T>)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<T> 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<T> 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);
}
}