From 8eb9deaf3a35b21e5822b2b55c42d0b3b026301d Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Sat, 25 Aug 2012 01:21:23 +0400 Subject: [PATCH] IDEA-19061 Integrate the Rearranger-plugin into core-IDE Add ability to add new arrangement rules --- ...stractArrangementRuleEditingModelTest.java | 5 +- .../ArrangementConfigUtilTest.groovy | 48 +++++++ ...rrangementRuleEditingModelBuilderTest.java | 13 +- .../arrangement/ArrangementUtil.java | 2 +- .../arrangement/ArrangementConfigUtil.java | 130 +++++++++-------- .../arrangement/ArrangementConstants.java | 10 +- .../ArrangementRuleEditingModelBuilder.java | 78 +++++----- .../arrangement/ArrangementRuleTree.java | 136 ++++++++++++++---- .../arrangement/ArrangementSettingsPanel.java | 56 +++++++- .../action/AddArrangementRuleAction.java | 43 ++++++ .../com/intellij/ui/treeStructure/Tree.java | 13 +- .../src/messages/ApplicationBundle.properties | 2 + .../src/idea/LangActions.xml | 4 + .../util/ui/tree/WideSelectionTreeUI.java | 31 ++-- 14 files changed, 407 insertions(+), 164 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/AddArrangementRuleAction.java diff --git a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/AbstractArrangementRuleEditingModelTest.java b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/AbstractArrangementRuleEditingModelTest.java index e941823eea21..f8226e985767 100644 --- a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/AbstractArrangementRuleEditingModelTest.java +++ b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/AbstractArrangementRuleEditingModelTest.java @@ -15,6 +15,7 @@ */ package com.intellij.application.options.codeStyle.arrangement; +import com.intellij.openapi.util.Pair; import com.intellij.psi.codeStyle.arrangement.JavaRearranger; import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType; import com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier; @@ -22,6 +23,7 @@ import com.intellij.psi.codeStyle.arrangement.model.ArrangementAtomMatchConditio import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition; import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingType; import com.intellij.ui.treeStructure.Tree; +import gnu.trove.TIntIntHashMap; import gnu.trove.TIntObjectHashMap; import org.jetbrains.annotations.NotNull; import org.junit.Before; @@ -55,7 +57,8 @@ public abstract class AbstractArrangementRuleEditingModelTest { } protected void configure(@NotNull ArrangementMatchCondition matchCondition) { - myBuilder.build(matchCondition, myTree, myRoot, myGrouper, myRowMappings); + Pair pair = myBuilder.build(matchCondition, myTree, myRoot, null, myGrouper); + myRowMappings.put(pair.first.getRow(), pair.first); } protected static ArrangementAtomMatchCondition atom(@NotNull Object condition) { diff --git a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtilTest.groovy b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtilTest.groovy index 50285ed58776..eb5ce889cda3 100644 --- a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtilTest.groovy +++ b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtilTest.groovy @@ -29,6 +29,7 @@ import static org.junit.Assert.assertEquals * @author Denis Zhdanov * @since 8/17/12 1:12 PM */ +@SuppressWarnings(["GroovyAssignabilityCheck", "GroovyVariableNotAssigned"]) class ArrangementConfigUtilTest { @Test @@ -412,6 +413,53 @@ to = '4'()} checkRowMappings([ 6 : 3 ], rowMappings) } + @Test + void removeSingleNodeWithMerge() { + def node; + def initial = new TreeNodeBuilder(). + '0' { + '1' { + '2'()} +node = '3'() + '1' { + '4'()} + } + + def expected = new TreeNodeBuilder(). + '0' { + '1' { + '2'() + '4'()} + } + + def rowMappings = doRemove(node, node) + assertNodesEqual(expected, initial) + checkRowMappings([ 5 : 3 ], rowMappings) + } + + @Test + void removeWithSingleNodeBelowMerge() { + def node; + def initial = new TreeNodeBuilder(). + '0' { + '1' { + '2'()} +node = '3'() + '1'() + } + + def expected = new TreeNodeBuilder(). + '0' { + '1' { + '2'()} + '1'() + } + + def rowMappings = doRemove(node, node) + assertNodesEqual(expected, initial) + checkRowMappings([ 4 : 3 ], rowMappings) + } + private static def doReplace(from, to, replacement) { ArrangementConfigUtil.replace(from, to, replacement, new DefaultTreeModel(ArrangementConfigUtil.getRoot(from)), true) } diff --git a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelBuilderTest.java b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelBuilderTest.java index be4ea08d840e..d313c7d3ec5d 100644 --- a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelBuilderTest.java +++ b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelBuilderTest.java @@ -15,7 +15,6 @@ */ package com.intellij.application.options.codeStyle.arrangement; -import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition; import org.junit.Test; import static com.intellij.psi.codeStyle.arrangement.ArrangementUtil.and; @@ -31,21 +30,19 @@ public class ArrangementRuleEditingModelBuilderTest extends AbstractArrangementR @Test public void mapToTheSameLayer() { - ArrangementMatchCondition matchCondition = and(atom(PUBLIC), atom(STATIC)); - myBuilder.build(matchCondition, myTree, myRoot, myGrouper, myRowMappings); + configure(and(atom(PUBLIC), atom(STATIC))); checkRows(1); ArrangementRuleEditingModel model = myRowMappings.get(1); assertTrue(model.hasCondition(PUBLIC)); assertTrue(model.hasCondition(STATIC)); assertFalse(model.hasCondition(PRIVATE)); assertEquals(1, myRoot.getChildCount()); - assertEquals(matchCondition, myRoot.getFirstChild().getBackingCondition()); + assertEquals(and(atom(PUBLIC), atom(STATIC)), myRoot.getFirstChild().getBackingCondition()); } @Test public void splitIntoTwoLayers() { - ArrangementMatchCondition matchCondition = and(atom(FIELD), atom(PUBLIC), atom(STATIC)); - myBuilder.build(matchCondition, myTree, myRoot, myGrouper, myRowMappings); + configure(and(atom(FIELD), atom(PUBLIC), atom(STATIC))); checkRows(2); @@ -60,8 +57,8 @@ public class ArrangementRuleEditingModelBuilderTest extends AbstractArrangementR @Test public void addToExistingLayer() { - myBuilder.build(and(atom(PUBLIC), atom(STATIC), atom(FIELD)), myTree, myRoot, myGrouper, myRowMappings); - myBuilder.build(and(atom(PRIVATE), atom(FIELD)), myTree, myRoot, myGrouper, myRowMappings); + configure(and(atom(PUBLIC), atom(STATIC), atom(FIELD))); + configure(and(atom(PRIVATE), atom(FIELD))); checkRows(2, 3); diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/ArrangementUtil.java b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/ArrangementUtil.java index 5db34dc05d55..6e53dc57bbf8 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/ArrangementUtil.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/ArrangementUtil.java @@ -137,7 +137,7 @@ public class ArrangementUtil { for (ArrangementMatchCondition node : nodes) { node.invite(visitor); } - return result; + return result.getOperands().size() == 1 ? result.getOperands().iterator().next() : result; } // TODO den remove diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtil.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtil.java index c019dc3ac698..867ae4183f76 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtil.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConfigUtil.java @@ -135,6 +135,7 @@ public class ArrangementConfigUtil { return result; } + @SuppressWarnings("ConstantConditions") @NotNull public static ArrangementTreeNode getLastBefore(@NotNull ArrangementTreeNode start, @NotNull ArrangementTreeNode stop) throws IllegalArgumentException @@ -223,7 +224,7 @@ public class ArrangementConfigUtil { * @param replacement root of the node sub-hierarchy which should replace the one identified by the given 'start' and 'end' nodes * @param treeModel model which should hold ui nodes * @param rootVisible determines if the root should be count during rows calculations - * @return collection of row changes at the form {@code 'old row -> new row'} + * @return collection of row changes at the form {@code 'old row -> new row'} (all rows are zero-based) */ public static TIntIntHashMap replace(@NotNull ArrangementTreeNode from, @NotNull ArrangementTreeNode to, @@ -244,18 +245,6 @@ public class ArrangementConfigUtil { @NotNull DefaultTreeModel treeModel, boolean rootVisible) { - markRows(from, rootVisible); - if (from == to) { - ArrangementTreeNode parent = from.getParent(); - assert parent != null; - int index = parent.getIndex(from); - treeModel.removeNodeFromParent(from); - if (replacement != null) { - insert(parent, index, replacement, treeModel); - } - return collectRowChangesAndUnmark(parent, rootVisible); - } - // The algorithm looks as follows: // 1. Cut sub-hierarchy which belongs to the given 'from' root and is located below the 'to -> from' path; // 2. Remove 'to -> from' sub-hierarchy' by going bottom-up and stopping as soon as a current node has a child over than one @@ -311,12 +300,49 @@ public class ArrangementConfigUtil { // // Note: we need to have a notion of 'equal nodes' for node re-usage. It's provided by comparing node user objects. + markRows(from, rootVisible); final ArrangementTreeNode root = from.getParent(); assert root != null; - //region Cut bottom sub-hierarchy + @Nullable ArrangementTreeNode cutHierarchy = cutSubHierarchy(root, treeModel, to); + + int childCountBefore = root.getChildCount(); + + for (ArrangementTreeNode current = to; current != root;) { + ArrangementTreeNode parent = current.getParent(); + treeModel.removeNodeFromParent(current); + current = parent; + if (parent == null || parent.getChildCount() > 0) { + break; + } + } + + int insertionIndex = root.getChildCount() < childCountBefore ? childCountBefore - 1 : childCountBefore; + if (replacement != null) { + insert(root, insertionIndex, replacement, treeModel); + } + if (cutHierarchy != null) { + insert(root, root.getChildCount(), cutHierarchy, treeModel); + } + + return collectRowChangesAndUnmark(root, rootVisible); + } + + /** + * Removes all nodes which lay below the path identified by the given 'top' and 'bottom' nodes from the tree structure and returns them. + * + * @param topMost top most node marker, i.e. all cut nodes descend from this node + * @param treeModel tree model which manages the nodes + * @param bottomMost bottom most node after which all other nodes should be cut + * @return removed nodes below the target path if any; null otherwise + */ + @Nullable + public static ArrangementTreeNode cutSubHierarchy(@NotNull ArrangementTreeNode topMost, + @NotNull DefaultTreeModel treeModel, + @NotNull ArrangementTreeNode bottomMost) + { ArrangementTreeNode cutHierarchy = null; - for (ArrangementTreeNode current = to; current != root; current = current.getParent()) { + for (ArrangementTreeNode current = bottomMost; current != null && current != topMost; current = current.getParent()) { ArrangementTreeNode parent = current.getParent(); assert parent != null; int i = parent.getIndex(current); @@ -338,43 +364,7 @@ public class ArrangementConfigUtil { } cutHierarchy = parentCopy; } - //endregion - - int childCountBefore = root.getChildCount(); - - //region Remove target sub-hierarchy - for (ArrangementTreeNode current = to; current != root;) { - ArrangementTreeNode parent = current.getParent(); - treeModel.removeNodeFromParent(current); - current = parent; - if (parent == null || parent.getChildCount() > 0) { - break; - } - } - //endregion - - //region Insert nodes. - int insertionIndex = root.getChildCount() < childCountBefore ? childCountBefore - 1 : childCountBefore; - if (replacement != null) { - insert(root, insertionIndex, replacement, treeModel); - } - if (cutHierarchy != null) { - List toInsert = new ArrayList(); - if (hasEqualSetting(root, cutHierarchy)) { - for (int i = 0; i < cutHierarchy.getChildCount(); i++) { - toInsert.add(cutHierarchy.getChildAt(i)); - } - } - else { - toInsert.add(cutHierarchy); - } - for (ArrangementTreeNode node : toInsert) { - insert(root, root.getChildCount(), node, treeModel); - } - } - //endregion - - return collectRowChangesAndUnmark(root, rootVisible); + return cutHierarchy; } /** @@ -383,7 +373,7 @@ public class ArrangementConfigUtil { * @param node reference to the target hierarchy * @param rootVisible determines if the root should be count during rows calculations */ - private static void markRows(@NotNull ArrangementTreeNode node, boolean rootVisible) { + public static void markRows(@NotNull ArrangementTreeNode node, boolean rootVisible) { ArrangementTreeNode root = getRoot(node); int row = rootVisible ? 0 : -1; Stack nodes = new Stack(); @@ -413,10 +403,10 @@ public class ArrangementConfigUtil { * Collects all row changes and returns them. All row information is dropped from the nodes during the current method processing. * * @param node reference to the target nodes hierarchy - * @return collection of row changes at the form {@code 'old row -> new row'} + * @return collection of row changes at the form {@code 'old row -> new row'} (all rows are zero-based) */ @NotNull - private static TIntIntHashMap collectRowChangesAndUnmark(@NotNull ArrangementTreeNode node, boolean rootVisible) { + public static TIntIntHashMap collectRowChangesAndUnmark(@NotNull ArrangementTreeNode node, boolean rootVisible) { @NotNull TIntIntHashMap changes = new TIntIntHashMap(); ArrangementTreeNode root = getRoot(node); int row = rootVisible ? 0 : -1; @@ -467,7 +457,7 @@ public class ArrangementConfigUtil { buffer.setLength(buffer.length() - separator.length()); throw new RuntimeException("Invalid ArrangementTreeNode detected: " + buffer.toString()); } - + /** * Inserts given child to the given parent re-using existing nodes under the parent sub-hierarchy if possible. *

@@ -493,14 +483,36 @@ public class ArrangementConfigUtil { * | | |_5 | | | |_6 | * *

- * + * * @param parent parent node to insert into * @param index insertion index to use for the given parent node * @param child node to insert to the given parent node at the given insertion index * @param treeModel model which should hold UI nodes * @return true if given child node has been merged to the existing node; false otherwise */ - public static boolean insert(@NotNull final ArrangementTreeNode parent, + public static void insert(@NotNull final ArrangementTreeNode parent, + final int index, + @NotNull final ArrangementTreeNode child, + @NotNull DefaultTreeModel treeModel) + { + ArrangementTreeNode root = getRoot(parent); + List toInsert = new ArrayList(); + if (hasEqualSetting(root, child)) { + for (int i = 0; i < child.getChildCount(); i++) { + toInsert.add(child.getChildAt(i)); + } + } + else { + toInsert.add(child); + } + int i = index; + for (ArrangementTreeNode node : toInsert) { + doInsert(parent, i++, node, treeModel); + } + } + + + private static boolean doInsert(@NotNull final ArrangementTreeNode parent, final int index, @NotNull final ArrangementTreeNode child, @NotNull DefaultTreeModel treeModel) @@ -562,7 +574,7 @@ public class ArrangementConfigUtil { * @param to indicates end of the node sub-hierarchy (bottom-most node) to be replaced (inclusive) * @param treeModel model which should hold ui nodes * @param rootVisible determines if the root should be count during rows calculations - * @return collection of row changes at the form {@code 'old row -> new row'} + * @return collection of row changes at the form {@code 'old row -> new row'} (all rows are zero-based) */ @NotNull public static TIntIntHashMap remove(@NotNull final ArrangementTreeNode from, diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConstants.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConstants.java index c9a343c613d5..ac1d12f83f2a 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConstants.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementConstants.java @@ -15,6 +15,7 @@ */ package com.intellij.application.options.codeStyle.arrangement; +import com.intellij.openapi.actionSystem.DataKey; import org.jetbrains.annotations.NonNls; /** @@ -24,11 +25,16 @@ import org.jetbrains.annotations.NonNls; public class ArrangementConstants { @NonNls public static final String ACTION_GROUP_RULE_EDITOR_CONTEXT_MENU = "Arrangement.RuleEditor.Context.Menu"; - @NonNls public static final String RULE_EDITOR_PLACE = "Arrangement.RuleEditor.Place"; - @NonNls public static final String RULE_TREE_PLACE = "Arrangement.RuleTree.Place"; + @NonNls public static final String ACTION_GROUP_RULE_EDITOR_TOOL_WINDOW = "Arrangement.RuleEditor.ToolWIndow"; + + @NonNls public static final String RULE_EDITOR_PLACE = "Arrangement.RuleEditor.Place"; + @NonNls public static final String RULE_EDITOR_TOOL_WINDOW_PLACE = "Arrangement.RuleEditor.ToolWindow.Place"; + @NonNls public static final String RULE_TREE_PLACE = "Arrangement.RuleTree.Place"; public static final boolean LOG_RULE_MODIFICATION = Boolean.parseBoolean(System.getProperty("log.arrangement.rule.modification")); + public static final DataKey NEW_RULE_FUNCTION_KEY = DataKey.create("Arrangement.Rule.Function.New"); + private ArrangementConstants() { } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelBuilder.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelBuilder.java index 0a2c9b521031..e293f2abb06f 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelBuilder.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelBuilder.java @@ -21,13 +21,13 @@ import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition; import com.intellij.psi.codeStyle.arrangement.model.HierarchicalArrangementConditionNode; import com.intellij.psi.codeStyle.arrangement.settings.ArrangementSettingsGrouper; import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsRepresentationAware; -import gnu.trove.TIntObjectHashMap; +import gnu.trove.TIntIntHashMap; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; -import javax.swing.tree.TreeNode; /** * Holds glue logic between arrangement settings and their representation - @@ -58,45 +58,41 @@ public class ArrangementRuleEditingModelBuilder { * * * - * @param matchCondition target settings to process - * @param tree UI tree which shows arrangement matcher rules - * @param root UI tree settings root to use (may be not the same as the tree root) - * @param grouper strategy that knows how to - * {@link ArrangementStandardSettingsRepresentationAware#getDisplayValue(ArrangementModifier) group} setting - * nodes for UI representation - * @param rowMappings container to hold built {@link ArrangementRuleEditingModel editing models} (UI tree row numbers are used as keys) + * @param matchCondition target settings to process + * @param tree UI tree which shows arrangement matcher rules + * @param root UI tree settings root to use (may be not the same as the tree root) + * @param anchor node after which should be previous sibling for the root node of the inserted condition; + * null as an indication that new condition nodes should be inserted as the last 'root' child + * @param grouper strategy that knows how to + * {@link ArrangementStandardSettingsRepresentationAware#getDisplayValue(ArrangementModifier) group} setting + * nodes for UI representation + * @return collection of row changes at the form {@code 'old row -> new row'} (all rows are zero-based) */ @SuppressWarnings("MethodMayBeStatic") - public void build(@NotNull ArrangementMatchCondition matchCondition, - @NotNull JTree tree, - @NotNull ArrangementTreeNode root, - @NotNull ArrangementSettingsGrouper grouper, - @NotNull TIntObjectHashMap rowMappings) + public Pair build( + @NotNull ArrangementMatchCondition matchCondition, + @NotNull JTree tree, + @NotNull ArrangementTreeNode root, + @Nullable ArrangementTreeNode anchor, + @NotNull ArrangementSettingsGrouper grouper) { - int initialInsertRow = 0; - - // Count rows before the settings root. - for (TreeNode n = root.getParent(); n != null; n = n.getParent()) { - for (int i = n.getChildCount() - 1; i >= 0; i--) { - TreeNode child = n.getChildAt(i); - if (child != root) { - initialInsertRow += calculateRowsCount(child); - } - } - initialInsertRow++; - } - - // Count root width. - initialInsertRow += calculateRowsCount(root); - if (!tree.isRootVisible()) { - initialInsertRow--; - } - HierarchicalArrangementConditionNode grouped = grouper.group(matchCondition); DefaultTreeModel treeModel = (DefaultTreeModel)tree.getModel(); - Pair pair = ArrangementConfigUtil.map(root, grouped, treeModel); - ArrangementTreeNode topMostNode = ArrangementConfigUtil.getLastBefore(pair.first, root); - int row = initialInsertRow + pair.second - 1; + Pair pair = ArrangementConfigUtil.map(null, grouped, null); + ArrangementTreeNode topMostNode = ArrangementConfigUtil.getRoot(pair.first); + ArrangementConfigUtil.markRows(root, tree.isRootVisible()); + ArrangementTreeNode bottomHierarchy = null; + if (anchor != null) { + bottomHierarchy = ArrangementConfigUtil.cutSubHierarchy(root, treeModel, anchor); + } + ArrangementConfigUtil.insert(root, root.getChildCount(), topMostNode, treeModel); + if (bottomHierarchy != null) { + ArrangementConfigUtil.insert(root, root.getChildCount(), bottomHierarchy, treeModel); + } + + TIntIntHashMap rowChanges = ArrangementConfigUtil.collectRowChangesAndUnmark(root, tree.isRootVisible()); + topMostNode = ArrangementConfigUtil.getLastBefore(pair.first, root); + int row = ArrangementConfigUtil.getRow(pair.first, tree.isRootVisible()); ArrangementRuleEditingModelImpl model = new ArrangementRuleEditingModelImpl( treeModel, matchCondition, @@ -106,14 +102,6 @@ public class ArrangementRuleEditingModelBuilder { row, tree.isRootVisible() ); - rowMappings.put(row, model); - } - - private static int calculateRowsCount(@NotNull TreeNode node) { - int result = 1; - for (int i = node.getChildCount() - 1; i >= 0; i--) { - result += calculateRowsCount(node.getChildAt(i)); - } - return result; + return Pair.create(model, rowChanges); } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleTree.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleTree.java index 2f23ceac8b22..db973799ece6 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleTree.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleTree.java @@ -16,14 +16,18 @@ package com.intellij.application.options.codeStyle.arrangement; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.Condition; +import com.intellij.openapi.util.Pair; import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType; import com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier; -import com.intellij.psi.codeStyle.arrangement.model.*; import com.intellij.psi.codeStyle.arrangement.model.ArrangementAtomMatchCondition; +import com.intellij.psi.codeStyle.arrangement.model.ArrangementCompositeMatchCondition; import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition; +import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingType; import com.intellij.psi.codeStyle.arrangement.settings.ArrangementSettingsGrouper; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.Alarm; import com.intellij.util.Consumer; import gnu.trove.*; import org.jetbrains.annotations.NotNull; @@ -47,13 +51,17 @@ import java.util.List; */ public class ArrangementRuleTree { - @NotNull private static final JLabel EMPTY_RENDERER = new JLabel(""); - private static final Logger LOG = Logger.getInstance("#" + ArrangementRuleTree.class.getName()); + @NotNull private static final JLabel EMPTY_RENDERER = new JLabel(""); + @NotNull private static final JLabel NEW_CONDITION_RENDERER = new JLabel("<empty condition>"); + @NotNull private static final Logger LOG = Logger.getInstance("#" + ArrangementRuleTree.class.getName()); @NotNull private final List myListeners = new ArrayList(); @NotNull private final TreeSelectionModel mySelectionModel = new MySelectionModel(); @NotNull private final MyModelChangeListener myModelChangeListener = new MyModelChangeListener(); @NotNull private final MyModelNodesRefresher myModelNodesRefresher = new MyModelNodesRefresher(); + @NotNull private final ArrangementRuleEditingModelBuilder myModelBuilder = new ArrangementRuleEditingModelBuilder(); + @NotNull private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD); + @NotNull private final RemoveInactiveNewModelRequest myRequest = new RemoveInactiveNewModelRequest(); @NotNull private final TIntObjectHashMap myRenderers = new TIntObjectHashMap(); @@ -64,11 +72,13 @@ public class ArrangementRuleTree { @NotNull private final DefaultTreeModel myTreeModel; @NotNull private final Tree myTree; @NotNull private final ArrangementNodeComponentFactory myFactory; + @NotNull private final ArrangementSettingsGrouper myGrouper; private boolean myExplicitSelectionChange; private boolean mySkipSelectionChange; public ArrangementRuleTree(@NotNull ArrangementSettingsGrouper grouper, @NotNull ArrangementNodeDisplayManager displayManager) { + myGrouper = grouper; myFactory = new ArrangementNodeComponentFactory(displayManager, new Consumer() { @Override public void consume(@NotNull ArrangementAtomMatchCondition setting) { @@ -78,6 +88,7 @@ public class ArrangementRuleTree { myRoot = new ArrangementTreeNode(null); myTreeModel = new DefaultTreeModel(myRoot); myTree = new Tree(myTreeModel) { + @Override protected void setExpandedState(TreePath path, boolean state) { // Don't allow node collapse @@ -86,16 +97,34 @@ public class ArrangementRuleTree { } } + @NotNull @Override - protected boolean isAlwaysPaintRowBackground() { - return false; + protected Condition getWideSelectionBackgroundCondition() { + return new Condition() { + @Override + public boolean value(Integer row) { + TreePath path = getPathForRow(row); + if (path == null) { + return false; + } + return isEmptyCondition(((ArrangementTreeNode)path.getLastPathComponent()).getBackingCondition()); + } + }; } @Override protected void processMouseEvent(MouseEvent e) { // JTree selects a node on mouse click at the same row (even outside the node bounds). We don't want to support // such selection because selected nodes are highlighted at the rule tree, so, it produces a 'blink' effect. - mySkipSelectionChange = e.getClickCount() > 0 && getNodeComponentAt(e.getLocationOnScreen()) == null; + if (e.getClickCount() > 0 && getNodeComponentAt(e.getLocationOnScreen()) == null) { + TreePath path = myTree.getPathForLocation(e.getX(), e.getY()); + if (path == null) { + mySkipSelectionChange = true; + } + if (path == null || !isEmptyCondition(((ArrangementTreeNode)path.getLastPathComponent()).getBackingCondition())) { + mySkipSelectionChange = true; + } + } try { super.processMouseEvent(e); if (mySkipSelectionChange) { @@ -106,7 +135,7 @@ public class ArrangementRuleTree { mySkipSelectionChange = false; } } - }; + }; myTree.setSelectionModel(mySelectionModel); myTree.setRootVisible(false); mySelectionModel.addTreeSelectionListener(new TreeSelectionListener() { @@ -166,7 +195,7 @@ public class ArrangementRuleTree { rules.add(new ArrangementCompositeMatchCondition(ArrangementCompositeMatchCondition.Operator.AND) .addOperand(new ArrangementAtomMatchCondition(ArrangementSettingType.TYPE, ArrangementEntryType.METHOD)) .addOperand(new ArrangementAtomMatchCondition(ArrangementSettingType.MODIFIER, ArrangementModifier.PRIVATE))); - map(myRoot, rules, grouper); + map(rules); expandAll(myTree, new TreePath(myRoot)); myTree.setShowsRootHandles(false); @@ -264,7 +293,7 @@ public class ArrangementRuleTree { } /** - * Updates renderer's {@link ArrangementNodeComponent#setSelected(boolean) 'selected'} state on tree ndoe selection change. + * Updates renderer {@link ArrangementNodeComponent#setSelected(boolean) 'selected'} state on tree node selection change. * * @param path changed selection path * @param selected true if given path is selected now; false if given path was selected anymore @@ -305,21 +334,12 @@ public class ArrangementRuleTree { tree.expandPath(parent); } - private void map(@NotNull ArrangementTreeNode root, - @NotNull List matchConditions, - @NotNull ArrangementSettingsGrouper grouper) - { - ArrangementRuleEditingModelBuilder builder = new ArrangementRuleEditingModelBuilder(); + private void map(@NotNull List matchConditions) { for (ArrangementMatchCondition matchCondition : matchConditions) { - builder.build(matchCondition, myTree, root, grouper, myModels); + Pair pair = myModelBuilder.build(matchCondition, myTree, myRoot, null, myGrouper); + myModels.put(pair.first.getRow(), pair.first); + pair.first.addListener(myModelChangeListener); } - myModels.forEachValue(new TObjectProcedure() { - @Override - public boolean execute(ArrangementRuleEditingModelImpl model) { - model.addListener(myModelChangeListener); - return true; - } - }); } public void addEditingListener(@NotNull ArrangementRuleSelectionListener listener) { @@ -330,7 +350,7 @@ public class ArrangementRuleTree { * @return matcher model for the selected tree row(s) if any; null otherwise */ @Nullable - public ArrangementRuleEditingModel getActiveModel() { + public ArrangementRuleEditingModelImpl getActiveModel() { TreePath[] paths = mySelectionModel.getSelectionPaths(); if (paths == null) { return null; @@ -340,14 +360,17 @@ public class ArrangementRuleTree { // for more than one rule. No model is registered for them then and we want just to skip them. for (int i = paths.length - 1; i >= 0; i--) { int row = myTree.getRowForPath(paths[i]); - ArrangementRuleEditingModel model = myModels.get(row); + ArrangementRuleEditingModelImpl model = myModels.get(row); if (model != null) { return model; } } return null; } - + + /** + * @return UI component encapsulated by the current object. Subsequent calls to this method return the same reference all the time + */ @NotNull public Tree getTreeComponent() { return myTree; @@ -387,7 +410,13 @@ public class ArrangementRuleTree { component.handleMouseClick(e); return; } - // Clear selection + + TreePath path = myTree.getPathForLocation(e.getX(), e.getY()); + if (path != null && isEmptyCondition(((ArrangementTreeNode)path.getLastPathComponent()).getBackingCondition())) { + return; + } + + // Clear selection if it was a click at the empty space doClearSelection(); } @@ -530,6 +559,31 @@ public class ArrangementRuleTree { } }); } + + /** + * Asks current model to create a new rule below the currently selected (or at the last position if no one is selected at the moment). + * + * @return model for the newly created row + */ + @NotNull + public ArrangementRuleEditingModel newModel() { + ArrangementRuleEditingModelImpl activeModel = getActiveModel(); + final ArrangementTreeNode anchor = activeModel == null ? null : activeModel.getBottomMost(); + doClearSelection(); + Pair pair = myModelBuilder.build( + new ArrangementCompositeMatchCondition(ArrangementCompositeMatchCondition.Operator.AND), myTree, myRoot, anchor, myGrouper + ); + processRowChanges(pair.second); + myModels.put(pair.first.getRow(), pair.first); + pair.first.addListener(myModelChangeListener); + mySelectionModel.setSelectionPath(myTree.getPathForRow(pair.first.getRow())); + return pair.first; + } + + private static boolean isEmptyCondition(@Nullable ArrangementMatchCondition condition) { + return condition instanceof ArrangementCompositeMatchCondition + && ((ArrangementCompositeMatchCondition)condition).getOperands().isEmpty(); + } private class MyCellRenderer implements TreeCellRenderer { @Override @@ -545,7 +599,10 @@ public class ArrangementRuleTree { if (node == null) { return EMPTY_RENDERER; } - + else if (isEmptyCondition(node)) { + return NEW_CONDITION_RENDERER; + } + if (row < 0) { return myFactory.getComponent(node).getUiComponent(); } @@ -568,6 +625,8 @@ public class ArrangementRuleTree { return; } + myAlarm.cancelAllRequests(); + myAlarm.addRequest(myRequest, 300); clearSelection(); ArrangementTreeNode component = (ArrangementTreeNode)path.getLastPathComponent(); @@ -626,4 +685,27 @@ public class ArrangementRuleTree { return true; } } + + private class RemoveInactiveNewModelRequest implements Runnable, TObjectProcedure { + + @Override + public void run() { + myAlarm.cancelAllRequests(); + ArrangementRuleEditingModelImpl model = getActiveModel(); + if (model != null && isEmptyCondition(model.getMatchCondition())) { + return; + } + myModels.forEachValue(this); + } + + @Override + public boolean execute(@NotNull ArrangementRuleEditingModelImpl model) { + if (isEmptyCondition(model.getMatchCondition())) { + model.destroy(); + // TODO den restore selection (e.g. from bottom) + return false; + } + return true; + } + } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementSettingsPanel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementSettingsPanel.java index 4c8e6ebffe9e..8b8859408c01 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementSettingsPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementSettingsPanel.java @@ -16,18 +16,27 @@ package com.intellij.application.options.codeStyle.arrangement; import com.intellij.application.options.CodeStyleAbstractPanel; +import com.intellij.ide.DataManager; import com.intellij.ide.ui.customization.CustomizationUtil; +import com.intellij.openapi.actionSystem.ActionGroup; +import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionToolbar; +import com.intellij.openapi.actionSystem.DataProvider; import com.intellij.openapi.application.ApplicationBundle; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.highlighter.EditorHighlighter; import com.intellij.openapi.util.Ref; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.arrangement.settings.*; +import com.intellij.ui.IdeBorderFactory; +import com.intellij.ui.SideBorder; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.ui.GridBag; import org.jdesktop.swingx.JXTaskPane; +import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; @@ -44,29 +53,38 @@ public abstract class ArrangementSettingsPanel extends CodeStyleAbstractPanel { @NotNull private final JPanel myContent = new JPanel(new GridBagLayout()); - public ArrangementSettingsPanel(@NotNull CodeStyleSettings settings, @NotNull ArrangementStandardSettingsAware filter) { + public ArrangementSettingsPanel(@NotNull CodeStyleSettings settings, @NotNull final ArrangementStandardSettingsAware filter) { super(settings); ArrangementStandardSettingsRepresentationAware representationManager = DefaultArrangementSettingsRepresentationManager.INSTANCE; if (filter instanceof ArrangementStandardSettingsRepresentationAware) { representationManager = (ArrangementStandardSettingsRepresentationAware)filter; } - ArrangementNodeDisplayManager displayManager = new ArrangementNodeDisplayManager(filter, representationManager); + final ArrangementNodeDisplayManager displayManager = new ArrangementNodeDisplayManager(filter, representationManager); ArrangementSettingsGrouper grouper = DefaultArrangementSettingsGrouper.INSTANCE; if (filter instanceof ArrangementSettingsGrouper) { grouper = (ArrangementSettingsGrouper)filter; } + + final ActionManager actionManager = ActionManager.getInstance(); + final ActionGroup actionGroup = (ActionGroup)actionManager.getAction(ArrangementConstants.ACTION_GROUP_RULE_EDITOR_TOOL_WINDOW); + ActionToolbar actionToolbar = actionManager.createActionToolbar(ArrangementConstants.RULE_EDITOR_TOOL_WINDOW_PLACE, actionGroup, true); + JPanel toolbarControl = new JPanel(new GridBagLayout()); + toolbarControl.add(actionToolbar.getComponent(), new GridBag().anchor(GridBagConstraints.WEST).weightx(1).fillCellHorizontally()); + toolbarControl.setBorder(IdeBorderFactory.createBorder(SideBorder.LEFT | SideBorder.TOP | SideBorder.RIGHT)); + myContent.add(toolbarControl, new GridBag().weightx(1).fillCellHorizontally().coverLine()); final ArrangementRuleTree ruleTree = new ArrangementRuleTree(grouper, displayManager); - Tree component = ruleTree.getTreeComponent(); - myContent.add(new JBScrollPane(component), new GridBag().weightx(1).weighty(1).fillCell().coverLine()); + Tree treeComponent = ruleTree.getTreeComponent(); + actionToolbar.setTargetComponent(treeComponent); + myContent.add(new JBScrollPane(treeComponent), new GridBag().weightx(1).weighty(1).fillCell().coverLine()); CustomizationUtil.installPopupHandler( - component, ArrangementConstants.ACTION_GROUP_RULE_EDITOR_CONTEXT_MENU, ArrangementConstants.RULE_EDITOR_PLACE + treeComponent, ArrangementConstants.ACTION_GROUP_RULE_EDITOR_CONTEXT_MENU, ArrangementConstants.RULE_EDITOR_PLACE ); final JXTaskPane editorPane = new JXTaskPane(ApplicationBundle.message("arrangement.title.editor")); final ArrangementRuleEditor ruleEditor = new ArrangementRuleEditor(filter, displayManager); - ruleEditor.applyBackground(component.getBackground()); - editorPane.getContentPane().setBackground(component.getBackground()); + ruleEditor.applyBackground(treeComponent.getBackground()); + editorPane.getContentPane().setBackground(treeComponent.getBackground()); editorPane.add(ruleEditor); editorPane.setCollapsed(true); final Ref resetEditor = new Ref(Boolean.TRUE); @@ -97,6 +115,30 @@ public abstract class ArrangementSettingsPanel extends CodeStyleAbstractPanel { editorPane.setCollapsed(true); } }); + final Runnable newRuleFunction = new Runnable() { + @Override + public void run() { + ArrangementRuleEditingModel model = ruleTree.newModel(); + ruleEditor.updateState(model); + resetEditor.set(Boolean.FALSE); + try { + editorPane.setCollapsed(false); + } + finally { + resetEditor.set(Boolean.TRUE); + } + } + }; + treeComponent.putClientProperty(DataManager.CLIENT_PROPERTY_DATA_PROVIDER, new DataProvider() { + @Nullable + @Override + public Object getData(@NonNls String dataId) { + if (ArrangementConstants.NEW_RULE_FUNCTION_KEY.is(dataId)) { + return newRuleFunction; + } + return null; + } + }); } @Override diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/AddArrangementRuleAction.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/AddArrangementRuleAction.java new file mode 100644 index 000000000000..70c75b178760 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/action/AddArrangementRuleAction.java @@ -0,0 +1,43 @@ +/* + * Copyright 2000-2012 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.application.options.codeStyle.arrangement.action; + +import com.intellij.application.options.codeStyle.arrangement.ArrangementConstants; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.ApplicationBundle; + +/** + * @author Denis Zhdanov + * @since 8/24/12 1:54 PM + */ +public class AddArrangementRuleAction extends AnAction { + + public AddArrangementRuleAction() { + getTemplatePresentation().setIcon(AllIcons.General.Add); + getTemplatePresentation().setText(ApplicationBundle.message("arrangement.action.condition.add.text")); + getTemplatePresentation().setDescription(ApplicationBundle.message("arrangement.action.rule.add.description")); + } + + @Override + public void actionPerformed(AnActionEvent e) { + Runnable function = ArrangementConstants.NEW_RULE_FUNCTION_KEY.getData(e.getDataContext()); + if (function != null) { + function.run(); + } + } +} diff --git a/platform/platform-api/src/com/intellij/ui/treeStructure/Tree.java b/platform/platform-api/src/com/intellij/ui/treeStructure/Tree.java index a914d546889a..00d496e01421 100644 --- a/platform/platform-api/src/com/intellij/ui/treeStructure/Tree.java +++ b/platform/platform-api/src/com/intellij/ui/treeStructure/Tree.java @@ -19,6 +19,7 @@ import com.intellij.Patches; import com.intellij.ide.util.treeView.*; import com.intellij.openapi.ui.GraphicsConfig; import com.intellij.openapi.ui.Queryable; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.SystemInfo; import com.intellij.ui.*; @@ -97,7 +98,7 @@ public class Tree extends JTree implements ComponentWithEmptyText, ComponentWith TreeUI actualUI = ui; if (!isCustomUI()) { if (!(ui instanceof WideSelectionTreeUI) && isWideSelection() && !UIUtil.isUnderGTKLookAndFeel()) { - actualUI = new WideSelectionTreeUI(isWideSelection(), isAlwaysPaintRowBackground()); + actualUI = new WideSelectionTreeUI(isWideSelection(), getWideSelectionBackgroundCondition()); } } super.setUI(actualUI); @@ -140,8 +141,14 @@ public class Tree extends JTree implements ComponentWithEmptyText, ComponentWith return true; } - protected boolean isAlwaysPaintRowBackground() { - return !SystemInfo.isMac; + /** + * @return a strategy which determines if a wide selection should be drawn for a target row (it's number is + * {@link Condition#value(Object) given} as an argument to the strategy) + */ + @SuppressWarnings("unchecked") + @NotNull + protected Condition getWideSelectionBackgroundCondition() { + return SystemInfo.isMac ? Condition.FALSE : Condition.TRUE; } public boolean isFileColorsEnabled() { diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index 12fdf629ee91..8d4e0c802b47 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -555,3 +555,5 @@ arrangement.title.editor=Edit rule arrangement.text.type=Type arrangement.text.modifier=Modifier arrangement.text.negate=Negate +arrangement.action.condition.add.text=Add condition +arrangement.action.condition.add.description=Add new arrangement rule condition \ No newline at end of file diff --git a/platform/platform-resources/src/idea/LangActions.xml b/platform/platform-resources/src/idea/LangActions.xml index a37ea9a06cfb..4072c2ce3ddf 100644 --- a/platform/platform-resources/src/idea/LangActions.xml +++ b/platform/platform-resources/src/idea/LangActions.xml @@ -811,6 +811,10 @@ + + + + diff --git a/platform/util/src/com/intellij/util/ui/tree/WideSelectionTreeUI.java b/platform/util/src/com/intellij/util/ui/tree/WideSelectionTreeUI.java index 5c0daa8a7cfa..14b98843230e 100644 --- a/platform/util/src/com/intellij/util/ui/tree/WideSelectionTreeUI.java +++ b/platform/util/src/com/intellij/util/ui/tree/WideSelectionTreeUI.java @@ -15,6 +15,7 @@ */ package com.intellij.util.ui.tree; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.SystemInfo; import com.intellij.util.containers.ComparatorUtil; import com.intellij.util.ui.UIUtil; @@ -36,26 +37,34 @@ import java.awt.event.MouseListener; * @author Konstantin Bulenkov */ public class WideSelectionTreeUI extends BasicTreeUI { + @NonNls public static final String SOURCE_LIST_CLIENT_PROPERTY = "mac.ui.source.list"; @NonNls public static final String STRIPED_CLIENT_PROPERTY = "mac.ui.striped"; private static final Border LIST_BACKGROUND_PAINTER = UIManager.getBorder("List.sourceListBackgroundPainter"); private static final Border LIST_SELECTION_BACKGROUND_PAINTER = UIManager.getBorder("List.sourceListSelectionBackgroundPainter"); private static final Border LIST_FOCUSED_SELECTION_BACKGROUND_PAINTER = UIManager.getBorder("List.sourceListFocusedSelectionBackgroundPainter"); - + + @NotNull private final Condition myWideSelectionCondition; private boolean myWideSelection; - private boolean myAlwaysPaintRowBackground; private boolean myOldRepaintAllRowValue; private boolean invertLineColor; - + @SuppressWarnings("unchecked") public WideSelectionTreeUI() { - this(true, true); + this(true, Condition.TRUE); } - public WideSelectionTreeUI(final boolean wideSelection, boolean alwaysPaintRowBackground) { + /** + * Creates new WideSelectionTreeUI object. + * + * @param wideSelection flag that determines if wide selection should be used + * @param wideSelectionCondition strategy that determine if wide selection should be used for a target row (it's zero-based index + * is given to the condition as an argument) + */ + public WideSelectionTreeUI(final boolean wideSelection, @NotNull Condition wideSelectionCondition) { myWideSelection = wideSelection; - myAlwaysPaintRowBackground = alwaysPaintRowBackground; + myWideSelectionCondition = wideSelectionCondition; } private final MouseListener mySelectionListener = new MouseAdapter() { @@ -232,7 +241,7 @@ public class WideSelectionTreeUI extends BasicTreeUI { @Override protected void paintVerticalLine(Graphics g, JComponent c, int x, int top, int bottom) { if (tree.hasFocus() && UIUtil.isUnderAlloyIDEALookAndFeel()) { - int y0 = top, y1 = top; + int y0, y1 = top; while (y1 < bottom) { y0 = y1; final int row = tree.getRowForPath(tree.getClosestPathForLocation(x, y0 + 1)); @@ -297,7 +306,7 @@ public class WideSelectionTreeUI extends BasicTreeUI { LIST_SELECTION_BACKGROUND_PAINTER.paintBorder(tree, rowGraphics, xOffset, bounds.y, containerWidth, bounds.height); } } - else if (myAlwaysPaintRowBackground) { + else if (myWideSelectionCondition.value(row)) { rowGraphics.setColor(background); rowGraphics.fillRect(xOffset, bounds.y, containerWidth, bounds.height); } @@ -309,7 +318,7 @@ public class WideSelectionTreeUI extends BasicTreeUI { bg = background; } - if (myAlwaysPaintRowBackground || selected) { + if (myWideSelectionCondition.value(row) || selected) { rowGraphics.setColor(bg); rowGraphics.fillRect(xOffset, bounds.y, containerWidth, bounds.height - 1); } @@ -330,7 +339,7 @@ public class WideSelectionTreeUI extends BasicTreeUI { @Override public void paint(Graphics g, JComponent c) { - if (myAlwaysPaintRowBackground && myWideSelection && !UIUtil.isUnderAquaBasedLookAndFeel()) { + if (myWideSelection && !UIUtil.isUnderAquaBasedLookAndFeel()) { paintSelectedRows(g, ((JTree)c)); } if (myWideSelection) { @@ -357,7 +366,7 @@ public class WideSelectionTreeUI extends BasicTreeUI { final int lastVisibleRow = tr.getClosestRowForLocation(rect.x, rect.y + rect.height); for (int row = firstVisibleRow; row <= lastVisibleRow; row++) { - if (tr.getSelectionModel().isRowSelected(row)) { + if (tr.getSelectionModel().isRowSelected(row) && myWideSelectionCondition.value(row)) { final Rectangle bounds = tr.getRowBounds(row); Color color = UIUtil.getTreeSelectionBackground(tr.hasFocus()); if (color != null) {