/* * Copyright 2000-2009 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.openapi.roots.ui.configuration.projectRoot; import com.intellij.ide.CommonActionsManager; import com.intellij.ide.TreeExpander; import com.intellij.openapi.Disposable; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.keymap.Keymap; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.module.Module; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.project.Project; import com.intellij.openapi.projectRoots.Sdk; import com.intellij.openapi.roots.libraries.Library; import com.intellij.openapi.roots.libraries.LibraryTable; import consulo.roots.ui.configuration.WholeWestConfigurable; import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureDaemonAnalyzerListener; import com.intellij.openapi.roots.ui.configuration.projectRoot.daemon.ProjectStructureElement; import com.intellij.openapi.ui.MasterDetailsComponent; import com.intellij.openapi.ui.MasterDetailsState; import com.intellij.openapi.ui.MasterDetailsStateService; import com.intellij.openapi.ui.NamedConfigurable; import com.intellij.openapi.util.ActionCallback; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.Disposer; import com.intellij.packaging.artifacts.Artifact; import com.intellij.ui.TreeSpeedSearch; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.navigation.Place; import com.intellij.util.Function; import com.intellij.util.IconUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; import com.intellij.util.ui.tree.TreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import consulo.annotations.RequiredDispatchThread; import javax.swing.*; import javax.swing.tree.TreePath; import java.awt.*; import java.util.*; import java.util.List; public abstract class BaseStructureConfigurable extends MasterDetailsComponent implements SearchableConfigurable, Disposable, Place.Navigator, WholeWestConfigurable { protected StructureConfigurableContext myContext; protected final Project myProject; protected boolean myUiDisposed = true; private boolean myWasTreeInitialized; protected boolean myAutoScrollEnabled = true; protected BaseStructureConfigurable(Project project, MasterDetailsState state) { super(state); myProject = project; } protected BaseStructureConfigurable(final Project project) { myProject = project; } public void init(StructureConfigurableContext context) { myContext = context; myContext.getDaemonAnalyzer().addListener(new ProjectStructureDaemonAnalyzerListener() { @Override public void problemsChanged(@NotNull ProjectStructureElement element) { if (!myTree.isShowing()) return; myTree.revalidate(); myTree.repaint(); } }); } @NotNull @Override public Couple<JComponent> createSplitterComponents() { reInitWholePanelIfNeeded(); updateSelectionFromTree(); return Couple.of(mySplitter.getFirstComponent(), mySplitter.getSecondComponent()); } @Override protected MasterDetailsStateService getStateService() { return MasterDetailsStateService.getInstance(myProject); } @Override public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) { if (place == null) return new ActionCallback.Done(); final Object object = place.getPath(TREE_OBJECT); final String byName = (String)place.getPath(TREE_NAME); if (object == null && byName == null) return new ActionCallback.Done(); final MyNode node = object == null ? null : findNodeByObject(myRoot, object); final MyNode nodeByName = byName == null ? null : findNodeByName(myRoot, byName); if (node == null && nodeByName == null) return new ActionCallback.Done(); final NamedConfigurable config; if (node != null) { config = node.getConfigurable(); } else { config = nodeByName.getConfigurable(); } final ActionCallback result = new ActionCallback().doWhenDone(new Runnable() { @Override public void run() { myAutoScrollEnabled = true; } }); myAutoScrollEnabled = false; myAutoScrollHandler.cancelAllRequests(); final MyNode nodeToSelect = node != null ? node : nodeByName; selectNodeInTree(nodeToSelect, requestFocus).doWhenDone(new Runnable() { @Override public void run() { setSelectedNode(nodeToSelect); Place.goFurther(config, place, requestFocus).notifyWhenDone(result); } }); return result; } @Override public void queryPlace(@NotNull final Place place) { if (myCurrentConfigurable != null) { place.putPath(TREE_OBJECT, myCurrentConfigurable.getEditableObject()); Place.queryFurther(myCurrentConfigurable, place); } } @Override protected void initTree() { if (myWasTreeInitialized) return; myWasTreeInitialized = true; super.initTree(); new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() { @Override public String convert(final TreePath treePath) { return ((MyNode)treePath.getLastPathComponent()).getDisplayName(); } }, true); ToolTipManager.sharedInstance().registerComponent(myTree); myTree.setCellRenderer(new ProjectStructureElementRenderer(myContext)); } @Override @RequiredDispatchThread public void disposeUIResources() { if (myUiDisposed) return; super.disposeUIResources(); myUiDisposed = true; myAutoScrollHandler.cancelAllRequests(); myContext.getDaemonAnalyzer().clear(); Disposer.dispose(this); } public void checkCanApply() throws ConfigurationException { } protected void addCollapseExpandActions(final List<AnAction> result) { final TreeExpander expander = new TreeExpander() { @Override public void expandAll() { TreeUtil.expandAll(myTree); } @Override public boolean canExpand() { return true; } @Override public void collapseAll() { TreeUtil.collapseAll(myTree, 0); } @Override public boolean canCollapse() { return true; } }; final CommonActionsManager actionsManager = CommonActionsManager.getInstance(); result.add(actionsManager.createExpandAllAction(expander, myTree)); result.add(actionsManager.createCollapseAllAction(expander, myTree)); } @Nullable public ProjectStructureElement getSelectedElement() { final TreePath selectionPath = myTree.getSelectionPath(); if (selectionPath != null && selectionPath.getLastPathComponent() instanceof MyNode) { MyNode node = (MyNode)selectionPath.getLastPathComponent(); final NamedConfigurable configurable = node.getConfigurable(); if (configurable instanceof ProjectStructureElementConfigurable) { return ((ProjectStructureElementConfigurable)configurable).getProjectStructureElement(); } } return null; } private class MyFindUsagesAction extends FindUsagesInProjectStructureActionBase { public MyFindUsagesAction(JComponent parentComponent) { super(parentComponent, myProject); } @Override protected boolean isEnabled() { final TreePath selectionPath = myTree.getSelectionPath(); if (selectionPath != null) { final MyNode node = (MyNode)selectionPath.getLastPathComponent(); return !node.isDisplayInBold(); } else { return false; } } @Override protected StructureConfigurableContext getContext() { return myContext; } @Override protected ProjectStructureElement getSelectedElement() { return BaseStructureConfigurable.this.getSelectedElement(); } @Override protected RelativePoint getPointToShowResults() { final int selectedRow = myTree.getSelectionRows()[0]; final Rectangle rowBounds = myTree.getRowBounds(selectedRow); final Point location = rowBounds.getLocation(); location.x += rowBounds.width; return new RelativePoint(myTree, location); } } @Override public void reset() { myUiDisposed = false; if (!myWasTreeInitialized) { initTree(); myTree.setShowsRootHandles(false); loadTree(); } else { super.disposeUIResources(); myTree.setShowsRootHandles(false); loadTree(); } for (ProjectStructureElement element : getProjectStructureElements()) { myContext.getDaemonAnalyzer().queueUpdate(element); } super.reset(); } @NotNull protected Collection<? extends ProjectStructureElement> getProjectStructureElements() { return Collections.emptyList(); } protected abstract void loadTree(); @Override @NotNull protected ArrayList<AnAction> createActions(final boolean fromPopup) { final ArrayList<AnAction> result = new ArrayList<AnAction>(); AbstractAddGroup addAction = createAddAction(); if (addAction != null) { result.add(addAction); } result.add(new MyRemoveAction()); final List<? extends AnAction> copyActions = createCopyActions(fromPopup); result.addAll(copyActions); result.add(AnSeparator.getInstance()); result.add(new MyFindUsagesAction(myTree)); return result; } @NotNull protected List<? extends AnAction> createCopyActions(boolean fromPopup) { return Collections.emptyList(); } public void onStructureUnselected() { } public void onStructureSelected() { } @Nullable protected abstract AbstractAddGroup createAddAction(); protected class MyRemoveAction extends MyDeleteAction { public MyRemoveAction() { super(new Condition<Object[]>() { @Override public boolean value(final Object[] objects) { Object[] editableObjects = ContainerUtil.mapNotNull(objects, new Function<Object, Object>() { @Override public Object fun(Object object) { if (object instanceof MyNode) { final NamedConfigurable namedConfigurable = ((MyNode)object).getConfigurable(); if (namedConfigurable != null) { return namedConfigurable.getEditableObject(); } } return null; } }, new Object[0]); return editableObjects.length == objects.length && canBeRemoved(editableObjects); } }); } @Override public void actionPerformed(AnActionEvent e) { final TreePath[] paths = myTree.getSelectionPaths(); if (paths == null) return; final Set<TreePath> pathsToRemove = new HashSet<TreePath>(); for (TreePath path : paths) { if (removeFromModel(path)) { pathsToRemove.add(path); } } removePaths(pathsToRemove.toArray(new TreePath[pathsToRemove.size()])); } private boolean removeFromModel(final TreePath selectionPath) { final Object last = selectionPath.getLastPathComponent(); if (!(last instanceof MyNode)) return false; final MyNode node = (MyNode)last; final NamedConfigurable configurable = node.getConfigurable(); if (configurable == null) return false; final Object editableObject = configurable.getEditableObject(); return removeObject(editableObject); } } protected boolean canBeRemoved(final Object[] editableObjects) { for (Object editableObject : editableObjects) { if (!canObjectBeRemoved(editableObject)) return false; } return true; } private static boolean canObjectBeRemoved(Object editableObject) { if (editableObject instanceof Sdk || editableObject instanceof Module || editableObject instanceof Artifact) { return true; } if (editableObject instanceof Library) { final LibraryTable table = ((Library)editableObject).getTable(); return table == null || table.isEditable(); } return false; } protected boolean removeObject(final Object editableObject) { // todo keep only removeModule() and removeFacet() here because other removeXXX() are empty here and overridden in subclasses? Override removeObject() instead? if (editableObject instanceof Sdk) { removeJdk((Sdk)editableObject); } else if (editableObject instanceof Module) { if (!removeModule((Module)editableObject)) return false; } else if (editableObject instanceof Library) { if (!removeLibrary((Library)editableObject)) return false; } else if (editableObject instanceof Artifact) { removeArtifact((Artifact)editableObject); } return true; } protected void removeArtifact(Artifact artifact) { } protected boolean removeLibrary(Library library) { return false; } protected boolean removeModule(final Module module) { return true; } protected void removeJdk(final Sdk editableObject) { } protected abstract static class AbstractAddGroup extends ActionGroup implements ActionGroupWithPreselection { protected AbstractAddGroup(String text, Icon icon) { super(text, true); final Presentation presentation = getTemplatePresentation(); presentation.setIcon(icon); final Keymap active = KeymapManager.getInstance().getActiveKeymap(); if (active != null) { final Shortcut[] shortcuts = active.getShortcuts("NewElement"); setShortcutSet(new CustomShortcutSet(shortcuts)); } } public AbstractAddGroup(String text) { this(text, IconUtil.getAddIcon()); } @Override public ActionGroup getActionGroup() { return this; } @Override public int getDefaultIndex() { return 0; } } }