diff --git a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java index 5ea57f8b3cdf..2deed0905ec4 100644 --- a/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java +++ b/java/java-impl/src/com/intellij/psi/codeStyle/arrangement/JavaRearranger.java @@ -161,10 +161,10 @@ public class JavaRearranger implements Rearranger, } } if (compositeWithoutType.getOperands().size() == 1) { - parent.addChild(new HierarchicalArrangementSettingsNode(compositeWithoutType.getOperands().iterator().next())); + parent.setChild(new HierarchicalArrangementSettingsNode(compositeWithoutType.getOperands().iterator().next())); } else { - parent.addChild(new HierarchicalArrangementSettingsNode(compositeWithoutType)); + parent.setChild(new HierarchicalArrangementSettingsNode(compositeWithoutType)); } result.set(parent); } 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 new file mode 100644 index 000000000000..01a5474d8fbd --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/AbstractArrangementRuleEditingModelTest.java @@ -0,0 +1,84 @@ +/* + * 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; + +import com.intellij.psi.codeStyle.arrangement.JavaRearranger; +import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType; +import com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier; +import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingType; +import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsAtomNode; +import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNode; +import com.intellij.ui.treeStructure.Tree; +import gnu.trove.TIntObjectHashMap; +import org.jetbrains.annotations.NotNull; +import org.junit.Before; + +import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreePath; +import java.util.Arrays; + +import static org.junit.Assert.assertTrue; + +/** + * @author Denis Zhdanov + * @since 8/16/12 11:04 AM + */ +public abstract class AbstractArrangementRuleEditingModelTest { + + @NotNull protected ArrangementRuleEditingModelBuilder myBuilder; + @NotNull protected JTree myTree; + @NotNull protected DefaultMutableTreeNode myRoot; + @NotNull protected TIntObjectHashMap myRowMappings; + @NotNull protected JavaRearranger myGrouper; + + @Before + public void setUp() { + myBuilder = new ArrangementRuleEditingModelBuilder(); + myRoot = new DefaultMutableTreeNode(); + myTree = new Tree(myRoot); + myTree.expandPath(new TreePath(myRoot)); + myRowMappings = new TIntObjectHashMap(); + myGrouper = new JavaRearranger(); + } + + protected void configure(@NotNull ArrangementSettingsNode settingsNode) { + myBuilder.build(settingsNode, myTree, myRoot, myGrouper, myRowMappings); + } + + protected static ArrangementSettingsAtomNode atom(@NotNull Object condition) { + final ArrangementSettingType type; + if (condition instanceof ArrangementEntryType) { + type = ArrangementSettingType.TYPE; + } + else if (condition instanceof ArrangementModifier) { + type = ArrangementSettingType.MODIFIER; + } + else { + throw new IllegalArgumentException(String.format("Unexpected condition of class %s: %s", condition.getClass(), condition)); + } + return new ArrangementSettingsAtomNode(type, condition); + } + + protected void checkRows(int ... rows) { + for (int row : rows) { + assertTrue( + String.format("Expected to find mappings for rows %s. Actual: %s", Arrays.toString(rows), Arrays.toString(myRowMappings.keys())), + myRowMappings.containsKey(row) + ); + } + } +} 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 72a02242688c..8c30c22c9b7c 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,23 +15,10 @@ */ package com.intellij.application.options.codeStyle.arrangement; -import com.intellij.psi.codeStyle.arrangement.JavaRearranger; -import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType; -import com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier; -import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingType; -import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsAtomNode; import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNode; -import com.intellij.ui.treeStructure.Tree; -import gnu.trove.TIntObjectHashMap; -import org.jetbrains.annotations.NotNull; -import org.junit.Before; import org.junit.Test; -import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; -import javax.swing.tree.TreePath; - -import java.util.Arrays; import static com.intellij.psi.codeStyle.arrangement.ArrangementUtil.and; import static com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType.FIELD; @@ -42,23 +29,7 @@ import static org.junit.Assert.*; * @author Denis Zhdanov * @since 08/15/2012 */ -public class ArrangementRuleEditingModelBuilderTest { - - @NotNull private ArrangementRuleEditingModelBuilder myBuilder; - @NotNull private JTree myTree; - @NotNull private DefaultMutableTreeNode myRoot; - @NotNull private TIntObjectHashMap myRowMappings; - @NotNull private JavaRearranger myGrouper; - - @Before - public void setUp() { - myBuilder = new ArrangementRuleEditingModelBuilder(); - myRoot = new DefaultMutableTreeNode(); - myTree = new Tree(myRoot); - myTree.expandPath(new TreePath(myRoot)); - myRowMappings = new TIntObjectHashMap(); - myGrouper = new JavaRearranger(); - } +public class ArrangementRuleEditingModelBuilderTest extends AbstractArrangementRuleEditingModelTest { @Test public void mapToTheSameLayer() { @@ -108,27 +79,4 @@ public class ArrangementRuleEditingModelBuilderTest { assertNotNull(privateUiNode); assertEquals(atom(PRIVATE), privateUiNode.getUserObject()); } - - private void checkRows(int ... rows) { - for (int row : rows) { - assertTrue( - String.format("Expected to find mappings for rows %s. Actual: %s", Arrays.toString(rows), Arrays.toString(myRowMappings.keys())), - myRowMappings.containsKey(row) - ); - } - } - - private static ArrangementSettingsAtomNode atom(@NotNull Object condition) { - final ArrangementSettingType type; - if (condition instanceof ArrangementEntryType) { - type = ArrangementSettingType.TYPE; - } - else if (condition instanceof ArrangementModifier) { - type = ArrangementSettingType.MODIFIER; - } - else { - throw new IllegalArgumentException(String.format("Unexpected condition of class %s: %s", condition.getClass(), condition)); - } - return new ArrangementSettingsAtomNode(type, condition); - } } diff --git a/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImplTest.java b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImplTest.java new file mode 100644 index 000000000000..6d3fd3378c76 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImplTest.java @@ -0,0 +1,94 @@ +/* + * 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; + +import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNode; +import org.junit.Test; + +import javax.swing.tree.DefaultMutableTreeNode; + +import static com.intellij.psi.codeStyle.arrangement.ArrangementUtil.and; +import static com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType.FIELD; +import static com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier.PUBLIC; +import static com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier.STATIC; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +/** + * @author Denis Zhdanov + * @since 8/16/12 10:07 AM + */ +public class ArrangementRuleEditingModelImplTest extends AbstractArrangementRuleEditingModelTest { + + @Test + public void addConditionToSameLevel() { + configure(atom(PUBLIC)); + ArrangementRuleEditingModel model = myRowMappings.get(1); + assertNotNull(model); + model.addAndCondition(atom(STATIC)); + + DefaultMutableTreeNode child = (DefaultMutableTreeNode)myRoot.getFirstChild(); + assertNotNull(child); + ArrangementSettingsNode expectedSettingsNode = and(atom(PUBLIC), atom(STATIC)); + assertEquals(expectedSettingsNode, child.getUserObject()); + assertEquals(expectedSettingsNode, model.getSettingsNode()); + } + + @Test + public void buildNewSingleLevel() { + configure(atom(PUBLIC)); + ArrangementRuleEditingModel model = myRowMappings.get(1); + assertNotNull(model); + assertEquals(1, myRowMappings.size()); + + model.addAndCondition(atom(FIELD)); + + assertEquals(1, myRowMappings.size()); + assertSame(model, myRowMappings.get(2)); + assertEquals(and(atom(FIELD), atom(PUBLIC)), model.getSettingsNode()); + + DefaultMutableTreeNode fieldNode = (DefaultMutableTreeNode)myRoot.getFirstChild(); + assertNotNull(fieldNode); + assertEquals(atom(FIELD), fieldNode.getUserObject()); + + DefaultMutableTreeNode publicNode = fieldNode.getFirstLeaf(); + assertNotNull(publicNode); + assertEquals(atom(PUBLIC), publicNode.getUserObject()); + } + + @Test + public void addConditionToSameNestedLevel() { + configure(and(atom(FIELD), atom(PUBLIC))); + ArrangementRuleEditingModel model = myRowMappings.get(2); + assertNotNull(model); + assertEquals(1, myRowMappings.size()); + + model.addAndCondition(atom(STATIC)); + + assertEquals(1, myRowMappings.size()); + assertSame(model, myRowMappings.get(2)); + assertEquals(and(atom(FIELD), atom(PUBLIC), atom(STATIC)), model.getSettingsNode()); + + DefaultMutableTreeNode fieldNode = (DefaultMutableTreeNode)myRoot.getFirstChild(); + assertNotNull(fieldNode); + assertEquals(atom(FIELD), fieldNode.getUserObject()); + + DefaultMutableTreeNode modifiersNode = fieldNode.getFirstLeaf(); + assertNotNull(modifiersNode); + assertEquals(and(atom(PUBLIC), atom(STATIC)), modifiersNode.getUserObject()); + } +} diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/model/ArrangementSettingsCompositeNode.java b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/model/ArrangementSettingsCompositeNode.java index 350c8dd7db2f..c557fc8141d4 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/model/ArrangementSettingsCompositeNode.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/model/ArrangementSettingsCompositeNode.java @@ -96,7 +96,7 @@ public class ArrangementSettingsCompositeNode implements ArrangementSettingsNode @Override public String toString() { - return StringUtil.join(myOperands, myOperator == Operator.AND ? " and " : " or "); + return String.format("(%s)", StringUtil.join(myOperands, myOperator == Operator.AND ? " and " : " or ")); } public enum Operator { diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/model/HierarchicalArrangementSettingsNode.java b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/model/HierarchicalArrangementSettingsNode.java index 207e15d667f5..e4685197ddad 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/model/HierarchicalArrangementSettingsNode.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/model/HierarchicalArrangementSettingsNode.java @@ -16,21 +16,21 @@ package com.intellij.psi.codeStyle.arrangement.model; import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; +import org.jetbrains.annotations.Nullable; /** - * // TODO den add doc + * {@link #getCurrent() Wraps} {@link ArrangementSettingsNode} in order to allow to build {@link #getChild() hierarchy} + * from a plain sequence of them + *

+ * Not thread-safe. * * @author Denis Zhdanov * @since 8/8/12 1:23 PM */ public class HierarchicalArrangementSettingsNode { - @NotNull private final List myChildren = new ArrayList(); - - @NotNull private final ArrangementSettingsNode myCurrent; + @NotNull private final ArrangementSettingsNode myCurrent; + @Nullable private HierarchicalArrangementSettingsNode myChild; public HierarchicalArrangementSettingsNode(@NotNull ArrangementSettingsNode current) { myCurrent = current; @@ -41,15 +41,15 @@ public class HierarchicalArrangementSettingsNode { return myCurrent; } - @NotNull - public List getChildren() { - return myChildren; + @Nullable + public HierarchicalArrangementSettingsNode getChild() { + return myChild; } - public void addChild(@NotNull HierarchicalArrangementSettingsNode child) { - myChildren.add(child); + public void setChild(@Nullable HierarchicalArrangementSettingsNode child) { + myChild = child; } - + @Override public String toString() { return myCurrent.toString(); diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementAndNodeComponent.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementAndNodeComponent.java index 182bc092ba4e..a2225453c11f 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementAndNodeComponent.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementAndNodeComponent.java @@ -15,8 +15,11 @@ */ package com.intellij.application.options.codeStyle.arrangement; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsAtomNode; import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsCompositeNode; import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNode; +import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNodeVisitor; import com.intellij.ui.awt.RelativePoint; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,10 +27,12 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** - * // TODO den add doc + * {@link ArrangementNodeComponent Component} for showing {@link ArrangementSettingsCompositeNode composite nodes}. *

* Not thread-safe. * @@ -43,11 +48,33 @@ public class ArrangementAndNodeComponent extends JPanel implements ArrangementNo @NotNull private final ArrangementSettingsCompositeNode mySettingsNode; @Nullable private Rectangle myScreenBounds; - public ArrangementAndNodeComponent(@NotNull ArrangementSettingsCompositeNode node, @NotNull ArrangementNodeComponentFactory factory) { + public ArrangementAndNodeComponent(@NotNull ArrangementSettingsCompositeNode node, + @NotNull ArrangementNodeComponentFactory factory, + @NotNull ArrangementNodeDisplayManager manager) + { mySettingsNode = node; setLayout(null); int x = 0; + final Map operands = new HashMap(); + ArrangementSettingsNodeVisitor visitor = new ArrangementSettingsNodeVisitor() { + @Override + public void visit(@NotNull ArrangementSettingsAtomNode node) { + operands.put(node.getValue(), node); + } + + @Override + public void visit(@NotNull ArrangementSettingsCompositeNode node) { + operands.put(node, node); + } + }; for (ArrangementSettingsNode operand : node.getOperands()) { + operand.invite(visitor); + } + + List ordered = manager.sort(operands.keySet()); + for (Object key : ordered) { + ArrangementSettingsNode operand = operands.get(key); + assert operand != null; ArrangementNodeComponent component = factory.getComponent(operand); myComponents.add(component); JComponent uiComponent = component.getUiComponent(); @@ -159,13 +186,16 @@ public class ArrangementAndNodeComponent extends JPanel implements ArrangementNo )); } int y = bounds.y + bounds.height / 2; - // TODO den shift y for the component x += bounds.width; - // TODO den use right 'y' if (i < components.length - 1) { g.drawLine(x, y, x + BUBBLE_CONNECTOR_LENGTH, y); } x += BUBBLE_CONNECTOR_LENGTH; } } + + @Override + public String toString() { + return String.format("(%s)", StringUtil.join(myComponents, " and ")); + } } 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 3fb162408e1e..7665db728d91 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 @@ -15,16 +15,21 @@ */ package com.intellij.application.options.codeStyle.arrangement; +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.ArrangementSettingType; import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNode; +import com.intellij.psi.codeStyle.arrangement.model.HierarchicalArrangementSettingsNode; import com.intellij.psi.codeStyle.arrangement.settings.ArrangementMatcherSettings; import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsAware; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.TreeNode; +import javax.swing.tree.TreePath; import java.awt.*; import java.util.*; import java.util.List; @@ -113,4 +118,76 @@ public class ArrangementConfigUtil { } return null; } + + public static int getDepth(@NotNull HierarchicalArrangementSettingsNode node) { + HierarchicalArrangementSettingsNode child = node.getChild(); + return child == null ? 1 : 1 + getDepth(child); + } + + @NotNull + public static HierarchicalArrangementSettingsNode getLast(@NotNull HierarchicalArrangementSettingsNode node) { + HierarchicalArrangementSettingsNode result = node; + for (HierarchicalArrangementSettingsNode child = node.getChild(); child != null; child = child.getChild()) { + result = child; + } + return result; + } + + @NotNull + public static TreeNode getLastBefore(@NotNull TreeNode start, @NotNull TreeNode stop) throws IllegalArgumentException { + TreeNode result = start; + for (TreeNode n = start.getParent(); n != stop; n = n.getParent()) { + if (n == null) { + throw new IllegalArgumentException(String.format( + "Non-crossing paths detected - start: %s, stop: %s", new TreePath(start), new TreePath(stop) + )); + } + result = n; + } + return result; + } + + public static int distance(@NotNull TreeNode parent, @NotNull TreeNode child) { + if (parent == child) { + return 1; + } + int result = 1; + for (TreeNode n = child; n != null && n != parent; n = n.getParent()) { + result++; + } + return result; + } + + /** + * @param uiParentNode UI tree node which should hold UI nodes created for representing given settings node + * @param settingsNode settings node which should be represented at the UI tree denoted by the given UI tree node + * @return pair {@code (bottom-most leaf node created; number of rows created)} + */ + @NotNull + public static Pair map(@NotNull DefaultMutableTreeNode uiParentNode, + @NotNull HierarchicalArrangementSettingsNode settingsNode) + { + DefaultMutableTreeNode uiNode = null; + int rowsCreated = 0; + for (int i = uiParentNode.getChildCount() - 1; i >= 0; i--) { + DefaultMutableTreeNode child = (DefaultMutableTreeNode)uiParentNode.getChildAt(i); + if (settingsNode.getCurrent().equals(child.getUserObject())) { + uiNode = child; + break; + } + } + if (uiNode == null) { + uiNode = new DefaultMutableTreeNode(settingsNode.getCurrent()); + uiParentNode.add(uiNode); + rowsCreated++; + } + DefaultMutableTreeNode leaf = uiNode; + HierarchicalArrangementSettingsNode childSettingsNode = settingsNode.getChild(); + if (childSettingsNode != null) { + Pair pair = map(uiNode, childSettingsNode); + leaf = pair.first; + rowsCreated += pair.second; + } + return Pair.create(leaf, rowsCreated); + } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementNodeComponentFactory.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementNodeComponentFactory.java index d4bd003bf054..8feeb53aaf4c 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementNodeComponentFactory.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementNodeComponentFactory.java @@ -43,7 +43,7 @@ public class ArrangementNodeComponentFactory { @Override public void visit(@NotNull ArrangementSettingsCompositeNode node) { switch (node.getOperator()) { - case AND: ref.set(new ArrangementAndNodeComponent(node, ArrangementNodeComponentFactory.this)); break; + case AND: ref.set(new ArrangementAndNodeComponent(node, ArrangementNodeComponentFactory.this, myDisplayManager)); break; case OR: // TODO den implement } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModel.java index 2bb5749ca681..15ba6702bccf 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModel.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModel.java @@ -21,6 +21,8 @@ import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsAtomNode; import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNode; import org.jetbrains.annotations.NotNull; +import javax.swing.tree.TreeNode; + /** * Combines and encapsulates information about arrangement matcher rules representation (tree nodes) and * underlying {@link ArrangementSettingsNode data model}. @@ -48,4 +50,10 @@ public interface ArrangementRuleEditingModel { void addAndCondition(@NotNull ArrangementSettingsAtomNode node); void removeAndCondition(@NotNull ArrangementSettingsNode node); + + void addListener(@NotNull Listener listener); + + interface Listener { + void onChanged(@NotNull TreeNode topMost, @NotNull TreeNode bottomMost); + } } 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 790e53e90352..8e3b4892eec5 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 @@ -15,6 +15,7 @@ */ package com.intellij.application.options.codeStyle.arrangement; +import com.intellij.openapi.util.Pair; import com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier; import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNode; import com.intellij.psi.codeStyle.arrangement.model.HierarchicalArrangementSettingsNode; @@ -91,9 +92,12 @@ public class ArrangementRuleEditingModelBuilder { } HierarchicalArrangementSettingsNode grouped = grouper.group(setting); - int rowsCreated = map(root, grouped); - ArrangementRuleEditingModelImpl model = new ArrangementRuleEditingModelImpl(setting); - rowMappings.put(initialInsertRow + rowsCreated - 1, model); + Pair pair = ArrangementConfigUtil.map(root, grouped); + DefaultMutableTreeNode topMostNode = (DefaultMutableTreeNode)ArrangementConfigUtil.getLastBefore(pair.first, root); + int row = initialInsertRow + pair.second - 1; + ArrangementRuleEditingModelImpl model + = new ArrangementRuleEditingModelImpl(setting, topMostNode, pair.first, grouper, rowMappings, row); + rowMappings.put(row, model); } private static int calculateRowsCount(@NotNull TreeNode node) { @@ -103,30 +107,4 @@ public class ArrangementRuleEditingModelBuilder { } return result; } - - /** - * @param uiParentNode UI tree node which should hold UI nodes created for representing given settings node - * @param settingsNode settings node which should be represented at the UI tree denoted by the given UI tree node - * @return number of rows created - */ - private static int map(@NotNull DefaultMutableTreeNode uiParentNode, @NotNull HierarchicalArrangementSettingsNode settingsNode) { - DefaultMutableTreeNode uiNode = null; - int result = 0; - for (int i = uiParentNode.getChildCount() - 1; i >= 0; i--) { - DefaultMutableTreeNode child = (DefaultMutableTreeNode)uiParentNode.getChildAt(i); - if (settingsNode.getCurrent().equals(child.getUserObject())) { - uiNode = child; - break; - } - } - if (uiNode == null) { - uiNode = new DefaultMutableTreeNode(settingsNode.getCurrent()); - uiParentNode.add(uiNode); - result++; - } - for (HierarchicalArrangementSettingsNode childSettingsNode : settingsNode.getChildren()) { - result += map(uiNode, childSettingsNode); - } - return result; - } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImpl.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImpl.java index d78756c128b7..45a0bfdb2c71 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImpl.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingModelImpl.java @@ -15,40 +15,76 @@ */ package com.intellij.application.options.codeStyle.arrangement; -import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsAtomNode; -import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsCompositeNode; -import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNode; -import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNodeVisitor; -import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsAware; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.codeStyle.arrangement.ArrangementUtil; +import com.intellij.psi.codeStyle.arrangement.model.*; +import com.intellij.psi.codeStyle.arrangement.settings.ArrangementSettingsGrouper; import com.intellij.util.containers.hash.HashSet; +import gnu.trove.TIntObjectHashMap; import org.jetbrains.annotations.NotNull; +import javax.swing.tree.DefaultMutableTreeNode; +import java.util.Arrays; import java.util.Set; /** + * Not thread-safe. + * * @author Denis Zhdanov * @since 8/15/12 2:40 PM */ public class ArrangementRuleEditingModelImpl implements ArrangementRuleEditingModel { - @NotNull private final Set myConditions = new HashSet(); + @NotNull private static final MyConditionsBuilder CONDITIONS_BUILDER = new MyConditionsBuilder(); + + @NotNull private final Set myListeners = new HashSet(); + @NotNull private final Set myConditions = new HashSet(); + + @NotNull private final TIntObjectHashMap myRowMappings; + @NotNull private final ArrangementSettingsGrouper myGrouper; + + @NotNull private DefaultMutableTreeNode myTopMost; + @NotNull private DefaultMutableTreeNode myBottomMost; @NotNull private ArrangementSettingsNode mySettingsNode; + private int myRow; - public ArrangementRuleEditingModelImpl(@NotNull ArrangementSettingsNode node) { + /** + * Creates new ArrangementRuleEditingModelImpl object. + * + * @param node backing settings node + * @param topMost there is a possible case that a single settings node is shown in more than one visual line + * ({@link HierarchicalArrangementSettingsNode}). This argument is the top-most UI node used for the + * settings node representation + * @param bottomMost bottom-most UI node used for the given settings node representation + * @param grouper strategy that encapsulates information on how settings node should be displayed + * @param mappings {@code 'row -> model'} mappings + * @param row row number for which current model is registered at the given model mappings + */ + public ArrangementRuleEditingModelImpl(@NotNull ArrangementSettingsNode node, + @NotNull DefaultMutableTreeNode topMost, + @NotNull DefaultMutableTreeNode bottomMost, + @NotNull ArrangementSettingsGrouper grouper, + @NotNull TIntObjectHashMap mappings, + int row) + { mySettingsNode = node; - node.invite(new ArrangementSettingsNodeVisitor() { - @Override - public void visit(@NotNull ArrangementSettingsAtomNode node) { - myConditions.add(node.getValue()); - } + myTopMost = topMost; + myBottomMost = bottomMost; + myGrouper = grouper; + myRowMappings = mappings; + myRow = row; + extractConditions(node); + } - @Override - public void visit(@NotNull ArrangementSettingsCompositeNode node) { - for (ArrangementSettingsNode operand : node.getOperands()) { - operand.invite(this); - } - } - }); + private void extractConditions(@NotNull ArrangementSettingsNode node) { + myConditions.clear(); + CONDITIONS_BUILDER.conditions = myConditions; + try { + node.invite(CONDITIONS_BUILDER); + } + finally { + CONDITIONS_BUILDER.conditions = null; + } } @NotNull @@ -64,11 +100,74 @@ public class ArrangementRuleEditingModelImpl implements ArrangementRuleEditingMo @Override public void addAndCondition(@NotNull ArrangementSettingsAtomNode node) { - // TODO den implement + doAddAndCondition(node); + extractConditions(mySettingsNode); + notifyListeners(); } - + + private void doAddAndCondition(@NotNull ArrangementSettingsAtomNode node) { + ArrangementSettingsNode newNode = ArrangementUtil.and(mySettingsNode.clone(), node); + HierarchicalArrangementSettingsNode grouped = myGrouper.group(newNode); + int newDepth = ArrangementConfigUtil.getDepth(grouped); + int oldDepth = ArrangementConfigUtil.distance(myTopMost, myBottomMost); + if (newDepth == oldDepth) { + mySettingsNode = newNode; + myBottomMost.setUserObject(ArrangementConfigUtil.getLast(grouped).getCurrent()); + return; + } + + mySettingsNode = newNode; + DefaultMutableTreeNode parent = (DefaultMutableTreeNode)myTopMost.getParent(); + parent.remove(myTopMost); + Pair pair = ArrangementConfigUtil.map(parent, grouped); + myTopMost = (DefaultMutableTreeNode)ArrangementConfigUtil.getLastBefore(pair.first, parent); + myBottomMost = pair.first; + int depthShift = newDepth - oldDepth; + int[] rows = myRowMappings.keys(); + Arrays.sort(rows); + for (int i = rows.length - 1; i >= 0; i--) { + int row = rows[i]; + if (row >= myRow) { + myRowMappings.put(row + depthShift, myRowMappings.get(row)); + myRowMappings.remove(row); + } + else { + break; + } + } + myRow += depthShift; + } + @Override public void removeAndCondition(@NotNull ArrangementSettingsNode node) { // TODO den implement } + + @Override + public void addListener(@NotNull Listener listener) { + myListeners.add(listener); + } + + private void notifyListeners() { + for (Listener listener : myListeners) { + listener.onChanged(myTopMost, myBottomMost); + } + } + + private static class MyConditionsBuilder implements ArrangementSettingsNodeVisitor { + + @NotNull Set conditions; + + @Override + public void visit(@NotNull ArrangementSettingsAtomNode node) { + conditions.add(node.getValue()); + } + + @Override + public void visit(@NotNull ArrangementSettingsCompositeNode node) { + for (ArrangementSettingsNode operand : node.getOperands()) { + operand.invite(this); + } + } + } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingListener.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleSelectionListener.java similarity index 84% rename from platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingListener.java rename to platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleSelectionListener.java index 2993e37eb9f0..e5e4c69742c4 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleEditingListener.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRuleSelectionListener.java @@ -21,9 +21,9 @@ import org.jetbrains.annotations.NotNull; * @author Denis Zhdanov * @since 8/15/12 9:14 AM */ -public interface ArrangementRuleEditingListener { +public interface ArrangementRuleSelectionListener { - void startEditing(@NotNull ArrangementRuleEditingModel settings); + void onSelected(@NotNull ArrangementRuleEditingModel settings); - void stopEditing(); + void selectionRemoved(); } 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 0809a9f95a50..679a654bd28f 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 @@ -46,14 +46,18 @@ import java.util.List; */ public class ArrangementRuleTree { - @NotNull private final List myListeners = new ArrayList(); - @NotNull private final TreeSelectionModel mySelectionModel = new MySelectionModel(); + @NotNull private static final JLabel EMPTY_RENDERER = new JLabel(""); + + @NotNull private final List myListeners = new ArrayList(); + @NotNull private final TreeSelectionModel mySelectionModel = new MySelectionModel(); + @NotNull private final MyModelChangeListener myModelChangeListener = new MyModelChangeListener(); @NotNull private final TIntObjectHashMap myRenderers = new TIntObjectHashMap(); @NotNull private final TIntObjectHashMap myModels = new TIntObjectHashMap(); + @NotNull private final DefaultTreeModel myTreeModel; @NotNull private final Tree myTree; @NotNull private final ArrangementNodeComponentFactory myFactory; @@ -62,8 +66,8 @@ public class ArrangementRuleTree { public ArrangementRuleTree(@NotNull ArrangementSettingsGrouper grouper, @NotNull ArrangementNodeDisplayManager displayManager) { myFactory = new ArrangementNodeComponentFactory(displayManager); DefaultMutableTreeNode root = new DefaultMutableTreeNode(); - DefaultTreeModel treeModel = new DefaultTreeModel(root); - myTree = new Tree(treeModel) { + myTreeModel = new DefaultTreeModel(root); + myTree = new Tree(myTreeModel) { @Override protected void setExpandedState(TreePath path, boolean state) { // Don't allow node collapse @@ -85,7 +89,7 @@ public class ArrangementRuleTree { try { super.processMouseEvent(e); if (mySkipSelectionChange) { - notifyEditingListeners(null); + notifySelectionListeners(null); } } finally { @@ -172,9 +176,16 @@ public class ArrangementRuleTree { for (ArrangementSettingsNode setting : settings) { builder.build(setting, myTree, root, grouper, myModels); } + myModels.forEachValue(new TObjectProcedure() { + @Override + public boolean execute(ArrangementRuleEditingModel model) { + model.addListener(myModelChangeListener); + return true; + } + }); } - public void addEditingListener(@NotNull ArrangementRuleEditingListener listener) { + public void addEditingListener(@NotNull ArrangementRuleSelectionListener listener) { myListeners.add(listener); } @@ -271,17 +282,37 @@ public class ArrangementRuleTree { } } - private void notifyEditingListeners(@Nullable ArrangementRuleEditingModel model) { - for (ArrangementRuleEditingListener listener : myListeners) { + private void notifySelectionListeners(@Nullable ArrangementRuleEditingModel model) { + for (ArrangementRuleSelectionListener listener : myListeners) { if (model == null) { - listener.stopEditing(); + listener.selectionRemoved(); } else { - listener.startEditing(model); + listener.onSelected(model); } } } + private void onModelChange(@NotNull TreeNode topMost, @NotNull TreeNode bottomMost) { + mySkipSelectionChange = true; + try { + for (DefaultMutableTreeNode node = (DefaultMutableTreeNode)bottomMost; node != null; node = (DefaultMutableTreeNode)node.getParent()) { + TreePath path = new TreePath(node.getPath()); + int row = myTree.getRowForPath(path); + myRenderers.remove(row); + myTreeModel.nodeChanged(node); + mySelectionModel.addSelectionPath(path); + getNodeComponentAt(row, (ArrangementSettingsNode)node.getUserObject()).setSelected(true); + if (node == topMost) { + break; + } + } + } + finally { + mySkipSelectionChange = false; + } + } + private class MyCellRenderer implements TreeCellRenderer { @Override public Component getTreeCellRendererComponent(JTree tree, @@ -290,8 +321,10 @@ public class ArrangementRuleTree { boolean expanded, boolean leaf, int row, - boolean hasFocus) - { + boolean hasFocus) { + if (row < 0) { + return EMPTY_RENDERER; + } ArrangementSettingsNode node = (ArrangementSettingsNode)((DefaultMutableTreeNode)value).getUserObject(); return getNodeComponentAt(row, node).getUiComponent(); } @@ -299,12 +332,26 @@ public class ArrangementRuleTree { private class MySelectionModel extends DefaultTreeSelectionModel { + @Override + public void addSelectionPath(TreePath path) { + if (!mySkipSelectionChange) { + super.addSelectionPath(path); + } + } + @Override public void setSelectionPath(TreePath path) { if (!mySkipSelectionChange) { super.setSelectionPath(path); - notifyEditingListeners(getActiveModel()); + notifySelectionListeners(getActiveModel()); } } } + + private class MyModelChangeListener implements ArrangementRuleEditingModel.Listener { + @Override + public void onChanged(@NotNull TreeNode topMost, @NotNull TreeNode bottomMost) { + onModelChange(topMost, bottomMost); + } + } } 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 a29343689220..06b964fd7711 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 @@ -79,9 +79,9 @@ public abstract class ArrangementSettingsPanel extends CodeStyleAbstractPanel { } } }); - ruleTree.addEditingListener(new ArrangementRuleEditingListener() { + ruleTree.addEditingListener(new ArrangementRuleSelectionListener() { @Override - public void startEditing(@NotNull ArrangementRuleEditingModel model) { + public void onSelected(@NotNull ArrangementRuleEditingModel model) { ruleEditor.updateState(model); resetEditor.set(Boolean.FALSE); try { @@ -93,7 +93,7 @@ public abstract class ArrangementSettingsPanel extends CodeStyleAbstractPanel { } @Override - public void stopEditing() { + public void selectionRemoved() { editorPane.setCollapsed(true); } });