2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com
.intellij
.openapi
.roots
.ui
.configuration
.projectRoot
;
18 import com
.intellij
.facet
.Facet
;
19 import com
.intellij
.ide
.CommonActionsManager
;
20 import com
.intellij
.ide
.TreeExpander
;
21 import com
.intellij
.ide
.impl
.convert
.ProjectFileVersion
;
22 import com
.intellij
.openapi
.Disposable
;
23 import com
.intellij
.openapi
.actionSystem
.*;
24 import com
.intellij
.openapi
.keymap
.Keymap
;
25 import com
.intellij
.openapi
.keymap
.KeymapManager
;
26 import com
.intellij
.openapi
.module
.Module
;
27 import com
.intellij
.openapi
.options
.Configurable
;
28 import com
.intellij
.openapi
.options
.SearchableConfigurable
;
29 import com
.intellij
.openapi
.project
.Project
;
30 import com
.intellij
.openapi
.project
.ProjectBundle
;
31 import com
.intellij
.openapi
.projectRoots
.Sdk
;
32 import com
.intellij
.openapi
.roots
.libraries
.Library
;
33 import com
.intellij
.openapi
.roots
.libraries
.LibraryTable
;
34 import com
.intellij
.openapi
.ui
.MasterDetailsComponent
;
35 import com
.intellij
.openapi
.ui
.NamedConfigurable
;
36 import com
.intellij
.openapi
.util
.ActionCallback
;
37 import com
.intellij
.openapi
.util
.Condition
;
38 import com
.intellij
.ui
.ColoredTreeCellRenderer
;
39 import com
.intellij
.ui
.SimpleTextAttributes
;
40 import com
.intellij
.ui
.TreeSpeedSearch
;
41 import com
.intellij
.ui
.TreeToolTipHandler
;
42 import com
.intellij
.ui
.awt
.RelativePoint
;
43 import com
.intellij
.ui
.navigation
.Place
;
44 import com
.intellij
.util
.Icons
;
45 import com
.intellij
.util
.StringBuilderSpinAllocator
;
46 import com
.intellij
.util
.containers
.Convertor
;
47 import com
.intellij
.util
.ui
.UIUtil
;
48 import com
.intellij
.util
.ui
.tree
.TreeUtil
;
49 import com
.intellij
.packaging
.artifacts
.Artifact
;
50 import org
.jetbrains
.annotations
.NotNull
;
51 import org
.jetbrains
.annotations
.Nullable
;
54 import javax
.swing
.tree
.TreePath
;
57 import java
.util
.List
;
59 public abstract class BaseStructureConfigurable
extends MasterDetailsComponent
implements SearchableConfigurable
, Disposable
, Configurable
.Assistant
, Place
.Navigator
{
61 protected StructureConfigurableContext myContext
;
63 protected final Project myProject
;
65 protected boolean myUiDisposed
= true;
67 private boolean myWasTreeInitialized
;
69 protected boolean myAutoScrollEnabled
= true;
71 protected BaseStructureConfigurable(final Project project
) {
75 public void init(StructureConfigurableContext context
) {
77 myContext
.addCacheUpdateListener(new Runnable() {
79 if (!myTree
.isShowing()) return;
88 public ActionCallback
navigateTo(@Nullable final Place place
, final boolean requestFocus
) {
89 if (place
== null) return new ActionCallback
.Done();
91 final Object object
= place
.getPath(TREE_OBJECT
);
92 final String byName
= (String
)place
.getPath(TREE_NAME
);
94 if (object
== null && byName
== null) return new ActionCallback
.Done();
96 final MyNode node
= object
== null ?
null : findNodeByObject(myRoot
, object
);
97 final MyNode nodeByName
= byName
== null ?
null : findNodeByName(myRoot
, byName
);
99 if (node
== null && nodeByName
== null) return new ActionCallback
.Done();
101 final NamedConfigurable config
;
103 config
= node
.getConfigurable();
105 config
= nodeByName
.getConfigurable();
108 final ActionCallback result
= new ActionCallback().doWhenDone(new Runnable() {
110 myAutoScrollEnabled
= true;
114 myAutoScrollEnabled
= false;
115 myAutoScrollHandler
.cancelAllRequests();
116 final MyNode nodeToSelect
= node
!= null ? node
: nodeByName
;
117 selectNodeInTree(nodeToSelect
, requestFocus
).doWhenDone(new Runnable() {
119 setSelectedNode(nodeToSelect
);
120 Place
.goFurther(config
, place
, requestFocus
).notifyWhenDone(result
);
128 public void queryPlace(@NotNull final Place place
) {
129 if (myCurrentConfigurable
!= null) {
130 place
.putPath(TREE_OBJECT
, myCurrentConfigurable
.getEditableObject());
131 Place
.queryFurther(myCurrentConfigurable
, place
);
135 protected void initTree() {
136 if (myWasTreeInitialized
) return;
137 myWasTreeInitialized
= true;
140 new TreeSpeedSearch(myTree
, new Convertor
<TreePath
, String
>() {
141 public String
convert(final TreePath treePath
) {
142 return ((MyNode
)treePath
.getLastPathComponent()).getDisplayName();
145 TreeToolTipHandler
.install(myTree
);
146 ToolTipManager
.sharedInstance().registerComponent(myTree
);
147 myTree
.setCellRenderer(new ColoredTreeCellRenderer(){
148 public void customizeCellRenderer(JTree tree
, Object value
, boolean selected
, boolean expanded
, boolean leaf
, int row
, boolean hasFocus
) {
149 if (value
instanceof MyNode
) {
150 final MyNode node
= (MyNode
)value
;
152 if (node
.getConfigurable() == null) {
156 final String displayName
= node
.getDisplayName();
157 final Icon icon
= node
.getConfigurable().getIcon(expanded
);
159 setToolTipText(null);
160 setFont(UIUtil
.getTreeFont());
161 if (node
.isDisplayInBold()){
162 append(displayName
, SimpleTextAttributes
.REGULAR_BOLD_ATTRIBUTES
);
164 final Object object
= node
.getConfigurable().getEditableObject();
165 final boolean unused
= myContext
.isUnused(object
);
166 final StructureConfigurableContext
.ValidityLevel level
= myContext
.isInvalid(object
);
167 final boolean invalid
= level
!= StructureConfigurableContext
.ValidityLevel
.VALID
;
168 if (unused
|| invalid
){
170 ? UIUtil
.getTextInactiveTextColor()
171 : selected
&& hasFocus ? UIUtil
.getTreeSelectionForeground() : UIUtil
.getTreeForeground();
172 append(displayName
, new SimpleTextAttributes(invalid ? SimpleTextAttributes
.STYLE_WAVED
: SimpleTextAttributes
.STYLE_PLAIN
,
174 level
== StructureConfigurableContext
.ValidityLevel
.ERROR ? Color
.RED
: Color
.GRAY
));
175 setToolTipText(composeTooltipMessage(invalid
, object
, displayName
, unused
));
178 append(displayName
, selected
&& hasFocus ? SimpleTextAttributes
.SELECTED_SIMPLE_CELL_ATTRIBUTES
: SimpleTextAttributes
.REGULAR_ATTRIBUTES
);
188 private String
composeTooltipMessage(final boolean invalid
, final Object object
, final String displayName
, final boolean unused
) {
189 final StringBuilder buf
= StringBuilderSpinAllocator
.alloc();
192 if (object
instanceof Module
) {
193 final Module module
= (Module
)object
;
194 final Map
<String
, Set
<String
>> problems
= myContext
.myValidityCache
.get(module
);
196 if (problems
.containsKey(StructureConfigurableContext
.DUPLICATE_MODULE_NAME
)) {
197 buf
.append(StructureConfigurableContext
.DUPLICATE_MODULE_NAME
).append("\n");
200 if (problems
.containsKey(StructureConfigurableContext
.NO_JDK
)){
201 buf
.append(StructureConfigurableContext
.NO_JDK
).append("\n");
203 final Set
<String
> deletedLibraries
= problems
.get(StructureConfigurableContext
.DELETED_LIBRARIES
);
204 if (deletedLibraries
!= null) {
205 buf
.append(ProjectBundle
.message("project.roots.library.problem.message", deletedLibraries
.size()));
206 for (String problem
: deletedLibraries
) {
207 if (deletedLibraries
.size() > 1) {
210 buf
.append("\'").append(problem
).append("\'").append("\n");
214 buf
.append(ProjectBundle
.message("project.roots.tooltip.library.misconfigured", displayName
)).append("\n");
218 buf
.append(ProjectBundle
.message("project.roots.tooltip.unused", displayName
));
220 return buf
.toString();
223 StringBuilderSpinAllocator
.dispose(buf
);
227 public void disposeUIResources() {
228 if (myUiDisposed
) return;
230 super.disposeUIResources();
234 myAutoScrollHandler
.cancelAllRequests();
235 myContext
.myUpdateDependenciesAlarm
.cancelAllRequests();
236 myContext
.myUpdateDependenciesAlarm
.addRequest(new Runnable(){
238 SwingUtilities
.invokeLater(new Runnable(){
247 protected void addCollapseExpandActions(final List
<AnAction
> result
) {
248 final TreeExpander expander
= new TreeExpander() {
249 public void expandAll() {
250 TreeUtil
.expandAll(myTree
);
253 public boolean canExpand() {
257 public void collapseAll() {
258 TreeUtil
.collapseAll(myTree
, 0);
261 public boolean canCollapse() {
265 final CommonActionsManager actionsManager
= CommonActionsManager
.getInstance();
266 result
.add(actionsManager
.createExpandAllAction(expander
, myTree
));
267 result
.add(actionsManager
.createCollapseAllAction(expander
, myTree
));
270 private class MyFindUsagesAction
extends FindUsagesInProjectStructureActionBase
{
272 public MyFindUsagesAction(JComponent parentComponent
) {
273 super(parentComponent
, myProject
);
276 protected boolean isEnabled() {
277 final TreePath selectionPath
= myTree
.getSelectionPath();
278 if (selectionPath
!= null){
279 final MyNode node
= (MyNode
)selectionPath
.getLastPathComponent();
280 return !node
.isDisplayInBold();
286 protected StructureConfigurableContext
getContext() {
290 protected Object
getSelectedObject() {
291 return BaseStructureConfigurable
.this.getSelectedObject();
294 protected RelativePoint
getPointToShowResults() {
295 final int selectedRow
= myTree
.getSelectionRows()[0];
296 final Rectangle rowBounds
= myTree
.getRowBounds(selectedRow
);
297 final Point location
= rowBounds
.getLocation();
298 location
.x
+= rowBounds
.width
;
299 return new RelativePoint(myTree
, location
);
304 public void reset() {
305 myUiDisposed
= false;
307 if (!myWasTreeInitialized
) {
309 myTree
.setShowsRootHandles(false);
312 super.disposeUIResources();
313 myTree
.setShowsRootHandles(false);
320 protected abstract void loadTree();
324 protected ArrayList
<AnAction
> createActions(final boolean fromPopup
) {
325 final ArrayList
<AnAction
> result
= new ArrayList
<AnAction
>();
326 AbstractAddGroup addAction
= createAddAction();
327 if (addAction
!= null) {
328 result
.add(addAction
);
330 result
.add(new MyRemoveAction());
332 final AnAction copyAction
= createCopyAction();
333 if (copyAction
!= null) {
334 result
.add(copyAction
);
336 result
.add(Separator
.getInstance());
338 result
.add(new MyFindUsagesAction(myTree
));
345 protected AnAction
createCopyAction() {
350 protected abstract AbstractAddGroup
createAddAction();
352 protected class MyRemoveAction
extends MyDeleteAction
{
353 public MyRemoveAction() {
354 super(new Condition
<Object
>() {
355 public boolean value(final Object object
) {
356 if (object
instanceof MyNode
) {
357 final NamedConfigurable namedConfigurable
= ((MyNode
)object
).getConfigurable();
358 if (namedConfigurable
!= null) {
359 final Object editableObject
= namedConfigurable
.getEditableObject();
360 if (editableObject
instanceof Sdk
|| editableObject
instanceof Module
|| editableObject
instanceof Facet
|| editableObject
instanceof Artifact
) return true;
361 if (editableObject
instanceof Library
) {
362 final LibraryTable table
= ((Library
)editableObject
).getTable();
363 return table
== null || table
.isEditable();
372 public void actionPerformed(AnActionEvent e
) {
373 final TreePath
[] paths
= myTree
.getSelectionPaths();
374 if (paths
== null) return;
376 final Set
<TreePath
> pathsToRemove
= new HashSet
<TreePath
>();
377 for (TreePath path
: paths
) {
378 if (removeFromModel(path
)) {
379 pathsToRemove
.add(path
);
382 removePaths(pathsToRemove
.toArray(new TreePath
[pathsToRemove
.size()]));
385 private boolean removeFromModel(final TreePath selectionPath
) {
386 final Object last
= selectionPath
.getLastPathComponent();
388 if (!(last
instanceof MyNode
)) return false;
390 final MyNode node
= (MyNode
)last
;
391 final NamedConfigurable configurable
= node
.getConfigurable();
392 final Object editableObject
= configurable
.getEditableObject();
393 if (editableObject
instanceof Sdk
) {
394 removeJdk((Sdk
)editableObject
);
396 else if (editableObject
instanceof Module
) {
397 if (!removeModule((Module
)editableObject
)) return false;
399 else if (editableObject
instanceof Facet
) {
400 if (removeFacet((Facet
)editableObject
).isEmpty()) return false;
402 else if (editableObject
instanceof Library
) {
403 removeLibrary((Library
)editableObject
);
405 else if (editableObject
instanceof Artifact
) {
406 removeArtifact((Artifact
)editableObject
);
412 protected void removeArtifact(Artifact artifact
) {
416 protected void removeLibrary(Library library
) {
420 protected void removeFacetNodes(@NotNull List
<Facet
> facets
) {
421 for (Facet facet
: facets
) {
422 MyNode node
= findNodeByObject(myRoot
, facet
);
424 removePaths(TreeUtil
.getPathFromRoot(node
));
429 protected List
<Facet
> removeFacet(final Facet facet
) {
430 return myContext
.myModulesConfigurator
.getFacetsConfigurator().removeFacet(facet
);
433 protected boolean removeModule(final Module module
) {
437 protected void removeJdk(final Sdk editableObject
) {
440 protected abstract static class AbstractAddGroup
extends ActionGroup
implements ActionGroupWithPreselection
{
442 protected AbstractAddGroup(String text
, Icon icon
) {
445 final Presentation presentation
= getTemplatePresentation();
446 presentation
.setIcon(icon
);
448 final Keymap active
= KeymapManager
.getInstance().getActiveKeymap();
449 if (active
!= null) {
450 final Shortcut
[] shortcuts
= active
.getShortcuts("NewElement");
451 setShortcutSet(new CustomShortcutSet(shortcuts
));
455 public AbstractAddGroup(String text
) {
456 this(text
, Icons
.ADD_ICON
);
459 public ActionGroup
getActionGroup() {
463 public int getDefaultIndex() {