package com.intellij.lang.javascript.flex.sdk;
import com.intellij.ide.DataManager;
import com.intellij.lang.javascript.flex.FlexBundle;
import com.intellij.lang.javascript.flex.projectStructure.FlexBuildConfigurationsExtension;
import com.intellij.lang.javascript.flex.projectStructure.model.impl.FlexProjectConfigurationEditor;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.SdkType;
import com.intellij.openapi.projectRoots.SdkTypeId;
import com.intellij.openapi.projectRoots.ui.ProjectJdksEditor;
import com.intellij.openapi.roots.ui.configuration.ProjectJdksConfigurable;
import com.intellij.openapi.roots.ui.configuration.ProjectStructureConfigurable;
import com.intellij.openapi.roots.ui.configuration.projectRoot.JdkListConfigurable;
import com.intellij.openapi.roots.ui.configuration.projectRoot.ProjectSdksModel;
import com.intellij.openapi.ui.MasterDetailsComponent;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.ComboboxWithBrowseButton;
import com.intellij.ui.ListCellRendererWrapper;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
public class FlexSdkComboBoxWithBrowseButton extends ComboboxWithBrowseButton {
public interface Listener {
void stateChanged();
}
// special combobox item that stands for sdk specified by the build configuration
private static class BCSdk {
private Sdk mySdk;
}
public static final Condition<Sdk> FLEX_SDK = sdk -> sdk != null && sdk.getSdkType() instanceof FlexSdkType2;
public static final Condition<Sdk> FLEX_OR_FLEXMOJOS_SDK =
sdk -> sdk != null && (sdk.getSdkType() instanceof FlexSdkType2 || sdk.getSdkType() instanceof FlexmojosSdkType);
public static final String BC_SDK_KEY = "BC SDK";
private final Condition<Sdk> mySdkFilter;
private BCSdk myBCSdk = new BCSdk();
private boolean myShowBCSdk = false;
public FlexSdkComboBoxWithBrowseButton() {
this(FLEX_SDK);
}
public FlexSdkComboBoxWithBrowseButton(final Condition<Sdk> sdkFilter) {
mySdkFilter = sdkFilter;
rebuildSdkListAndSelectSdk(null); // if SDKs exist first will be selected automatically
final JComboBox sdkCombo = getComboBox();
sdkCombo.setRenderer(new ListCellRendererWrapper() {
@Override
public void customize(JList list, Object value, int index, boolean selected, boolean hasFocus) {
if (value instanceof BCSdk) {
final Sdk sdk = ((BCSdk)value).mySdk;
if (sdk == null) {
if (sdkCombo.isEnabled()) {
setText("<html>SDK set for the build configuration <font color='red'>[not set]</font></html>");
setIcon(null);
}
else {
setText("SDK set for the build configuration [not set]");
setIcon(null);
}
}
else {
setText("SDK set for the build configuration [" + sdk.getName() + "]");
setIcon(((SdkType)((BCSdk)value).mySdk.getSdkType()).getIcon());
}
}
else if (value instanceof String) {
if (sdkCombo.isEnabled()) {
setText("<html><font color='red'>" + value + " [Invalid]</font></html>");
setIcon(null);
}
else {
setText(value + " [Invalid]");
setIcon(null);
}
}
else if (value instanceof Sdk) {
setText(((Sdk)value).getName());
setIcon(((SdkType)((Sdk)value).getSdkType()).getIcon());
}
else {
if (sdkCombo.isEnabled()) {
setText("<html><font color='red'>[none]</font></html>");
}
else {
setText("[none]");
}
}
}
});
addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext());
if (project == null) {
project = ProjectManager.getInstance().getDefaultProject();
}
final FlexProjectConfigurationEditor currentFlexEditor =
FlexBuildConfigurationsExtension.getInstance().getConfigurator().getConfigEditor();
ProjectSdksModel sdksModel = ProjectStructureConfigurable.getInstance(project).getProjectJdksModel();
if (currentFlexEditor != null) {
// project structure is open, don't directly commit model
sdksModel = new NonCommittingWrapper(sdksModel, JdkListConfigurable.getInstance(project));
}
final ProjectJdksEditor editor =
new ProjectJdksEditor(null, FlexSdkComboBoxWithBrowseButton.this, new ProjectJdksConfigurable(project, sdksModel));
if (editor.showAndGet()) {
final Sdk selectedSdk = editor.getSelectedJdk();
if (mySdkFilter.value(selectedSdk)) {
rebuildSdkListAndSelectSdk(selectedSdk);
}
else {
rebuildSdkListAndSelectSdk(null);
if (selectedSdk != null) {
Messages
.showErrorDialog(FlexSdkComboBoxWithBrowseButton.this, FlexBundle.message("sdk.can.not.be.selected", selectedSdk.getName()),
FlexBundle.message("select.flex.sdk"));
}
}
}
}
});
}
private void rebuildSdkListAndSelectSdk(@Nullable final Sdk selectedSdk) {
final String previousSelectedSdkName = getSelectedSdkRaw();
final List<Object> sdkList = new ArrayList<>();
if (myShowBCSdk) {
sdkList.add(myBCSdk);
}
final Sdk[] sdks = FlexSdkUtils.getAllSdks();
for (final Sdk sdk : sdks) {
if (mySdkFilter.value(sdk)) {
sdkList.add(sdk);
}
}
if (!sdkList.isEmpty()) {
// sort by version descending, Flexmojos SDKs - to the end of the list
Collections.sort(sdkList, (sdk1, sdk2) -> {
if (sdk1 == myBCSdk && sdk2 != myBCSdk) return -1;
if (sdk1 != myBCSdk && sdk2 == myBCSdk) return 1;
if (sdk1 instanceof Sdk && sdk2 instanceof Sdk) {
final SdkTypeId type1 = ((Sdk)sdk1).getSdkType();
final SdkTypeId type2 = ((Sdk)sdk2).getSdkType();
if (type1 == type2) return -StringUtil.compareVersionNumbers(((Sdk)sdk1).getVersionString(), ((Sdk)sdk2).getVersionString());
if (type1 == FlexSdkType2.getInstance()) return -1;
if (type2 == FlexSdkType2.getInstance()) return 1;
}
return 0;
});
getComboBox().setModel(new DefaultComboBoxModel(ArrayUtil.toObjectArray(sdkList)));
if (selectedSdk != null) {
setSelectedSdkRaw(selectedSdk.getName(), false);
}
else if (previousSelectedSdkName != null) {
setSelectedSdkRaw(previousSelectedSdkName, false);
}
}
else {
getComboBox().setModel(new DefaultComboBoxModel(new Object[]{null}));
}
}
public void addComboboxListener(final Listener listener) {
getComboBox().addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
listener.stateChanged();
}
});
getComboBox().addPropertyChangeListener("model", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
listener.stateChanged();
}
});
}
@Nullable
public Sdk getSelectedSdk() {
final Object selectedItem = getComboBox().getSelectedItem();
if (selectedItem instanceof BCSdk) {
return ((BCSdk)selectedItem).mySdk;
}
else if (selectedItem instanceof Sdk) {
return (Sdk)selectedItem;
}
else {
return null;
}
}
public String getSelectedSdkRaw() {
final Object selectedItem = getComboBox().getSelectedItem();
if (selectedItem instanceof BCSdk) {
return BC_SDK_KEY;
}
else if (selectedItem instanceof Sdk) {
return ((Sdk)selectedItem).getName();
}
else if (selectedItem instanceof String) {
return (String)selectedItem;
}
else {
return "";
}
}
public void setSelectedSdkRaw(final String sdkName) {
setSelectedSdkRaw(sdkName, true);
}
private void setSelectedSdkRaw(final String sdkName, final boolean addErrorItemIfSdkNotFound) {
final JComboBox combo = getComboBox();
if (BC_SDK_KEY.equals(sdkName)) {
combo.setSelectedItem(myBCSdk);
return;
}
else {
for (int i = 0; i < combo.getItemCount(); i++) {
final Object item = combo.getItemAt(i);
if (item instanceof Sdk && ((Sdk)item).getName().equals(sdkName)) {
combo.setSelectedItem(item);
return;
}
}
}
// sdk not found
if (addErrorItemIfSdkNotFound) {
final List<Object> items = new ArrayList<>();
items.add(sdkName);
for (int i = 0; i < combo.getItemCount(); i++) {
final Object item = combo.getItemAt(i);
if (!(item instanceof String)) {
items.add(item);
}
}
combo.setModel(new DefaultComboBoxModel(ArrayUtil.toObjectArray(items)));
}
}
public void showBCSdk(final boolean showBCSdk) {
if (myShowBCSdk != showBCSdk) {
myShowBCSdk = showBCSdk;
final Object selectedItem = getComboBox().getSelectedItem();
rebuildSdkListAndSelectSdk(null);
if (selectedItem instanceof String) {
setSelectedSdkRaw((String)selectedItem, true);
}
}
}
public void setBCSdk(final Sdk sdk) {
if (sdk != myBCSdk.mySdk) {
myBCSdk.mySdk = sdk;
}
}
private static class NonCommittingWrapper extends ProjectSdksModel {
private final ProjectSdksModel myOriginal;
private JdkListConfigurable myConfigurable;
public NonCommittingWrapper(final ProjectSdksModel original, JdkListConfigurable configurable) {
myOriginal = original;
myConfigurable = configurable;
}
public void apply() throws ConfigurationException {
apply(null);
}
public void apply(@Nullable final MasterDetailsComponent configurable) throws ConfigurationException {
myConfigurable.reset(); // just update the view
}
public void reset(@Nullable final Project project) {
// ignore
}
public void addListener(final Listener listener) {
myOriginal.addListener(listener);
}
public void removeListener(final Listener listener) {
myOriginal.removeListener(listener);
}
public Listener getMulticaster() {
return myOriginal.getMulticaster();
}
public Sdk[] getSdks() {
return myOriginal.getSdks();
}
public Sdk findSdk(final String sdkName) {
return myOriginal.findSdk(sdkName);
}
public void disposeUIResources() {
// ignore
}
public HashMap<Sdk, Sdk> getProjectSdks() {
return myOriginal.getProjectSdks();
}
public boolean isModified() {
return myOriginal.isModified();
}
public void removeSdk(final Sdk editableObject) {
myOriginal.removeSdk(editableObject);
}
public void createAddActions(@NotNull final DefaultActionGroup group,
@NotNull final JComponent parent,
@NotNull final Consumer<Sdk> updateTree,
@Nullable final Condition<SdkTypeId> filter) {
myOriginal.createAddActions(group, parent, updateTree, filter);
}
public void doAdd(@NotNull JComponent parent, @NotNull final SdkType type, @NotNull final Consumer<Sdk> updateTree) {
myOriginal.doAdd(parent, type, updateTree);
}
public void addSdk(final Sdk sdk) {
myOriginal.addSdk(sdk);
}
public void doAdd(final Sdk newSdk, @Nullable final Consumer<Sdk> updateTree) {
myOriginal.doAdd(newSdk, updateTree);
}
public Sdk findSdk(@Nullable final Sdk modelJdk) {
return myOriginal.findSdk(modelJdk);
}
public Sdk getProjectSdk() {
return myOriginal.getProjectSdk();
}
public void setProjectSdk(final Sdk projectSdk) {
myOriginal.setProjectSdk(projectSdk);
}
public boolean isInitialized() {
return myOriginal.isInitialized();
}
}
}