blob: 2d7a8e3fb2765c18aa3a547feac7a9073ffbb97a [file] [log] [blame]
/*
* 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.ui.popup;
import com.intellij.CommonBundle;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.ide.IdeEventQueue;
import com.intellij.ide.IdeTooltipManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.actionSystem.impl.ActionMenu;
import com.intellij.openapi.actionSystem.impl.Utils;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.VisualPosition;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.ui.popup.*;
import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.EmptyRunnable;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.openapi.wm.ex.WindowManagerEx;
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.awt.RelativePoint;
import com.intellij.ui.components.panels.NonOpaquePanel;
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.PlatformIcons;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.WeakHashMap;
import com.intellij.util.ui.EmptyIcon;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class PopupFactoryImpl extends JBPopupFactory {
/**
* Allows to get an editor position for which a popup with auxiliary information might be shown.
* <p/>
* Primary intention for this key is to hint popup position for the non-caret location.
*/
public static final Key<VisualPosition> ANCHOR_POPUP_POSITION = Key.create("popup.anchor.position");
private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.PopupFactoryImpl");
private final Map<Disposable, List<Balloon>> myStorage = new WeakHashMap<Disposable, List<Balloon>>();
@NotNull
@Override
public ListPopup createConfirmation(String title, final Runnable onYes, int defaultOptionIndex) {
return createConfirmation(title, CommonBundle.getYesButtonText(), CommonBundle.getNoButtonText(), onYes, defaultOptionIndex);
}
@NotNull
@Override
public ListPopup createConfirmation(String title, final String yesText, String noText, final Runnable onYes, int defaultOptionIndex) {
return createConfirmation(title, yesText, noText, onYes, EmptyRunnable.getInstance(), defaultOptionIndex);
}
@NotNull
@Override
public JBPopup createMessage(String text) {
return createListPopup(new BaseListPopupStep<String>(null, new String[]{text}));
}
@Override
public Balloon getParentBalloonFor(@Nullable Component c) {
if (c == null) return null;
Component eachParent = c;
while (eachParent != null) {
if (eachParent instanceof JComponent) {
Object balloon = ((JComponent)eachParent).getClientProperty(Balloon.KEY);
if (balloon instanceof Balloon) {
return (Balloon)balloon;
}
}
eachParent = eachParent.getParent();
}
return null;
}
@NotNull
@Override
public ListPopup createConfirmation(String title,
final String yesText,
String noText,
final Runnable onYes,
final Runnable onNo,
int defaultOptionIndex)
{
final BaseListPopupStep<String> step = new BaseListPopupStep<String>(title, new String[]{yesText, noText}) {
@Override
public PopupStep onChosen(String selectedValue, final boolean finalChoice) {
if (selectedValue.equals(yesText)) {
onYes.run();
}
else {
onNo.run();
}
return FINAL_CHOICE;
}
@Override
public void canceled() {
onNo.run();
}
@Override
public boolean isMnemonicsNavigationEnabled() {
return true;
}
};
step.setDefaultOptionIndex(defaultOptionIndex);
final ApplicationEx app = ApplicationManagerEx.getApplicationEx();
return app == null || !app.isUnitTestMode() ? new ListPopupImpl(step) : new MockConfirmation(step, yesText);
}
private static ListPopup createActionGroupPopup(final String title,
@NotNull ActionGroup actionGroup,
@NotNull DataContext dataContext,
boolean showNumbers,
boolean useAlphaAsNumbers,
boolean showDisabledActions,
boolean honorActionMnemonics,
final Runnable disposeCallback,
final int maxRowCount) {
return createActionGroupPopup(title, actionGroup, dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions, honorActionMnemonics, disposeCallback,
maxRowCount, null, null);
}
public ListPopup createActionGroupPopup(final String title,
final ActionGroup actionGroup,
@NotNull DataContext dataContext,
boolean showNumbers,
boolean showDisabledActions,
boolean honorActionMnemonics,
final Runnable disposeCallback,
final int maxRowCount) {
return createActionGroupPopup(title, actionGroup, dataContext, showNumbers, showDisabledActions, honorActionMnemonics, disposeCallback,
maxRowCount, null);
}
private static ListPopup createActionGroupPopup(final String title,
@NotNull ActionGroup actionGroup,
@NotNull DataContext dataContext,
boolean showNumbers,
boolean useAlphaAsNumbers,
boolean showDisabledActions,
boolean honorActionMnemonics,
final Runnable disposeCallback,
final int maxRowCount,
final Condition<AnAction> preselectActionCondition, @Nullable final String actionPlace) {
return new ActionGroupPopup(title, actionGroup, dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions, honorActionMnemonics,
disposeCallback, maxRowCount, preselectActionCondition, actionPlace);
}
public static class ActionGroupPopup extends ListPopupImpl {
private final Runnable myDisposeCallback;
private final Component myComponent;
public ActionGroupPopup(final String title,
@NotNull ActionGroup actionGroup,
@NotNull DataContext dataContext,
boolean showNumbers,
boolean useAlphaAsNumbers,
boolean showDisabledActions,
boolean honorActionMnemonics,
final Runnable disposeCallback,
final int maxRowCount,
final Condition<AnAction> preselectActionCondition,
@Nullable final String actionPlace) {
super(createStep(title, actionGroup, dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions, honorActionMnemonics,
preselectActionCondition, actionPlace),
maxRowCount);
myDisposeCallback = disposeCallback;
myComponent = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext);
addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
final JList list = (JList)e.getSource();
final ActionItem actionItem = (ActionItem)list.getSelectedValue();
if (actionItem == null) return;
AnAction action = actionItem.getAction();
Presentation presentation = new Presentation();
presentation.setDescription(action.getTemplatePresentation().getDescription());
final String actualActionPlace = actionPlace == null ? ActionPlaces.UNKNOWN : actionPlace;
final AnActionEvent actionEvent =
new AnActionEvent(null, DataManager.getInstance().getDataContext(myComponent), actualActionPlace, presentation,
ActionManager.getInstance(), 0);
actionEvent.setInjectedContext(action.isInInjectedContext());
action.update(actionEvent);
ActionMenu.showDescriptionInStatusBar(true, myComponent, presentation.getDescription());
}
});
}
private static ListPopupStep createStep(String title,
@NotNull ActionGroup actionGroup,
@NotNull DataContext dataContext,
boolean showNumbers,
boolean useAlphaAsNumbers,
boolean showDisabledActions,
boolean honorActionMnemonics,
Condition<AnAction> preselectActionCondition,
@Nullable String actionPlace) {
final Component component = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext);
LOG.assertTrue(component != null, "dataContext has no component for new ListPopupStep");
final ActionStepBuilder builder =
new ActionStepBuilder(dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions, honorActionMnemonics);
if (actionPlace != null) {
builder.setActionPlace(actionPlace);
}
builder.buildGroup(actionGroup);
final List<ActionItem> items = builder.getItems();
return new ActionPopupStep(items, title, component, showNumbers || honorActionMnemonics && itemsHaveMnemonics(items),
preselectActionCondition, false, showDisabledActions);
}
@Override
public void dispose() {
if (myDisposeCallback != null) {
myDisposeCallback.run();
}
ActionMenu.showDescriptionInStatusBar(true, myComponent, null);
super.dispose();
}
}
@NotNull
@Override
public ListPopup createActionGroupPopup(final String title,
@NotNull final ActionGroup actionGroup,
@NotNull DataContext dataContext,
boolean showNumbers,
boolean showDisabledActions,
boolean honorActionMnemonics,
final Runnable disposeCallback,
final int maxRowCount,
final Condition<AnAction> preselectActionCondition) {
return createActionGroupPopup(title, actionGroup, dataContext, showNumbers, true, showDisabledActions, honorActionMnemonics,
disposeCallback, maxRowCount, preselectActionCondition, null);
}
@NotNull
@Override
public ListPopup createActionGroupPopup(String title,
@NotNull ActionGroup actionGroup,
@NotNull DataContext dataContext,
ActionSelectionAid selectionAidMethod,
boolean showDisabledActions) {
return createActionGroupPopup(title, actionGroup, dataContext,
selectionAidMethod == ActionSelectionAid.NUMBERING || selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
showDisabledActions,
selectionAidMethod == ActionSelectionAid.MNEMONICS,
null, -1);
}
@NotNull
@Override
public ListPopup createActionGroupPopup(String title,
@NotNull ActionGroup actionGroup,
@NotNull DataContext dataContext,
ActionSelectionAid selectionAidMethod,
boolean showDisabledActions,
@Nullable String actionPlace) {
return createActionGroupPopup(title, actionGroup, dataContext,
selectionAidMethod == ActionSelectionAid.NUMBERING || selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
showDisabledActions,
selectionAidMethod == ActionSelectionAid.MNEMONICS,
null, -1, null, actionPlace);
}
@NotNull
@Override
public ListPopup createActionGroupPopup(String title,
@NotNull ActionGroup actionGroup,
@NotNull DataContext dataContext,
ActionSelectionAid selectionAidMethod,
boolean showDisabledActions,
Runnable disposeCallback,
int maxRowCount) {
return createActionGroupPopup(title, actionGroup, dataContext,
selectionAidMethod == ActionSelectionAid.NUMBERING || selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
showDisabledActions,
selectionAidMethod == ActionSelectionAid.MNEMONICS,
disposeCallback,
maxRowCount);
}
@NotNull
@Override
public ListPopupStep createActionsStep(@NotNull final ActionGroup actionGroup,
@NotNull DataContext dataContext,
final boolean showNumbers,
final boolean showDisabledActions,
final String title,
final Component component,
final boolean honorActionMnemonics) {
return createActionsStep(actionGroup, dataContext, showNumbers, showDisabledActions, title, component, honorActionMnemonics, 0, false);
}
private static ListPopupStep createActionsStep(@NotNull ActionGroup actionGroup, @NotNull DataContext dataContext,
boolean showNumbers, boolean useAlphaAsNumbers, boolean showDisabledActions,
String title, Component component, boolean honorActionMnemonics,
final int defaultOptionIndex, final boolean autoSelectionEnabled) {
final List<ActionItem> items = makeActionItemsFromActionGroup(actionGroup, dataContext, showNumbers, useAlphaAsNumbers,
showDisabledActions, honorActionMnemonics);
return new ActionPopupStep(items, title, component, showNumbers || honorActionMnemonics && itemsHaveMnemonics(items),
new Condition<AnAction>() {
@Override
public boolean value(AnAction action) {
return defaultOptionIndex >= 0 &&
defaultOptionIndex < items.size() &&
items.get(defaultOptionIndex).getAction().equals(action);
}
}, autoSelectionEnabled, showDisabledActions);
}
@NotNull
private static List<ActionItem> makeActionItemsFromActionGroup(@NotNull ActionGroup actionGroup,
@NotNull DataContext dataContext,
boolean showNumbers,
boolean useAlphaAsNumbers,
boolean showDisabledActions,
boolean honorActionMnemonics) {
final ActionStepBuilder builder = new ActionStepBuilder(dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions,
honorActionMnemonics);
builder.buildGroup(actionGroup);
return builder.getItems();
}
@NotNull
private static ListPopupStep createActionsStep(@NotNull ActionGroup actionGroup, @NotNull DataContext dataContext,
boolean showNumbers, boolean useAlphaAsNumbers, boolean showDisabledActions,
String title, Component component, boolean honorActionMnemonics,
Condition<AnAction> preselectActionCondition, boolean autoSelectionEnabled) {
final List<ActionItem> items = makeActionItemsFromActionGroup(actionGroup, dataContext, showNumbers, useAlphaAsNumbers,
showDisabledActions, honorActionMnemonics);
return new ActionPopupStep(items, title, component, showNumbers || honorActionMnemonics && itemsHaveMnemonics(items), preselectActionCondition,
autoSelectionEnabled, showDisabledActions);
}
@NotNull
@Override
public ListPopupStep createActionsStep(@NotNull ActionGroup actionGroup, @NotNull DataContext dataContext, boolean showNumbers, boolean showDisabledActions,
String title, Component component, boolean honorActionMnemonics, int defaultOptionIndex,
final boolean autoSelectionEnabled) {
return createActionsStep(actionGroup, dataContext, showNumbers, true, showDisabledActions, title, component, honorActionMnemonics,
defaultOptionIndex, autoSelectionEnabled);
}
private static boolean itemsHaveMnemonics(final List<ActionItem> items) {
for (ActionItem item : items) {
if (item.getAction().getTemplatePresentation().getMnemonic() != 0) return true;
}
return false;
}
@NotNull
@Override
public ListPopup createWizardStep(@NotNull PopupStep step) {
return new ListPopupImpl((ListPopupStep) step);
}
@NotNull
@Override
public ListPopup createListPopup(@NotNull ListPopupStep step) {
return new ListPopupImpl(step);
}
@NotNull
@Override
public TreePopup createTree(JBPopup parent, @NotNull TreePopupStep aStep, Object parentValue) {
return new TreePopupImpl(parent, aStep, parentValue);
}
@NotNull
@Override
public TreePopup createTree(@NotNull TreePopupStep aStep) {
return new TreePopupImpl(aStep);
}
@NotNull
@Override
public ComponentPopupBuilder createComponentPopupBuilder(@NotNull JComponent content, JComponent prefferableFocusComponent) {
return new ComponentPopupBuilderImpl(content, prefferableFocusComponent);
}
@NotNull
@Override
public RelativePoint guessBestPopupLocation(@NotNull DataContext dataContext) {
Component component = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext);
JComponent focusOwner = component instanceof JComponent ? (JComponent)component : null;
if (focusOwner == null) {
Project project = CommonDataKeys.PROJECT.getData(dataContext);
IdeFrameImpl frame = project == null ? null : ((WindowManagerEx)WindowManager.getInstance()).getFrame(project);
focusOwner = frame == null ? null : frame.getRootPane();
if (focusOwner == null) {
throw new IllegalArgumentException("focusOwner cannot be null");
}
}
final Point point = PlatformDataKeys.CONTEXT_MENU_POINT.getData(dataContext);
if (point != null) {
return new RelativePoint(focusOwner, point);
}
Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
if (editor != null && focusOwner == editor.getContentComponent()) {
return guessBestPopupLocation(editor);
}
else {
return guessBestPopupLocation(focusOwner);
}
}
@NotNull
@Override
public RelativePoint guessBestPopupLocation(@NotNull final JComponent component) {
Point popupMenuPoint = null;
final Rectangle visibleRect = component.getVisibleRect();
if (component instanceof JList) { // JList
JList list = (JList)component;
int firstVisibleIndex = list.getFirstVisibleIndex();
int lastVisibleIndex = list.getLastVisibleIndex();
int[] selectedIndices = list.getSelectedIndices();
for (int index : selectedIndices) {
if (firstVisibleIndex <= index && index <= lastVisibleIndex) {
Rectangle cellBounds = list.getCellBounds(index, index);
popupMenuPoint = new Point(visibleRect.x + visibleRect.width / 4, cellBounds.y + cellBounds.height);
break;
}
}
}
else if (component instanceof JTree) { // JTree
JTree tree = (JTree)component;
int[] selectionRows = tree.getSelectionRows();
if (selectionRows != null) {
Arrays.sort(selectionRows);
for (int i = 0; i < selectionRows.length; i++) {
int row = selectionRows[i];
Rectangle rowBounds = tree.getRowBounds(row);
if (visibleRect.contains(rowBounds)) {
popupMenuPoint = new Point(rowBounds.x + 2, rowBounds.y + rowBounds.height - 1);
break;
}
}
if (popupMenuPoint == null) {//All selected rows are out of visible rect
Point visibleCenter = new Point(visibleRect.x + visibleRect.width / 2, visibleRect.y + visibleRect.height / 2);
double minDistance = Double.POSITIVE_INFINITY;
int bestRow = -1;
Point rowCenter;
double distance;
for (int i = 0; i < selectionRows.length; i++) {
int row = selectionRows[i];
Rectangle rowBounds = tree.getRowBounds(row);
rowCenter = new Point(rowBounds.x + rowBounds.width / 2, rowBounds.y + rowBounds.height / 2);
distance = visibleCenter.distance(rowCenter);
if (minDistance > distance) {
minDistance = distance;
bestRow = row;
}
}
if (bestRow != -1) {
Rectangle rowBounds = tree.getRowBounds(bestRow);
tree.scrollRectToVisible(new Rectangle(rowBounds.x, rowBounds.y, Math.min(visibleRect.width, rowBounds.width), rowBounds.height));
popupMenuPoint = new Point(rowBounds.x + 2, rowBounds.y + rowBounds.height - 1);
}
}
}
}
else if (component instanceof JTable) {
JTable table = (JTable)component;
int column = table.getColumnModel().getSelectionModel().getLeadSelectionIndex();
int row = Math.max(table.getSelectionModel().getLeadSelectionIndex(), table.getSelectionModel().getAnchorSelectionIndex());
Rectangle rect = table.getCellRect(row, column, false);
if (!visibleRect.intersects(rect)) {
table.scrollRectToVisible(rect);
}
popupMenuPoint = new Point(rect.x, rect.y + rect.height);
}
else if (component instanceof PopupOwner) {
popupMenuPoint = ((PopupOwner)component).getBestPopupPosition();
}
if (popupMenuPoint == null) {
popupMenuPoint = new Point(visibleRect.x + visibleRect.width / 2, visibleRect.y + visibleRect.height / 2);
}
return new RelativePoint(component, popupMenuPoint);
}
@Override
public boolean isBestPopupLocationVisible(@NotNull Editor editor) {
return getVisibleBestPopupLocation(editor) != null;
}
@NotNull
@Override
public RelativePoint guessBestPopupLocation(@NotNull Editor editor) {
Point p = getVisibleBestPopupLocation(editor);
if (p == null) {
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
p = new Point((visibleArea.x + visibleArea.width) / 2, (visibleArea.y + visibleArea.height) / 2);
}
return new RelativePoint(editor.getContentComponent(), p);
}
@Nullable
private static Point getVisibleBestPopupLocation(@NotNull Editor editor) {
VisualPosition visualPosition = editor.getUserData(ANCHOR_POPUP_POSITION);
if (visualPosition == null) {
CaretModel caretModel = editor.getCaretModel();
if (caretModel.isUpToDate()) {
visualPosition = caretModel.getVisualPosition();
}
else {
visualPosition = editor.offsetToVisualPosition(caretModel.getOffset());
}
}
Point p = editor.visualPositionToXY(new VisualPosition(visualPosition.line + 1, visualPosition.column));
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
return visibleArea.contains(p) ? p : null;
}
@Override
public Point getCenterOf(JComponent container, JComponent content) {
return AbstractPopup.getCenterOf(container, content);
}
public static class ActionItem {
private final AnAction myAction;
private final String myText;
private final boolean myIsEnabled;
private final Icon myIcon;
private final boolean myPrependWithSeparator;
private final String mySeparatorText;
private ActionItem(@NotNull AnAction action, @NotNull String text, boolean enabled, Icon icon, final boolean prependWithSeparator, String separatorText) {
myAction = action;
myText = text;
myIsEnabled = enabled;
myIcon = icon;
myPrependWithSeparator = prependWithSeparator;
mySeparatorText = separatorText;
}
@NotNull
public AnAction getAction() {
return myAction;
}
@NotNull
public String getText() {
return myText;
}
public Icon getIcon() {
return myIcon;
}
public boolean isPrependWithSeparator() {
return myPrependWithSeparator;
}
public String getSeparatorText() {
return mySeparatorText;
}
public boolean isEnabled() { return myIsEnabled; }
}
private static class ActionPopupStep implements ListPopupStepEx<ActionItem>, MnemonicNavigationFilter<ActionItem>, SpeedSearchFilter<ActionItem> {
private final List<ActionItem> myItems;
private final String myTitle;
private final Component myContext;
private final boolean myEnableMnemonics;
private final int myDefaultOptionIndex;
private final boolean myAutoSelectionEnabled;
private final boolean myShowDisabledActions;
private Runnable myFinalRunnable;
@Nullable private final Condition<AnAction> myPreselectActionCondition;
private ActionPopupStep(@NotNull final List<ActionItem> items, final String title, Component context, boolean enableMnemonics,
@Nullable Condition<AnAction> preselectActionCondition, final boolean autoSelection, boolean showDisabledActions) {
myItems = items;
myTitle = title;
myContext = context;
myEnableMnemonics = enableMnemonics;
myDefaultOptionIndex = getDefaultOptionIndexFromSelectCondition(preselectActionCondition, items);
myPreselectActionCondition = preselectActionCondition;
myAutoSelectionEnabled = autoSelection;
myShowDisabledActions = showDisabledActions;
}
private static int getDefaultOptionIndexFromSelectCondition(@Nullable Condition<AnAction> preselectActionCondition,
@NotNull List<ActionItem> items) {
int defaultOptionIndex = 0;
if (preselectActionCondition != null) {
for (int i = 0; i < items.size(); i++) {
final AnAction action = items.get(i).getAction();
if (preselectActionCondition.value(action)) {
defaultOptionIndex = i;
break;
}
}
}
return defaultOptionIndex;
}
@Override
@NotNull
public List<ActionItem> getValues() {
return myItems;
}
@Override
public boolean isSelectable(final ActionItem value) {
return value.isEnabled();
}
@Override
public int getMnemonicPos(final ActionItem value) {
final String text = getTextFor(value);
int i = text.indexOf(UIUtil.MNEMONIC);
if (i < 0) {
i = text.indexOf('&');
}
if (i < 0) {
i = text.indexOf('_');
}
return i;
}
@Override
public Icon getIconFor(final ActionItem aValue) {
return aValue.getIcon();
}
@Override
@NotNull
public String getTextFor(final ActionItem value) {
return value.getText();
}
@Override
public ListSeparator getSeparatorAbove(final ActionItem value) {
return value.isPrependWithSeparator() ? new ListSeparator(value.getSeparatorText()) : null;
}
@Override
public int getDefaultOptionIndex() {
return myDefaultOptionIndex;
}
@Override
public String getTitle() {
return myTitle;
}
@Override
public PopupStep onChosen(final ActionItem actionChoice, final boolean finalChoice) {
return onChosen(actionChoice, finalChoice, 0);
}
@Override
public PopupStep onChosen(ActionItem actionChoice, boolean finalChoice, final int eventModifiers) {
if (!actionChoice.isEnabled()) return FINAL_CHOICE;
final AnAction action = actionChoice.getAction();
DataManager mgr = DataManager.getInstance();
final DataContext dataContext = myContext != null ? mgr.getDataContext(myContext) : mgr.getDataContext();
if (action instanceof ActionGroup && (!finalChoice || !((ActionGroup)action).canBePerformed(dataContext))) {
return createActionsStep((ActionGroup)action, dataContext, myEnableMnemonics, true, myShowDisabledActions, null, myContext, false,
myPreselectActionCondition, false);
}
else {
myFinalRunnable = new Runnable() {
@Override
public void run() {
final AnActionEvent event = new AnActionEvent(null, dataContext, ActionPlaces.UNKNOWN, action.getTemplatePresentation().clone(),
ActionManager.getInstance(), eventModifiers);
event.setInjectedContext(action.isInInjectedContext());
action.actionPerformed(event);
}
};
return FINAL_CHOICE;
}
}
@Override
public Runnable getFinalRunnable() {
return myFinalRunnable;
}
@Override
public boolean hasSubstep(final ActionItem selectedValue) {
return selectedValue != null && selectedValue.isEnabled() && selectedValue.getAction() instanceof ActionGroup;
}
@Override
public void canceled() {
}
@Override
public boolean isMnemonicsNavigationEnabled() {
return myEnableMnemonics;
}
@Override
public MnemonicNavigationFilter<ActionItem> getMnemonicNavigationFilter() {
return this;
}
@Override
public boolean canBeHidden(final ActionItem value) {
return true;
}
@Override
public String getIndexedString(final ActionItem value) {
return getTextFor(value);
}
@Override
public boolean isSpeedSearchEnabled() {
return true;
}
@Override
public boolean isAutoSelectionEnabled() {
return myAutoSelectionEnabled;
}
@Override
public SpeedSearchFilter<ActionItem> getSpeedSearchFilter() {
return this;
}
}
@Override
@NotNull
public List<JBPopup> getChildPopups(@NotNull final Component component) {
return FocusTrackback.getChildPopups(component);
}
@Override
public boolean isPopupActive() {
return IdeEventQueue.getInstance().isPopupActive();
}
private static class ActionStepBuilder {
private final List<ActionItem> myListModel;
private final DataContext myDataContext;
private final boolean myShowNumbers;
private final boolean myUseAlphaAsNumbers;
private final boolean myShowDisabled;
private final HashMap<AnAction, Presentation> myAction2presentation;
private int myCurrentNumber;
private boolean myPrependWithSeparator;
private String mySeparatorText;
private final boolean myHonorActionMnemonics;
private Icon myEmptyIcon;
private int myMaxIconWidth = -1;
private int myMaxIconHeight = -1;
@NotNull private String myActionPlace;
private ActionStepBuilder(@NotNull DataContext dataContext, final boolean showNumbers, final boolean useAlphaAsNumbers,
final boolean showDisabled, final boolean honorActionMnemonics)
{
myUseAlphaAsNumbers = useAlphaAsNumbers;
myListModel = new ArrayList<ActionItem>();
myDataContext = dataContext;
myShowNumbers = showNumbers;
myShowDisabled = showDisabled;
myAction2presentation = new HashMap<AnAction, Presentation>();
myCurrentNumber = 0;
myPrependWithSeparator = false;
mySeparatorText = null;
myHonorActionMnemonics = honorActionMnemonics;
myActionPlace = ActionPlaces.UNKNOWN;
}
public void setActionPlace(@NotNull String actionPlace) {
myActionPlace = actionPlace;
}
@NotNull
public List<ActionItem> getItems() {
return myListModel;
}
public void buildGroup(@NotNull ActionGroup actionGroup) {
calcMaxIconSize(actionGroup);
myEmptyIcon = myMaxIconHeight != -1 && myMaxIconWidth != -1 ? new EmptyIcon(myMaxIconWidth, myMaxIconHeight) : null;
appendActionsFromGroup(actionGroup);
if (myListModel.isEmpty()) {
myListModel.add(new ActionItem(Utils.EMPTY_MENU_FILLER, Utils.NOTHING_HERE, false, null, false, null));
}
}
private void calcMaxIconSize(final ActionGroup actionGroup) {
AnAction[] actions = actionGroup.getChildren(createActionEvent(actionGroup));
for (AnAction action : actions) {
if (action == null) continue;
if (action instanceof ActionGroup) {
final ActionGroup group = (ActionGroup)action;
if (!group.isPopup()) {
calcMaxIconSize(group);
continue;
}
}
Icon icon = action.getTemplatePresentation().getIcon();
if (icon == null && action instanceof Toggleable) icon = PlatformIcons.CHECK_ICON;
if (icon != null) {
final int width = icon.getIconWidth();
final int height = icon.getIconHeight();
if (myMaxIconWidth < width) {
myMaxIconWidth = width;
}
if (myMaxIconHeight < height) {
myMaxIconHeight = height;
}
}
}
}
@NotNull
private AnActionEvent createActionEvent(@NotNull AnAction actionGroup) {
final AnActionEvent actionEvent =
new AnActionEvent(null, myDataContext, myActionPlace, getPresentation(actionGroup), ActionManager.getInstance(), 0);
actionEvent.setInjectedContext(actionGroup.isInInjectedContext());
return actionEvent;
}
private void appendActionsFromGroup(@NotNull ActionGroup actionGroup) {
AnAction[] actions = actionGroup.getChildren(createActionEvent(actionGroup));
for (AnAction action : actions) {
if (action == null) {
LOG.error("null action in group " + actionGroup);
continue;
}
if (action instanceof Separator) {
myPrependWithSeparator = true;
mySeparatorText = ((Separator)action).getText();
}
else {
if (action instanceof ActionGroup) {
ActionGroup group = (ActionGroup)action;
if (group.isPopup()) {
appendAction(group);
}
else {
appendActionsFromGroup(group);
}
}
else {
appendAction(action);
}
}
}
}
private void appendAction(@NotNull AnAction action) {
Presentation presentation = getPresentation(action);
AnActionEvent event = createActionEvent(action);
ActionUtil.performDumbAwareUpdate(action, event, true);
if ((myShowDisabled || presentation.isEnabled()) && presentation.isVisible()) {
String text = presentation.getText();
if (myShowNumbers) {
if (myCurrentNumber < 9) {
text = "&" + (myCurrentNumber + 1) + ". " + text;
}
else if (myCurrentNumber == 9) {
text = "&" + 0 + ". " + text;
}
else if (myUseAlphaAsNumbers) {
text = "&" + (char)('A' + myCurrentNumber - 10) + ". " + text;
}
myCurrentNumber++;
}
else if (myHonorActionMnemonics) {
text = Presentation.restoreTextWithMnemonic(text, action.getTemplatePresentation().getMnemonic());
}
Icon icon = presentation.getIcon();
if (icon == null) {
@NonNls final String actionId = ActionManager.getInstance().getId(action);
if (actionId != null && actionId.startsWith("QuickList.")) {
icon = 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;
}
else {
icon = myEmptyIcon;
}
}
else {
icon = new IconWrapper(icon);
}
boolean prependSeparator = (!myListModel.isEmpty() || mySeparatorText != null) && myPrependWithSeparator;
assert text != null : action + " has no presentation";
myListModel.add(new ActionItem(action, text, presentation.isEnabled(), icon, prependSeparator, mySeparatorText));
myPrependWithSeparator = false;
mySeparatorText = null;
}
}
/**
* Adjusts icon size to maximum, so that icons with different sizes were aligned correctly.
*/
private class IconWrapper implements Icon {
private Icon myIcon;
IconWrapper(Icon icon) {
myIcon = icon;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
myIcon.paintIcon(c, g, x, y);
}
@Override
public int getIconWidth() {
return myMaxIconWidth;
}
@Override
public int getIconHeight() {
return myMaxIconHeight;
}
}
private Presentation getPresentation(@NotNull AnAction action) {
Presentation presentation = myAction2presentation.get(action);
if (presentation == null) {
presentation = action.getTemplatePresentation().clone();
myAction2presentation.put(action, presentation);
}
return presentation;
}
}
@NotNull
@Override
public BalloonBuilder createBalloonBuilder(@NotNull final JComponent content) {
return new BalloonPopupBuilderImpl(myStorage, content);
}
@NotNull
@Override
public BalloonBuilder createDialogBalloonBuilder(@NotNull JComponent content, String title) {
final BalloonPopupBuilderImpl builder = new BalloonPopupBuilderImpl(myStorage, content);
final Color bg = UIManager.getColor("Panel.background");
final Color borderOriginal = Color.darkGray;
final Color border = ColorUtil.toAlpha(borderOriginal, 75);
builder
.setDialogMode(true)
.setTitle(title)
.setAnimationCycle(200)
.setFillColor(bg)
.setBorderColor(border)
.setHideOnClickOutside(false)
.setHideOnKeyOutside(false)
.setHideOnAction(false)
.setCloseButtonEnabled(true)
.setShadow(true);
return builder;
}
@NotNull
@Override
public BalloonBuilder createHtmlTextBalloonBuilder(@NotNull final String htmlContent, @Nullable final Icon icon, final Color fillColor,
@Nullable final HyperlinkListener listener)
{
JEditorPane text = IdeTooltipManager.initPane(htmlContent, new HintHint().setAwtTooltip(true), null);
if (listener != null) {
text.addHyperlinkListener(listener);
}
text.setEditable(false);
NonOpaquePanel.setTransparent(text);
text.setBorder(null);
JLabel label = new JLabel();
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);
scrolledText.setBackground(fillColor);
scrolledText.getViewport().setBackground(fillColor);
scrolledText.getViewport().setBorder(null);
scrolledText.setBorder(null);
textWrapper.add(scrolledText);
content.add(textWrapper, BorderLayout.CENTER);
final NonOpaquePanel north = new NonOpaquePanel(new BorderLayout());
north.add(new JLabel(icon), BorderLayout.NORTH);
content.add(north, BorderLayout.WEST);
content.setBorder(new EmptyBorder(2, 4, 2, 4));
final BalloonBuilder builder = createBalloonBuilder(content);
builder.setFillColor(fillColor);
return builder;
}
@NotNull
@Override
public BalloonBuilder createHtmlTextBalloonBuilder(@NotNull String htmlContent,
MessageType messageType,
@Nullable HyperlinkListener listener)
{
return createHtmlTextBalloonBuilder(htmlContent, messageType.getDefaultIcon(), messageType.getPopupBackground(), listener);
}
}