From 8b60941b288d92a7eddf65494a91e84908ed7589 Mon Sep 17 00:00:00 2001 From: "Denis.Zhdanov" Date: Thu, 23 Aug 2012 13:20:49 +0400 Subject: [PATCH] IDEA-19061 Integrate the Rearranger-plugin into core-IDE Showing 'close' icon for the condition nodes --- .../ArrangementAndNodeComponent.java | 13 +++ .../ArrangementAtomNodeComponent.java | 82 +++++++++++++++++-- .../arrangement/ArrangementConstants.java | 1 + .../ArrangementMatcherRuleEditor.java | 2 +- .../arrangement/ArrangementNodeComponent.java | 14 ++++ .../ArrangementNodeComponentFactory.java | 12 ++- .../ArrangementRemoveConditionAction.java | 43 ++++++++++ .../arrangement/ArrangementRuleTree.java | 42 ++++++++-- 8 files changed, 195 insertions(+), 14 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRemoveConditionAction.java 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 a2225453c11f..8cd3e0d82492 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 @@ -26,6 +26,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -194,6 +195,18 @@ public class ArrangementAndNodeComponent extends JPanel implements ArrangementNo } } + @Override + public Rectangle handleMouseMove(@NotNull MouseEvent event) { + Point location = event.getLocationOnScreen(); + for (ArrangementNodeComponent component : myComponents) { + Rectangle bounds = component.getScreenBounds(); + if (bounds != null && bounds.contains(location)) { + return component.handleMouseMove(event); + } + } + return null; + } + @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/ArrangementAtomNodeComponent.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementAtomNodeComponent.java index 1e2b1cf43702..6a53e0f5ec69 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementAtomNodeComponent.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementAtomNodeComponent.java @@ -15,9 +15,11 @@ */ package com.intellij.application.options.codeStyle.arrangement; +import com.intellij.openapi.actionSystem.impl.ActionButton; import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsAtomNode; import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.awt.RelativePoint; +import com.intellij.util.Consumer; import com.intellij.util.ui.GridBag; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -25,9 +27,12 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.event.MouseEvent; /** - * // TODO den add doc + * {@link ArrangementNodeComponent} for {@link ArrangementSettingsAtomNode} representation. + *

+ * Not thread-safe. * * @author Denis Zhdanov * @since 8/8/12 10:06 AM @@ -68,7 +73,8 @@ public class ArrangementAtomNodeComponent implements ArrangementNodeComponent { } }; - @NotNull private final ArrangementSettingsAtomNode mySettingsNode; + @NotNull private final ArrangementSettingsAtomNode mySettingsNode; + @Nullable private final ActionButton myCloseButton; @Nullable private Dimension mySize; @Nullable private Rectangle myScreenBounds; @@ -76,18 +82,60 @@ public class ArrangementAtomNodeComponent implements ArrangementNodeComponent { private boolean myEnabled = true; private boolean mySelected; private boolean myInverted; + private boolean myCloseButtonHovered; - public ArrangementAtomNodeComponent(@NotNull ArrangementNodeDisplayManager manager, @NotNull ArrangementSettingsAtomNode node) { + public ArrangementAtomNodeComponent(@NotNull ArrangementNodeDisplayManager manager, + @NotNull ArrangementSettingsAtomNode node, + @Nullable Consumer closeCallback) + { mySettingsNode = node; myLabel.setHorizontalAlignment(SwingConstants.CENTER); myLabel.setText(manager.getDisplayValue(node)); - mySize = new Dimension(manager.getMaxWidth(node.getType()), myLabel.getPreferredSize().height); + + int width = manager.getMaxWidth(node.getType()); + int height = myLabel.getPreferredSize().height; + final ArrangementRemoveConditionAction action; + if (closeCallback == null) { + myCloseButton = null; + action = null; + } + else { + action = new ArrangementRemoveConditionAction(); + + Icon buttonIcon = action.getTemplatePresentation().getIcon(); + Dimension buttonSize = new Dimension(buttonIcon.getIconWidth(), buttonIcon.getIconHeight()); + myCloseButton = new ActionButton(action, action.getTemplatePresentation().clone(), ArrangementConstants.RULE_TREE_PLACE, buttonSize) { + @Override + protected Icon getIcon() { + return myCloseButtonHovered ? action.getTemplatePresentation().getHoveredIcon() : action.getTemplatePresentation().getIcon(); + } + }; + Dimension preferredButtonSize = myCloseButton.getPreferredSize(); + width += preferredButtonSize.width; + height = Math.max(height, preferredButtonSize.height); + } + + mySize = new Dimension(width, height); GridBagConstraints constraints = new GridBag().anchor(GridBagConstraints.CENTER).insets(0, 0, 0, 0); - JPanel labelPanel = new JPanel(new GridBagLayout()); + JPanel labelPanel = new JPanel(new GridBagLayout()) { + @Override + public void paint(Graphics g) { + Rectangle buttonBounds = getCloseButtonScreenLocation(); + if (buttonBounds != null && action != null) { + Point mouseScreenLocation = MouseInfo.getPointerInfo().getLocation(); + myCloseButtonHovered = buttonBounds.contains(mouseScreenLocation); + } + super.paint(g); + } + }; myLabel.setBackground(Color.red); labelPanel.add(myLabel, constraints); + if (myCloseButton != null) { + labelPanel.add(myCloseButton, new GridBag().anchor(GridBagConstraints.EAST).insets(0, 0, 0, 0)); + } + labelPanel.setBorder(IdeBorderFactory.createEmptyBorder(PADDING)); labelPanel.setOpaque(false); @@ -168,6 +216,30 @@ public class ArrangementAtomNodeComponent implements ArrangementNodeComponent { myInverted = inverted; } + @Nullable + @Override + public Rectangle handleMouseMove(@NotNull MouseEvent event) { + Rectangle buttonBounds = getCloseButtonScreenLocation(); + if (buttonBounds == null) { + return null; + } + boolean mouseOverButton = buttonBounds.contains(event.getLocationOnScreen()); + return (mouseOverButton ^ myCloseButtonHovered) ? buttonBounds : null; + } + + @Nullable + private Rectangle getCloseButtonScreenLocation() { + if (myCloseButton == null || myScreenBounds == null) { + return null; + } + + Rectangle buttonBounds = myCloseButton.getBounds(); + buttonBounds = SwingUtilities.convertRectangle(myCloseButton.getParent(), buttonBounds, myRenderer); + buttonBounds.x += myScreenBounds.x; + buttonBounds.y += myScreenBounds.y; + return buttonBounds; + } + @Override public String toString() { return myLabel.getText(); 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 9540b87b87ac..1f9bd64fe144 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 @@ -25,6 +25,7 @@ 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"; private ArrangementConstants() { } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementMatcherRuleEditor.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementMatcherRuleEditor.java index 2ecdd0f355a0..bb6e2001488b 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementMatcherRuleEditor.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementMatcherRuleEditor.java @@ -80,7 +80,7 @@ public class ArrangementMatcherRuleEditor extends JPanel { JPanel valuesPanel = new MultiRowFlowPanel(FlowLayout.LEFT, 8, 5); for (Object value : manager.sort(values)) { - ArrangementAtomNodeComponent component = new ArrangementAtomNodeComponent(manager, new ArrangementSettingsAtomNode(key, value)); + ArrangementAtomNodeComponent component = new ArrangementAtomNodeComponent(manager, new ArrangementSettingsAtomNode(key, value), null); myComponents.put(value, component); valuesPanel.add(component.getUiComponent()); } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementNodeComponent.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementNodeComponent.java index a28c4bdc837a..a6a9fe5184cc 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementNodeComponent.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementNodeComponent.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; +import java.awt.event.MouseEvent; /** * // TODO den add doc @@ -54,4 +55,17 @@ public interface ArrangementNodeComponent { * @param selected flag that indicates if current component should be drawn as 'selected' */ void setSelected(boolean selected); + + /** + * Instructs current component about mose move event. + *

+ * Primary intention is to allow to react on event like 'on mouse hover' etc. We can't do that by subscribing to the + * mouse events at the {@link #getUiComponent() corresponding UI control} because it's used only as a renderer and is not put + * to the containers hierarchy, hence, doesn't receive mouse events. + * + * @param event target mouse move event + * @return bounds to be repainted (in screen coordinates) if any; null otherwise + */ + @Nullable + Rectangle handleMouseMove(@NotNull MouseEvent event); } 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 8feeb53aaf4c..a5b39bc741b4 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 @@ -17,6 +17,7 @@ package com.intellij.application.options.codeStyle.arrangement; import com.intellij.openapi.util.Ref; import com.intellij.psi.codeStyle.arrangement.model.*; +import com.intellij.util.Consumer; import org.jetbrains.annotations.NotNull; /** @@ -26,9 +27,13 @@ import org.jetbrains.annotations.NotNull; public class ArrangementNodeComponentFactory { @NotNull private final ArrangementNodeDisplayManager myDisplayManager; + private Consumer myRemoveConditionCallback; - public ArrangementNodeComponentFactory(@NotNull ArrangementNodeDisplayManager manager) { + public ArrangementNodeComponentFactory(@NotNull ArrangementNodeDisplayManager manager, + @NotNull Consumer removeConditionCallback) + { myDisplayManager = manager; + myRemoveConditionCallback = removeConditionCallback; } @NotNull @@ -37,13 +42,14 @@ public class ArrangementNodeComponentFactory { node.invite(new ArrangementSettingsNodeVisitor() { @Override public void visit(@NotNull ArrangementSettingsAtomNode node) { - ref.set(new ArrangementAtomNodeComponent(myDisplayManager, node)); + ref.set(new ArrangementAtomNodeComponent(myDisplayManager, node, myRemoveConditionCallback)); } @Override public void visit(@NotNull ArrangementSettingsCompositeNode node) { switch (node.getOperator()) { - case AND: ref.set(new ArrangementAndNodeComponent(node, ArrangementNodeComponentFactory.this, myDisplayManager)); 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/ArrangementRemoveConditionAction.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRemoveConditionAction.java new file mode 100644 index 000000000000..f1f797c2cf03 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementRemoveConditionAction.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; + +import com.intellij.icons.AllIcons; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; + +import javax.swing.*; + +/** + * @author Denis Zhdanov + * @since 8/23/12 11:41 AM + */ +public class ArrangementRemoveConditionAction extends AnAction { + + private static final Icon ICON = AllIcons.Actions.CloseNew; + private static final Icon HOVER_ICON = AllIcons.Actions.CloseNewHovered; + + public ArrangementRemoveConditionAction() { + getTemplatePresentation().setIcon(ICON); + getTemplatePresentation().setHoveredIcon(HOVER_ICON); + } + + @Override + public void actionPerformed(AnActionEvent e) { + // TODO den implement + System.out.println("ArrangementRemoveConditionAction.actionPerformed()"); + } +} 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 29ed01a786ad..bb551517ffcc 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 @@ -24,6 +24,7 @@ import com.intellij.psi.codeStyle.arrangement.model.ArrangementSettingsNode; import com.intellij.psi.codeStyle.arrangement.settings.ArrangementSettingsGrouper; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.Consumer; import gnu.trove.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -67,7 +68,12 @@ public class ArrangementRuleTree { private boolean mySkipSelectionChange; public ArrangementRuleTree(@NotNull ArrangementSettingsGrouper grouper, @NotNull ArrangementNodeDisplayManager displayManager) { - myFactory = new ArrangementNodeComponentFactory(displayManager); + myFactory = new ArrangementNodeComponentFactory(displayManager, new Consumer() { + @Override + public void consume(@NotNull ArrangementSettingsAtomNode node) { + removeConditionFromActiveModel(node); + } + }); myRoot = new ArrangementTreeNode(null); myTreeModel = new DefaultTreeModel(myRoot); myTree = new Tree(myTreeModel) { @@ -117,6 +123,12 @@ public class ArrangementRuleTree { } } }); + myTree.addMouseMotionListener(new MouseAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + onMouseMoved(e); + } + }); myTree.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { @@ -159,7 +171,7 @@ public class ArrangementRuleTree { myTree.setShowsRootHandles(false); myTree.setCellRenderer(new MyCellRenderer()); } - + private void selectPreviousRule() { ArrangementTreeNode currentSelectionBottom = getCurrentSelectionBottom(); @@ -338,6 +350,22 @@ public class ArrangementRuleTree { return result; } + private void removeConditionFromActiveModel(@NotNull ArrangementSettingsAtomNode condition) { + // TODO den implement + System.out.println("Remove condition " + condition); + } + + private void onMouseMoved(@NotNull MouseEvent e) { + ArrangementNodeComponent component = getNodeComponentAt(e.getLocationOnScreen()); + if (component == null) { + return; + } + Rectangle changedScreenRectangle = component.handleMouseMove(e); + if (changedScreenRectangle != null) { + repaintScreenBounds(changedScreenRectangle); + } + } + private void onMouseClicked(@NotNull MouseEvent e) { ArrangementNodeComponent component = getNodeComponentAt(e.getLocationOnScreen()); if (component != null) { @@ -381,12 +409,16 @@ public class ArrangementRuleTree { private void repaintComponent(@NotNull ArrangementNodeComponent component) { Rectangle bounds = component.getScreenBounds(); if (bounds != null) { - Point location = bounds.getLocation(); - SwingUtilities.convertPointFromScreen(location, myTree); - myTree.repaint(location.x, location.y, bounds.width, bounds.height); + repaintScreenBounds(bounds); } } + private void repaintScreenBounds(@NotNull Rectangle bounds) { + Point location = bounds.getLocation(); + SwingUtilities.convertPointFromScreen(location, myTree); + myTree.repaint(location.x, location.y, bounds.width, bounds.height); + } + private void notifySelectionListeners(@Nullable ArrangementRuleEditingModel model) { for (ArrangementRuleSelectionListener listener : myListeners) { if (model == null) {