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