10899d84a6cce27b7be1105f6d1b08165fd8dee2
[idea/community.git] / java / idea-ui / src / com / intellij / openapi / roots / ui / configuration / artifacts / LayoutTreeComponent.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.ide.JavaUiBundle;
5 import com.intellij.ide.dnd.DnDEvent;
6 import com.intellij.ide.dnd.DnDManager;
7 import com.intellij.ide.dnd.DnDTarget;
8 import com.intellij.openapi.Disposable;
9 import com.intellij.openapi.application.ApplicationManager;
10 import com.intellij.openapi.roots.ui.configuration.artifacts.nodes.ArtifactRootNode;
11 import com.intellij.openapi.roots.ui.configuration.artifacts.nodes.PackagingElementNode;
12 import com.intellij.openapi.roots.ui.configuration.artifacts.nodes.PackagingNodeSource;
13 import com.intellij.openapi.roots.ui.configuration.artifacts.nodes.PackagingTreeNodeFactory;
14 import com.intellij.openapi.ui.Messages;
15 import com.intellij.openapi.util.Comparing;
16 import com.intellij.openapi.util.Disposer;
17 import com.intellij.openapi.util.text.StringUtil;
18 import com.intellij.packaging.artifacts.Artifact;
19 import com.intellij.packaging.artifacts.ArtifactType;
20 import com.intellij.packaging.elements.CompositePackagingElement;
21 import com.intellij.packaging.elements.PackagingElement;
22 import com.intellij.packaging.elements.PackagingElementFactory;
23 import com.intellij.packaging.elements.PackagingElementType;
24 import com.intellij.packaging.impl.elements.DirectoryPackagingElement;
25 import com.intellij.packaging.ui.ArtifactEditorContext;
26 import com.intellij.packaging.ui.PackagingElementPropertiesPanel;
27 import com.intellij.packaging.ui.PackagingSourceItem;
28 import com.intellij.ui.ScrollPaneFactory;
29 import com.intellij.ui.awt.RelativeRectangle;
30 import com.intellij.ui.border.CustomLineBorder;
31 import com.intellij.ui.tree.AsyncTreeModel;
32 import com.intellij.ui.tree.StructureTreeModel;
33 import com.intellij.ui.tree.TreeVisitor;
34 import com.intellij.ui.treeStructure.SimpleTreeStructure;
35 import com.intellij.ui.treeStructure.WeightBasedComparator;
36 import com.intellij.util.containers.ContainerUtil;
37 import com.intellij.util.io.URLUtil;
38 import com.intellij.util.ui.JBUI;
39 import com.intellij.util.ui.tree.TreeUtil;
40 import org.jetbrains.annotations.NonNls;
41 import org.jetbrains.annotations.NotNull;
42 import org.jetbrains.annotations.Nullable;
43 import org.jetbrains.annotations.TestOnly;
44 import org.jetbrains.concurrency.Promise;
45 import org.jetbrains.concurrency.Promises;
46
47 import javax.swing.*;
48 import javax.swing.event.TreeSelectionEvent;
49 import javax.swing.event.TreeSelectionListener;
50 import javax.swing.tree.DefaultMutableTreeNode;
51 import javax.swing.tree.TreePath;
52 import java.awt.*;
53 import java.util.List;
54 import java.util.*;
55 import java.util.function.Predicate;
56
57 public class LayoutTreeComponent implements DnDTarget, Disposable {
58   @NonNls private static final String EMPTY_CARD = "<empty>";
59   @NonNls private static final String PROPERTIES_CARD = "properties";
60   private final ArtifactEditorImpl myArtifactsEditor;
61   private final LayoutTree myTree;
62   private final JPanel myTreePanel;
63   private final ComplexElementSubstitutionParameters mySubstitutionParameters;
64   private final ArtifactEditorContext myContext;
65   private final Artifact myOriginalArtifact;
66   private final StructureTreeModel<LayoutTreeStructure> myStructureTreeModel;
67   private SelectedElementInfo<?> mySelectedElementInfo = new SelectedElementInfo<>(null);
68   private JPanel myPropertiesPanelWrapper;
69   private JPanel myPropertiesPanel;
70   private boolean mySortElements;
71   private final LayoutTreeStructure myTreeStructure;
72
73   public LayoutTreeComponent(ArtifactEditorImpl artifactsEditor, ComplexElementSubstitutionParameters substitutionParameters,
74                              ArtifactEditorContext context, Artifact originalArtifact, boolean sortElements) {
75     myArtifactsEditor = artifactsEditor;
76     mySubstitutionParameters = substitutionParameters;
77     myContext = context;
78     myOriginalArtifact = originalArtifact;
79     mySortElements = sortElements;
80     myTreeStructure = new LayoutTreeStructure();
81     myStructureTreeModel = new StructureTreeModel<>(myTreeStructure, getComparator(), this);
82     myTree = new LayoutTree(myArtifactsEditor, myStructureTreeModel);
83     myTree.setModel(new AsyncTreeModel(myStructureTreeModel, this));
84     Disposer.register(this, myTree);
85
86     myTree.addTreeSelectionListener(new TreeSelectionListener() {
87       @Override
88       public void valueChanged(TreeSelectionEvent e) {
89         updatePropertiesPanel(false);
90       }
91     });
92     createPropertiesPanel();
93     myTreePanel = new JPanel(new BorderLayout());
94     myTreePanel.add(ScrollPaneFactory.createScrollPane(myTree), BorderLayout.CENTER);
95     myTreePanel.add(myPropertiesPanelWrapper, BorderLayout.SOUTH);
96     if (!ApplicationManager.getApplication().isUnitTestMode()) {
97       DnDManager.getInstance().registerTarget(this, myTree);
98     }
99   }
100
101   @Nullable
102   private WeightBasedComparator getComparator() {
103     return mySortElements ? new WeightBasedComparator(true) : null;
104   }
105
106   public void setSortElements(boolean sortElements) {
107     mySortElements = sortElements;
108     myStructureTreeModel.setComparator(getComparator());
109     myArtifactsEditor.getContext().getParent().getDefaultSettings().setSortElements(sortElements);
110   }
111
112   @Nullable
113   private static PackagingElementNode getNode(Object value) {
114     if (!(value instanceof DefaultMutableTreeNode)) return null;
115     final Object userObject = ((DefaultMutableTreeNode)value).getUserObject();
116     return userObject instanceof PackagingElementNode ? (PackagingElementNode)userObject : null;
117   }
118
119   private void createPropertiesPanel() {
120     myPropertiesPanel = new JPanel(new BorderLayout());
121     final JPanel emptyPanel = new JPanel();
122     emptyPanel.setMinimumSize(JBUI.emptySize());
123     emptyPanel.setPreferredSize(JBUI.emptySize());
124
125     myPropertiesPanelWrapper = new JPanel(new CardLayout());
126     myPropertiesPanel.setBorder(new CustomLineBorder(1, 0, 0, 0));
127     myPropertiesPanelWrapper.add(EMPTY_CARD, emptyPanel);
128     myPropertiesPanelWrapper.add(PROPERTIES_CARD, myPropertiesPanel);
129   }
130
131   public Artifact getArtifact() {
132     return myArtifactsEditor.getArtifact();
133   }
134
135   public LayoutTree getLayoutTree() {
136     return myTree;
137   }
138
139   public void updatePropertiesPanel(final boolean force) {
140     final PackagingElement<?> selected = getSelection().getElementIfSingle();
141     if (!force && Comparing.equal(selected, mySelectedElementInfo.myElement)) {
142       return;
143     }
144     mySelectedElementInfo.save();
145     mySelectedElementInfo = new SelectedElementInfo<PackagingElement<?>>(selected);
146     mySelectedElementInfo.showPropertiesPanel();
147   }
148
149   public void saveElementProperties() {
150     mySelectedElementInfo.save();
151   }
152
153   public void rebuildTree() {
154     myTreeStructure.clearCaches();
155     myStructureTreeModel.invalidate();
156     updatePropertiesPanel(true);
157     myArtifactsEditor.queueValidation();
158   }
159
160   public LayoutTreeSelection getSelection() {
161     return myTree.getSelection();
162   }
163
164   public void addNewPackagingElement(@NotNull PackagingElementType<?> type) {
165     PackagingElementNode<?> parentNode = getParentNode(myTree.getSelection());
166     final PackagingElement<?> element = parentNode.getFirstElement();
167     final CompositePackagingElement<?> parent;
168     if (element instanceof CompositePackagingElement<?>) {
169       parent = (CompositePackagingElement<?>)element;
170     }
171     else {
172       parent = getArtifact().getRootElement();
173       parentNode = getRootNode();
174     }
175     if (!checkCanAdd(parent, parentNode)) return;
176
177     final List<? extends PackagingElement<?>> children = type.chooseAndCreate(myContext, getArtifact(), parent);
178     final PackagingElementNode<?> finalParentNode = parentNode;
179     editLayout(() -> {
180       CompositePackagingElement<?> actualParent = getOrCreateModifiableParent(parent, finalParentNode);
181       for (PackagingElement<?> child : children) {
182         actualParent.addOrFindChild(child);
183       }
184     });
185     updateAndSelect(parentNode, children);
186   }
187
188   private static CompositePackagingElement<?> getOrCreateModifiableParent(CompositePackagingElement<?> parentElement, PackagingElementNode<?> node) {
189     PackagingElementNode<?> current = node;
190     List<String> dirNames = new ArrayList<>();
191     while (current != null && !(current instanceof ArtifactRootNode)) {
192       final PackagingElement<?> packagingElement = current.getFirstElement();
193       if (!(packagingElement instanceof DirectoryPackagingElement)) {
194         return parentElement;
195       }
196       dirNames.add(((DirectoryPackagingElement)packagingElement).getDirectoryName());
197       current = current.getParentNode();
198     }
199
200     if (current == null) return parentElement;
201     final PackagingElement<?> rootElement = current.getElementIfSingle();
202     if (!(rootElement instanceof CompositePackagingElement<?>)) return parentElement;
203
204     Collections.reverse(dirNames);
205     String path = StringUtil.join(dirNames, "/");
206     return PackagingElementFactory.getInstance().getOrCreateDirectory((CompositePackagingElement<?>)rootElement, path);
207   }
208
209   public boolean checkCanModify(@NotNull PackagingElement<?> element, @NotNull PackagingElementNode<?> node) {
210     return checkCanModify(node.getNodeSource(element));
211   }
212
213   public boolean checkCanModifyChildren(@NotNull PackagingElement<?> parentElement,
214                                         @NotNull PackagingElementNode<?> parentNode,
215                                         @NotNull Collection<? extends PackagingElementNode<?>> children) {
216     final List<PackagingNodeSource> sources = new ArrayList<>(parentNode.getNodeSource(parentElement));
217     for (PackagingElementNode<?> child : children) {
218       sources.addAll(child.getNodeSources());
219     }
220     return checkCanModify(sources);
221   }
222
223   public boolean checkCanModify(final Collection<? extends PackagingNodeSource> nodeSources) {
224     if (nodeSources.isEmpty()) {
225       return true;
226     }
227
228     if (nodeSources.size() > 1) {
229       Messages.showErrorDialog(myArtifactsEditor.getMainComponent(),
230                                JavaUiBundle.message(
231                                  "error.message.the.selected.node.consist.of.several.elements.so.it.cannot.be.edited"));
232     }
233     else {
234     final PackagingNodeSource source = ContainerUtil.getFirstItem(nodeSources, null);
235       if (source != null) {
236         Messages.showErrorDialog(myArtifactsEditor.getMainComponent(),
237                                  JavaUiBundle.message(
238                                    "error.message.the.selected.node.belongs.to.0.element.so.it.cannot.be.edited",
239                                    source.getPresentableName()));
240       }
241     }
242     return false;
243
244   }
245
246   public boolean checkCanAdd(CompositePackagingElement<?> parentElement, PackagingElementNode<?> parentNode) {
247     boolean allParentsAreDirectories = true;
248     PackagingElementNode<?> current = parentNode;
249     while (current != null && !(current instanceof ArtifactRootNode)) {
250       final PackagingElement<?> element = current.getFirstElement();
251       if (!(element instanceof DirectoryPackagingElement)) {
252         allParentsAreDirectories = false;
253         break;
254       }
255       current = current.getParentNode();
256     }
257
258     return allParentsAreDirectories || checkCanModify(parentElement, parentNode);
259   }
260
261   public boolean checkCanRemove(final List<? extends PackagingElementNode<?>> nodes) {
262     Set<PackagingNodeSource> rootSources = new HashSet<>();
263     for (PackagingElementNode<?> node : nodes) {
264       rootSources.addAll(getRootNodeSources(node.getNodeSources()));
265     }
266
267     if (!rootSources.isEmpty()) {
268       final String message;
269       if (rootSources.size() == 1) {
270         final String name = rootSources.iterator().next().getPresentableName();
271         message = "The selected node belongs to '" + name + "' element. Do you want to remove the whole '" + name + "' element from the artifact?";
272       }
273       else {
274         message = "The selected node belongs to " + nodes.size() + " elements. Do you want to remove all these elements from the artifact?";
275       }
276       final int answer = Messages.showYesNoDialog(myArtifactsEditor.getMainComponent(), message, JavaUiBundle.message(
277         "dialog.title.remove.elements"), null);
278       if (answer != Messages.YES) return false;
279     }
280     return true;
281   }
282
283   public void updateAndSelect(PackagingElementNode<?> node, final List<? extends PackagingElement<?>> toSelect) {
284     myArtifactsEditor.queueValidation();
285     myTreeStructure.clearCaches();
286     List<PackagingElementNode<?>> nodesToSelect = Collections.synchronizedList(new ArrayList<>(toSelect.size()));
287     myStructureTreeModel.invalidate(node, true)
288       .thenAsync(result -> TreeUtil.promiseVisit(myTree, (path) -> {
289         Object nodeObject = TreeUtil.getLastUserObject(path);
290         if (nodeObject instanceof PackagingElementNode && ContainerUtil.intersects(((PackagingElementNode<?>)nodeObject).getPackagingElements(), toSelect)) {
291           nodesToSelect.add((PackagingElementNode)nodeObject);
292         }
293         return TreeVisitor.Action.CONTINUE;
294       }))
295       .thenAsync(result -> Promises.collectResults(ContainerUtil.map(nodesToSelect, nodeToSelect -> myStructureTreeModel.promiseVisitor(nodeToSelect))))
296       .thenAsync(visitors -> TreeUtil.promiseSelect(myTree, visitors.stream()));
297   }
298
299   public Promise<TreePath> selectNode(@NotNull String parentPath, @NotNull PackagingElement<?> element) {
300     Predicate<PackagingElementNode<?>> filter = node -> node.getPackagingElements().stream().anyMatch(element::isEqualTo);
301     return TreeUtil.promiseSelect(myTree, myTree.createVisitorCompositeNodeChild(parentPath, filter));
302   }
303
304   @TestOnly
305   public Promise<TreePath> selectNode(@NotNull String parentPath, @NotNull String nodeName) {
306     Predicate<PackagingElementNode<?>> filter = node -> node.getElementPresentation().getSearchName().equals(nodeName);
307     return TreeUtil.promiseSelect(myTree, myTree.createVisitorCompositeNodeChild(parentPath, filter));
308   }
309
310   public void editLayout(Runnable action) {
311     myContext.editLayout(myOriginalArtifact, action);
312   }
313
314   public void removeSelectedElements() {
315     final LayoutTreeSelection selection = myTree.getSelection();
316     if (!checkCanRemove(selection.getNodes())) return;
317
318     editLayout(() -> removeNodes(selection.getNodes()));
319
320     myArtifactsEditor.rebuildTries();
321   }
322
323   public void removeNodes(final List<? extends PackagingElementNode<?>> nodes) {
324     Set<PackagingElementNode<?>> parents = new HashSet<>();
325     for (PackagingElementNode<?> node : nodes) {
326       final List<? extends PackagingElement<?>> toDelete = node.getPackagingElements();
327       for (PackagingElement<?> element : toDelete) {
328         final Collection<PackagingNodeSource> nodeSources = node.getNodeSource(element);
329         if (nodeSources.isEmpty()) {
330           final CompositePackagingElement<?> parent = node.getParentElement(element);
331           if (parent != null) {
332             ContainerUtil.addIfNotNull(parents, node.getParentNode());
333             parent.removeChild(element);
334           }
335         }
336         else {
337           Collection<PackagingNodeSource> rootSources = getRootNodeSources(nodeSources);
338           for (PackagingNodeSource source : rootSources) {
339             parents.add(source.getSourceParentNode());
340             source.getSourceParentElement().removeChild(source.getSourceElement());
341           }
342         }
343       }
344     }
345     for (PackagingElementNode<?> parent : parents) {
346       myTree.addSubtreeToUpdate(parent);
347     }
348   }
349
350   private static Collection<PackagingNodeSource> getRootNodeSources(Collection<? extends PackagingNodeSource> nodeSources) {
351     Set<PackagingNodeSource> result = new HashSet<>();
352     collectRootNodeSources(nodeSources, result);
353     return result;
354   }
355
356   private static void collectRootNodeSources(Collection<? extends PackagingNodeSource> nodeSources, Set<? super PackagingNodeSource> result) {
357     for (PackagingNodeSource nodeSource : nodeSources) {
358       final Collection<PackagingNodeSource> parentSources = nodeSource.getParentSources();
359       if (parentSources.isEmpty()) {
360         result.add(nodeSource);
361       }
362       else {
363         collectRootNodeSources(parentSources, result);
364       }
365     }
366   }
367
368   private PackagingElementNode<?> getParentNode(final LayoutTreeSelection selection) {
369     final PackagingElementNode<?> node = selection.getNodeIfSingle();
370     if (node != null) {
371       if (node.getFirstElement() instanceof CompositePackagingElement) {
372         return node;
373       }
374       final PackagingElementNode<?> parent = node.getParentNode();
375       if (parent != null) {
376         return parent;
377       }
378     }
379     return getRootNode();
380   }
381
382   public JPanel getTreePanel() {
383     return myTreePanel;
384   }
385
386   @Override
387   public void dispose() {
388     if (!ApplicationManager.getApplication().isUnitTestMode()) {
389       DnDManager.getInstance().unregisterTarget(this, myTree);
390     }
391   }
392
393   @Override
394   public boolean update(DnDEvent aEvent) {
395     aEvent.setDropPossible(false);
396     aEvent.hideHighlighter();
397     final Object object = aEvent.getAttachedObject();
398     if (object instanceof PackagingElementDraggingObject) {
399       final DefaultMutableTreeNode parent = findParentCompositeElementNode(aEvent.getRelativePoint().getPoint(myTree));
400       if (parent != null) {
401         final PackagingElementDraggingObject draggingObject = (PackagingElementDraggingObject)object;
402         final PackagingElementNode node = getNode(parent);
403         if (node != null && draggingObject.canDropInto(node)) {
404           final PackagingElement element = node.getFirstElement();
405           if (element instanceof CompositePackagingElement) {
406             draggingObject.setTargetNode(node);
407             draggingObject.setTargetElement((CompositePackagingElement<?>)element);
408             final Rectangle bounds = myTree.getPathBounds(TreeUtil.getPathFromRoot(parent));
409             aEvent.setHighlighting(new RelativeRectangle(myTree, bounds), DnDEvent.DropTargetHighlightingType.RECTANGLE);
410             aEvent.setDropPossible(true);
411           }
412         }
413       }
414     }
415     return false;
416   }
417
418   @Override
419   public void drop(DnDEvent aEvent) {
420     final Object object = aEvent.getAttachedObject();
421     if (object instanceof PackagingElementDraggingObject) {
422       final PackagingElementDraggingObject draggingObject = (PackagingElementDraggingObject)object;
423       final PackagingElementNode<?> targetNode = draggingObject.getTargetNode();
424       final CompositePackagingElement<?> targetElement = draggingObject.getTargetElement();
425       if (targetElement == null || targetNode == null || !draggingObject.checkCanDrop()) return;
426       if (!checkCanAdd(targetElement, targetNode)) {
427         return;
428       }
429       final List<PackagingElement<?>> toSelect = new ArrayList<>();
430       editLayout(() -> {
431         draggingObject.beforeDrop();
432         final CompositePackagingElement<?> parent = getOrCreateModifiableParent(targetElement, targetNode);
433         for (PackagingElement<?> element : draggingObject.createPackagingElements(myContext)) {
434           toSelect.add(element);
435           parent.addOrFindChild(element);
436         }
437       });
438       updateAndSelect(targetNode, toSelect);
439       myArtifactsEditor.getSourceItemsTree().rebuildTree();
440     }
441   }
442
443   @Nullable
444   private DefaultMutableTreeNode findParentCompositeElementNode(Point point) {
445     TreePath path = myTree.getPathForLocation(point.x, point.y);
446     while (path != null) {
447       final PackagingElement<?> element = myTree.getElementByPath(path);
448       if (element instanceof CompositePackagingElement) {
449         return (DefaultMutableTreeNode)path.getLastPathComponent();
450       }
451       path = path.getParentPath();
452     }
453     return null;
454   }
455
456   public void startRenaming(TreePath path) {
457     myTree.startEditingAtPath(path);
458   }
459
460   public boolean isEditing() {
461     return myTree.isEditing();
462   }
463
464   public void setRootElement(CompositePackagingElement<?> rootElement) {
465     myContext.getOrCreateModifiableArtifactModel().getOrCreateModifiableArtifact(myOriginalArtifact).setRootElement(rootElement);
466     myTreeStructure.updateRootElement();
467     rebuildTree();
468     myArtifactsEditor.getSourceItemsTree().rebuildTree();
469   }
470
471   public PackagingElementNode<?> getRootNode() {
472     return myTreeStructure.getRootNode();
473   }
474
475   @NotNull
476   public CompositePackagingElement<?> getRootElement() {
477     return myContext.getRootElement(myOriginalArtifact);
478   }
479
480   public void updateTreeNodesPresentation() {
481     myStructureTreeModel.invalidate();
482   }
483
484   public void updateRootNode() {
485     myStructureTreeModel.invalidate(myTreeStructure.getRootElement(), false);
486   }
487
488   public void initTree() {
489     mySelectedElementInfo.showPropertiesPanel();
490   }
491
492   public void putIntoDefaultLocations(@NotNull final List<? extends PackagingSourceItem> items) {
493     final List<PackagingElement<?>> toSelect = new ArrayList<>();
494     editLayout(() -> {
495       final CompositePackagingElement<?> rootElement = getArtifact().getRootElement();
496       final ArtifactType artifactType = getArtifact().getArtifactType();
497       for (PackagingSourceItem item : items) {
498         final String path = artifactType.getDefaultPathFor(item);
499         if (path != null) {
500           final CompositePackagingElement<?> parent;
501           if (path.endsWith(URLUtil.JAR_SEPARATOR)) {
502             parent = PackagingElementFactory.getInstance().getOrCreateArchive(rootElement, StringUtil.trimEnd(path, URLUtil.JAR_SEPARATOR));
503           }
504           else {
505             parent = PackagingElementFactory.getInstance().getOrCreateDirectory(rootElement, path);
506           }
507           final List<? extends PackagingElement<?>> elements = item.createElements(myContext);
508           toSelect.addAll(parent.addOrFindChildren(elements));
509         }
510       }
511     });
512
513     myArtifactsEditor.getSourceItemsTree().rebuildTree();
514     updateAndSelect(getRootNode(), toSelect);
515   }
516
517   public void putElements(@NotNull final String path, @NotNull final List<? extends PackagingElement<?>> elements) {
518     final List<PackagingElement<?>> toSelect = new ArrayList<>();
519     editLayout(() -> {
520       final CompositePackagingElement<?> directory =
521         PackagingElementFactory.getInstance().getOrCreateDirectory(getArtifact().getRootElement(), path);
522       toSelect.addAll(directory.addOrFindChildren(elements));
523     });
524     myArtifactsEditor.getSourceItemsTree().rebuildTree();
525     updateAndSelect(getRootNode(), toSelect);
526   }
527
528   public void packInto(@NotNull final List<? extends PackagingSourceItem> items, final String pathToJar) {
529     final List<PackagingElement<?>> toSelect = new ArrayList<>();
530     final CompositePackagingElement<?> rootElement = getArtifact().getRootElement();
531     editLayout(() -> {
532       final CompositePackagingElement<?> archive = PackagingElementFactory.getInstance().getOrCreateArchive(rootElement, pathToJar);
533       for (PackagingSourceItem item : items) {
534         final List<? extends PackagingElement<?>> elements = item.createElements(myContext);
535         archive.addOrFindChildren(elements);
536       }
537       toSelect.add(archive);
538     });
539
540     myArtifactsEditor.getSourceItemsTree().rebuildTree();
541     updateAndSelect(getRootNode(), toSelect);
542   }
543
544   public boolean isPropertiesModified() {
545     final PackagingElementPropertiesPanel panel = mySelectedElementInfo.myCurrentPanel;
546     return panel != null && panel.isModified();
547   }
548
549   public void resetElementProperties() {
550     final PackagingElementPropertiesPanel panel = mySelectedElementInfo.myCurrentPanel;
551     if (panel != null) {
552       panel.reset();
553     }
554   }
555
556   public boolean isSortElements() {
557     return mySortElements;
558   }
559
560   private final class SelectedElementInfo<E extends PackagingElement<?>> {
561     private final E myElement;
562     private PackagingElementPropertiesPanel myCurrentPanel;
563
564     private SelectedElementInfo(@Nullable E element) {
565       myElement = element;
566       if (myElement != null) {
567         //noinspection unchecked
568         myCurrentPanel = element.getType().createElementPropertiesPanel(myElement, myContext);
569         myPropertiesPanel.removeAll();
570         if (myCurrentPanel != null) {
571           myPropertiesPanel.add(BorderLayout.CENTER, ScrollPaneFactory.createScrollPane(myCurrentPanel.createComponent(), true));
572           myCurrentPanel.reset();
573           myPropertiesPanel.revalidate();
574         }
575       }
576     }
577
578     public void save() {
579       if (myCurrentPanel != null && myCurrentPanel.isModified()) {
580         editLayout(() -> myCurrentPanel.apply());
581       }
582     }
583
584     public void showPropertiesPanel() {
585       final CardLayout cardLayout = (CardLayout)myPropertiesPanelWrapper.getLayout();
586       if (myCurrentPanel != null) {
587         cardLayout.show(myPropertiesPanelWrapper, PROPERTIES_CARD);
588       }
589       else {
590         cardLayout.show(myPropertiesPanelWrapper, EMPTY_CARD);
591       }
592       myPropertiesPanelWrapper.repaint();
593     }
594   }
595
596   private class LayoutTreeStructure extends SimpleTreeStructure {
597     private ArtifactRootNode myRootNode;
598
599     @NotNull
600     @Override
601     public Object getRootElement() {
602       return getRootNode();
603     }
604
605     @NotNull
606     ArtifactRootNode getRootNode() {
607       if (myRootNode == null) {
608         myRootNode = PackagingTreeNodeFactory.createRootNode(LayoutTreeComponent.this.getRootElement(), myContext, mySubstitutionParameters, getArtifact().getArtifactType());
609       }
610       return myRootNode;
611     }
612
613     public void updateRootElement() {
614       myRootNode = null;
615     }
616   }
617 }