From f6b58939b8b470cbbdbcd557092465f2c05b58cf Mon Sep 17 00:00:00 2001 From: Nadya Zabrodina Date: Thu, 8 Dec 2016 19:34:07 +0300 Subject: [PATCH] [ui]: reuse Icon Wrapper from IconUtil and support onHover icon * draw an appropriate icon depending on the MouseLocation; * add an interface to detect if mouse position hovers its icon: it can be done properly only with related renderer component; * update Icons if action presentation icon changed; * use hoverFlag instead of manually detecting in paintIcon; * repaint listCell if needed; --- .../intellij/ui/popup/PopupFactoryImpl.java | 143 ++++++++++++++---- .../ui/popup/list/IconListPopupRenderer.java | 25 +++ 2 files changed, 142 insertions(+), 26 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/ui/popup/list/IconListPopupRenderer.java diff --git a/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java b/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java index 7b6f8ee69f1a..beb680063934 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java @@ -48,15 +48,16 @@ import com.intellij.ui.FocusTrackback; import com.intellij.ui.HintHint; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.panels.NonOpaquePanel; +import com.intellij.ui.popup.list.IconListPopupRenderer; import com.intellij.ui.popup.list.ListPopupImpl; import com.intellij.ui.popup.mock.MockConfirmation; import com.intellij.ui.popup.tree.TreePopupImpl; +import com.intellij.util.IconUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.PlatformIcons; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.WeakHashMap; -import com.intellij.util.ui.EmptyIcon; import com.intellij.util.ui.StatusText; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NonNls; @@ -69,14 +70,17 @@ import javax.swing.event.HyperlinkListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; -import java.awt.event.ActionEvent; -import java.awt.event.InputEvent; -import java.awt.event.KeyEvent; +import java.awt.event.*; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; +import static com.intellij.openapi.actionSystem.Presentation.PROP_HOVERED_ICON; +import static com.intellij.openapi.actionSystem.Presentation.PROP_ICON; + public class PopupFactoryImpl extends JBPopupFactory { /** @@ -232,6 +236,7 @@ public class PopupFactoryImpl extends JBPopupFactory { private final Runnable myDisposeCallback; private final Component myComponent; private final String myActionPlace; + private IconHoverListener myIconsHoverListener; public ActionGroupPopup(final String title, @NotNull ActionGroup actionGroup, @@ -337,6 +342,8 @@ public class PopupFactoryImpl extends JBPopupFactory { if (myDisposeCallback != null) { myDisposeCallback.run(); } + getList().removeMouseMotionListener(myIconsHoverListener); + getList().removeListSelectionListener(myIconsHoverListener); ActionMenu.showDescriptionInStatusBar(true, myComponent, null); super.dispose(); } @@ -381,6 +388,19 @@ public class PopupFactoryImpl extends JBPopupFactory { getList().repaint(); } + public void installOnHoverIconsSupport(@NotNull IconListPopupRenderer iconListPopupRenderer) { + //OnHover icons listener should be installed once + assert myIconsHoverListener == null; + myIconsHoverListener = new IconHoverListener(iconListPopupRenderer); + } + + @Override + protected boolean beforeShow() { + getList().addMouseMotionListener(myIconsHoverListener); + getList().addListSelectionListener(myIconsHoverListener); + return super.beforeShow(); + } + @Nullable private static T getActionByClass(@Nullable Object value, @NotNull ActionPopupStep actionPopupStep, @NotNull Class actionClass) { ActionItem item = value instanceof ActionItem ? (ActionItem)value : null; @@ -388,6 +408,42 @@ public class PopupFactoryImpl extends JBPopupFactory { if (!actionPopupStep.isSelectable(item)) return null; return actionClass.isInstance(item.getAction()) ? actionClass.cast(item.getAction()) : null; } + + private class IconHoverListener extends MouseMotionAdapter implements ListSelectionListener { + @NotNull private IconListPopupRenderer myRenderer; + + public IconHoverListener(@NotNull IconListPopupRenderer renderer) { + myRenderer = renderer; + } + + @Override + public void mouseMoved(MouseEvent e) { + Point point = e.getPoint(); + int index = getList().locationToIndex(point); + Rectangle bounds = getList().getCellBounds(index, index); + Object selectedValue = getList().getSelectedValue(); + if (selectedValue instanceof ActionItem) { + ((ActionItem)selectedValue).setIconHovered(myRenderer.iconContains(point)); + } + if (bounds != null) { + getList().repaint(bounds); + } + } + + @Override + public void valueChanged(ListSelectionEvent e) { + if (!e.getValueIsAdjusting()) { + int selected = getSelectedIndex(); + int unselected = e.getFirstIndex() == selected ? e.getLastIndex() : e.getFirstIndex(); + Object elementAt = getList().getModel().getElementAt(unselected); + if (elementAt instanceof ActionItem) { + ActionItem actionItem = (ActionItem)elementAt; + actionItem.setIconHovered(false); + getList().repaint(); + } + } + } + } } @NotNull @@ -697,7 +753,7 @@ public class PopupFactoryImpl extends JBPopupFactory { private final AnAction myAction; private final String myText; private final boolean myIsEnabled; - private final Icon myIcon; + @Nullable private ActionStepBuilder.IconWrapper myIcon; private final boolean myPrependWithSeparator; private final String mySeparatorText; private final String myDescription; @@ -706,7 +762,7 @@ public class PopupFactoryImpl extends JBPopupFactory { @NotNull String text, @Nullable String description, boolean enabled, - Icon icon, + @Nullable ActionStepBuilder.IconWrapper icon, final boolean prependWithSeparator, String separatorText) { myAction = action; @@ -716,6 +772,21 @@ public class PopupFactoryImpl extends JBPopupFactory { myPrependWithSeparator = prependWithSeparator; mySeparatorText = separatorText; myDescription = description; + myAction.getTemplatePresentation().addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName() == PROP_ICON || evt.getPropertyName() == PROP_HOVERED_ICON) { + updateIcons(); + } + } + }); + } + + private void updateIcons() { + // we can't set icons if it hasn't existed before, because alignment will be destroyed; use IconWrapper with null icon instead of null + if (myIcon == null) return; + Presentation presentation = myAction.getTemplatePresentation(); + myIcon.setIcons(presentation.getIcon(), presentation.getHoveredIcon()); } @NotNull @@ -728,7 +799,8 @@ public class PopupFactoryImpl extends JBPopupFactory { return myText; } - public Icon getIcon() { + @Nullable + public ActionStepBuilder.IconWrapper getIcon() { return myIcon; } @@ -756,6 +828,12 @@ public class PopupFactoryImpl extends JBPopupFactory { public String toString() { return myText; } + + public void setIconHovered(boolean isHovered) { + if (myIcon != null) { + myIcon.setHovered(isHovered); + } + } } private static class ActionPopupStep implements ListPopupStepEx, MnemonicNavigationFilter, SpeedSearchFilter { @@ -966,7 +1044,7 @@ public class PopupFactoryImpl extends JBPopupFactory { private boolean myPrependWithSeparator; private String mySeparatorText; private final boolean myHonorActionMnemonics; - private Icon myEmptyIcon; + private IconWrapper myEmptyIcon; private int myMaxIconWidth = -1; private int myMaxIconHeight = -1; @NotNull private String myActionPlace; @@ -998,7 +1076,7 @@ public class PopupFactoryImpl extends JBPopupFactory { public void buildGroup(@NotNull ActionGroup actionGroup) { calcMaxIconSize(actionGroup); - myEmptyIcon = myMaxIconHeight != -1 && myMaxIconWidth != -1 ? EmptyIcon.create(myMaxIconWidth, myMaxIconHeight) : null; + myEmptyIcon = myMaxIconHeight != -1 && myMaxIconWidth != -1 ? createWrapper(null) : null; appendActionsFromGroup(actionGroup); @@ -1094,26 +1172,27 @@ public class PopupFactoryImpl extends JBPopupFactory { } Icon icon = presentation.isEnabled() ? presentation.getIcon() : IconLoader.getDisabledIcon(presentation.getIcon()); - if (icon == null) { + IconWrapper iconWrapper; + if (icon == null && presentation.getHoveredIcon() == null) { @NonNls final String actionId = ActionManager.getInstance().getId(action); if (actionId != null && actionId.startsWith("QuickList.")) { - icon = AllIcons.Actions.QuickList; + iconWrapper = createWrapper(AllIcons.Actions.QuickList); } else if (action instanceof Toggleable) { boolean toggled = Boolean.TRUE.equals(presentation.getClientProperty(Toggleable.SELECTED_PROPERTY)); - icon = toggled? new IconWrapper(PlatformIcons.CHECK_ICON) : myEmptyIcon; + iconWrapper = toggled ? createWrapper(PlatformIcons.CHECK_ICON) : myEmptyIcon; } else { - icon = myEmptyIcon; + iconWrapper = myEmptyIcon; } } else { - icon = new IconWrapper(icon); + iconWrapper = new IconWrapper(icon, presentation.getHoveredIcon(), myMaxIconWidth, myMaxIconHeight); } boolean prependSeparator = (!myListModel.isEmpty() || mySeparatorText != null) && myPrependWithSeparator; assert text != null : action + " has no presentation"; myListModel.add( - new ActionItem(action, text, (String)presentation.getClientProperty(JComponent.TOOL_TIP_TEXT_KEY), presentation.isEnabled(), icon, + new ActionItem(action, text, (String)presentation.getClientProperty(JComponent.TOOL_TIP_TEXT_KEY), presentation.isEnabled(), iconWrapper, prependSeparator, mySeparatorText)); myPrependWithSeparator = false; mySeparatorText = null; @@ -1123,28 +1202,40 @@ public class PopupFactoryImpl extends JBPopupFactory { /** * Adjusts icon size to maximum, so that icons with different sizes were aligned correctly. */ - private class IconWrapper implements Icon { + public static class IconWrapper extends IconUtil.IconSizeWrapper { - private Icon myIcon; + @Nullable private Icon myIcon; + @Nullable private Icon myHoverIcon; - IconWrapper(Icon icon) { - myIcon = icon; + private boolean isHovered; + + public IconWrapper(@Nullable Icon icon, @Nullable Icon hoverIcon, int width, int height) { + super(null, width, height); + setIcons(icon, hoverIcon); } @Override public void paintIcon(Component c, Graphics g, int x, int y) { - myIcon.paintIcon(c, g, x, y); + paintIcon(myHoverIcon != null && isHovered ? myHoverIcon : myIcon, c, g, x, y); } - @Override - public int getIconWidth() { - return myMaxIconWidth; + public boolean isHovered() { + return isHovered; } - @Override - public int getIconHeight() { - return myMaxIconHeight; + public void setHovered(boolean hovered) { + isHovered = hovered; } + + public void setIcons(@Nullable Icon icon, @Nullable Icon hoveredIcon) { + myIcon = icon; + myHoverIcon = hoveredIcon; + } + } + + @NotNull + public IconWrapper createWrapper(@Nullable Icon icon) { + return new IconWrapper(icon, null, myMaxIconWidth, myMaxIconHeight); } private Presentation getPresentation(@NotNull AnAction action) { diff --git a/platform/platform-impl/src/com/intellij/ui/popup/list/IconListPopupRenderer.java b/platform/platform-impl/src/com/intellij/ui/popup/list/IconListPopupRenderer.java new file mode 100644 index 000000000000..64672d8f6e48 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/popup/list/IconListPopupRenderer.java @@ -0,0 +1,25 @@ +/* + * Copyright 2000-2016 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.ui.popup.list; + +import org.jetbrains.annotations.NotNull; + +import java.awt.*; + +public interface IconListPopupRenderer { + + boolean iconContains(@NotNull Point point); +}