| /* |
| * 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.options.newEditor; |
| |
| import com.intellij.icons.AllIcons; |
| import com.intellij.ide.util.treeView.NodeDescriptor; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.options.*; |
| import com.intellij.openapi.options.ex.ConfigurableWrapper; |
| import com.intellij.openapi.options.ex.SortedConfigurableGroup; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.ActionCallback; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.ui.*; |
| import com.intellij.ui.treeStructure.CachingSimpleNode; |
| import com.intellij.ui.treeStructure.SimpleNode; |
| import com.intellij.ui.treeStructure.SimpleTree; |
| import com.intellij.ui.treeStructure.SimpleTreeStructure; |
| import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder; |
| import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.ui.ButtonlessScrollBarUI; |
| import com.intellij.util.ui.GraphicsUtil; |
| import com.intellij.util.ui.UIUtil; |
| import com.intellij.util.ui.tree.TreeUtil; |
| import com.intellij.util.ui.tree.WideSelectionTreeUI; |
| import com.intellij.util.ui.update.MergingUpdateQueue; |
| import com.intellij.util.ui.update.Update; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import javax.swing.*; |
| import javax.swing.event.TreeExpansionEvent; |
| import javax.swing.event.TreeExpansionListener; |
| import javax.swing.event.TreeSelectionEvent; |
| import javax.swing.event.TreeSelectionListener; |
| import javax.swing.plaf.TreeUI; |
| import javax.swing.tree.DefaultMutableTreeNode; |
| import javax.swing.tree.TreeCellRenderer; |
| import javax.swing.tree.TreePath; |
| import javax.swing.tree.TreeSelectionModel; |
| import java.awt.*; |
| import java.awt.event.ComponentAdapter; |
| import java.awt.event.ComponentEvent; |
| import java.awt.event.KeyEvent; |
| import java.awt.event.MouseEvent; |
| import java.util.*; |
| import java.util.List; |
| |
| /** |
| * @author Sergey.Malenkov |
| */ |
| final class SettingsTreeView extends JComponent implements Disposable, OptionsEditorColleague { |
| private static final String NODE_ICON = "settings.tree.view.icon"; |
| private static final Color NORMAL_NODE = new JBColor(Gray._60, Gray._140); |
| private static final Color WRONG_CONTENT = JBColor.RED; |
| private static final Color MODIFIED_CONTENT = JBColor.BLUE; |
| |
| final SimpleTree myTree; |
| final FilteringTreeBuilder myBuilder; |
| |
| private final SettingsFilter myFilter; |
| private final MyRoot myRoot; |
| private final JScrollPane myScroller; |
| private JLabel mySeparator; |
| private final IdentityHashMap<Configurable, MyNode> myConfigurableToNodeMap = new IdentityHashMap<Configurable, MyNode>(); |
| private final IdentityHashMap<UnnamedConfigurable, ConfigurableWrapper> myConfigurableToWrapperMap |
| = new IdentityHashMap<UnnamedConfigurable, ConfigurableWrapper>(); |
| private final MergingUpdateQueue myQueue = new MergingUpdateQueue("SettingsTreeView", 150, false, this, this, this) |
| .setRestartTimerOnAdd(true); |
| |
| private Configurable myQueuedConfigurable; |
| |
| SettingsTreeView(SettingsFilter filter, ConfigurableGroup... groups) { |
| myFilter = filter; |
| myRoot = new MyRoot(groups); |
| myTree = new MyTree(); |
| myTree.putClientProperty(WideSelectionTreeUI.TREE_TABLE_TREE_KEY, Boolean.TRUE); |
| myTree.setBackground(UIUtil.getSidePanelColor()); |
| myTree.getInputMap().clear(); |
| TreeUtil.installActions(myTree); |
| |
| myTree.setOpaque(true); |
| myTree.setBorder(BorderFactory.createEmptyBorder(0, 1, 0, 0)); |
| |
| myTree.setRowHeight(-1); |
| myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION); |
| |
| myTree.setCellRenderer(new MyRenderer()); |
| myTree.setRootVisible(false); |
| myTree.setShowsRootHandles(false); |
| myTree.setExpandableItemsEnabled(false); |
| |
| myScroller = ScrollPaneFactory.createScrollPane(myTree, true); |
| myScroller.getVerticalScrollBar().setUI(ButtonlessScrollBarUI.createTransparent()); |
| myScroller.setBackground(UIUtil.getSidePanelColor()); |
| myScroller.getViewport().setBackground(UIUtil.getSidePanelColor()); |
| myScroller.getVerticalScrollBar().setBackground(UIUtil.getSidePanelColor()); |
| add(myScroller); |
| |
| myTree.addComponentListener(new ComponentAdapter() { |
| @Override |
| public void componentResized(ComponentEvent e) { |
| myBuilder.revalidateTree(); |
| } |
| |
| @Override |
| public void componentMoved(ComponentEvent e) { |
| myBuilder.revalidateTree(); |
| } |
| |
| @Override |
| public void componentShown(ComponentEvent e) { |
| myBuilder.revalidateTree(); |
| } |
| }); |
| |
| myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { |
| public void valueChanged(TreeSelectionEvent event) { |
| MyNode node = extractNode(event.getNewLeadSelectionPath()); |
| select(node == null ? null : node.myConfigurable); |
| } |
| }); |
| |
| myBuilder = new MyBuilder(new SimpleTreeStructure.Impl(myRoot)); |
| myBuilder.setFilteringMerge(300, null); |
| Disposer.register(this, myBuilder); |
| } |
| |
| @NotNull |
| String[] getPathNames(Configurable configurable) { |
| ArrayDeque<String> path = new ArrayDeque<String>(); |
| MyNode node = findNode(configurable); |
| while (node != null) { |
| path.push(node.myDisplayName); |
| SimpleNode parent = node.getParent(); |
| node = parent instanceof MyNode |
| ? (MyNode)parent |
| : null; |
| } |
| return ArrayUtil.toStringArray(path); |
| } |
| |
| static Configurable getConfigurable(SimpleNode node) { |
| return node instanceof MyNode |
| ? ((MyNode)node).myConfigurable |
| : null; |
| } |
| |
| @Nullable |
| MyNode findNode(Configurable configurable) { |
| ConfigurableWrapper wrapper = myConfigurableToWrapperMap.get(configurable); |
| return myConfigurableToNodeMap.get(wrapper != null ? wrapper : configurable); |
| } |
| |
| @Nullable |
| SearchableConfigurable findConfigurableById(@NotNull String id) { |
| for (Configurable configurable : myConfigurableToNodeMap.keySet()) { |
| if (configurable instanceof SearchableConfigurable) { |
| SearchableConfigurable searchable = (SearchableConfigurable)configurable; |
| if (id.equals(searchable.getId())) { |
| return searchable; |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| <T extends UnnamedConfigurable> T findConfigurable(@NotNull Class<T> type) { |
| for (UnnamedConfigurable configurable : myConfigurableToNodeMap.keySet()) { |
| if (configurable instanceof ConfigurableWrapper) { |
| ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable; |
| configurable = wrapper.getConfigurable(); |
| myConfigurableToWrapperMap.put(configurable, wrapper); |
| } |
| if (type.isInstance(configurable)) { |
| return type.cast(configurable); |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| Project findConfigurableProject(@Nullable Configurable configurable) { |
| if (configurable instanceof ConfigurableWrapper) { |
| ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable; |
| return wrapper.getExtensionPoint().getProject(); |
| } |
| return findConfigurableProject(findNode(configurable)); |
| } |
| |
| @Nullable |
| private static Project findConfigurableProject(@Nullable MyNode node) { |
| if (node != null) { |
| Configurable configurable = node.myConfigurable; |
| if (configurable instanceof ConfigurableWrapper) { |
| ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable; |
| return wrapper.getExtensionPoint().getProject(); |
| } |
| SimpleNode parent = node.getParent(); |
| if (parent instanceof MyNode) { |
| return findConfigurableProject((MyNode)parent); |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| private String findGroupNameAt(int x, int y) { |
| TreePath path = myTree.getClosestPathForLocation(x - myTree.getX(), y - myTree.getY()); |
| while (path != null) { |
| MyNode node = extractNode(path); |
| if (node == null) { |
| return null; |
| } |
| if (myRoot == node.getParent()) { |
| return node.myDisplayName; |
| } |
| path = path.getParentPath(); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private static MyNode extractNode(@Nullable Object object) { |
| if (object instanceof TreePath) { |
| TreePath path = (TreePath)object; |
| object = path.getLastPathComponent(); |
| } |
| if (object instanceof DefaultMutableTreeNode) { |
| DefaultMutableTreeNode node = (DefaultMutableTreeNode)object; |
| object = node.getUserObject(); |
| } |
| if (object instanceof FilteringTreeStructure.FilteringNode) { |
| FilteringTreeStructure.FilteringNode node = (FilteringTreeStructure.FilteringNode)object; |
| object = node.getDelegate(); |
| } |
| return object instanceof MyNode |
| ? (MyNode)object |
| : null; |
| } |
| |
| @Override |
| public void doLayout() { |
| myScroller.setBounds(0, 0, getWidth(), getHeight()); |
| } |
| |
| @Override |
| public void paint(Graphics g) { |
| super.paint(g); |
| |
| if (0 == myTree.getY()) { |
| return; // separator is not needed without scrolling |
| } |
| if (mySeparator == null) { |
| mySeparator = new JLabel(); |
| mySeparator.setForeground(NORMAL_NODE); |
| mySeparator.setFont(UIUtil.getLabelFont()); |
| mySeparator.setFont(getFont().deriveFont(Font.BOLD)); |
| } |
| int height = mySeparator.getPreferredSize().height; |
| String group = findGroupNameAt(0, height); |
| if (group != null && group.equals(findGroupNameAt(0, 0))) { |
| mySeparator.setBorder(BorderFactory.createEmptyBorder(0, 18, 0, 0)); |
| mySeparator.setText(group); |
| |
| Rectangle bounds = myScroller.getViewport().getBounds(); |
| if (bounds.height > height) { |
| bounds.height = height; |
| } |
| g.setColor(myTree.getBackground()); |
| g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height); |
| if (g instanceof Graphics2D) { |
| int h = 4; // gradient height |
| int y = bounds.y + bounds.height; |
| ((Graphics2D)g).setPaint(UIUtil.getGradientPaint( |
| 0, y, g.getColor(), |
| 0, y + h, ColorUtil.toAlpha(g.getColor(), 0))); |
| g.fillRect(bounds.x, y, bounds.width, h); |
| } |
| mySeparator.setSize(bounds.width - 1, bounds.height); |
| mySeparator.paint(g.create(bounds.x + 1, bounds.y, bounds.width - 1, bounds.height)); |
| } |
| } |
| |
| void selectFirst() { |
| for (ConfigurableGroup eachGroup : myRoot.myGroups) { |
| Configurable[] kids = eachGroup.getConfigurables(); |
| if (kids.length > 0) { |
| select(kids[0]); |
| return; |
| } |
| } |
| } |
| |
| ActionCallback select(@Nullable final Configurable configurable) { |
| if (myBuilder.isSelectionBeingAdjusted()) { |
| return new ActionCallback.Rejected(); |
| } |
| final ActionCallback callback = new ActionCallback(); |
| myQueuedConfigurable = configurable; |
| myQueue.queue(new Update(this) { |
| public void run() { |
| if (configurable == myQueuedConfigurable) { |
| if (configurable == null) { |
| fireSelected(null, callback); |
| } |
| else { |
| myBuilder.getReady(this).doWhenDone(new Runnable() { |
| @Override |
| public void run() { |
| if (configurable != myQueuedConfigurable) return; |
| |
| MyNode editorNode = findNode(configurable); |
| FilteringTreeStructure.FilteringNode editorUiNode = myBuilder.getVisibleNodeFor(editorNode); |
| if (editorUiNode == null) return; |
| |
| if (!myBuilder.getSelectedElements().contains(editorUiNode)) { |
| myBuilder.select(editorUiNode, new Runnable() { |
| public void run() { |
| fireSelected(configurable, callback); |
| } |
| }); |
| } |
| else { |
| myBuilder.scrollSelectionToVisible(new Runnable() { |
| public void run() { |
| fireSelected(configurable, callback); |
| } |
| }, false); |
| } |
| } |
| }); |
| } |
| } |
| } |
| |
| @Override |
| public void setRejected() { |
| super.setRejected(); |
| callback.setRejected(); |
| } |
| }); |
| return callback; |
| } |
| |
| private void fireSelected(Configurable configurable, ActionCallback callback) { |
| ConfigurableWrapper wrapper = myConfigurableToWrapperMap.get(configurable); |
| myFilter.myContext.fireSelected(wrapper != null ? wrapper : configurable, this).doWhenProcessed(callback.createSetDoneRunnable()); |
| } |
| |
| @Override |
| public void dispose() { |
| myQueuedConfigurable = null; |
| } |
| |
| @Override |
| public ActionCallback onSelected(@Nullable Configurable configurable, Configurable oldConfigurable) { |
| return select(configurable); |
| } |
| |
| @Override |
| public ActionCallback onModifiedAdded(Configurable configurable) { |
| myTree.repaint(); |
| return new ActionCallback.Done(); |
| } |
| |
| @Override |
| public ActionCallback onModifiedRemoved(Configurable configurable) { |
| myTree.repaint(); |
| return new ActionCallback.Done(); |
| } |
| |
| @Override |
| public ActionCallback onErrorsChanged() { |
| return new ActionCallback.Done(); |
| } |
| |
| private final class MyRoot extends CachingSimpleNode { |
| private final ConfigurableGroup[] myGroups; |
| |
| private MyRoot(ConfigurableGroup[] groups) { |
| super(null); |
| myGroups = groups; |
| } |
| |
| @Override |
| protected SimpleNode[] buildChildren() { |
| if (myGroups == null || myGroups.length == 0) { |
| return NO_CHILDREN; |
| } |
| ArrayList<MyNode> list = new ArrayList<MyNode>(); |
| for (ConfigurableGroup group : myGroups) { |
| for (Configurable configurable : group.getConfigurables()) { |
| list.add(new MyNode(this, configurable, 0)); |
| } |
| } |
| return list.toArray(new SimpleNode[list.size()]); |
| } |
| } |
| |
| private final class MyNode extends CachingSimpleNode { |
| private final Configurable.Composite myComposite; |
| private final Configurable myConfigurable; |
| private final String myDisplayName; |
| private final int myLevel; |
| |
| private MyNode(CachingSimpleNode parent, Configurable configurable, int level) { |
| super(parent); |
| myComposite = configurable instanceof Configurable.Composite ? (Configurable.Composite)configurable : null; |
| myConfigurable = configurable; |
| String name = configurable.getDisplayName(); |
| myDisplayName = name != null ? name.replace("\n", " ") : "{ " + configurable.getClass().getSimpleName() + " }"; |
| myLevel = level; |
| } |
| |
| @Override |
| protected SimpleNode[] buildChildren() { |
| if (myConfigurable != null) { |
| myConfigurableToNodeMap.put(myConfigurable, this); |
| } |
| if (myComposite == null) { |
| return NO_CHILDREN; |
| } |
| Configurable[] configurables = myComposite.getConfigurables(); |
| if (configurables == null || configurables.length == 0) { |
| return NO_CHILDREN; |
| } |
| SimpleNode[] result = new SimpleNode[configurables.length]; |
| for (int i = 0; i < configurables.length; i++) { |
| result[i] = new MyNode(this, configurables[i], myLevel + 1); |
| if (myConfigurable != null) { |
| myFilter.myContext.registerKid(myConfigurable, configurables[i]); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public boolean isAlwaysLeaf() { |
| return myComposite == null; |
| } |
| } |
| |
| private final class MyRenderer extends JPanel implements TreeCellRenderer { |
| private final JLabel myTextLabel = new ErrorLabel(); |
| private final JLabel myNodeIcon = new JLabel(" ", SwingConstants.RIGHT); |
| private final JLabel myProjectIcon = new JLabel(" ", SwingConstants.LEFT); |
| |
| public MyRenderer() { |
| super(new BorderLayout()); |
| myNodeIcon.setName(NODE_ICON); |
| add(BorderLayout.CENTER, myTextLabel); |
| add(BorderLayout.WEST, myNodeIcon); |
| add(BorderLayout.EAST, myProjectIcon); |
| } |
| |
| public Component getTreeCellRendererComponent(JTree tree, |
| Object value, |
| boolean selected, |
| boolean expanded, |
| boolean leaf, |
| int row, |
| boolean focused) { |
| myTextLabel.setFont(UIUtil.getLabelFont()); |
| setPreferredSize(null); |
| |
| MyNode node = extractNode(value); |
| if (node == null) { |
| myTextLabel.setText(value.toString()); |
| } |
| else { |
| myTextLabel.setText(node.myDisplayName); |
| // show groups in bold |
| if (myRoot == node.getParent()) { |
| myTextLabel.setFont(myTextLabel.getFont().deriveFont(Font.BOLD)); |
| } |
| if (tree.isVisible()) { |
| int indent = node.myLevel * (UIUtil.getTreeLeftChildIndent() + UIUtil.getTreeRightChildIndent()); |
| Insets treeInsets = tree.getInsets(); |
| if (treeInsets != null) { |
| indent += treeInsets.left + treeInsets.right; |
| } |
| int visibleWidth = tree.getVisibleRect().width; |
| if (visibleWidth > indent) { |
| Dimension size = getPreferredSize(); |
| size.width = visibleWidth - indent; |
| //setPreferredSize(size); |
| } |
| } |
| } |
| // update font color for modified configurables |
| myTextLabel.setForeground(selected ? UIUtil.getTreeSelectionForeground() : NORMAL_NODE); |
| if (!selected && node != null) { |
| Configurable configurable = node.myConfigurable; |
| if (configurable != null) { |
| if (myFilter.myContext.getErrors().containsKey(configurable)) { |
| myTextLabel.setForeground(WRONG_CONTENT); |
| } |
| else if (myFilter.myContext.getModified().contains(configurable)) { |
| myTextLabel.setForeground(MODIFIED_CONTENT); |
| } |
| } |
| } |
| // configure project icon |
| Project project = null; |
| if (node != null) { |
| SimpleNode parent = node.getParent(); |
| if (parent instanceof MyNode) { |
| if (myRoot == parent.getParent()) { |
| project = findConfigurableProject(node); // show icon for top-level nodes |
| if (node.myConfigurable instanceof SortedConfigurableGroup) { // special case for custom subgroups (build.tools) |
| Configurable[] configurables = ((SortedConfigurableGroup)node.myConfigurable).getConfigurables(); |
| if (configurables != null) { // assume that all configurables have the same project |
| project = findConfigurableProject(configurables[0]); |
| } |
| } |
| } |
| else if (((MyNode)parent).myConfigurable instanceof SortedConfigurableGroup) { |
| if (((MyNode)node.getParent()).myConfigurable instanceof SortedConfigurableGroup) { |
| project = findConfigurableProject(node); // special case for custom subgroups |
| } |
| } |
| } |
| } |
| if (project != null) { |
| myProjectIcon.setIcon(selected |
| ? AllIcons.General.ProjectConfigurableSelected |
| : AllIcons.General.ProjectConfigurable); |
| myProjectIcon.setToolTipText(OptionsBundle.message(project.isDefault() |
| ? "configurable.default.project.tooltip" |
| : "configurable.current.project.tooltip")); |
| myProjectIcon.setVisible(true); |
| } |
| else { |
| myProjectIcon.setVisible(false); |
| } |
| // configure node icon |
| Icon nodeIcon = null; |
| if (node != null) { |
| if (0 == node.getChildCount()) { |
| nodeIcon = myTree.getEmptyHandle(); |
| } |
| else if (value instanceof DefaultMutableTreeNode) { |
| DefaultMutableTreeNode treeNode = (DefaultMutableTreeNode)value; |
| nodeIcon = myTree.isExpanded(new TreePath(treeNode.getPath())) |
| ? myTree.getExpandedHandle() |
| : myTree.getCollapsedHandle(); |
| } |
| } |
| myNodeIcon.setIcon(nodeIcon); |
| return this; |
| } |
| } |
| |
| private final class MyTree extends SimpleTree { |
| @Override |
| public String getToolTipText(MouseEvent event) { |
| if (event != null) { |
| Component component = getDeepestRendererComponentAt(event.getX(), event.getY()); |
| if (component instanceof JLabel) { |
| JLabel label = (JLabel)component; |
| if (label.getIcon() != null) { |
| String text = label.getToolTipText(); |
| if (text != null) { |
| return text; |
| } |
| } |
| } |
| } |
| return super.getToolTipText(event); |
| } |
| |
| @Override |
| protected boolean paintNodes() { |
| return false; |
| } |
| |
| @Override |
| protected boolean highlightSingleNode() { |
| return false; |
| } |
| |
| @Override |
| public void setUI(TreeUI ui) { |
| super.setUI(ui instanceof MyTreeUi ? ui : new MyTreeUi()); |
| } |
| |
| @Override |
| protected boolean isCustomUI() { |
| return true; |
| } |
| |
| @Override |
| protected void configureUiHelper(TreeUIHelper helper) { |
| } |
| |
| @Override |
| public boolean getScrollableTracksViewportWidth() { |
| return true; |
| } |
| |
| |
| @Override |
| public void processKeyEvent(KeyEvent e) { |
| TreePath path = myTree.getSelectionPath(); |
| if (path != null) { |
| if (e.getKeyCode() == KeyEvent.VK_LEFT) { |
| if (isExpanded(path)) { |
| collapsePath(path); |
| return; |
| } |
| } |
| else if (e.getKeyCode() == KeyEvent.VK_RIGHT) { |
| if (isCollapsed(path)) { |
| expandPath(path); |
| return; |
| } |
| } |
| } |
| super.processKeyEvent(e); |
| } |
| |
| @Override |
| protected void processMouseEvent(MouseEvent event) { |
| MyTreeUi ui = (MyTreeUi)myTree.getUI(); |
| if (!ui.processMouseEvent(event)) { |
| super.processMouseEvent(event); |
| } |
| } |
| } |
| |
| private static final class MyTreeUi extends WideSelectionTreeUI { |
| boolean processMouseEvent(MouseEvent event) { |
| if (super.tree instanceof SimpleTree) { |
| SimpleTree tree = (SimpleTree)super.tree; |
| |
| boolean toggleNow = MouseEvent.MOUSE_RELEASED == event.getID() |
| && UIUtil.isActionClick(event, MouseEvent.MOUSE_RELEASED) |
| && !isToggleEvent(event); |
| |
| if (toggleNow || MouseEvent.MOUSE_PRESSED == event.getID()) { |
| Component component = tree.getDeepestRendererComponentAt(event.getX(), event.getY()); |
| if (component != null && NODE_ICON.equals(component.getName())) { |
| if (toggleNow) { |
| toggleExpandState(tree.getPathForLocation(event.getX(), event.getY())); |
| } |
| event.consume(); |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| protected boolean shouldPaintExpandControl(TreePath path, |
| int row, |
| boolean isExpanded, |
| boolean hasBeenExpanded, |
| boolean isLeaf) { |
| return false; |
| } |
| |
| @Override |
| protected void paintHorizontalPartOfLeg(Graphics g, |
| Rectangle clipBounds, |
| Insets insets, |
| Rectangle bounds, |
| TreePath path, |
| int row, |
| boolean isExpanded, |
| boolean hasBeenExpanded, |
| boolean isLeaf) { |
| |
| } |
| |
| @Override |
| protected void paintVerticalPartOfLeg(Graphics g, Rectangle clipBounds, Insets insets, TreePath path) { |
| } |
| |
| @Override |
| public void paint(Graphics g, JComponent c) { |
| GraphicsUtil.setupAntialiasing(g); |
| super.paint(g, c); |
| } |
| |
| @Override |
| protected void paintRow(Graphics g, |
| Rectangle clipBounds, |
| Insets insets, |
| Rectangle bounds, |
| TreePath path, |
| int row, |
| boolean isExpanded, |
| boolean hasBeenExpanded, |
| boolean isLeaf) { |
| if (tree != null) { |
| int width = tree.getWidth(); |
| Container parent = tree.getParent(); |
| if (parent instanceof JViewport) { |
| JViewport viewport = (JViewport)parent; |
| width = viewport.getWidth() - viewport.getViewPosition().x; |
| } |
| width -= bounds.x; |
| if (bounds.width < width) { |
| bounds.width = width; |
| } |
| } |
| super.paintRow(g, clipBounds, insets, bounds, path, row, isExpanded, hasBeenExpanded, isLeaf); |
| } |
| } |
| |
| private final class MyBuilder extends FilteringTreeBuilder { |
| |
| List<Object> myToExpandOnResetFilter; |
| boolean myRefilteringNow; |
| boolean myWasHoldingFilter; |
| |
| public MyBuilder(SimpleTreeStructure structure) { |
| super(myTree, myFilter, structure, null); |
| myTree.addTreeExpansionListener(new TreeExpansionListener() { |
| public void treeExpanded(TreeExpansionEvent event) { |
| invalidateExpansions(); |
| } |
| |
| public void treeCollapsed(TreeExpansionEvent event) { |
| invalidateExpansions(); |
| } |
| }); |
| } |
| |
| private void invalidateExpansions() { |
| if (!myRefilteringNow) { |
| myToExpandOnResetFilter = null; |
| } |
| } |
| |
| @Override |
| protected boolean isSelectable(Object object) { |
| return object instanceof MyNode; |
| } |
| |
| @Override |
| public boolean isAutoExpandNode(NodeDescriptor nodeDescriptor) { |
| return myFilter.myContext.isHoldingFilter(); |
| } |
| |
| @Override |
| public boolean isToEnsureSelectionOnFocusGained() { |
| return false; |
| } |
| |
| @Override |
| protected ActionCallback refilterNow(Object preferredSelection, boolean adjustSelection) { |
| final List<Object> toRestore = new ArrayList<Object>(); |
| if (myFilter.myContext.isHoldingFilter() && !myWasHoldingFilter && myToExpandOnResetFilter == null) { |
| myToExpandOnResetFilter = myBuilder.getUi().getExpandedElements(); |
| } |
| else if (!myFilter.myContext.isHoldingFilter() && myWasHoldingFilter && myToExpandOnResetFilter != null) { |
| toRestore.addAll(myToExpandOnResetFilter); |
| myToExpandOnResetFilter = null; |
| } |
| |
| myWasHoldingFilter = myFilter.myContext.isHoldingFilter(); |
| |
| ActionCallback result = super.refilterNow(preferredSelection, adjustSelection); |
| myRefilteringNow = true; |
| return result.doWhenDone(new Runnable() { |
| public void run() { |
| myRefilteringNow = false; |
| if (!myFilter.myContext.isHoldingFilter() && getSelectedElements().isEmpty()) { |
| restoreExpandedState(toRestore); |
| } |
| } |
| }); |
| } |
| |
| private void restoreExpandedState(List<Object> toRestore) { |
| TreePath[] selected = myTree.getSelectionPaths(); |
| if (selected == null) { |
| selected = new TreePath[0]; |
| } |
| |
| List<TreePath> toCollapse = new ArrayList<TreePath>(); |
| |
| for (int eachRow = 0; eachRow < myTree.getRowCount(); eachRow++) { |
| if (!myTree.isExpanded(eachRow)) continue; |
| |
| TreePath eachVisiblePath = myTree.getPathForRow(eachRow); |
| if (eachVisiblePath == null) continue; |
| |
| Object eachElement = myBuilder.getElementFor(eachVisiblePath.getLastPathComponent()); |
| if (toRestore.contains(eachElement)) continue; |
| |
| |
| for (TreePath eachSelected : selected) { |
| if (!eachVisiblePath.isDescendant(eachSelected)) { |
| toCollapse.add(eachVisiblePath); |
| } |
| } |
| } |
| |
| for (TreePath each : toCollapse) { |
| myTree.collapsePath(each); |
| } |
| } |
| } |
| } |