Don't show welcome screen in DataGrip on window closing if `Skip welcome screen and...
[idea/community.git] / java / idea-ui / src / com / intellij / ide / util / importProject / ProjectLayoutPanel.java
1 // Copyright 2000-2020 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.ide.util.importProject;
3
4 import com.intellij.CommonBundle;
5 import com.intellij.icons.AllIcons;
6 import com.intellij.ide.JavaUiBundle;
7 import com.intellij.ide.util.ElementsChooser;
8 import com.intellij.ide.util.projectWizard.importSources.DetectedProjectRoot;
9 import com.intellij.openapi.actionSystem.*;
10 import com.intellij.openapi.ui.DialogWrapper;
11 import com.intellij.openapi.ui.InputValidator;
12 import com.intellij.openapi.ui.Messages;
13 import com.intellij.openapi.ui.Splitter;
14 import com.intellij.openapi.ui.ex.MultiLineLabel;
15 import com.intellij.openapi.util.NlsContexts;
16 import com.intellij.openapi.util.NlsSafe;
17 import com.intellij.ui.DocumentAdapter;
18 import com.intellij.ui.IdeBorderFactory;
19 import com.intellij.ui.ScrollPaneFactory;
20 import com.intellij.ui.components.JBList;
21 import com.intellij.util.IconUtil;
22 import com.intellij.util.PlatformIcons;
23 import com.intellij.util.containers.ContainerUtil;
24 import com.intellij.util.ui.FormBuilder;
25 import com.intellij.util.ui.JBUI;
26 import org.jetbrains.annotations.Nls;
27 import org.jetbrains.annotations.NotNull;
28 import org.jetbrains.annotations.Nullable;
29
30 import javax.swing.*;
31 import javax.swing.event.DocumentEvent;
32 import javax.swing.event.ListSelectionEvent;
33 import javax.swing.event.ListSelectionListener;
34 import java.awt.*;
35 import java.awt.event.InputEvent;
36 import java.awt.event.KeyEvent;
37 import java.io.File;
38 import java.util.List;
39 import java.util.*;
40
41 /**
42  * @author Eugene Zhuravlev
43  */
44 abstract class ProjectLayoutPanel<T> extends JPanel {
45
46   private final ElementsChooser<T> myEntriesChooser;
47   private final JList myDependenciesList;
48   private final ModuleInsight myInsight;
49
50   private final Comparator<T> COMPARATOR = (o1, o2) -> {
51     final int w1 = getWeight(o1);
52     final int w2 = getWeight(o2);
53     if (w1 != w2) {
54       return w1 - w2;
55     }
56     return getElementText(o1).compareToIgnoreCase(getElementText(o2));
57   };
58
59   ProjectLayoutPanel(final ModuleInsight insight) {
60     super(new BorderLayout());
61     setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
62     myInsight = insight;
63
64     myEntriesChooser = new ElementsChooser<T>(true) {
65       @Override
66       public String getItemText(@NotNull T element) {
67         return getElementText(element);
68       }
69     };
70     myDependenciesList = createList();
71
72     final Splitter splitter = new Splitter(false);
73
74     final JPanel entriesPanel = new JPanel(new BorderLayout());
75     entriesPanel.add(myEntriesChooser, BorderLayout.CENTER);
76     entriesPanel.setBorder(IdeBorderFactory.createTitledBorder(getElementTypeNamePlural(), false));
77     splitter.setFirstComponent(entriesPanel);
78
79     final JScrollPane depsPane = ScrollPaneFactory.createScrollPane(myDependenciesList);
80     final JPanel depsPanel = new JPanel(new BorderLayout());
81     depsPanel.add(depsPane, BorderLayout.CENTER);
82     depsPanel.setBorder(IdeBorderFactory.createTitledBorder(getDependenciesTitle(), false));
83     splitter.setSecondComponent(depsPanel);
84
85     JPanel groupPanel = new JPanel(new BorderLayout());
86     groupPanel.add(createEntriesActionToolbar().getComponent(), BorderLayout.NORTH);
87     groupPanel.add(splitter, BorderLayout.CENTER);
88     groupPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
89
90     final MultiLineLabel description = new MultiLineLabel(getStepDescriptionText());
91     add(description, BorderLayout.NORTH);
92     add(groupPanel, BorderLayout.CENTER);
93
94     myEntriesChooser.addListSelectionListener(new ListSelectionListener() {
95       @Override
96       public void valueChanged(final ListSelectionEvent e) {
97         if (e.getValueIsAdjusting()) {
98           return;
99         }
100         final List<T> entries = getSelectedEntries();
101         final Collection deps = getDependencies(entries);
102
103         final DefaultListModel depsModel = (DefaultListModel)myDependenciesList.getModel();
104         depsModel.clear();
105         for (Object dep : alphaSortList(new ArrayList(deps))) {
106           depsModel.addElement(dep);
107         }
108       }
109     });
110   }
111
112   private ActionToolbar createEntriesActionToolbar() {
113     final DefaultActionGroup entriesActions = new DefaultActionGroup();
114
115     final RenameAction rename = new RenameAction();
116     rename.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F6, InputEvent.SHIFT_DOWN_MASK)), this);
117     entriesActions.add(rename);
118
119     final MergeAction merge = new MergeAction();
120     merge.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0)), this);
121     entriesActions.add(merge);
122
123     final SplitAction split = new SplitAction();
124     split.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0)), this);
125     entriesActions.add(split);
126
127     return ActionManager.getInstance().createActionToolbar("ProjectLayoutPanel.Entries", entriesActions, true);
128   }
129
130   public final ModuleInsight getInsight() {
131     return myInsight;
132   }
133
134   private JList createList() {
135     final JList list = new JBList(new DefaultListModel());
136     list.getSelectionModel().setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
137     list.setCellRenderer(new MyListCellRenderer());
138     return list;
139   }
140
141   public final Collection getDependencies(final List<? extends T> entries) {
142     final Set deps = new HashSet();
143     for (T et : entries) {
144       deps.addAll(getDependencies(et));
145     }
146     return deps;
147   }
148
149   @NotNull
150   public List<T> getSelectedEntries() {
151     return myEntriesChooser.getSelectedElements();
152   }
153
154   @NotNull
155   public List<T> getChosenEntries() {
156     return myEntriesChooser.getMarkedElements();
157   }
158
159   public void rebuild() {
160     myEntriesChooser.clear();
161     for (final T entry : alphaSortList(getEntries())) {
162       myEntriesChooser.addElement(entry, true, new EntryProperties(entry));
163     }
164     if (myEntriesChooser.getElementCount() > 0) {
165       myEntriesChooser.selectElements(Collections.singleton(myEntriesChooser.getElementAt(0)));
166     }
167   }
168
169   private List<T> alphaSortList(final List<T> entries) {
170     entries.sort(COMPARATOR);
171     return entries;
172   }
173
174   @Nullable
175   protected Icon getElementIcon(Object element) {
176     if (element instanceof ModuleDescriptor) {
177       return ((ModuleDescriptor)element).getModuleType().getIcon();
178     }
179     if (element instanceof LibraryDescriptor) {
180       return PlatformIcons.LIBRARY_ICON;
181     }
182     if (element instanceof File) {
183       final File file = (File)element;
184       return file.isDirectory()? PlatformIcons.FOLDER_ICON : PlatformIcons.JAR_ICON;
185     }
186     return null;
187   }
188
189   protected int getWeight(Object element) {
190     if (element instanceof File) {
191       return 10;
192     }
193     if (element instanceof ModuleDescriptor) {
194       return 20;
195     }
196     if (element instanceof LibraryDescriptor) {
197       return ((LibraryDescriptor)element).getJars().size() > 1? 30 : 40;
198     }
199     return Integer.MAX_VALUE;
200   }
201
202   protected static @NlsSafe String getElementText(Object element) {
203     if (element instanceof LibraryDescriptor) {
204       return getElementTextFromLibraryDescriptor((LibraryDescriptor)element);
205     }
206
207     if (element instanceof File) {
208       return getElementTextFromFile((File)element);
209     }
210
211     if (element instanceof ModuleDescriptor) {
212       final ModuleDescriptor moduleDescriptor = (ModuleDescriptor)element;
213       final StringBuilder builder = new StringBuilder(moduleDescriptor.getName());
214
215       final Set<File> contents = moduleDescriptor.getContentRoots();
216       final int rootCount = contents.size();
217       if (rootCount > 0) {
218         builder.append(" (");
219         builder.append(contents.iterator().next().getPath());
220         if (rootCount > 1) {
221           builder.append("...");
222         }
223         builder.append(")");
224       }
225
226       final Collection<? extends DetectedProjectRoot> sourceRoots = moduleDescriptor.getSourceRoots();
227       if (sourceRoots.size() > 0) {
228         StringJoiner joiner = new StringJoiner(",", " [", "]");
229         for (DetectedProjectRoot root : sourceRoots) {
230           joiner.add(root.getDirectory().getName());
231         }
232         builder.append(joiner);
233       }
234       return builder.toString();
235     }
236
237     return "";
238   }
239
240   @NotNull
241   private static @NlsSafe String getElementTextFromFile(File element) {
242     final File parentFile = element.getParentFile();
243     if (parentFile == null) return element.getName();
244
245     return element.getName() + " (" + parentFile.getPath() + ")";
246   }
247
248   @NotNull
249   private static @NlsSafe String getElementTextFromLibraryDescriptor(LibraryDescriptor element) {
250     final Collection<File> jars = element.getJars();
251     if (jars.size() != 1) return element.getName();
252
253     final File parentFile = jars.iterator().next().getParentFile();
254
255     return element.getName() + " (" + parentFile.getPath() + ")";
256   }
257
258   protected abstract List<T> getEntries();
259
260   protected abstract Collection getDependencies(T entry);
261
262   @Nullable
263   protected abstract T merge(List<? extends T> entries);
264
265   @Nullable
266   protected abstract T split(T entry, String newEntryName, Collection<? extends File> extractedData);
267
268   protected abstract Collection<File> getContent(T entry);
269
270   protected abstract @Nls String getElementName(T entry);
271
272   protected abstract void setElementName(T entry, String name);
273
274   protected abstract @NlsContexts.Label String getSplitDialogChooseFilesPrompt();
275
276   protected abstract @NlsContexts.DialogMessage String getNameAlreadyUsedMessage(final String name);
277
278   protected abstract @NlsContexts.DialogMessage String getStepDescriptionText();
279
280   protected abstract @NlsContexts.TabTitle String getEntriesChooserTitle();
281
282   protected abstract @Nls(capitalization = Nls.Capitalization.Title) String getDependenciesTitle();
283
284   protected abstract @Nls String getElementTypeName();
285
286   protected abstract @Nls(capitalization = Nls.Capitalization.Title) String getElementTypeNameTitle();
287
288   protected abstract @Nls(capitalization = Nls.Capitalization.Title) String getElementTypeNamePlural();
289
290   private boolean isNameAlreadyUsed(String entryName) {
291     final Set<T> itemsToIgnore = new HashSet<>(myEntriesChooser.getSelectedElements());
292     for (T entry : getEntries()) {
293       if (itemsToIgnore.contains(entry)) {
294         continue;
295       }
296       if (entryName.equals(getElementName(entry))) {
297         return true;
298       }
299     }
300     return false;
301   }
302
303   @NotNull
304   private InputValidator getValidator() {
305     return new InputValidator() {
306       @Override
307       public boolean checkInput(final String inputString) {
308         return true;
309       }
310
311       @Override
312       public boolean canClose(final String inputString) {
313         if (isNameAlreadyUsed(inputString.trim())) {
314           Messages.showErrorDialog(getNameAlreadyUsedMessage(inputString), "");
315           return false;
316         }
317         return true;
318       }
319     };
320   }
321
322   private final class MergeAction extends AnAction {
323     private MergeAction() {
324       super(CommonBundle.messagePointer("action.text.merge"), () -> "", AllIcons.Vcs.Merge); // todo
325     }
326
327     @Override
328     public void actionPerformed(@NotNull final AnActionEvent e) {
329       final List<T> elements = myEntriesChooser.getSelectedElements();
330       if (elements.size() > 1) {
331         final String newName = Messages.showInputDialog(
332           ProjectLayoutPanel.this,
333           JavaUiBundle.message("label.enter.new.name.for.merge.result"),
334           JavaUiBundle.message("dialog.title.merge.module.or.library"),
335           Messages.getQuestionIcon(),
336           getElementName(elements.get(0)),
337           getValidator());
338         if (newName != null) {
339           final T merged = merge(elements);
340           setElementName(merged, newName);
341           for (T element : elements) {
342             myEntriesChooser.removeElement(element);
343           }
344           myEntriesChooser.addElement(merged, true, new EntryProperties(merged));
345           myEntriesChooser.sort(COMPARATOR);
346           myEntriesChooser.selectElements(Collections.singleton(merged));
347         }
348       }
349     }
350
351     @Override
352     public void update(@NotNull final AnActionEvent e) {
353       e.getPresentation().setEnabled(myEntriesChooser.getSelectedElements().size() > 1);
354     }
355
356   }
357
358   private final class SplitAction extends AnAction {
359     private SplitAction() {
360       super(CommonBundle.messagePointer("action.text.split"), () -> "", AllIcons.Modules.Split); // todo
361     }
362
363     @Override
364     public void actionPerformed(@NotNull final AnActionEvent e) {
365       final List<T> elements = myEntriesChooser.getSelectedElements();
366
367       if (elements.size() == 1) {
368         final T entry = elements.get(0);
369         final Collection<File> files = getContent(entry);
370
371         final SplitDialog dialog = new SplitDialog(files);
372         if (dialog.showAndGet()) {
373           final String newName = dialog.getName();
374           final Collection<File> chosenFiles = dialog.getChosenFiles();
375
376           final T extracted = split(entry, newName, chosenFiles);
377           if (extracted != null) {
378             if (!getEntries().contains(entry)) {
379               myEntriesChooser.removeElement(entry);
380             }
381             myEntriesChooser.addElement(extracted, true, new EntryProperties(extracted));
382             myEntriesChooser.sort(COMPARATOR);
383             myEntriesChooser.selectElements(Collections.singleton(extracted));
384           }
385         }
386       }
387     }
388     @Override
389     public void update(@NotNull final AnActionEvent e) {
390       final List<T> elements = myEntriesChooser.getSelectedElements();
391       e.getPresentation().setEnabled(elements.size() == 1 && getContent(elements.get(0)).size() > 1);
392     }
393   }
394
395   private final class RenameAction extends AnAction {
396     private RenameAction() {
397       super(CommonBundle.messagePointer("action.text.rename"), () -> "", IconUtil.getEditIcon()); // todo
398     }
399
400     @Override
401     public void actionPerformed(@NotNull final AnActionEvent e) {
402       final List<T> elements = myEntriesChooser.getSelectedElements();
403       if (elements.size() == 1) {
404         final T element = elements.get(0);
405         final String newName = Messages.showInputDialog(ProjectLayoutPanel.this,
406                                                         JavaUiBundle.message("label.new.name.for.0.1", getElementTypeName(), getElementName(element)),
407                                                         JavaUiBundle.message("dialog.title.rename.module.or.library.0", getElementTypeNameTitle()),
408                                                         Messages.getQuestionIcon(),
409                                                         getElementName(element),
410                                                         getValidator()
411         );
412         if (newName != null) {
413           setElementName(element, newName);
414           myEntriesChooser.sort(COMPARATOR);
415           myEntriesChooser.selectElements(Collections.singleton(element));
416         }
417       }
418     }
419
420     @Override
421     public void update(@NotNull final AnActionEvent e) {
422       e.getPresentation().setEnabled(myEntriesChooser.getSelectedElements().size() == 1);
423     }
424   }
425
426   private class MyListCellRenderer extends DefaultListCellRenderer {
427     @Override
428     public Component getListCellRendererComponent(final JList list, final Object value, final int index, final boolean isSelected, final boolean cellHasFocus) {
429       final Component comp = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
430       setText(getElementText(value));
431       setIcon(getElementIcon(value));
432       return comp;
433     }
434   }
435
436   private final class SplitDialog extends DialogWrapper {
437     final JTextField myNameField;
438     final ElementsChooser<File> myChooser;
439
440     private SplitDialog(final Collection<File> files) {
441       super(myEntriesChooser, true);
442       setTitle(JavaUiBundle.message("dialog.title.split.module.or.library.0", getElementTypeNameTitle()));
443
444       myNameField = new JTextField();
445       myChooser = new ElementsChooser<File>(true) {
446         @Override
447         protected String getItemText(@NotNull final File value) {
448           return getElementText(value);
449         }
450       };
451       for (final File file : files) {
452         myChooser.addElement(file, false, new ElementsChooser.ElementProperties() {
453           @Override
454           public Icon getIcon() {
455             return getElementIcon(file);
456           }
457         });
458       }
459       myChooser.selectElements(ContainerUtil.createMaybeSingletonList(ContainerUtil.getFirstItem(files)));
460       myChooser.addElementsMarkListener(new ElementsChooser.ElementsMarkListener<File>() {
461         @Override
462         public void elementMarkChanged(File element, boolean isMarked) {
463           updateOkButton();
464         }
465       });
466       myNameField.getDocument().addDocumentListener(new DocumentAdapter() {
467         @Override
468         protected void textChanged(@NotNull DocumentEvent e) {
469           updateOkButton();
470         }
471       });
472
473       init();
474       updateOkButton();
475     }
476
477     private void updateOkButton() {
478       setOKActionEnabled(!getName().isEmpty() && !getChosenFiles().isEmpty());
479     }
480
481     @Override
482     protected void doOKAction() {
483       final String name = getName();
484       if (isNameAlreadyUsed(name)) {
485         Messages.showErrorDialog(getNameAlreadyUsedMessage(name), "");
486         return;
487       }
488       super.doOKAction();
489     }
490
491     @Override
492     @Nullable
493     protected JComponent createCenterPanel() {
494       FormBuilder builder = FormBuilder.createFormBuilder().setVertical(true);
495       builder.addLabeledComponent(JavaUiBundle.message("label.project.layout.panel.name"), myNameField);
496       builder.addLabeledComponent(getSplitDialogChooseFilesPrompt(), myChooser);
497       myChooser.setPreferredSize(JBUI.size(450, 300));
498       return builder.getPanel();
499     }
500
501     @Override
502     public JComponent getPreferredFocusedComponent() {
503       return myNameField;
504     }
505
506     public String getName() {
507       return myNameField.getText().trim();
508     }
509
510     public Collection<File> getChosenFiles() {
511       return myChooser.getMarkedElements();
512     }
513   }
514
515   private class EntryProperties implements ElementsChooser.ElementProperties {
516     private final T myEntry;
517
518     EntryProperties(final T entry) {
519       myEntry = entry;
520     }
521
522     @Override
523     public Icon getIcon() {
524       return getElementIcon(myEntry);
525     }
526   }
527 }