blob: ed52a7f50bf601334dc2417b04da9e15da003dc0 [file] [log] [blame]
/*
* 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.Configurable;
import com.intellij.openapi.options.ConfigurableGroup;
import com.intellij.openapi.options.OptionsBundle;
import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.options.ex.ConfigurableWrapper;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.ui.*;
import com.intellij.ui.components.panels.NonOpaquePanel;
import com.intellij.ui.treeStructure.*;
import com.intellij.ui.treeStructure.filtered.FilteringTreeBuilder;
import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure;
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.border.EmptyBorder;
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.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;
public class OptionsTree extends JPanel implements Disposable, OptionsEditorColleague {
private final SettingsFilter myFilter;
final SimpleTree myTree;
List<ConfigurableGroup> myGroups;
FilteringTreeBuilder myBuilder;
Root myRoot;
Map<Configurable, EditorNode> myConfigurable2Node = new HashMap<Configurable, EditorNode>();
MergingUpdateQueue mySelection;
private final OptionsTree.Renderer myRenderer;
public OptionsTree(SettingsFilter filter, ConfigurableGroup... groups) {
myFilter = filter;
myGroups = Arrays.asList(groups);
myRoot = new Root();
final SimpleTreeStructure structure = new SimpleTreeStructure() {
public Object getRootElement() {
return myRoot;
}
};
myTree = new MyTree();
TreeUtil.installActions(myTree);
myTree.setBorder(new EmptyBorder(0, 1, 0, 0));
myTree.setRowHeight(-1);
myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
myRenderer = new Renderer();
myTree.setCellRenderer(myRenderer);
myTree.setRootVisible(false);
myTree.setShowsRootHandles(false);
myBuilder = new MyBuilder(structure);
myBuilder.setFilteringMerge(300, null);
Disposer.register(this, myBuilder);
setLayout(new BorderLayout());
myTree.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(final ComponentEvent e) {
myBuilder.revalidateTree();
}
@Override
public void componentMoved(final ComponentEvent e) {
myBuilder.revalidateTree();
}
@Override
public void componentShown(final ComponentEvent e) {
myBuilder.revalidateTree();
}
});
add(new StickySeparator(myTree), BorderLayout.CENTER);
mySelection = new MergingUpdateQueue("OptionsTree", 150, false, this, this, this).setRestartTimerOnAdd(true);
myTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
public void valueChanged(final TreeSelectionEvent e) {
final TreePath path = e.getNewLeadSelectionPath();
if (path == null) {
queueSelection(null);
}
else {
final Base base = extractNode(path.getLastPathComponent());
queueSelection(base != null ? base.getConfigurable() : null);
}
}
});
}
ActionCallback select(@Nullable Configurable configurable) {
return queueSelection(configurable);
}
public void selectFirst() {
for (ConfigurableGroup eachGroup : myGroups) {
final Configurable[] kids = eachGroup.getConfigurables();
if (kids.length > 0) {
queueSelection(kids[0]);
return;
}
}
}
private Configurable myQueuedConfigurable;
ActionCallback queueSelection(final Configurable configurable) {
if (myBuilder.isSelectionBeingAdjusted()) {
return new ActionCallback.Rejected();
}
final ActionCallback callback = new ActionCallback();
myQueuedConfigurable = configurable;
final Update update = new Update(this) {
public void run() {
if (configurable != myQueuedConfigurable) return;
if (configurable == null) {
myTree.getSelectionModel().clearSelection();
myFilter.myContext.fireSelected(null, OptionsTree.this);
}
else {
myBuilder.getReady(this).doWhenDone(new Runnable() {
@Override
public void run() {
if (configurable != myQueuedConfigurable) return;
final EditorNode editorNode = myConfigurable2Node.get(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();
}
};
mySelection.queue(update);
return callback;
}
private void fireSelected(Configurable configurable, final ActionCallback callback) {
myFilter.myContext.fireSelected(configurable, this).doWhenProcessed(callback.createSetDoneRunnable());
}
public JTree getTree() {
return myTree;
}
public List<Configurable> getPathToRoot(final Configurable configurable) {
final ArrayList<Configurable> path = new ArrayList<Configurable>();
EditorNode eachNode = myConfigurable2Node.get(configurable);
if (eachNode == null) return path;
while (eachNode != null) {
path.add(eachNode.getConfigurable());
final SimpleNode parent = eachNode.getParent();
if (parent instanceof EditorNode) {
eachNode = (EditorNode)parent;
}
else {
break;
}
}
return path;
}
public SimpleNode findNodeFor(final Configurable toSelect) {
return myConfigurable2Node.get(toSelect);
}
@Nullable
public <T extends Configurable> T findConfigurable(Class<T> configurableClass) {
for (Configurable configurable : myConfigurable2Node.keySet()) {
if (configurableClass.isInstance(configurable)) {
return configurableClass.cast(configurable);
}
}
return null;
}
@Nullable
public SearchableConfigurable findConfigurableById(@NotNull String configurableId) {
for (Configurable configurable : myConfigurable2Node.keySet()) {
if (configurable instanceof SearchableConfigurable) {
SearchableConfigurable searchableConfigurable = (SearchableConfigurable) configurable;
if (configurableId.equals(searchableConfigurable.getId())) {
return searchableConfigurable;
}
}
}
return null;
}
class Renderer extends GroupedElementsRenderer.Tree {
private GroupSeparator mySeparator;
private JLabel myProjectIcon;
private JLabel myHandle;
@Override
protected void layout() {
myRendererComponent.setOpaqueActive(false);
mySeparator = new GroupSeparator();
myRendererComponent.add(Registry.is("ide.new.settings.dialog") ? mySeparator : mySeparatorComponent, BorderLayout.NORTH);
final NonOpaquePanel content = new NonOpaquePanel(new BorderLayout());
myHandle = new JLabel("", SwingConstants.CENTER);
if (!SystemInfo.isMac) {
myHandle.setBorder(new EmptyBorder(0, 2, 0, 2));
}
myHandle.setOpaque(false);
content.add(myHandle, BorderLayout.WEST);
content.add(myComponent, BorderLayout.CENTER);
myProjectIcon = new JLabel(" ", SwingConstants.LEFT);
myProjectIcon.setOpaque(true);
content.add(myProjectIcon, BorderLayout.EAST);
myRendererComponent.add(content, BorderLayout.CENTER);
}
public Component getTreeCellRendererComponent(final JTree tree,
final Object value,
final boolean selected,
final boolean expanded,
final boolean leaf,
final int row,
final boolean hasFocus) {
JComponent result;
Color fg = UIUtil.getTreeTextForeground();
mySeparator.configure(null, false);
final Base base = extractNode(value);
if (base instanceof EditorNode) {
final EditorNode editor = (EditorNode)base;
ConfigurableGroup group = null;
if (editor.getParent() == myRoot) {
final DefaultMutableTreeNode prevValue = ((DefaultMutableTreeNode)value).getPreviousSibling();
if (prevValue == null || prevValue instanceof LoadingNode) {
group = editor.getGroup();
mySeparator.configure(group, false);
}
else {
final Base prevBase = extractNode(prevValue);
if (prevBase instanceof EditorNode) {
final EditorNode prevEditor = (EditorNode)prevBase;
if (prevEditor.getGroup() != editor.getGroup()) {
group = editor.getGroup();
mySeparator.configure(group, true);
}
}
}
}
TreePath path = tree.getPathForRow(row);
if (path == null) {
if (value instanceof DefaultMutableTreeNode) {
path = new TreePath(((DefaultMutableTreeNode)value).getPath());
}
}
final boolean toStretch = tree.isVisible() && path != null;
int forcedWidth = 2000;
if (toStretch) {
final Rectangle visibleRect = tree.getVisibleRect();
int nestingLevel = tree.isRootVisible() ? path.getPathCount() - 1 : path.getPathCount() - 2;
final int left = UIUtil.getTreeLeftChildIndent();
final int right = UIUtil.getTreeRightChildIndent();
final Insets treeInsets = tree.getInsets();
int indent = (left + right) * nestingLevel + (treeInsets != null ? treeInsets.left + treeInsets.right : 0);
forcedWidth = visibleRect.width > 0 ? visibleRect.width - indent : forcedWidth;
}
result = configureComponent(base.getText(), base.getText(), null, null, row == -1 || selected, group != null,
group != null ? group.getDisplayName() : null, forcedWidth - 4);
if (base.isError()) {
fg = JBColor.red;
}
else if (base.isModified()) {
fg = JBColor.blue;
}
}
else {
result = configureComponent(value.toString(), null, null, null, selected, false, null, -1);
}
if (value instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
TreePath nodePath = new TreePath(node.getPath());
myHandle.setIcon(((SimpleTree)tree).getHandleIcon(node, nodePath));
} else {
myHandle.setIcon(null);
}
myTextLabel.setForeground(selected ? UIUtil.getTreeSelectionForeground() : fg);
myTextLabel.setOpaque(selected);
if (Registry.is("ide.new.settings.dialog")) {
myTextLabel.setBorder(new EmptyBorder(1,2,1,0));
}
Project project = null;
if (base != null && Registry.is("ide.new.settings.dialog")) {
SimpleNode parent = base.getParent();
if (parent == myRoot) {
project = getConfigurableProject(base); // show icon for top-level nodes
}
}
if (project != null) {
myProjectIcon.setBackground(selected ? getSelectionBackground() : getBackground());
myProjectIcon.setIcon(selected ? AllIcons.General.ProjectConfigurableSelected : AllIcons.General.ProjectConfigurable);
myProjectIcon.setVisible(true);
myProjectIcon.setToolTipText(OptionsBundle.message(project.isDefault()
? "configurable.default.project.tooltip"
: "configurable.current.project.tooltip"));
} else {
myProjectIcon.setVisible(false);
}
if (Registry.is("ide.new.settings.dialog")) {
result.setBackground(selected ? UIUtil.getTreeSelectionBackground() : UIUtil.getSidePanelColor());
}
return result;
}
protected JComponent createItemComponent() {
myTextLabel = new ErrorLabel();
return myTextLabel;
}
public boolean isUnderHandle(final Point point) {
final Point handlePoint = SwingUtilities.convertPoint(myRendererComponent, point, myHandle);
final Rectangle bounds = myHandle.getBounds();
return bounds.x < handlePoint.x && bounds.getMaxX() >= handlePoint.x;
}
}
@Nullable
private Base extractNode(Object object) {
if (object instanceof DefaultMutableTreeNode) {
final DefaultMutableTreeNode uiNode = (DefaultMutableTreeNode)object;
final Object o = uiNode.getUserObject();
if (o instanceof FilteringTreeStructure.FilteringNode) {
return (Base)((FilteringTreeStructure.FilteringNode)o).getDelegate();
}
}
return null;
}
abstract static class Base extends CachingSimpleNode {
protected Base(final SimpleNode aParent) {
super(aParent);
}
String getText() {
return null;
}
boolean isModified() {
return false;
}
boolean isError() {
return false;
}
Configurable getConfigurable() {
return null;
}
}
class Root extends Base {
Root() {
super(null);
}
protected SimpleNode[] buildChildren() {
List<SimpleNode> result = new ArrayList<SimpleNode>();
for (ConfigurableGroup eachGroup : myGroups) {
result.addAll(buildGroup(eachGroup));
}
return result.isEmpty() ? NO_CHILDREN : result.toArray(new SimpleNode[result.size()]);
}
private List<EditorNode> buildGroup(final ConfigurableGroup eachGroup) {
List<EditorNode> result = new ArrayList<EditorNode>();
final Configurable[] kids = eachGroup.getConfigurables();
if (kids.length > 0) {
for (Configurable eachKid : kids) {
if (!isInvisibleNode(eachKid)) {
result.add(new EditorNode(this, eachKid, eachGroup));
}
}
}
return sort(result);
}
}
private static boolean isInvisibleNode(final Configurable child) {
return child instanceof SearchableConfigurable.Parent && !((SearchableConfigurable.Parent)child).isVisible();
}
private static List<EditorNode> sort(List<EditorNode> c) {
List<EditorNode> cc = new ArrayList<EditorNode>(c);
Collections.sort(cc, new Comparator<EditorNode>() {
public int compare(final EditorNode o1, final EditorNode o2) {
return getConfigurableDisplayName(o1.getConfigurable()).compareToIgnoreCase(getConfigurableDisplayName(o2.getConfigurable()));
}
});
return cc;
}
private static String getConfigurableDisplayName(final Configurable c) {
final String name = c.getDisplayName();
return name != null ? name : "{ Unnamed Page:" + c.getClass().getSimpleName() + " }";
}
private List<EditorNode> buildChildren(final Configurable configurable, SimpleNode parent, final ConfigurableGroup group) {
if (configurable instanceof Configurable.Composite) {
final Configurable[] kids = ((Configurable.Composite)configurable).getConfigurables();
final List<EditorNode> result = new ArrayList<EditorNode>(kids.length);
for (Configurable child : kids) {
result.add(new EditorNode(parent, child, group));
myFilter.myContext.registerKid(configurable, child);
}
return result; // TODO: DECIDE IF INNERS SHOULD BE SORTED: sort(result);
}
else {
return Collections.emptyList();
}
}
private static final EditorNode[] EMPTY_EN_ARRAY = new EditorNode[0];
class EditorNode extends Base {
Configurable myConfigurable;
ConfigurableGroup myGroup;
EditorNode(SimpleNode parent, Configurable configurable, @Nullable ConfigurableGroup group) {
super(parent);
myConfigurable = configurable;
myGroup = group;
myConfigurable2Node.put(configurable, this);
addPlainText(getConfigurableDisplayName(configurable));
}
protected EditorNode[] buildChildren() {
List<EditorNode> list = OptionsTree.this.buildChildren(myConfigurable, this, null);
return list.isEmpty() ? EMPTY_EN_ARRAY : list.toArray(new EditorNode[list.size()]);
}
@Override
public boolean isAlwaysLeaf() {
return !(myConfigurable instanceof Configurable.Composite);
}
@Override
public boolean isContentHighlighted() {
return getParent() == myRoot;
}
@Override
Configurable getConfigurable() {
return myConfigurable;
}
@Override
public int getWeight() {
if (getParent() == myRoot) {
return Integer.MAX_VALUE - myGroups.indexOf(myGroup);
}
else {
return WeightBasedComparator.UNDEFINED_WEIGHT;
}
}
public ConfigurableGroup getGroup() {
return myGroup;
}
@Override
String getText() {
return getConfigurableDisplayName(myConfigurable).replace("\n", " ");
}
@Override
boolean isModified() {
return myFilter.myContext.getModified().contains(myConfigurable);
}
@Override
boolean isError() {
return myFilter.myContext.getErrors().containsKey(myConfigurable);
}
}
public void dispose() {
myQueuedConfigurable = null;
}
public ActionCallback onSelected(final Configurable configurable, final Configurable oldConfigurable) {
return queueSelection(configurable);
}
public ActionCallback onModifiedAdded(final Configurable colleague) {
myTree.repaint();
return new ActionCallback.Done();
}
public ActionCallback onModifiedRemoved(final Configurable configurable) {
myTree.repaint();
return new ActionCallback.Done();
}
public ActionCallback onErrorsChanged() {
return new ActionCallback.Done();
}
public void processTextEvent(KeyEvent e) {
myTree.processKeyEvent(e);
}
private class MyTree extends SimpleTree {
private MyTree() {
getInputMap().clear();
setOpaque(true);
}
@Override
public final String getToolTipText(MouseEvent event) {
if (event != null) {
Point point = event.getPoint();
Component component = getDeepestRendererComponentAt(point.x, point.y);
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(final TreeUI ui) {
TreeUI actualUI = ui;
if (!(ui instanceof MyTreeUi)) {
actualUI = new MyTreeUi();
}
super.setUI(actualUI);
}
@Override
protected boolean isCustomUI() {
return true;
}
@Override
protected void configureUiHelper(final TreeUIHelper helper) {
}
@Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
@Override
public void processKeyEvent(final 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(final MouseEvent e) {
final MyTreeUi ui = (MyTreeUi)myTree.getUI();
final boolean toggleNow =
e.getID() == MouseEvent.MOUSE_RELEASED && UIUtil.isActionClick(e, MouseEvent.MOUSE_RELEASED) && !ui.isToggleEvent(e);
final boolean toggleLater =
e.getID() == MouseEvent.MOUSE_PRESSED;
if (toggleNow || toggleLater) {
final TreePath path = getPathForLocation(e.getX(), e.getY());
if (path != null) {
final Rectangle bounds = getPathBounds(path);
if (bounds != null && path.getLastPathComponent() instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
final boolean selected = isPathSelected(path);
final boolean expanded = isExpanded(path);
final Component comp =
myRenderer.getTreeCellRendererComponent(this, node, selected, expanded, node.isLeaf(), getRowForPath(path), isFocusOwner());
comp.setBounds(bounds);
comp.validate();
Point point = new Point(e.getX() - bounds.x, e.getY() - bounds.y);
if (myRenderer.isUnderHandle(point)) {
if (toggleNow) {
ui.toggleExpandState(path);
e.consume();
return;
} else if (toggleLater) {
e.consume();
return;
}
}
}
}
}
super.processMouseEvent(e);
}
private class MyTreeUi extends WideSelectionTreeUI {
@Override
public void toggleExpandState(final TreePath path) {
super.toggleExpandState(path);
}
@Override
public boolean isToggleEvent(final MouseEvent event) {
return super.isToggleEvent(event);
}
@Override
protected boolean shouldPaintExpandControl(final TreePath path,
final int row,
final boolean isExpanded,
final boolean hasBeenExpanded,
final boolean isLeaf) {
return false;
}
@Override
protected void paintHorizontalPartOfLeg(final Graphics g,
final Rectangle clipBounds,
final Insets insets,
final Rectangle bounds,
final TreePath path,
final int row,
final boolean isExpanded,
final boolean hasBeenExpanded,
final boolean isLeaf) {
}
@Override
protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds, final Insets insets, final TreePath path) {
}
@Override
public void paint(Graphics g, JComponent c) {
GraphicsUtil.setupAntialiasing(g);
super.paint(g, c);
}
}
}
private class MyBuilder extends FilteringTreeBuilder {
List<Object> myToExpandOnResetFilter;
boolean myRefilteringNow;
boolean myWasHoldingFilter;
public MyBuilder(SimpleTreeStructure structure) {
super(myTree, myFilter, structure, new WeightBasedComparator(false));
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(final Object nodeObject) {
return nodeObject instanceof EditorNode;
}
@Override
public boolean isAutoExpandNode(final 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);
}
}
}
Project getConfigurableProject(Configurable configurable) {
if (configurable instanceof ConfigurableWrapper) {
ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable;
return wrapper.getExtensionPoint().getProject();
}
return getConfigurableProject(myConfigurable2Node.get(configurable));
}
private static Project getConfigurableProject(SimpleNode node) {
if (node == null) {
return null;
}
if (node instanceof EditorNode) {
EditorNode editor = (EditorNode)node;
Configurable configurable = editor.getConfigurable();
if (configurable instanceof ConfigurableWrapper) {
ConfigurableWrapper wrapper = (ConfigurableWrapper)configurable;
return wrapper.getExtensionPoint().getProject();
}
}
return getConfigurableProject(node.getParent());
}
private static final class GroupSeparator extends JLabel {
public static final int SPACE = 10;
public GroupSeparator() {
setFont(UIUtil.getLabelFont());
setFont(getFont().deriveFont(Font.BOLD));
}
public void configure(ConfigurableGroup group, boolean isSpaceNeeded) {
if (group == null) {
setVisible(false);
}
else {
setVisible(true);
int bottom = UIUtil.isUnderNativeMacLookAndFeel() ? 1 : 3;
int top = isSpaceNeeded
? bottom + SPACE
: bottom;
setBorder(BorderFactory.createEmptyBorder(top, 3, bottom, 3));
setText(group.getDisplayName());
}
}
}
private static final class StickySeparator extends JComponent {
private final SimpleTree myTree;
private final JScrollPane myScroller;
private final GroupSeparator mySeparator;
public StickySeparator(SimpleTree tree) {
myTree = tree;
myScroller = ScrollPaneFactory.createScrollPane(myTree);
myScroller.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
mySeparator = new GroupSeparator();
add(myScroller);
}
@Override
public void doLayout() {
myScroller.setBounds(0, 0, getWidth(), getHeight());
}
@Override
public void paint(Graphics g) {
super.paint(g);
if (Registry.is("ide.new.settings.dialog")) {
ConfigurableGroup group = getGroup(GroupSeparator.SPACE + mySeparator.getFont().getSize());
if (group != null && group == getGroup(-GroupSeparator.SPACE)) {
mySeparator.configure(group, false);
Rectangle bounds = myScroller.getViewport().getBounds();
int height = mySeparator.getPreferredSize().height;
if (bounds.height > height) {
bounds.height = height;
}
g.setColor(myTree.getBackground());
if (g instanceof Graphics2D) {
int h = bounds.height / 3;
int y = bounds.y + bounds.height - h;
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height - h);
((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);
}
else {
g.fillRect(bounds.x, bounds.y, bounds.width, bounds.height);
}
mySeparator.setSize(bounds.width - 1, bounds.height);
mySeparator.paint(g.create(bounds.x + 1, bounds.y, bounds.width - 1, bounds.height));
}
}
}
private ConfigurableGroup getGroup(int offset) {
TreePath path = myTree.getClosestPathForLocation(-myTree.getX(), -myTree.getY() + offset);
SimpleNode node = myTree.getNodeFor(path);
if (node instanceof FilteringTreeStructure.FilteringNode) {
Object delegate = ((FilteringTreeStructure.FilteringNode)node).getDelegate();
while (delegate instanceof EditorNode) {
EditorNode editor = (EditorNode)delegate;
ConfigurableGroup group = editor.getGroup();
if (group != null) {
return group;
}
delegate = editor.getParent();
}
}
return null;
}
}
}