terminal: use WinPtyCommand.getCommand to avoid using Win API (EA-235644 (plugin...
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / artifacts / ArtifactsStructureConfigurable.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.openapi.roots.ui.configuration.artifacts;
3
4 import com.intellij.CommonBundle;
5 import com.intellij.ide.JavaUiBundle;
6 import com.intellij.java.JavaBundle;
7 import com.intellij.openapi.actionSystem.AnAction;
8 import com.intellij.openapi.actionSystem.AnActionEvent;
9 import com.intellij.openapi.actionSystem.DefaultActionGroup;
10 import com.intellij.openapi.application.WriteAction;
11 import com.intellij.openapi.extensions.ExtensionPointListener;
12 import com.intellij.openapi.extensions.PluginDescriptor;
13 import com.intellij.openapi.module.Module;
14 import com.intellij.openapi.options.ConfigurationException;
15 import com.intellij.openapi.project.DumbAwareAction;
16 import com.intellij.openapi.project.Project;
17 import com.intellij.openapi.roots.ModifiableRootModel;
18 import com.intellij.openapi.roots.impl.libraries.LibraryTableImplUtil;
19 import com.intellij.openapi.roots.libraries.Library;
20 import com.intellij.openapi.roots.libraries.LibraryTable;
21 import com.intellij.openapi.roots.ui.configuration.ModuleEditor;
22 import com.intellij.openapi.roots.ui.configuration.libraryEditor.LibraryEditorListener;
23 import com.intellij.openapi.roots.ui.configuration.projectRoot.*;
24 import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement;
25 import com.intellij.openapi.ui.MasterDetailsState;
26 import com.intellij.openapi.ui.Messages;
27 import com.intellij.openapi.ui.NamedConfigurable;
28 import com.intellij.openapi.ui.NonEmptyInputValidator;
29 import com.intellij.openapi.util.Comparing;
30 import com.intellij.openapi.util.NlsActions;
31 import com.intellij.packaging.artifacts.*;
32 import com.intellij.packaging.elements.ComplexPackagingElementType;
33 import com.intellij.packaging.elements.CompositePackagingElement;
34 import com.intellij.packaging.elements.PackagingElementType;
35 import com.intellij.packaging.impl.artifacts.ArtifactUtil;
36 import com.intellij.packaging.impl.artifacts.InvalidArtifact;
37 import com.intellij.packaging.impl.artifacts.PackagingElementPath;
38 import com.intellij.packaging.impl.artifacts.PackagingElementProcessor;
39 import com.intellij.packaging.impl.elements.LibraryElementType;
40 import com.intellij.packaging.impl.elements.LibraryPackagingElement;
41 import org.jetbrains.annotations.Nls;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
44
45 import javax.swing.*;
46 import java.util.ArrayList;
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.List;
50
51 public class ArtifactsStructureConfigurable extends BaseStructureConfigurable {
52   private ArtifactsStructureConfigurableContextImpl myPackagingEditorContext;
53   private final ArtifactEditorSettings myDefaultSettings = new ArtifactEditorSettings();
54
55   public ArtifactsStructureConfigurable(@NotNull Project project) {
56     super(project, new ArtifactStructureConfigurableState());
57     PackagingElementType.EP_NAME.getPoint().addExtensionPointListener(new ExtensionPointListener<PackagingElementType>() {
58       @Override
59       public void extensionRemoved(@NotNull PackagingElementType extension, @NotNull PluginDescriptor pluginDescriptor) {
60         if (extension instanceof ComplexPackagingElementType && myDefaultSettings.getTypesToShowContent().contains(extension)) {
61           List<ComplexPackagingElementType<?>> updated = new ArrayList<>(myDefaultSettings.getTypesToShowContent());
62           updated.remove(extension);
63           myDefaultSettings.setTypesToShowContent(updated);
64         }
65       }
66     }, false, this);
67   }
68
69   @Override
70   protected String getComponentStateKey() {
71     return "ArtifactsStructureConfigurable.UI";
72   }
73
74   public void init(StructureConfigurableContext context, ModuleStructureConfigurable moduleStructureConfigurable,
75                    ProjectLibrariesConfigurable projectLibrariesConfig, GlobalLibrariesConfigurable globalLibrariesConfig) {
76     super.init(context);
77     myPackagingEditorContext = new ArtifactsStructureConfigurableContextImpl(myContext, myProject, myDefaultSettings, new ArtifactAdapter() {
78       @Override
79       public void artifactAdded(@NotNull Artifact artifact) {
80         final MyNode node = addArtifactNode(artifact);
81         selectNodeInTree(node);
82         myContext.getDaemonAnalyzer().queueUpdate(myPackagingEditorContext.getOrCreateArtifactElement(artifact));
83       }
84     });
85
86     context.getModulesConfigurator().addAllModuleChangeListener(new ModuleEditor.ChangeListener() {
87       @Override
88       public void moduleStateChanged(ModifiableRootModel moduleRootModel) {
89         for (ProjectStructureElement element : getProjectStructureElements()) {
90           myContext.getDaemonAnalyzer().queueUpdate(element);
91         }
92       }
93     });
94
95     final ItemsChangeListener listener = new ItemsChangeListener() {
96       @Override
97       public void itemChanged(@Nullable Object deletedItem) {
98         if (deletedItem instanceof Library || deletedItem instanceof Module) {
99           onElementDeleted();
100         }
101       }
102     };
103     moduleStructureConfigurable.addItemsChangeListener(listener);
104     projectLibrariesConfig.addItemsChangeListener(listener);
105     globalLibrariesConfig.addItemsChangeListener(listener);
106
107     context.addLibraryEditorListener(new LibraryEditorListener() {
108       @Override
109       public void libraryRenamed(@NotNull Library library, String oldName, String newName) {
110         final Artifact[] artifacts = myPackagingEditorContext.getArtifactModel().getArtifacts();
111         for (Artifact artifact : artifacts) {
112           updateLibraryElements(artifact, library, oldName, newName);
113         }
114       }
115
116     });
117   }
118
119   private void updateLibraryElements(final Artifact artifact, final Library library, final String oldName, final String newName) {
120     if (ArtifactUtil.processPackagingElements(myPackagingEditorContext.getRootElement(artifact), LibraryElementType.LIBRARY_ELEMENT_TYPE,
121                                               new PackagingElementProcessor<LibraryPackagingElement>() {
122                                                 @Override
123                                                 public boolean process(@NotNull LibraryPackagingElement element,
124                                                                        @NotNull PackagingElementPath path) {
125                                                   return !isResolvedToLibrary(element, library, oldName);
126                                                 }
127                                               }, myPackagingEditorContext, false, artifact.getArtifactType())) {
128       return;
129     }
130     myPackagingEditorContext.editLayout(artifact, () -> {
131       final ModifiableArtifact modifiableArtifact = myPackagingEditorContext.getOrCreateModifiableArtifactModel().getOrCreateModifiableArtifact(artifact);
132       ArtifactUtil.processPackagingElements(modifiableArtifact, LibraryElementType.LIBRARY_ELEMENT_TYPE, new PackagingElementProcessor<LibraryPackagingElement>() {
133         @Override
134         public boolean process(@NotNull LibraryPackagingElement element, @NotNull PackagingElementPath path) {
135           if (isResolvedToLibrary(element, library, oldName)) {
136             element.setLibraryName(newName);
137           }
138           return true;
139         }
140       }, myPackagingEditorContext, false);
141     });
142     final ArtifactEditorImpl artifactEditor = myPackagingEditorContext.getArtifactEditor(artifact);
143     if (artifactEditor != null) {
144       artifactEditor.rebuildTries();
145     }
146   }
147
148   private static boolean isResolvedToLibrary(LibraryPackagingElement element, Library library, String name) {
149     if (!element.getLibraryName().equals(name)) {
150       return false;
151     }
152
153     final LibraryTable table = library.getTable();
154     if (table != null) {
155       return table.getTableLevel().equals(element.getLevel());
156     }
157     return element.getLevel().equals(LibraryTableImplUtil.MODULE_LEVEL);
158   }
159
160   private void onElementDeleted() {
161     for (ArtifactEditorImpl editor : myPackagingEditorContext.getArtifactEditors()) {
162       editor.getSourceItemsTree().rebuildTree();
163       editor.queueValidation();
164     }
165   }
166
167   @Override
168   protected MasterDetailsState getState() {
169     ((ArtifactStructureConfigurableState)myState).setDefaultArtifactSettings(myDefaultSettings.getState());
170     return super.getState();
171   }
172
173   @Override
174   public void loadState(MasterDetailsState object) {
175     super.loadState(object);
176     myDefaultSettings.loadState(((ArtifactStructureConfigurableState)myState).getDefaultArtifactSettings());
177   }
178
179   @Override
180   @Nls
181   public String getDisplayName() {
182     return JavaUiBundle.message("display.name.artifacts");
183   }
184
185   @Override
186   protected void loadTree() {
187     myTree.setRootVisible(false);
188     myTree.setShowsRootHandles(false);
189     for (Artifact artifact : myPackagingEditorContext.getArtifactModel().getAllArtifactsIncludingInvalid()) {
190       addArtifactNode(artifact);
191     }
192   }
193
194   @NotNull
195   @Override
196   protected Collection<? extends ProjectStructureElement> getProjectStructureElements() {
197     final List<ProjectStructureElement> elements = new ArrayList<>();
198     for (Artifact artifact : myPackagingEditorContext.getArtifactModel().getAllArtifactsIncludingInvalid()) {
199       elements.add(myPackagingEditorContext.getOrCreateArtifactElement(artifact));
200     }
201     return elements;
202   }
203
204   private MyNode addArtifactNode(final Artifact artifact) {
205     final NamedConfigurable<Artifact> configurable;
206     if (artifact instanceof InvalidArtifact) {
207       configurable = new InvalidArtifactConfigurable((InvalidArtifact)artifact, myPackagingEditorContext, TREE_UPDATER);
208     }
209     else {
210       configurable = new ArtifactConfigurable(artifact, myPackagingEditorContext, TREE_UPDATER);
211     }
212     final MyNode node = new MyNode(configurable);
213     addNode(node, myRoot);
214     return node;
215   }
216
217   @Override
218   public void reset() {
219     loadComponentState();
220     myPackagingEditorContext.resetModifiableModel();
221     super.reset();
222   }
223
224   @Override
225   public boolean isModified() {
226     final ModifiableArtifactModel modifiableModel = myPackagingEditorContext.getActualModifiableModel();
227     if (modifiableModel != null && modifiableModel.isModified()) {
228       return true;
229     }
230     return myPackagingEditorContext.getManifestFilesInfo().isManifestFilesModified() || super.isModified();
231   }
232
233   public ArtifactsStructureConfigurableContext getArtifactsStructureContext() {
234     return myPackagingEditorContext;
235   }
236
237   public ModifiableArtifactModel getModifiableArtifactModel() {
238     return myPackagingEditorContext.getOrCreateModifiableArtifactModel();
239   }
240
241   @Override
242   protected AbstractAddGroup createAddAction() {
243     return new AbstractAddGroup(JavaUiBundle.message("add.new.header.text")) {
244       @Override
245       public AnAction @NotNull [] getChildren(@Nullable AnActionEvent e) {
246         final ArtifactType[] types = ArtifactType.getAllTypes();
247         final AnAction[] actions = new AnAction[types.length];
248         for (int i = 0; i < types.length; i++) {
249           actions[i] = createAddArtifactAction(types[i]);
250         }
251         return actions;
252       }
253     };
254   }
255
256   private AnAction createAddArtifactAction(@NotNull final ArtifactType type) {
257     final List<? extends ArtifactTemplate> templates = type.getNewArtifactTemplates(myPackagingEditorContext);
258     final ArtifactTemplate emptyTemplate = new ArtifactTemplate() {
259       @Override
260       public @Nls(capitalization = Nls.Capitalization.Title) String getPresentableName() {
261         return JavaBundle.message("empty.title");
262       }
263
264       @Override
265       public NewArtifactConfiguration createArtifact() {
266         final String name = "unnamed";
267         return new NewArtifactConfiguration(type.createRootElement(name), name, type);
268       }
269     };
270
271     if (templates.isEmpty()) {
272       return new AddArtifactAction(type, emptyTemplate, type.getPresentableName(), type.getIcon());
273     }
274     final DefaultActionGroup group = DefaultActionGroup.createPopupGroup(() -> type.getPresentableName());
275     group.getTemplatePresentation().setIcon(type.getIcon());
276     group.add(new AddArtifactAction(type, emptyTemplate, emptyTemplate.getPresentableName(), null));
277     group.addSeparator();
278     for (ArtifactTemplate template : templates) {
279       group.add(new AddArtifactAction(type, template, template.getPresentableName(), null));
280     }
281     return group;
282   }
283
284   private void addArtifact(@NotNull ArtifactType type, @NotNull ArtifactTemplate artifactTemplate) {
285     Artifact artifact = ArtifactUtil.addArtifact(myPackagingEditorContext.getOrCreateModifiableArtifactModel(), type, artifactTemplate);
286     selectNodeInTree(findNodeByObject(myRoot, artifact));
287   }
288
289   @NotNull
290   @Override
291   protected List<? extends AnAction> createCopyActions(boolean fromPopup) {
292     final ArrayList<AnAction> actions = new ArrayList<>();
293     actions.add(new CopyArtifactAction());
294     return actions;
295   }
296
297   @Override
298   public void apply() throws ConfigurationException {
299     myPackagingEditorContext.saveEditorSettings();
300     checkForEmptyAndDuplicatedNames(JavaUiBundle.message("configurable.artifact.prefix"), CommonBundle.getErrorTitle(), ArtifactConfigurableBase.class);
301     super.apply();
302
303     myPackagingEditorContext.getManifestFilesInfo().saveManifestFiles();
304     final ModifiableArtifactModel modifiableModel = myPackagingEditorContext.getActualModifiableModel();
305     if (modifiableModel != null) {
306       WriteAction.run(() -> modifiableModel.commit());
307     }
308     myPackagingEditorContext.resetModifiableModel();
309     reloadTreeNodes();
310     restoreLastSelection();
311   }
312
313   @Override
314   public void disposeUIResources() {
315     myPackagingEditorContext.saveEditorSettings();
316     super.disposeUIResources();
317     myPackagingEditorContext.disposeUIResources();
318   }
319
320   @Override
321   protected void updateSelection(@Nullable NamedConfigurable configurable) {
322     boolean selectionChanged = !Comparing.equal(myCurrentConfigurable, configurable);
323     if (selectionChanged && myCurrentConfigurable instanceof ArtifactConfigurable) {
324       ArtifactEditorImpl editor = myPackagingEditorContext.getArtifactEditor(((ArtifactConfigurable)myCurrentConfigurable).getArtifact());
325       if (editor != null) {
326         editor.getLayoutTreeComponent().saveElementProperties();
327       }
328     }
329     super.updateSelection(configurable);
330     if (selectionChanged && configurable instanceof ArtifactConfigurable) {
331       ArtifactEditorImpl editor = myPackagingEditorContext.getArtifactEditor(((ArtifactConfigurable)configurable).getArtifact());
332       if (editor != null) {
333         editor.getLayoutTreeComponent().resetElementProperties();
334       }
335     }
336   }
337
338   @Override
339   public String getHelpTopic() {
340     final String topic = super.getHelpTopic();
341     return topic != null ? topic : "reference.settingsdialog.project.structure.artifacts";
342   }
343
344   @Override
345   protected List<? extends RemoveConfigurableHandler<?>> getRemoveHandlers() {
346     return Collections.singletonList(new ArtifactRemoveHandler());
347   }
348
349   @Override
350   @NotNull
351   public String getId() {
352     return "project.artifacts";
353   }
354
355   @Override
356   public void dispose() {
357   }
358
359   private class ArtifactRemoveHandler extends RemoveConfigurableHandler<Artifact> {
360     ArtifactRemoveHandler() {
361       super(ArtifactConfigurableBase.class);
362     }
363
364     @Override
365     public boolean remove(@NotNull Collection<? extends Artifact> artifacts) {
366       for (Artifact artifact : artifacts) {
367         myPackagingEditorContext.getOrCreateModifiableArtifactModel().removeArtifact(artifact);
368         myContext.getDaemonAnalyzer().removeElement(myPackagingEditorContext.getOrCreateArtifactElement(artifact));
369       }
370       return true;
371     }
372   }
373
374   private class AddArtifactAction extends DumbAwareAction {
375     private final ArtifactType myType;
376     private final ArtifactTemplate myArtifactTemplate;
377
378     AddArtifactAction(@NotNull ArtifactType type, @NotNull ArtifactTemplate artifactTemplate, final @NotNull @NlsActions.ActionText String actionText,
379                              final Icon icon) {
380       super(actionText, null, icon);
381       myType = type;
382       myArtifactTemplate = artifactTemplate;
383     }
384
385     @Override
386     public void actionPerformed(@NotNull AnActionEvent e) {
387       addArtifact(myType, myArtifactTemplate);
388     }
389   }
390
391   private final class CopyArtifactAction extends AnAction {
392    private CopyArtifactAction() {
393       super(CommonBundle.messagePointer("button.copy"), CommonBundle.messagePointer("button.copy"), COPY_ICON);
394     }
395
396     @Override
397     public void actionPerformed(@NotNull final AnActionEvent e) {
398       final Object o = getSelectedObject();
399       if (o instanceof Artifact) {
400         final Artifact selected = (Artifact)o;
401         ModifiableArtifactModel artifactModel = myPackagingEditorContext.getOrCreateModifiableArtifactModel();
402         String suggestedName = ArtifactUtil.generateUniqueArtifactName(selected.getName(), artifactModel);
403         final String newName = Messages.showInputDialog(JavaUiBundle.message("label.enter.artifact.name"),
404                                                         JavaUiBundle.message("dialog.title.copy.artifact"),
405                                                         COPY_ICON,
406                                                         suggestedName,
407                                                         new NonEmptyInputValidator());
408         if (newName == null) return;
409
410         CompositePackagingElement<?> rootCopy = ArtifactUtil.copyFromRoot(selected.getRootElement(), myProject);
411         artifactModel.addArtifact(newName, selected.getArtifactType(), rootCopy);
412       }
413     }
414
415     @Override
416     public void update(@NotNull final AnActionEvent e) {
417       if (myTree.getSelectionPaths() == null || myTree.getSelectionPaths().length != 1) {
418         e.getPresentation().setEnabled(false);
419       }
420       else {
421         e.getPresentation().setEnabled(getSelectedObject() instanceof Artifact);
422       }
423     }
424   }
425 }