support selected icons in our list-based popup menus with new checkmarks

This commit is contained in:
Sergey Malenkov
2017-12-20 22:42:41 +03:00
parent c8810774d1
commit 1ed6dda749
10 changed files with 78 additions and 169 deletions
@@ -43,7 +43,7 @@ public abstract class BranchActionGroup extends ActionGroup implements DumbAware
myIcon = new LayeredIcon(favorite, notFavorite);
myHoveredIcon = new LayeredIcon(favoriteOnHover, notFavoriteOnHover);
getTemplatePresentation().setIcon(myIcon);
getTemplatePresentation().setHoveredIcon(myHoveredIcon);
getTemplatePresentation().setSelectedIcon(myHoveredIcon);
updateIcons();
}
@@ -71,7 +71,6 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup {
preselectActionCondition, true);
myProject = project;
DataManager.registerDataProvider(getList(), dataId -> POPUP_MODEL.is(dataId) ? getListModel() : null);
installOnHoverIconsSupport(getListElementRenderer());
myKey = dimensionKey;
if (myKey != null) {
Dimension storedSize = WindowStateService.getInstance(myProject).getSizeFor(myProject, myKey);
@@ -126,7 +125,6 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup {
// don't store children popup userSize;
myKey = null;
DataManager.registerDataProvider(getList(), dataId -> POPUP_MODEL.is(dataId) ? getListModel() : null);
installOnHoverIconsSupport(getListElementRenderer());
}
private void trackDimensions(@Nullable String dimensionKey) {
@@ -364,10 +362,7 @@ public class BranchActionGroupPopup extends FlatSpeedSearchPopup {
super.customizeComponent(list, value, isSelected);
myTextLabel.setIcon(null);
myTextLabel.setDisabledIcon(null);
if (value instanceof PopupFactoryImpl.ActionItem) {
((PopupFactoryImpl.ActionItem)value).setIconHovered(isSelected);
}
myIconLabel.setIcon(myDescriptor.getIconFor(value));
myIconLabel.setIcon(isSelected ? myDescriptor.getSelectedIconFor(value) : myDescriptor.getIconFor(value));
PopupElementWithAdditionalInfo additionalInfoAction = getSpecificAction(value, PopupElementWithAdditionalInfo.class);
updateInfoComponent(myInfoLabel, additionalInfoAction != null ? additionalInfoAction.getInfoText() : null, isSelected);
}
@@ -312,6 +312,7 @@ public final class Presentation implements Cloneable {
setText(presentation.getTextWithMnemonic(), presentation.myDisplayedMnemonicIndex > -1);
setDescription(presentation.getDescription());
setIcon(presentation.getIcon());
setSelectedIcon(presentation.getSelectedIcon());
setDisabledIcon(presentation.getDisabledIcon());
setHoveredIcon(presentation.getHoveredIcon());
setVisible(presentation.isVisible());
@@ -32,6 +32,10 @@ public interface ListItemDescriptor<T> {
@Nullable
Icon getIconFor(T value);
default Icon getSelectedIconFor(T value) {
return getIconFor(value);
}
boolean hasSeparatorAboveOf(T value);
@Nullable
@@ -55,6 +55,10 @@ public interface ListPopupStep<T> extends PopupStep<T> {
@Nullable
Icon getIconFor(T aValue);
default Icon getSelectedIconFor(T value) {
return getIconFor(value);
}
/**
* Returns the text to display for the specified list item.
*
@@ -19,6 +19,7 @@ import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.ui.laf.DarculaMetalTheme;
import com.intellij.ide.ui.laf.IdeaLaf;
import com.intellij.ide.ui.laf.LafManagerImpl;
import com.intellij.ide.ui.laf.intellij.MacIntelliJIconCache;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
@@ -575,4 +576,19 @@ public class DarculaLaf extends BasicLookAndFeel {
public static Icon loadIcon(String iconName) {
return IconLoader.findIcon("/com/intellij/ide/ui/laf/darcula/icons/" + iconName, DarculaLaf.class, true);
}
public static Icon getCheckmarkIcon(boolean darcula) {
if (darcula) return loadIcon("checkmark.png");
return MacIntelliJIconCache.getIcon("checkmark", false, false, true);
}
public static Icon getCheckmarkSelectedIcon(boolean darcula) {
if (darcula) return loadIcon("checkmarkSelected.png");
return MacIntelliJIconCache.getIcon("checkmark", true, false, true);
}
public static Icon getCheckmarkDisabledIcon(boolean darcula) {
if (darcula) return loadIcon("checkmarkDisabled.png");
return MacIntelliJIconCache.getIcon("checkmark", false, false, false);
}
}
@@ -4,7 +4,6 @@ package com.intellij.openapi.actionSystem.impl;
import com.intellij.featureStatistics.FeatureUsageTracker;
import com.intellij.ide.ui.UISettings;
import com.intellij.ide.ui.laf.darcula.DarculaLaf;
import com.intellij.ide.ui.laf.intellij.MacIntelliJIconCache;
import com.intellij.internal.statistic.customUsageCollectors.actions.MainMenuCollector;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
@@ -221,9 +220,9 @@ public class ActionMenuItem extends JBCheckBoxMenuItem {
else if (!(getUI() instanceof GtkMenuItemUI)) {
if (myToggled) {
boolean darcula = UIUtil.isUnderDarcula();
setIcon(getIcon(darcula));
setSelectedIcon(getSelectedIcon(darcula));
setDisabledIcon(getDisabledIcon(darcula));
setIcon(DarculaLaf.getCheckmarkIcon(darcula));
setSelectedIcon(DarculaLaf.getCheckmarkSelectedIcon(darcula));
setDisabledIcon(DarculaLaf.getCheckmarkDisabledIcon(darcula));
}
else {
setIcon(EmptyIcon.ICON_16);
@@ -247,21 +246,6 @@ public class ActionMenuItem extends JBCheckBoxMenuItem {
}
}
private static Icon getIcon(boolean darcula) {
if (darcula) return DarculaLaf.loadIcon("checkmark.png");
return MacIntelliJIconCache.getIcon("checkmark", false, false, true);
}
private static Icon getSelectedIcon(boolean darcula) {
if (darcula) return DarculaLaf.loadIcon("checkmarkSelected.png");
return MacIntelliJIconCache.getIcon("checkmark", true, false, true);
}
private static Icon getDisabledIcon(boolean darcula) {
if (darcula) return DarculaLaf.loadIcon("checkmarkDisabled.png");
return MacIntelliJIconCache.getIcon("checkmark", false, false, false);
}
@Override
public void setIcon(Icon icon) {
if (SystemInfo.isMacSystemMenu && ActionPlaces.MAIN_MENU.equals(myPlace)) {
@@ -20,6 +20,7 @@ import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.ide.ui.laf.darcula.DarculaLaf;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
@@ -47,16 +48,16 @@ import com.intellij.openapi.wm.impl.IdeFrameImpl;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.FocusTrackback;
import com.intellij.ui.HintHint;
import com.intellij.ui.ScrollPaneFactory;
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.ui.EmptyIcon;
import com.intellij.util.ui.StatusText;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
@@ -228,7 +229,6 @@ 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,
@@ -334,8 +334,6 @@ public class PopupFactoryImpl extends JBPopupFactory {
if (myDisposeCallback != null) {
myDisposeCallback.run();
}
getList().removeMouseMotionListener(myIconsHoverListener);
getList().removeListSelectionListener(myIconsHoverListener);
ActionMenu.showDescriptionInStatusBar(true, myComponent, null);
super.dispose();
}
@@ -380,19 +378,6 @@ 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> T getActionByClass(@Nullable Object value, @NotNull ActionPopupStep actionPopupStep, @NotNull Class<T> actionClass) {
ActionItem item = value instanceof ActionItem ? (ActionItem)value : null;
@@ -400,42 +385,6 @@ 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.isIconAt(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
@@ -645,8 +594,7 @@ public class PopupFactoryImpl extends JBPopupFactory {
int[] selectionRows = tree.getSelectionRows();
if (selectionRows != null) {
Arrays.sort(selectionRows);
for (int i = 0; i < selectionRows.length; i++) {
int row = selectionRows[i];
for (int row : selectionRows) {
Rectangle rowBounds = tree.getRowBounds(row);
if (visibleRect.contains(rowBounds)) {
popupMenuPoint = new Point(rowBounds.x + 2, rowBounds.y + rowBounds.height - 1);
@@ -659,8 +607,7 @@ public class PopupFactoryImpl extends JBPopupFactory {
int bestRow = -1;
Point rowCenter;
double distance;
for (int i = 0; i < selectionRows.length; i++) {
int row = selectionRows[i];
for (int row : selectionRows) {
Rectangle rowBounds = tree.getRowBounds(row);
rowCenter = new Point(rowBounds.x + rowBounds.width / 2, rowBounds.y + rowBounds.height / 2);
distance = visibleCenter.distance(rowCenter);
@@ -745,7 +692,8 @@ public class PopupFactoryImpl extends JBPopupFactory {
private final AnAction myAction;
private String myText;
private final boolean myIsEnabled;
@Nullable private ActionStepBuilder.IconWrapper myIcon;
private final Icon myIcon;
private final Icon mySelectedIcon;
private final boolean myPrependWithSeparator;
private final String mySeparatorText;
private final String myDescription;
@@ -754,36 +702,28 @@ public class PopupFactoryImpl extends JBPopupFactory {
@NotNull String text,
@Nullable String description,
boolean enabled,
@Nullable ActionStepBuilder.IconWrapper icon,
@Nullable Icon icon,
@Nullable Icon selectedIcon,
final boolean prependWithSeparator,
String separatorText) {
myAction = action;
myText = text;
myIsEnabled = enabled;
myIcon = icon;
mySelectedIcon = selectedIcon;
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();
}
else if (evt.getPropertyName() == PROP_TEXT) {
if (evt.getPropertyName() == PROP_TEXT) {
myText = myAction.getTemplatePresentation().getText();
}
}
});
}
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
public AnAction getAction() {
return myAction;
@@ -795,8 +735,8 @@ public class PopupFactoryImpl extends JBPopupFactory {
}
@Nullable
public ActionStepBuilder.IconWrapper getIcon() {
return myIcon;
public Icon getIcon(boolean selected) {
return selected && mySelectedIcon != null ? mySelectedIcon : myIcon;
}
public boolean isPrependWithSeparator() {
@@ -823,12 +763,6 @@ 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<ActionItem>, MnemonicNavigationFilter<ActionItem>, SpeedSearchFilter<ActionItem> {
@@ -895,7 +829,12 @@ public class PopupFactoryImpl extends JBPopupFactory {
@Override
public Icon getIconFor(final ActionItem aValue) {
return aValue.getIcon();
return aValue.getIcon(false);
}
@Override
public Icon getSelectedIconFor(ActionItem value) {
return value.getIcon(true);
}
@Override
@@ -1038,7 +977,7 @@ public class PopupFactoryImpl extends JBPopupFactory {
private boolean myPrependWithSeparator;
private String mySeparatorText;
private final boolean myHonorActionMnemonics;
private IconWrapper myEmptyIcon;
private Icon myEmptyIcon;
private int myMaxIconWidth = -1;
private int myMaxIconHeight = -1;
@NotNull private String myActionPlace;
@@ -1069,12 +1008,12 @@ public class PopupFactoryImpl extends JBPopupFactory {
public void buildGroup(@NotNull ActionGroup actionGroup) {
calcMaxIconSize(actionGroup);
myEmptyIcon = myMaxIconHeight != -1 && myMaxIconWidth != -1 ? createWrapper(null) : null;
myEmptyIcon = myMaxIconHeight != -1 && myMaxIconWidth != -1 ? EmptyIcon.create(myMaxIconWidth, myMaxIconHeight) : null;
appendActionsFromGroup(actionGroup);
if (myListModel.isEmpty()) {
myListModel.add(new ActionItem(Utils.EMPTY_MENU_FILLER, Utils.NOTHING_HERE, null, false, null, false, null));
myListModel.add(new ActionItem(Utils.EMPTY_MENU_FILLER, Utils.NOTHING_HERE, null, false, null, null, false, null));
}
}
@@ -1136,7 +1075,8 @@ public class PopupFactoryImpl extends JBPopupFactory {
AnActionEvent event = createActionEvent(action);
ActionUtil.performDumbAwareUpdate(LaterInvocator.isInModalContext(), action, event, true);
if ((myShowDisabled || presentation.isEnabled()) && presentation.isVisible()) {
boolean enabled = presentation.isEnabled();
if ((myShowDisabled || enabled) && presentation.isVisible()) {
String text = presentation.getText();
if (myShowNumbers) {
if (myCurrentNumber < 9) {
@@ -1151,76 +1091,38 @@ public class PopupFactoryImpl extends JBPopupFactory {
myCurrentNumber++;
}
else if (myHonorActionMnemonics) {
text = Presentation.restoreTextWithMnemonic(text, action.getTemplatePresentation().getMnemonic());
text = restoreTextWithMnemonic(text, action.getTemplatePresentation().getMnemonic());
}
Icon icon = presentation.isEnabled() ? presentation.getIcon() : IconLoader.getDisabledIcon(presentation.getIcon());
IconWrapper iconWrapper;
if (icon == null && presentation.getHoveredIcon() == null) {
Icon icon = presentation.getIcon();
Icon selectedIcon = presentation.getSelectedIcon();
Icon disabledIcon = presentation.getDisabledIcon();
if (icon == null && selectedIcon == null) {
@NonNls final String actionId = ActionManager.getInstance().getId(action);
if (actionId != null && actionId.startsWith("QuickList.")) {
iconWrapper = createWrapper(AllIcons.Actions.QuickList);
icon = AllIcons.Actions.QuickList;
}
else if (action instanceof Toggleable) {
boolean toggled = Boolean.TRUE.equals(presentation.getClientProperty(Toggleable.SELECTED_PROPERTY));
iconWrapper = toggled ? createWrapper(PlatformIcons.CHECK_ICON) : myEmptyIcon;
}
else {
iconWrapper = myEmptyIcon;
else if (action instanceof Toggleable && Boolean.TRUE.equals(presentation.getClientProperty(Toggleable.SELECTED_PROPERTY))) {
boolean darcula = UIUtil.isUnderDarcula();
icon = DarculaLaf.getCheckmarkIcon(darcula);
selectedIcon = DarculaLaf.getCheckmarkSelectedIcon(darcula);
disabledIcon = DarculaLaf.getCheckmarkDisabledIcon(darcula);
}
}
else {
iconWrapper = new IconWrapper(icon, presentation.getHoveredIcon(), myMaxIconWidth, myMaxIconHeight);
if (!enabled) {
icon = disabledIcon != null ? disabledIcon : IconLoader.getDisabledIcon(icon);
selectedIcon = disabledIcon != null ? disabledIcon : IconLoader.getDisabledIcon(selectedIcon);
}
if (icon == null) icon = selectedIcon != null ? selectedIcon : myEmptyIcon;
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(), iconWrapper,
prependSeparator, mySeparatorText));
new ActionItem(action, text, (String)presentation.getClientProperty(JComponent.TOOL_TIP_TEXT_KEY),
enabled, icon, selectedIcon, prependSeparator, mySeparatorText));
myPrependWithSeparator = false;
mySeparatorText = null;
}
}
/**
* Adjusts icon size to maximum, so that icons with different sizes were aligned correctly.
*/
public static class IconWrapper extends IconUtil.IconSizeWrapper {
@Nullable private Icon myIcon;
@Nullable private Icon myHoverIcon;
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) {
paintIcon(myHoverIcon != null && isHovered ? myHoverIcon : myIcon, c, g, x, y);
}
public boolean isHovered() {
return isHovered;
}
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);
}
}
@NotNull
@@ -1267,11 +1169,9 @@ public class PopupFactoryImpl extends JBPopupFactory {
final JPanel content = new NonOpaquePanel(new BorderLayout((int)(label.getIconTextGap() * 1.5), (int)(label.getIconTextGap() * 1.5)));
final NonOpaquePanel textWrapper = new NonOpaquePanel(new GridBagLayout());
JScrollPane scrolledText = new JScrollPane(text);
JScrollPane scrolledText = ScrollPaneFactory.createScrollPane(text, true);
scrolledText.setBackground(fillColor);
scrolledText.getViewport().setBackground(fillColor);
scrolledText.getViewport().setBorder(null);
scrolledText.setBorder(null);
textWrapper.add(scrolledText);
content.add(textWrapper, BorderLayout.CENTER);
@@ -44,7 +44,7 @@ public class GroupedItemsListRenderer<E> extends GroupedElementsRenderer.List im
boolean hasSeparator = myDescriptor.hasSeparatorAboveOf(value);
if (index == 0 && StringUtil.isEmptyOrSpaces(caption)) hasSeparator = false;
Icon icon = myDescriptor.getIconFor(value);
Icon icon = isSelected ? myDescriptor.getSelectedIconFor(value) : myDescriptor.getIconFor(value);
final JComponent result = configureComponent(myDescriptor.getTextFor(value), myDescriptor.getTooltipFor(value),
icon, icon, isSelected, hasSeparator,
caption, -1);
@@ -49,6 +49,11 @@ public class PopupListElementRenderer<E> extends GroupedItemsListRenderer<E> {
return aPopup.getListStep().getIconFor(value);
}
@Override
public Icon getSelectedIconFor(E value) {
return aPopup.getListStep().getSelectedIconFor(value);
}
@Override
public boolean hasSeparatorAboveOf(E value) {
return aPopup.getListModel().isSeparatorAboveOf(value);