some JComboBox usages generified
[idea/community.git] / java / idea-ui / src / com / intellij / facet / impl / ui / libraries / LibraryOptionsPanel.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.facet.impl.ui.libraries;
17
18 import com.intellij.framework.library.DownloadableLibraryDescription;
19 import com.intellij.framework.library.DownloadableLibraryType;
20 import com.intellij.framework.library.FrameworkLibraryVersion;
21 import com.intellij.framework.library.FrameworkLibraryVersionFilter;
22 import com.intellij.icons.AllIcons;
23 import com.intellij.ide.IdeBundle;
24 import com.intellij.ide.util.frameworkSupport.OldCustomLibraryDescription;
25 import com.intellij.openapi.Disposable;
26 import com.intellij.openapi.application.Result;
27 import com.intellij.openapi.application.WriteAction;
28 import com.intellij.openapi.diagnostic.Logger;
29 import com.intellij.openapi.project.DumbModePermission;
30 import com.intellij.openapi.project.DumbService;
31 import com.intellij.openapi.project.Project;
32 import com.intellij.openapi.project.ProjectManager;
33 import com.intellij.openapi.roots.OrderRootType;
34 import com.intellij.openapi.roots.impl.libraries.LibraryEx;
35 import com.intellij.openapi.roots.libraries.Library;
36 import com.intellij.openapi.roots.libraries.NewLibraryConfiguration;
37 import com.intellij.openapi.roots.ui.OrderEntryAppearanceService;
38 import com.intellij.openapi.roots.ui.configuration.libraries.CustomLibraryDescription;
39 import com.intellij.openapi.roots.ui.configuration.libraries.LibraryPresentationManager;
40 import com.intellij.openapi.roots.ui.configuration.libraryEditor.ExistingLibraryEditor;
41 import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditor;
42 import com.intellij.openapi.roots.ui.configuration.libraryEditor.NewLibraryEditor;
43 import com.intellij.openapi.roots.ui.configuration.projectRoot.LibrariesContainer;
44 import com.intellij.openapi.util.Comparing;
45 import com.intellij.openapi.util.Disposer;
46 import com.intellij.openapi.util.NotNullComputable;
47 import com.intellij.openapi.util.io.FileUtil;
48 import com.intellij.openapi.util.text.StringUtil;
49 import com.intellij.openapi.vfs.LocalFileSystem;
50 import com.intellij.openapi.vfs.VirtualFile;
51 import com.intellij.ui.ColoredListCellRenderer;
52 import com.intellij.ui.SortedComboBoxModel;
53 import com.intellij.ui.components.JBLabel;
54 import com.intellij.util.PathUtil;
55 import com.intellij.util.PlatformIcons;
56 import com.intellij.util.containers.ContainerUtil;
57 import com.intellij.util.download.DownloadableFileSetVersions;
58 import com.intellij.util.ui.RadioButtonEnumModel;
59 import com.intellij.xml.util.XmlStringUtil;
60 import org.jetbrains.annotations.NotNull;
61 import org.jetbrains.annotations.Nullable;
62
63 import javax.swing.*;
64 import java.awt.*;
65 import java.awt.event.ActionEvent;
66 import java.awt.event.ActionListener;
67 import java.awt.event.ItemEvent;
68 import java.awt.event.ItemListener;
69 import java.text.MessageFormat;
70 import java.util.ArrayList;
71 import java.util.List;
72
73 /**
74  * @author Dmitry Avdeev
75  */
76 public class LibraryOptionsPanel implements Disposable {
77   private static final Logger LOG = Logger.getInstance("#com.intellij.facet.impl.ui.libraries.LibraryOptionsPanel");
78
79   private JBLabel myMessageLabel;
80   private JPanel myPanel;
81   private JButton myConfigureButton;
82   private JComboBox<LibraryEditor> myExistingLibraryComboBox;
83   private JRadioButton myDoNotCreateRadioButton;
84   private JPanel myConfigurationPanel;
85   private JButton myCreateButton;
86   private JRadioButton myDownloadRadioButton;
87   private JRadioButton myUseLibraryRadioButton;
88   private JLabel myUseLibraryLabel;
89   private JLabel myHiddenLabel;
90   private JPanel myRootPanel;
91   private JRadioButton myUseFromProviderRadioButton;
92   private JPanel mySimplePanel;
93   private ButtonGroup myButtonGroup;
94
95   private LibraryCompositionSettings mySettings;
96   private final CustomLibraryDescription myLibraryDescription;
97   private final LibrariesContainer myLibrariesContainer;
98   private SortedComboBoxModel<LibraryEditor> myLibraryComboBoxModel;
99   private FrameworkLibraryProvider myLibraryProvider;
100   private boolean myDisposed;
101
102   private enum Choice {
103     USE_LIBRARY,
104     DOWNLOAD,
105     SETUP_LIBRARY_LATER,
106     USE_FROM_PROVIDER
107   }
108
109   private RadioButtonEnumModel<Choice> myButtonEnumModel;
110
111   public LibraryOptionsPanel(@NotNull final CustomLibraryDescription libraryDescription,
112                              @NotNull final String path,
113                              @NotNull final FrameworkLibraryVersionFilter versionFilter,
114                              @NotNull final LibrariesContainer librariesContainer,
115                              final boolean showDoNotCreateOption) {
116
117     this(libraryDescription, () -> path, versionFilter, librariesContainer, showDoNotCreateOption);
118   }
119
120   public LibraryOptionsPanel(@NotNull final CustomLibraryDescription libraryDescription,
121                              @NotNull final NotNullComputable<String> pathProvider,
122                              @NotNull final FrameworkLibraryVersionFilter versionFilter,
123                              @NotNull final LibrariesContainer librariesContainer,
124                              final boolean showDoNotCreateOption) {
125     myLibraryDescription = libraryDescription;
126     myLibrariesContainer = librariesContainer;
127     final DownloadableLibraryDescription description = getDownloadableDescription(libraryDescription);
128     if (description != null) {
129       showCard("loading");
130       description.fetchVersions(new DownloadableFileSetVersions.FileSetVersionsCallback<FrameworkLibraryVersion>() {
131         @Override
132         public void onSuccess(@NotNull final List<? extends FrameworkLibraryVersion> versions) {
133           //noinspection SSBasedInspection
134           SwingUtilities.invokeLater(() -> {
135             if (!myDisposed) {
136               showSettingsPanel(libraryDescription, pathProvider, versionFilter, showDoNotCreateOption, versions);
137               onVersionChanged(getPresentableVersion());
138             }
139           });
140         }
141       });
142     }
143     else {
144       showSettingsPanel(libraryDescription, pathProvider, versionFilter, showDoNotCreateOption,
145                         new ArrayList<>());
146     }
147   }
148
149   @Nullable
150   private String getPresentableVersion() {
151     switch (myButtonEnumModel.getSelected()) {
152       case DOWNLOAD:
153         LibraryDownloadSettings settings = mySettings.getDownloadSettings();
154         if (settings != null) {
155           return settings.getVersion().getVersionNumber();
156         }
157         break;
158       case USE_LIBRARY:
159         LibraryEditor item = myLibraryComboBoxModel.getSelectedItem();
160         if (item instanceof ExistingLibraryEditor) {
161           return item.getName();
162         }
163         break;
164       default:
165         return null;
166     }
167     return null;
168   }
169
170   protected void onVersionChanged(@Nullable String version) {
171   }
172
173   public JPanel getSimplePanel() {
174     return mySimplePanel;
175   }
176
177   @Nullable
178   private static DownloadableLibraryDescription getDownloadableDescription(CustomLibraryDescription libraryDescription) {
179     final DownloadableLibraryType type = libraryDescription.getDownloadableLibraryType();
180     if (type != null) return type.getLibraryDescription();
181     if (libraryDescription instanceof OldCustomLibraryDescription) {
182       return ((OldCustomLibraryDescription)libraryDescription).getDownloadableDescription();
183     }
184     return null;
185   }
186
187   private void showCard(final String editing) {
188     ((CardLayout)myRootPanel.getLayout()).show(myRootPanel, editing);
189   }
190
191   private void showSettingsPanel(CustomLibraryDescription libraryDescription,
192                                  NotNullComputable<String> pathProvider,
193                                  FrameworkLibraryVersionFilter versionFilter,
194                                  boolean showDoNotCreateOption, final List<? extends FrameworkLibraryVersion> versions) {
195     //todo[nik] create mySettings only in apply() method
196     mySettings = new LibraryCompositionSettings(libraryDescription, pathProvider, versionFilter, versions);
197     Disposer.register(this, mySettings);
198     List<Library> libraries = calculateSuitableLibraries();
199
200     myButtonEnumModel = RadioButtonEnumModel.bindEnum(Choice.class, myButtonGroup);
201     myButtonEnumModel.addActionListener(new ActionListener() {
202       @Override
203       public void actionPerformed(ActionEvent e) {
204         updateState();
205         onVersionChanged(getPresentableVersion());
206       }
207     });
208
209     myDoNotCreateRadioButton.setVisible(showDoNotCreateOption);
210     myLibraryComboBoxModel = new SortedComboBoxModel<>((o1, o2) -> {
211       final String name1 = o1.getName();
212       final String name2 = o2.getName();
213       return -StringUtil.notNullize(name1).compareToIgnoreCase(StringUtil.notNullize(name2));
214     });
215
216     for (Library library : libraries) {
217       ExistingLibraryEditor libraryEditor = myLibrariesContainer.getLibraryEditor(library);
218       if (libraryEditor == null) {
219         libraryEditor = mySettings.getOrCreateEditor(library);
220       }
221       myLibraryComboBoxModel.add(libraryEditor);
222     }
223     myExistingLibraryComboBox.setModel(myLibraryComboBoxModel);
224     if (libraries.isEmpty()) {
225       myLibraryComboBoxModel.add(null);
226     }
227     myExistingLibraryComboBox.setSelectedIndex(0);
228     myExistingLibraryComboBox.addItemListener(new ItemListener() {
229       @Override
230       public void itemStateChanged(ItemEvent e) {
231         if (e.getStateChange() == ItemEvent.SELECTED && e.getItem() != null) {
232           myButtonEnumModel.setSelected(Choice.USE_LIBRARY);
233         }
234         updateState();
235         onVersionChanged(getPresentableVersion());
236       }
237     });
238     myExistingLibraryComboBox.setRenderer(new ColoredListCellRenderer<LibraryEditor>(myExistingLibraryComboBox) {
239       @Override
240       protected void customizeCellRenderer(@NotNull JList<? extends LibraryEditor> list, LibraryEditor value, int index, boolean selected,
241                                            boolean hasFocus) {
242         if (value == null) {
243           append("[No library selected]");
244         }
245         else if (value instanceof ExistingLibraryEditor) {
246           final Library library = ((ExistingLibraryEditor)value).getLibrary();
247           final boolean invalid = !((LibraryEx)library).getInvalidRootUrls(OrderRootType.CLASSES).isEmpty();
248           OrderEntryAppearanceService.getInstance().forLibrary(getProject(), library, invalid).customize(this);
249         }
250         else if (value instanceof NewLibraryEditor) {
251           setIcon(PlatformIcons.LIBRARY_ICON);
252           final String name = value.getName();
253           append(name != null ? name : "<unnamed>");
254         }
255       }
256     });
257
258     boolean canDownload = mySettings.getDownloadSettings() != null;
259     boolean canUseFromProvider = myLibraryProvider != null;
260     myDownloadRadioButton.setVisible(canDownload);
261     myUseFromProviderRadioButton.setVisible(canUseFromProvider);
262     Choice selectedOption;
263     if (canUseFromProvider) {
264       selectedOption = Choice.USE_FROM_PROVIDER;
265     }
266     else if (libraries.isEmpty() && canDownload) {
267       selectedOption = Choice.DOWNLOAD;
268     }
269     else {
270       selectedOption = Choice.USE_LIBRARY;
271       doCreate(true);
272     }
273     myButtonEnumModel.setSelected(selectedOption);
274
275     if (!canDownload && !canUseFromProvider && !showDoNotCreateOption) {
276       myUseLibraryRadioButton.setVisible(false);
277       myUseLibraryLabel.setVisible(true);
278     }
279     else {
280       myUseLibraryLabel.setVisible(false);
281     }
282
283     final Dimension minimumSize = new Dimension(-1, myMessageLabel.getFontMetrics(myMessageLabel.getFont()).getHeight() * 2);
284     myHiddenLabel.setMinimumSize(minimumSize);
285
286     myCreateButton.addActionListener(new ActionListener() {
287       @Override
288       public void actionPerformed(ActionEvent e) {
289         doCreate(false);
290       }
291     });
292     myConfigureButton.addActionListener(new ActionListener() {
293       public void actionPerformed(final ActionEvent e) {
294         DumbService.allowStartingDumbModeInside(DumbModePermission.MAY_START_BACKGROUND, () -> doConfigure());
295       }
296     });
297     updateState();
298     showCard("editing");
299   }
300
301   private Project getProject() {
302     Project project = myLibrariesContainer.getProject();
303     if (project == null) {
304       project = ProjectManager.getInstance().getDefaultProject();
305     }
306     return project;
307   }
308
309   private void doConfigure() {
310     switch (myButtonEnumModel.getSelected()) {
311       case DOWNLOAD:
312         final LibraryDownloadSettings oldDownloadSettings = mySettings.getDownloadSettings();
313         LOG.assertTrue(oldDownloadSettings != null);
314         final LibraryDownloadSettings newDownloadSettings = DownloadingOptionsDialog.showDialog(myPanel, oldDownloadSettings,
315                                                                                                 mySettings.getCompatibleVersions(), true);
316         if (newDownloadSettings != null) {
317           mySettings.setDownloadSettings(newDownloadSettings);
318         }
319         break;
320
321       case USE_LIBRARY:
322         final Object item = myExistingLibraryComboBox.getSelectedItem();
323         if (item instanceof LibraryEditor) {
324           EditLibraryDialog dialog = new EditLibraryDialog(myPanel, mySettings, (LibraryEditor)item);
325           dialog.show();
326           if (item instanceof ExistingLibraryEditor) {
327             new WriteAction() {
328               protected void run(@NotNull final Result result) {
329                 ((ExistingLibraryEditor)item).commit();
330               }
331             }.execute();
332           }
333         }
334         break;
335
336       case USE_FROM_PROVIDER:
337       case SETUP_LIBRARY_LATER:
338         break;
339     }
340     updateState();
341   }
342
343   public void setLibraryProvider(@Nullable FrameworkLibraryProvider provider) {
344     if (provider != null && !ContainerUtil.intersects(provider.getAvailableLibraryKinds(), myLibraryDescription.getSuitableLibraryKinds())) {
345       provider = null;
346     }
347
348     if (!Comparing.equal(myLibraryProvider, provider)) {
349       myLibraryProvider = provider;
350
351       if (mySettings != null) {
352         if (provider != null && !myUseFromProviderRadioButton.isVisible()) {
353           myUseFromProviderRadioButton.setSelected(true);
354         }
355         myUseFromProviderRadioButton.setVisible(provider != null);
356         updateState();
357       }
358     }
359   }
360
361   public void setVersionFilter(@NotNull FrameworkLibraryVersionFilter versionFilter) {
362     if (mySettings != null) {
363       mySettings.setVersionFilter(versionFilter);
364       updateState();
365     }
366   }
367
368   private void doCreate(boolean useDefaultSettings) {
369     final NewLibraryConfiguration libraryConfiguration = useDefaultSettings
370                                                          ? myLibraryDescription.createNewLibraryWithDefaultSettings(getBaseDirectory())
371                                                          : myLibraryDescription.createNewLibrary(myCreateButton, getBaseDirectory());
372     if (libraryConfiguration != null) {
373       final NewLibraryEditor libraryEditor = new NewLibraryEditor(libraryConfiguration.getLibraryType(), libraryConfiguration.getProperties());
374       libraryEditor.setName(myLibrariesContainer.suggestUniqueLibraryName(libraryConfiguration.getDefaultLibraryName()));
375       libraryConfiguration.addRoots(libraryEditor);
376       if (myLibraryComboBoxModel.get(0) == null) {
377         myLibraryComboBoxModel.remove(0);
378       }
379       myLibraryComboBoxModel.add(libraryEditor);
380       myLibraryComboBoxModel.setSelectedItem(libraryEditor);
381       myButtonEnumModel.setSelected(Choice.USE_LIBRARY);
382     }
383   }
384
385   private List<Library> calculateSuitableLibraries() {
386     List<Library> suitableLibraries = new ArrayList<>();
387     for (Library library : myLibrariesContainer.getAllLibraries()) {
388       if (myLibraryDescription instanceof OldCustomLibraryDescription &&
389           ((OldCustomLibraryDescription)myLibraryDescription).isSuitableLibrary(library, myLibrariesContainer)
390           || LibraryPresentationManager.getInstance().isLibraryOfKind(library, myLibrariesContainer, myLibraryDescription.getSuitableLibraryKinds())) {
391         suitableLibraries.add(library);
392       }
393     }
394     return suitableLibraries;
395   }
396
397   @Nullable
398   private VirtualFile getBaseDirectory() {
399     String path = mySettings.getBaseDirectoryPath();
400     VirtualFile dir = LocalFileSystem.getInstance().findFileByPath(path);
401     if (dir == null) {
402       path = path.substring(0, path.lastIndexOf('/'));
403       dir = LocalFileSystem.getInstance().findFileByPath(path);
404     }
405     return dir;
406   }
407
408   private void updateState() {
409     myMessageLabel.setIcon(null);
410     myConfigureButton.setVisible(true);
411     final LibraryDownloadSettings settings = mySettings.getDownloadSettings();
412     myDownloadRadioButton.setVisible(settings != null);
413     myUseFromProviderRadioButton.setVisible(myLibraryProvider != null);
414     if (!myUseFromProviderRadioButton.isVisible() && myUseFromProviderRadioButton.isSelected()) {
415       if (myDownloadRadioButton.isVisible()) {
416         myDownloadRadioButton.setSelected(true);
417       }
418       else {
419         myUseLibraryRadioButton.setSelected(true);
420       }
421     }
422     if (!myDownloadRadioButton.isVisible() && myDownloadRadioButton.isSelected() && myUseLibraryRadioButton.isVisible()) {
423       myUseLibraryRadioButton.setSelected(true);
424     }
425     String message = "";
426     boolean showConfigurePanel = true;
427     switch (myButtonEnumModel.getSelected()) {
428       case DOWNLOAD:
429         message = getDownloadFilesMessage();
430         break;
431       case USE_FROM_PROVIDER:
432         if (myLibraryProvider != null) {
433           message = "Library from " + myLibraryProvider.getPresentableName() + " will be used";
434         }
435         myConfigureButton.setVisible(false);
436         break;
437       case USE_LIBRARY:
438         final Object item = myExistingLibraryComboBox.getSelectedItem();
439         if (item == null) {
440           myMessageLabel.setIcon(AllIcons.RunConfigurations.ConfigurationWarning);
441           message = "<b>Error:</b> library is not specified";
442           myConfigureButton.setVisible(false);
443         }
444         else if (item instanceof NewLibraryEditor) {
445           final LibraryEditor libraryEditor = (LibraryEditor)item;
446           message = IdeBundle.message("label.library.will.be.created.description.text", mySettings.getNewLibraryLevel(),
447                                       libraryEditor.getName(), libraryEditor.getFiles(OrderRootType.CLASSES).length);
448         }
449         else {
450           message = MessageFormat.format("<b>{0}</b> library will be used", ((ExistingLibraryEditor)item).getName());
451         }
452         break;
453       default:
454         showConfigurePanel = false;
455     }
456
457     if (myLibraryProvider != null) {
458       myUseFromProviderRadioButton.setText("Use library from " + myLibraryProvider.getPresentableName());
459     }
460
461     //show the longest message on the hidden card to ensure that dialog won't jump if user selects another option
462     if (mySettings.getDownloadSettings() != null) {
463       myHiddenLabel.setText(getDownloadFilesMessage());
464     }
465     else {
466       myHiddenLabel.setText(IdeBundle.message("label.library.will.be.created.description.text", mySettings.getNewLibraryLevel(),
467                                               "name", 10));
468     }
469     ((CardLayout)myConfigurationPanel.getLayout()).show(myConfigurationPanel, showConfigurePanel ? "configure" : "empty");
470     myMessageLabel.setText(XmlStringUtil.wrapInHtml(message));
471   }
472
473   private String getDownloadFilesMessage() {
474     final LibraryDownloadSettings downloadSettings = mySettings.getDownloadSettings();
475     if (downloadSettings == null) return "";
476
477     final String downloadPath = downloadSettings.getDirectoryForDownloadedLibrariesPath();
478     final String basePath = mySettings.getBaseDirectoryPath();
479     String path;
480     if (!StringUtil.isEmpty(basePath) && FileUtil.startsWith(downloadPath, basePath)) {
481       path = FileUtil.getRelativePath(basePath, downloadPath, '/');
482     }
483     else {
484       path = PathUtil.getFileName(downloadPath);
485     }
486     return MessageFormat.format("{0} {0, choice, 1#JAR|2#JARs} will be downloaded into <b>{1}</b> directory<br>" +
487                                    "{2} library <b>{3}</b> will be created",
488                                    downloadSettings.getSelectedDownloads().size(),
489                                    path,
490                                    downloadSettings.getLibraryLevel(),
491                                    downloadSettings.getLibraryName());
492   }
493
494   public LibraryCompositionSettings getSettings() {
495     return mySettings;
496   }
497
498   @Nullable
499   public LibraryCompositionSettings apply() {
500     if (mySettings == null) return null;
501
502     final Choice option = myButtonEnumModel.getSelected();
503     mySettings.setDownloadLibraries(option == Choice.DOWNLOAD);
504
505     final Object item = myExistingLibraryComboBox.getSelectedItem();
506     if (option == Choice.USE_LIBRARY && item instanceof ExistingLibraryEditor) {
507       mySettings.setSelectedExistingLibrary(((ExistingLibraryEditor)item).getLibrary());
508     }
509     else {
510       mySettings.setSelectedExistingLibrary(null);
511     }
512
513     if (option == Choice.USE_LIBRARY && item instanceof NewLibraryEditor) {
514       mySettings.setNewLibraryEditor((NewLibraryEditor)item);
515     }
516     else {
517       mySettings.setNewLibraryEditor(null);
518     }
519
520     mySettings.setLibraryProvider(option == Choice.USE_FROM_PROVIDER ? myLibraryProvider : null);
521     return mySettings;
522   }
523
524   public JComponent getMainPanel() {
525     return myRootPanel;
526   }
527
528   @Override
529   public void dispose() {
530     myDisposed = true;
531   }
532 }