| /* |
| * Copyright 2000-2014 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.ui; |
| |
| import com.intellij.CommonBundle; |
| import com.intellij.openapi.actionSystem.*; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.options.Configurable; |
| import com.intellij.openapi.options.ConfigurationException; |
| import com.intellij.openapi.options.MasterDetails; |
| import com.intellij.openapi.project.DumbAware; |
| import com.intellij.openapi.ui.popup.JBPopupFactory; |
| import com.intellij.openapi.ui.popup.ListPopup; |
| import com.intellij.openapi.ui.popup.ListPopupStep; |
| import com.intellij.openapi.util.ActionCallback; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.util.Conditions; |
| import com.intellij.openapi.util.registry.Registry; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.ui.*; |
| import com.intellij.ui.navigation.History; |
| import com.intellij.ui.navigation.Place; |
| import com.intellij.ui.treeStructure.Tree; |
| import com.intellij.util.Function; |
| import com.intellij.util.PlatformIcons; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.HashSet; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.tree.TreeUtil; |
| import com.intellij.util.xmlb.XmlSerializerUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.border.EmptyBorder; |
| import javax.swing.tree.*; |
| import java.awt.*; |
| import java.util.*; |
| import java.util.List; |
| |
| /** |
| * @author anna |
| * @since 29-May-2006 |
| */ |
| public abstract class MasterDetailsComponent implements Configurable, DetailsComponent.Facade, MasterDetails { |
| protected static final Logger LOG = Logger.getInstance("#com.intellij.openapi.ui.MasterDetailsComponent"); |
| |
| protected static final Icon COPY_ICON = PlatformIcons.COPY_ICON; |
| |
| protected NamedConfigurable myCurrentConfigurable; |
| private final JBSplitter mySplitter; |
| |
| @NonNls public static final String TREE_OBJECT = "treeObject"; |
| @NonNls public static final String TREE_NAME = "treeName"; |
| |
| protected History myHistory = new History(new Place.Navigator() { |
| public void setHistory(final History history) { |
| myHistory = history; |
| } |
| |
| @Nullable |
| public ActionCallback navigateTo(@Nullable final Place place, final boolean requestFocus) { |
| return null; |
| } |
| |
| public void queryPlace(@NotNull final Place place) { |
| } |
| }); |
| private JComponent myMaster; |
| |
| public void setHistory(final History history) { |
| myHistory = history; |
| } |
| |
| protected final MasterDetailsState myState; |
| |
| protected Runnable TREE_UPDATER; |
| |
| { |
| TREE_UPDATER = new Runnable() { |
| public void run() { |
| final TreePath selectionPath = myTree.getSelectionPath(); |
| if (selectionPath == null) return; |
| |
| MyNode node = (MyNode)selectionPath.getLastPathComponent(); |
| if (node == null) return; |
| |
| myState.setLastEditedConfigurable(getNodePathString(node)); //survive after rename; |
| myDetails.setText(node.getConfigurable().getBannerSlogan()); |
| ((DefaultTreeModel)myTree.getModel()).reload(node); |
| fireItemsChangedExternally(); |
| } |
| }; |
| } |
| |
| protected MyNode myRoot = new MyRootNode(); |
| protected Tree myTree = new Tree(); |
| |
| private final DetailsComponent myDetails = new DetailsComponent(!Registry.is("ide.new.project.settings"), !Registry.is("ide.new.project.settings")); |
| protected JPanel myWholePanel; |
| public JPanel myNorthPanel = new JPanel(new BorderLayout()); |
| |
| private final List<ItemsChangeListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList(); |
| |
| private final Set<NamedConfigurable> myInitializedConfigurables = new HashSet<NamedConfigurable>(); |
| |
| private boolean myHasDeletedItems; |
| protected AutoScrollToSourceHandler myAutoScrollHandler; |
| |
| protected boolean myToReInitWholePanel = true; |
| |
| protected MasterDetailsComponent() { |
| this(new MasterDetailsState()); |
| } |
| |
| protected MasterDetailsComponent(MasterDetailsState state) { |
| myState = state; |
| |
| mySplitter = isNewProjectSettings() ? new OnePixelSplitter(false, .2f) : new JBSplitter(false, .2f); |
| mySplitter.setSplitterProportionKey("ProjectStructure.SecondLevelElements"); |
| mySplitter.setHonorComponentsMinimumSize(true); |
| |
| installAutoScroll(); |
| reInitWholePanelIfNeeded(); |
| } |
| |
| private boolean isNewProjectSettings() { |
| if (!Registry.is("ide.new.project.settings")) { |
| return false; |
| } |
| try { |
| // assume that only project structure dialog uses the following base class for details: |
| String name = "com.intellij.openapi.roots.ui.configuration.projectRoot.BaseStructureConfigurable"; |
| return Class.forName(name).isAssignableFrom(getClass()); |
| } |
| catch (ClassNotFoundException ignored) { |
| return false; |
| } |
| } |
| |
| protected void reInitWholePanelIfNeeded() { |
| if (!myToReInitWholePanel) return; |
| |
| myWholePanel = new JPanel(new BorderLayout()) { |
| public void addNotify() { |
| super.addNotify(); |
| MasterDetailsComponent.this.addNotify(); |
| |
| TreeModel m = myTree.getModel(); |
| if (m instanceof DefaultTreeModel) { |
| DefaultTreeModel model = (DefaultTreeModel)m; |
| for (int eachRow = 0; eachRow < myTree.getRowCount(); eachRow++) { |
| TreePath eachPath = myTree.getPathForRow(eachRow); |
| Object component = eachPath.getLastPathComponent(); |
| if (component instanceof TreeNode) { |
| model.nodeChanged((TreeNode)component); |
| } |
| } |
| } |
| } |
| }; |
| mySplitter.setHonorComponentsMinimumSize(true); |
| myWholePanel.add(mySplitter, BorderLayout.CENTER); |
| |
| JPanel left = new JPanel(new BorderLayout()) { |
| public Dimension getMinimumSize() { |
| final Dimension original = super.getMinimumSize(); |
| return new Dimension(Math.max(original.width, 100), original.height); |
| } |
| }; |
| |
| if (isNewProjectSettings()) { |
| ToolbarDecorator decorator = ToolbarDecorator.createDecorator(myTree); |
| DefaultActionGroup group = createToolbarActionGroup(); |
| if (group != null) { |
| decorator.setActionGroup(group); |
| } |
| //left.add(myNorthPanel, BorderLayout.NORTH); |
| myMaster = decorator.setAsUsualTopToolbar().setPanelBorder(new EmptyBorder(0, 0, 0, 0)).createPanel(); |
| myNorthPanel.setVisible(false); |
| } else { |
| left.add(myNorthPanel, BorderLayout.NORTH); |
| myMaster = ScrollPaneFactory.createScrollPane(myTree); |
| } |
| left.add(myMaster, BorderLayout.CENTER); |
| mySplitter.setFirstComponent(left); |
| |
| final JPanel right = new JPanel(new BorderLayout()); |
| right.add(myDetails.getComponent(), BorderLayout.CENTER); |
| |
| mySplitter.setSecondComponent(right); |
| |
| GuiUtils.replaceJSplitPaneWithIDEASplitter(myWholePanel); |
| |
| myToReInitWholePanel = false; |
| } |
| |
| private void installAutoScroll() { |
| myAutoScrollHandler = new AutoScrollToSourceHandler() { |
| protected boolean isAutoScrollMode() { |
| return isAutoScrollEnabled(); |
| } |
| |
| protected void setAutoScrollMode(boolean state) { |
| //do nothing |
| } |
| |
| protected void scrollToSource(Component tree) { |
| updateSelectionFromTree(); |
| } |
| |
| protected boolean needToCheckFocus() { |
| return false; |
| } |
| }; |
| myAutoScrollHandler.install(myTree); |
| } |
| |
| protected void addNotify() { |
| updateSelectionFromTree(); |
| } |
| |
| private void updateSelectionFromTree() { |
| TreePath[] treePaths = myTree.getSelectionPaths(); |
| if (treePaths != null) { |
| List<NamedConfigurable> selectedConfigurables = new ArrayList<NamedConfigurable>(); |
| for (TreePath path : treePaths) { |
| Object lastPathComponent = path.getLastPathComponent(); |
| if (lastPathComponent instanceof MyNode) { |
| selectedConfigurables.add(((MyNode)lastPathComponent).getConfigurable()); |
| } |
| } |
| if (selectedConfigurables.size() > 1 && updateMultiSelection(selectedConfigurables)) { |
| return; |
| } |
| } |
| |
| final TreePath path = myTree.getSelectionPath(); |
| if (path != null) { |
| final Object lastPathComp = path.getLastPathComponent(); |
| if (!(lastPathComp instanceof MyNode)) return; |
| final MyNode node = (MyNode)lastPathComp; |
| setSelectedNode(node); |
| } else { |
| setSelectedNode(null); |
| } |
| } |
| |
| protected boolean updateMultiSelection(final List<NamedConfigurable> selectedConfigurables) { |
| return false; |
| } |
| |
| public DetailsComponent getDetailsComponent() { |
| return myDetails; |
| } |
| |
| public Splitter getSplitter() { |
| return mySplitter; |
| } |
| |
| protected boolean isAutoScrollEnabled() { |
| return myHistory == null || !myHistory.isNavigatingNow(); |
| } |
| |
| protected DefaultActionGroup createToolbarActionGroup() { |
| final ArrayList<AnAction> actions = createActions(false); |
| if (actions != null) { |
| final DefaultActionGroup group = new DefaultActionGroup(); |
| for (AnAction action : actions) { |
| if (action instanceof ActionGroupWithPreselection) { |
| group.add(new MyActionGroupWrapper((ActionGroupWithPreselection)action)); |
| } |
| else { |
| group.add(action); |
| } |
| } |
| return group; |
| } |
| return null; |
| } |
| |
| private void initToolbar() { |
| if (isNewProjectSettings()) return; |
| DefaultActionGroup group = createToolbarActionGroup(); |
| if (group != null) { |
| final JComponent component = ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true).getComponent(); |
| myNorthPanel.add(component, BorderLayout.NORTH); |
| } |
| } |
| |
| public void addItemsChangeListener(ItemsChangeListener l) { |
| myListeners.add(l); |
| } |
| |
| protected Dimension getPanelPreferredSize() { |
| return new Dimension(800, 600); |
| } |
| |
| @NotNull |
| public JComponent createComponent() { |
| myTree.updateUI(); |
| reInitWholePanelIfNeeded(); |
| |
| updateSelectionFromTree(); |
| |
| final JPanel panel = new JPanel(new BorderLayout()) { |
| public Dimension getPreferredSize() { |
| return getPanelPreferredSize(); |
| } |
| }; |
| panel.add(myWholePanel, BorderLayout.CENTER); |
| return panel; |
| } |
| |
| public boolean isModified() { |
| if (myHasDeletedItems) return true; |
| final boolean[] modified = new boolean[1]; |
| TreeUtil.traverseDepth(myRoot, new TreeUtil.Traverse() { |
| public boolean accept(Object node) { |
| if (node instanceof MyNode) { |
| final NamedConfigurable configurable = ((MyNode)node).getConfigurable(); |
| if (isInitialized(configurable) && configurable.isModified()) { |
| modified[0] = true; |
| return false; |
| } |
| } |
| return true; |
| } |
| }); |
| return modified[0]; |
| } |
| |
| protected boolean isInitialized(final NamedConfigurable configurable) { |
| return myInitializedConfigurables.contains(configurable); |
| } |
| |
| public void apply() throws ConfigurationException { |
| processRemovedItems(); |
| final ConfigurationException[] ex = new ConfigurationException[1]; |
| TreeUtil.traverse(myRoot, new TreeUtil.Traverse() { |
| public boolean accept(Object node) { |
| if (node instanceof MyNode) { |
| try { |
| final NamedConfigurable configurable = ((MyNode)node).getConfigurable(); |
| if (isInitialized(configurable) && configurable.isModified()) { |
| configurable.apply(); |
| } |
| } |
| catch (ConfigurationException e) { |
| ex[0] = e; |
| return false; |
| } |
| } |
| return true; |
| } |
| }); |
| if (ex[0] != null) { |
| throw ex[0]; |
| } |
| myHasDeletedItems = false; |
| } |
| |
| protected abstract void processRemovedItems(); |
| |
| protected abstract boolean wasObjectStored(Object editableObject); |
| |
| public void reset() { |
| loadComponentState(); |
| myHasDeletedItems = false; |
| ((DefaultTreeModel)myTree.getModel()).reload(); |
| //myTree.requestFocus(); |
| myState.getProportions().restoreSplitterProportions(myWholePanel); |
| |
| final Enumeration enumeration = myRoot.breadthFirstEnumeration(); |
| boolean selected = false; |
| while (enumeration.hasMoreElements()) { |
| final MyNode node = (MyNode)enumeration.nextElement(); |
| if (node instanceof MyRootNode) continue; |
| final String path = getNodePathString(node); |
| if (!selected && Comparing.strEqual(path, myState.getLastEditedConfigurable())) { |
| TreeUtil.selectInTree(node, false, myTree); |
| selected = true; |
| } |
| } |
| if (!selected) { |
| TreeUtil.selectFirstNode(myTree); |
| } |
| updateSelectionFromTree(); |
| } |
| |
| protected void loadComponentState() { |
| final String key = getComponentStateKey(); |
| final MasterDetailsStateService stateService = getStateService(); |
| if (key != null && stateService != null) { |
| final MasterDetailsState state = stateService.getComponentState(key, myState.getClass()); |
| if (state != null) { |
| loadState(state); |
| } |
| } |
| } |
| |
| private static String getNodePathString(final MyNode node) { |
| StringBuilder path = new StringBuilder(); |
| MyNode current = node; |
| while (current != null) { |
| final Object userObject = current.getUserObject(); |
| if (!(userObject instanceof NamedConfigurable)) break; |
| final String displayName = current.getDisplayName(); |
| if (StringUtil.isEmptyOrSpaces(displayName)) break; |
| if (path.length() > 0) { |
| path.append('|'); |
| } |
| path.append(displayName); |
| |
| final TreeNode parent = current.getParent(); |
| if (!(parent instanceof MyNode)) break; |
| current = (MyNode)parent; |
| } |
| return path.toString(); |
| } |
| |
| @Nullable |
| @NonNls |
| protected String getComponentStateKey() { |
| return null; |
| } |
| |
| @Nullable |
| protected MasterDetailsStateService getStateService() { |
| return null; |
| } |
| |
| protected MasterDetailsState getState() { |
| return myState; |
| } |
| |
| protected void loadState(final MasterDetailsState object) { |
| XmlSerializerUtil.copyBean(object, myState); |
| } |
| |
| public void disposeUIResources() { |
| myState.getProportions().saveSplitterProportions(myWholePanel); |
| myAutoScrollHandler.cancelAllRequests(); |
| myDetails.disposeUIResources(); |
| myInitializedConfigurables.clear(); |
| clearChildren(); |
| final String key = getComponentStateKey(); |
| final MasterDetailsStateService stateService = getStateService(); |
| if (key != null && stateService != null) { |
| stateService.setComponentState(key, getState()); |
| } |
| myCurrentConfigurable = null; |
| } |
| |
| protected void clearChildren() { |
| TreeUtil.traverseDepth(myRoot, new TreeUtil.Traverse() { |
| public boolean accept(Object node) { |
| if (node instanceof MyNode) { |
| final MyNode treeNode = ((MyNode)node); |
| treeNode.getConfigurable().disposeUIResources(); |
| if (!(treeNode instanceof MyRootNode)) { |
| treeNode.setUserObject(null); |
| } |
| } |
| return true; |
| } |
| }); |
| myRoot.removeAllChildren(); |
| } |
| |
| @Nullable |
| protected ArrayList<AnAction> createActions(final boolean fromPopup) { |
| return null; |
| } |
| |
| |
| protected void initTree() { |
| ((DefaultTreeModel)myTree.getModel()).setRoot(myRoot); |
| myTree.setRootVisible(false); |
| myTree.setShowsRootHandles(true); |
| UIUtil.setLineStyleAngled(myTree); |
| TreeUtil.installActions(myTree); |
| myTree.setCellRenderer(new ColoredTreeCellRenderer() { |
| public void customizeCellRenderer(JTree tree, |
| Object value, |
| boolean selected, |
| boolean expanded, |
| boolean leaf, |
| int row, |
| boolean hasFocus) { |
| if (value instanceof MyNode) { |
| final MyNode node = ((MyNode)value); |
| setIcon(node.getIcon(expanded)); |
| final Font font = UIUtil.getTreeFont(); |
| if (node.isDisplayInBold()) { |
| setFont(font.deriveFont(Font.BOLD)); |
| } |
| else { |
| setFont(font.deriveFont(Font.PLAIN)); |
| } |
| append(node.getDisplayName(), |
| node.isDisplayInBold() ? SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES : SimpleTextAttributes.REGULAR_ATTRIBUTES); |
| } |
| } |
| }); |
| initToolbar(); |
| ArrayList<AnAction> actions = createActions(true); |
| if (actions != null) { |
| final DefaultActionGroup group = new DefaultActionGroup(); |
| for (AnAction action : actions) { |
| group.add(action); |
| } |
| actions = getAdditionalActions(); |
| if (actions != null) { |
| group.addSeparator(); |
| for (AnAction action : actions) { |
| group.add(action); |
| } |
| } |
| PopupHandler |
| .installPopupHandler(myTree, group, ActionPlaces.UNKNOWN, ActionManager.getInstance()); //popup should follow the selection |
| } |
| } |
| |
| @Nullable |
| protected ArrayList<AnAction> getAdditionalActions() { |
| return null; |
| } |
| |
| public void fireItemsChangeListener(final Object editableObject) { |
| for (ItemsChangeListener listener : myListeners) { |
| listener.itemChanged(editableObject); |
| } |
| } |
| |
| private void fireItemsChangedExternally() { |
| for (ItemsChangeListener listener : myListeners) { |
| listener.itemsExternallyChanged(); |
| } |
| } |
| |
| private void createUIComponents() { |
| myTree = new Tree() { |
| public Dimension getPreferredScrollableViewportSize() { |
| Dimension size = super.getPreferredScrollableViewportSize(); |
| size = new Dimension(size.width + 20, size.height); |
| return size; |
| } |
| |
| @SuppressWarnings({"NonStaticInitializer"}) |
| public JToolTip createToolTip() { |
| final JToolTip toolTip = new JToolTip() { |
| { |
| setUI(new MultiLineTooltipUI()); |
| } |
| }; |
| toolTip.setComponent(this); |
| return toolTip; |
| } |
| }; |
| } |
| |
| protected void addNode(MyNode nodeToAdd, MyNode parent) { |
| parent.add(nodeToAdd); |
| TreeUtil.sort(parent, getNodeComparator()); |
| ((DefaultTreeModel)myTree.getModel()).reload(parent); |
| } |
| |
| protected Comparator<MyNode> getNodeComparator() { |
| return new Comparator<MyNode>() { |
| public int compare(final MyNode o1, final MyNode o2) { |
| return StringUtil.naturalCompare(o1.getDisplayName(), o2.getDisplayName()); |
| } |
| }; |
| } |
| |
| public ActionCallback selectNodeInTree(final DefaultMutableTreeNode nodeToSelect) { |
| return selectNodeInTree(nodeToSelect, true, false); |
| } |
| |
| public ActionCallback selectNodeInTree(final DefaultMutableTreeNode nodeToSelect, boolean requestFocus) { |
| return selectNodeInTree(nodeToSelect, true, requestFocus); |
| } |
| |
| public ActionCallback selectNodeInTree(final DefaultMutableTreeNode nodeToSelect, boolean center, final boolean requestFocus) { |
| if (requestFocus) { |
| myTree.requestFocus(); |
| } |
| if (nodeToSelect != null) { |
| return TreeUtil.selectInTree(nodeToSelect, requestFocus, myTree, center); |
| } |
| else { |
| return TreeUtil.selectFirstNode(myTree); |
| } |
| } |
| |
| @Nullable |
| public Object getSelectedObject() { |
| final TreePath selectionPath = myTree.getSelectionPath(); |
| if (selectionPath != null && selectionPath.getLastPathComponent() instanceof MyNode) { |
| MyNode node = (MyNode)selectionPath.getLastPathComponent(); |
| final NamedConfigurable configurable = node.getConfigurable(); |
| LOG.assertTrue(configurable != null, "already disposed"); |
| return configurable.getEditableObject(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| public NamedConfigurable getSelectedConfigurable() { |
| final TreePath selectionPath = myTree.getSelectionPath(); |
| if (selectionPath != null) { |
| MyNode node = (MyNode)selectionPath.getLastPathComponent(); |
| final NamedConfigurable configurable = node.getConfigurable(); |
| LOG.assertTrue(configurable != null, "already disposed"); |
| return configurable; |
| } |
| return null; |
| } |
| |
| public void selectNodeInTree(String displayName) { |
| final MyNode nodeByName = findNodeByName(myRoot, displayName); |
| selectNodeInTree(nodeByName, true); |
| } |
| |
| public void selectNodeInTree(final Object object) { |
| selectNodeInTree(findNodeByObject(myRoot, object), true); |
| } |
| |
| @Nullable |
| protected static MyNode findNodeByName(final TreeNode root, final String profileName) { |
| if (profileName == null) return null; //do not suggest root node |
| return findNodeByCondition(root, new Condition<NamedConfigurable>() { |
| public boolean value(final NamedConfigurable configurable) { |
| return Comparing.strEqual(profileName, configurable.getDisplayName()); |
| } |
| }); |
| } |
| |
| @Nullable |
| public static MyNode findNodeByObject(final TreeNode root, final Object editableObject) { |
| if (editableObject == null) return null; //do not suggest root node |
| return findNodeByCondition(root, new Condition<NamedConfigurable>() { |
| public boolean value(final NamedConfigurable configurable) { |
| return Comparing.equal(editableObject, configurable.getEditableObject()); |
| } |
| }); |
| } |
| |
| protected static MyNode findNodeByCondition(final TreeNode root, final Condition<NamedConfigurable> condition) { |
| final MyNode[] nodeToSelect = new MyNode[1]; |
| TreeUtil.traverseDepth(root, new TreeUtil.Traverse() { |
| public boolean accept(Object node) { |
| if (condition.value(((MyNode)node).getConfigurable())) { |
| nodeToSelect[0] = (MyNode)node; |
| return false; |
| } |
| return true; |
| } |
| }); |
| return nodeToSelect[0]; |
| } |
| |
| protected void setSelectedNode(@Nullable MyNode node) { |
| if (node != null) { |
| myState.setLastEditedConfigurable(getNodePathString(node)); |
| } |
| updateSelection(node != null ? node.getConfigurable() : null); |
| } |
| |
| protected void updateSelection(@Nullable NamedConfigurable configurable) { |
| myDetails.setText(configurable != null ? configurable.getBannerSlogan() : null); |
| |
| myCurrentConfigurable = configurable; |
| |
| if (configurable != null) { |
| final JComponent comp = configurable.createComponent(); |
| if (comp == null) { |
| setEmpty(); |
| LOG.error("createComponent() returned null. configurable=" + configurable); |
| } else { |
| myDetails.setContent(comp); |
| ensureInitialized(configurable); |
| myHistory.pushPlaceForElement(TREE_OBJECT, configurable.getEditableObject()); |
| } |
| } else { |
| setEmpty(); |
| } |
| } |
| |
| public void ensureInitialized(NamedConfigurable configurable) { |
| if (!isInitialized(configurable)) { |
| configurable.reset(); |
| initializeConfigurable(configurable); |
| } |
| } |
| |
| private void setEmpty() { |
| myDetails.setContent(null); |
| myDetails.setEmptyContentText(getEmptySelectionString()); |
| } |
| |
| public String getHelpTopic() { |
| if (myCurrentConfigurable != null) { |
| return myCurrentConfigurable.getHelpTopic(); |
| } |
| return null; |
| } |
| |
| protected @Nullable String getEmptySelectionString() { |
| return null; |
| } |
| |
| protected void initializeConfigurable(final NamedConfigurable configurable) { |
| myInitializedConfigurables.add(configurable); |
| } |
| |
| protected void checkApply(Set<MyNode> rootNodes, String prefix, String title) throws ConfigurationException { |
| for (MyNode rootNode : rootNodes) { |
| final Set<String> names = new HashSet<String>(); |
| for (int i = 0; i < rootNode.getChildCount(); i++) { |
| final MyNode node = (MyNode)rootNode.getChildAt(i); |
| final NamedConfigurable scopeConfigurable = node.getConfigurable(); |
| final String name = scopeConfigurable.getDisplayName(); |
| if (name.trim().length() == 0) { |
| selectNodeInTree(node); |
| throw new ConfigurationException("Name should contain non-space characters"); |
| } |
| if (names.contains(name)) { |
| final NamedConfigurable selectedConfigurable = getSelectedConfigurable(); |
| if (selectedConfigurable == null || !Comparing.strEqual(selectedConfigurable.getDisplayName(), name)) { |
| selectNodeInTree(node); |
| } |
| throw new ConfigurationException(CommonBundle.message("smth.already.exist.error.message", prefix, name), title); |
| } |
| names.add(name); |
| } |
| } |
| } |
| |
| public Tree getTree() { |
| return myTree; |
| } |
| |
| protected void removePaths(final TreePath... paths) { |
| MyNode parentNode = null; |
| int idx = -1; |
| for (TreePath path : paths) { |
| final MyNode node = (MyNode)path.getLastPathComponent(); |
| final NamedConfigurable namedConfigurable = node.getConfigurable(); |
| final Object editableObject = namedConfigurable.getEditableObject(); |
| parentNode = (MyNode)node.getParent(); |
| idx = parentNode.getIndex(node); |
| ((DefaultTreeModel)myTree.getModel()).removeNodeFromParent(node); |
| myHasDeletedItems |= wasObjectStored(editableObject); |
| fireItemsChangeListener(editableObject); |
| onItemDeleted(editableObject); |
| namedConfigurable.disposeUIResources(); |
| } |
| |
| if (paths.length > 0) { |
| if (parentNode != null && idx != -1) { |
| DefaultMutableTreeNode toSelect = null; |
| if (idx < parentNode.getChildCount()) { |
| toSelect = (DefaultMutableTreeNode) parentNode.getChildAt(idx); |
| } else { |
| if (idx > 0 && parentNode.getChildCount() > 0) { |
| if (idx - 1 < parentNode.getChildCount()) { |
| toSelect = (DefaultMutableTreeNode) parentNode.getChildAt(idx - 1); |
| } else { |
| toSelect = (DefaultMutableTreeNode) parentNode.getFirstChild(); |
| } |
| } else { |
| if (parentNode.isRoot() && myTree.isRootVisible()) { |
| toSelect = parentNode; |
| } else if (parentNode.getChildCount() > 0) { |
| toSelect = (DefaultMutableTreeNode) parentNode.getFirstChild(); |
| } |
| } |
| } |
| |
| if (toSelect != null) { |
| TreeUtil.selectInTree(toSelect, true, myTree); |
| } |
| } |
| else { |
| TreeUtil.selectFirstNode(myTree); |
| } |
| } |
| } |
| |
| protected void onItemDeleted(Object item) { |
| } |
| |
| protected class MyDeleteAction extends AnAction implements DumbAware { |
| private final Condition<Object[]> myCondition; |
| |
| public MyDeleteAction() { |
| this(Conditions.<Object[]>alwaysTrue()); |
| } |
| |
| public MyDeleteAction(Condition<Object[]> availableCondition) { |
| super(CommonBundle.message("button.delete"), CommonBundle.message("button.delete"), PlatformIcons.DELETE_ICON); |
| registerCustomShortcutSet(CommonShortcuts.getDelete(), myTree); |
| myCondition = availableCondition; |
| } |
| |
| public void update(AnActionEvent e) { |
| final Presentation presentation = e.getPresentation(); |
| presentation.setEnabled(false); |
| final TreePath[] selectionPath = myTree.getSelectionPaths(); |
| if (selectionPath != null) { |
| Object[] nodes = ContainerUtil.map2Array(selectionPath, new Function<TreePath, Object>() { |
| @Override |
| public Object fun(TreePath treePath) { |
| return treePath.getLastPathComponent(); |
| } |
| }); |
| if (!myCondition.value(nodes)) return; |
| presentation.setEnabled(true); |
| } |
| } |
| |
| public void actionPerformed(AnActionEvent e) { |
| removePaths(myTree.getSelectionPaths()); |
| } |
| } |
| |
| protected static Condition<Object[]> forAll(final Condition<Object> condition) { |
| return new Condition<Object[]>() { |
| @Override |
| public boolean value(Object[] objects) { |
| for (Object object : objects) { |
| if (!condition.value(object)) return false; |
| } |
| return true; |
| } |
| }; |
| } |
| |
| public static class MyNode extends DefaultMutableTreeNode { |
| private boolean myDisplayInBold; |
| |
| public MyNode(@NotNull NamedConfigurable userObject) { |
| super(userObject); |
| } |
| |
| public MyNode(@NotNull NamedConfigurable userObject, boolean displayInBold) { |
| super(userObject); |
| myDisplayInBold = displayInBold; |
| } |
| |
| @NotNull |
| public String getDisplayName() { |
| final NamedConfigurable configurable = ((NamedConfigurable)getUserObject()); |
| LOG.assertTrue(configurable != null, "Tree was already disposed"); |
| return configurable.getDisplayName(); |
| } |
| |
| public NamedConfigurable getConfigurable() { |
| return (NamedConfigurable)getUserObject(); |
| } |
| |
| public boolean isDisplayInBold() { |
| return myDisplayInBold; |
| } |
| |
| public void setDisplayInBold(boolean displayInBold) { |
| myDisplayInBold = displayInBold; |
| } |
| |
| @Nullable |
| public Icon getIcon(boolean expanded) { |
| // thanks to invokeLater() in TreeUtil.showAndSelect(), we can get calls to getIcon() after the tree has been disposed |
| final NamedConfigurable configurable = getConfigurable(); |
| if (configurable != null) { |
| return configurable.getIcon(expanded); |
| } |
| return null; |
| } |
| } |
| |
| @SuppressWarnings({"ConstantConditions"}) |
| protected static class MyRootNode extends MyNode { |
| public MyRootNode() { |
| super(new NamedConfigurable(false, null) { |
| public void setDisplayName(String name) { |
| } |
| |
| public Object getEditableObject() { |
| return null; |
| } |
| |
| public String getBannerSlogan() { |
| return null; |
| } |
| |
| public String getDisplayName() { |
| return ""; |
| } |
| |
| @Nullable |
| @NonNls |
| public String getHelpTopic() { |
| return null; |
| } |
| |
| public JComponent createOptionsPanel() { |
| return null; |
| } |
| |
| public boolean isModified() { |
| return false; |
| } |
| |
| public void apply() throws ConfigurationException { |
| } |
| |
| public void reset() { |
| } |
| |
| public void disposeUIResources() { |
| } |
| |
| }, false); |
| } |
| } |
| |
| protected interface ItemsChangeListener { |
| void itemChanged(@Nullable Object deletedItem); |
| |
| void itemsExternallyChanged(); |
| } |
| |
| public interface ActionGroupWithPreselection { |
| ActionGroup getActionGroup(); |
| |
| int getDefaultIndex(); |
| } |
| |
| protected class MyActionGroupWrapper extends AnAction implements DumbAware { |
| private ActionGroup myActionGroup; |
| private ActionGroupWithPreselection myPreselection; |
| |
| public MyActionGroupWrapper(final ActionGroupWithPreselection actionGroup) { |
| this(actionGroup.getActionGroup()); |
| myPreselection = actionGroup; |
| } |
| |
| public MyActionGroupWrapper(final ActionGroup actionGroup) { |
| super(actionGroup.getTemplatePresentation().getText(), actionGroup.getTemplatePresentation().getDescription(), |
| actionGroup.getTemplatePresentation().getIcon()); |
| myActionGroup = actionGroup; |
| registerCustomShortcutSet(actionGroup.getShortcutSet(), myTree); |
| } |
| |
| public void actionPerformed(AnActionEvent e) { |
| final JBPopupFactory popupFactory = JBPopupFactory.getInstance(); |
| final ListPopupStep step = popupFactory.createActionsStep(myActionGroup, e.getDataContext(), false, false, |
| myActionGroup.getTemplatePresentation().getText(), myTree, true, |
| myPreselection != null ? myPreselection.getDefaultIndex() : 0, true); |
| final ListPopup listPopup = popupFactory.createListPopup(step); |
| listPopup.setHandleAutoSelectionBeforeShow(true); |
| if (e instanceof AnActionButton.AnActionEventWrapper) { |
| ((AnActionButton.AnActionEventWrapper)e).showPopup(listPopup); |
| } else { |
| listPopup.showUnderneathOf(myNorthPanel); |
| } |
| } |
| } |
| |
| public JComponent getToolbar() { |
| myToReInitWholePanel = true; |
| return myNorthPanel; |
| } |
| |
| public JComponent getMaster() { |
| myToReInitWholePanel = true; |
| return myMaster; |
| } |
| |
| public DetailsComponent getDetails() { |
| myToReInitWholePanel = true; |
| return myDetails; |
| } |
| |
| public void initUi() { |
| createComponent(); |
| } |
| } |