/platform/platform-impl/src/com/intellij/ui/popup/PopupFactoryImpl.java

https://bitbucket.org/nbargnesi/idea · Java · 966 lines · 813 code · 129 blank · 24 comment · 143 complexity · 3e2ff6860031ef25e8463b77504bdc8e MD5 · raw file

  1. /*
  2. * Copyright 2000-2012 JetBrains s.r.o.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.intellij.ui.popup;
  17. import com.intellij.CommonBundle;
  18. import com.intellij.icons.AllIcons;
  19. import com.intellij.ide.DataManager;
  20. import com.intellij.ide.IdeEventQueue;
  21. import com.intellij.ide.IdeTooltipManager;
  22. import com.intellij.openapi.Disposable;
  23. import com.intellij.openapi.actionSystem.*;
  24. import com.intellij.openapi.actionSystem.ex.ActionUtil;
  25. import com.intellij.openapi.actionSystem.impl.ActionMenu;
  26. import com.intellij.openapi.actionSystem.impl.Utils;
  27. import com.intellij.openapi.application.ex.ApplicationEx;
  28. import com.intellij.openapi.application.ex.ApplicationManagerEx;
  29. import com.intellij.openapi.diagnostic.Logger;
  30. import com.intellij.openapi.editor.CaretModel;
  31. import com.intellij.openapi.editor.Editor;
  32. import com.intellij.openapi.editor.VisualPosition;
  33. import com.intellij.openapi.project.Project;
  34. import com.intellij.openapi.ui.MessageType;
  35. import com.intellij.openapi.ui.popup.*;
  36. import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
  37. import com.intellij.openapi.util.Condition;
  38. import com.intellij.openapi.util.EmptyRunnable;
  39. import com.intellij.openapi.util.Key;
  40. import com.intellij.openapi.wm.WindowManager;
  41. import com.intellij.openapi.wm.ex.WindowManagerEx;
  42. import com.intellij.openapi.wm.impl.IdeFrameImpl;
  43. import com.intellij.ui.ColorUtil;
  44. import com.intellij.ui.FocusTrackback;
  45. import com.intellij.ui.HintHint;
  46. import com.intellij.ui.awt.RelativePoint;
  47. import com.intellij.ui.components.panels.NonOpaquePanel;
  48. import com.intellij.ui.popup.list.ListPopupImpl;
  49. import com.intellij.ui.popup.mock.MockConfirmation;
  50. import com.intellij.ui.popup.tree.TreePopupImpl;
  51. import com.intellij.util.containers.HashMap;
  52. import com.intellij.util.containers.WeakHashMap;
  53. import com.intellij.util.ui.EmptyIcon;
  54. import com.intellij.util.ui.UIUtil;
  55. import org.jetbrains.annotations.NonNls;
  56. import org.jetbrains.annotations.NotNull;
  57. import org.jetbrains.annotations.Nullable;
  58. import javax.swing.*;
  59. import javax.swing.border.EmptyBorder;
  60. import javax.swing.event.HyperlinkListener;
  61. import javax.swing.event.ListSelectionEvent;
  62. import javax.swing.event.ListSelectionListener;
  63. import java.awt.*;
  64. import java.util.ArrayList;
  65. import java.util.Arrays;
  66. import java.util.List;
  67. import java.util.Map;
  68. public class PopupFactoryImpl extends JBPopupFactory {
  69. /**
  70. * Allows to get an editor position for which a popup with auxiliary information might be shown.
  71. * <p/>
  72. * Primary intention for this key is to hint popup position for the non-caret location.
  73. */
  74. public static final Key<VisualPosition> ANCHOR_POPUP_POSITION = Key.create("popup.anchor.position");
  75. private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.PopupFactoryImpl");
  76. private final Map<Disposable, List<Balloon>> myStorage = new WeakHashMap<Disposable, List<Balloon>>();
  77. public ListPopup createConfirmation(String title, final Runnable onYes, int defaultOptionIndex) {
  78. return createConfirmation(title, CommonBundle.getYesButtonText(), CommonBundle.getNoButtonText(), onYes, defaultOptionIndex);
  79. }
  80. public ListPopup createConfirmation(String title, final String yesText, String noText, final Runnable onYes, int defaultOptionIndex) {
  81. return createConfirmation(title, yesText, noText, onYes, EmptyRunnable.getInstance(), defaultOptionIndex);
  82. }
  83. public JBPopup createMessage(String text) {
  84. return createListPopup(new BaseListPopupStep<String>(null, new String[]{text}));
  85. }
  86. @Override
  87. public Balloon getParentBalloonFor(@Nullable Component c) {
  88. if (c == null) return null;
  89. Component eachParent = c;
  90. while (eachParent != null) {
  91. if (eachParent instanceof JComponent) {
  92. Object balloon = ((JComponent)eachParent).getClientProperty(Balloon.KEY);
  93. if (balloon instanceof Balloon) {
  94. return (Balloon)balloon;
  95. }
  96. }
  97. eachParent = eachParent.getParent();
  98. }
  99. return null;
  100. }
  101. public ListPopup createConfirmation(String title,
  102. final String yesText,
  103. String noText,
  104. final Runnable onYes,
  105. final Runnable onNo,
  106. int defaultOptionIndex)
  107. {
  108. final BaseListPopupStep<String> step = new BaseListPopupStep<String>(title, new String[]{yesText, noText}) {
  109. public PopupStep onChosen(String selectedValue, final boolean finalChoice) {
  110. if (selectedValue.equals(yesText)) {
  111. onYes.run();
  112. }
  113. else {
  114. onNo.run();
  115. }
  116. return FINAL_CHOICE;
  117. }
  118. public void canceled() {
  119. onNo.run();
  120. }
  121. public boolean isMnemonicsNavigationEnabled() {
  122. return true;
  123. }
  124. };
  125. step.setDefaultOptionIndex(defaultOptionIndex);
  126. final ApplicationEx app = ApplicationManagerEx.getApplicationEx();
  127. return app == null || !app.isUnitTestMode() ? new ListPopupImpl(step) : new MockConfirmation(step, yesText);
  128. }
  129. private static ListPopup createActionGroupPopup(final String title,
  130. final ActionGroup actionGroup,
  131. @NotNull DataContext dataContext,
  132. boolean showNumbers,
  133. boolean useAlphaAsNumbers,
  134. boolean showDisabledActions,
  135. boolean honorActionMnemonics,
  136. final Runnable disposeCallback,
  137. final int maxRowCount) {
  138. return createActionGroupPopup(title, actionGroup, dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions, honorActionMnemonics, disposeCallback,
  139. maxRowCount, null, null);
  140. }
  141. public ListPopup createActionGroupPopup(final String title,
  142. final ActionGroup actionGroup,
  143. @NotNull DataContext dataContext,
  144. boolean showNumbers,
  145. boolean showDisabledActions,
  146. boolean honorActionMnemonics,
  147. final Runnable disposeCallback,
  148. final int maxRowCount) {
  149. return createActionGroupPopup(title, actionGroup, dataContext, showNumbers, showDisabledActions, honorActionMnemonics, disposeCallback,
  150. maxRowCount, null);
  151. }
  152. private static ListPopup createActionGroupPopup(final String title,
  153. final ActionGroup actionGroup,
  154. @NotNull DataContext dataContext,
  155. boolean showNumbers,
  156. boolean useAlphaAsNumbers,
  157. boolean showDisabledActions,
  158. boolean honorActionMnemonics,
  159. final Runnable disposeCallback,
  160. final int maxRowCount,
  161. final Condition<AnAction> preselectActionCondition, @Nullable final String actionPlace) {
  162. return new ActionGroupPopup(title, actionGroup, dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions, honorActionMnemonics,
  163. disposeCallback, maxRowCount, preselectActionCondition, actionPlace);
  164. }
  165. public static class ActionGroupPopup extends ListPopupImpl {
  166. private final Runnable myDisposeCallback;
  167. private final Component myComponent;
  168. public ActionGroupPopup(final String title, final ActionGroup actionGroup, @NotNull DataContext dataContext,
  169. boolean showNumbers, boolean useAlphaAsNumbers, boolean showDisabledActions, boolean honorActionMnemonics,
  170. final Runnable disposeCallback, final int maxRowCount, final Condition<AnAction> preselectActionCondition,
  171. @Nullable final String actionPlace) {
  172. super(createStep(title, actionGroup, dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions, honorActionMnemonics,
  173. preselectActionCondition, actionPlace),
  174. maxRowCount);
  175. myDisposeCallback = disposeCallback;
  176. myComponent = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext);
  177. addListSelectionListener(new ListSelectionListener() {
  178. public void valueChanged(ListSelectionEvent e) {
  179. final JList list = (JList)e.getSource();
  180. final ActionItem actionItem = (ActionItem)list.getSelectedValue();
  181. if (actionItem == null) return;
  182. AnAction action = actionItem.getAction();
  183. Presentation presentation = new Presentation();
  184. presentation.setDescription(action.getTemplatePresentation().getDescription());
  185. final String actualActionPlace = actionPlace == null ? ActionPlaces.UNKNOWN : actionPlace;
  186. action.update(new AnActionEvent(null, DataManager.getInstance().getDataContext(myComponent), actualActionPlace, presentation,
  187. ActionManager.getInstance(), 0));
  188. ActionMenu.showDescriptionInStatusBar(true, myComponent, presentation.getDescription());
  189. }
  190. });
  191. }
  192. private static ListPopupStep createStep(String title, ActionGroup actionGroup, @NotNull DataContext dataContext,
  193. boolean showNumbers, boolean useAlphaAsNumbers, boolean showDisabledActions,
  194. boolean honorActionMnemonics,
  195. Condition<AnAction> preselectActionCondition, @Nullable String actionPlace) {
  196. final Component component = PlatformDataKeys.CONTEXT_COMPONENT.getData(dataContext);
  197. final ActionStepBuilder builder =
  198. new ActionStepBuilder(dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions, honorActionMnemonics);
  199. if (actionPlace != null) {
  200. builder.setActionPlace(actionPlace);
  201. }
  202. builder.buildGroup(actionGroup);
  203. final List<ActionItem> items = builder.getItems();
  204. return new ActionPopupStep(items, title, component, showNumbers || honorActionMnemonics && itemsHaveMnemonics(items),
  205. preselectActionCondition, false, showDisabledActions);
  206. }
  207. @Override
  208. public void dispose() {
  209. if (myDisposeCallback != null) {
  210. myDisposeCallback.run();
  211. }
  212. ActionMenu.showDescriptionInStatusBar(true, myComponent, null);
  213. super.dispose();
  214. }
  215. }
  216. public ListPopup createActionGroupPopup(final String title,
  217. final ActionGroup actionGroup,
  218. @NotNull DataContext dataContext,
  219. boolean showNumbers,
  220. boolean showDisabledActions,
  221. boolean honorActionMnemonics,
  222. final Runnable disposeCallback,
  223. final int maxRowCount,
  224. final Condition<AnAction> preselectActionCondition) {
  225. return createActionGroupPopup(title, actionGroup, dataContext, showNumbers, true, showDisabledActions, honorActionMnemonics,
  226. disposeCallback, maxRowCount, preselectActionCondition, null);
  227. }
  228. public ListPopup createActionGroupPopup(String title,
  229. ActionGroup actionGroup,
  230. @NotNull DataContext dataContext,
  231. ActionSelectionAid selectionAidMethod,
  232. boolean showDisabledActions) {
  233. return createActionGroupPopup(title, actionGroup, dataContext,
  234. selectionAidMethod == ActionSelectionAid.NUMBERING || selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
  235. selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
  236. showDisabledActions,
  237. selectionAidMethod == ActionSelectionAid.MNEMONICS,
  238. null, -1);
  239. }
  240. public ListPopup createActionGroupPopup(String title,
  241. ActionGroup actionGroup,
  242. @NotNull DataContext dataContext,
  243. ActionSelectionAid selectionAidMethod,
  244. boolean showDisabledActions,
  245. @Nullable String actionPlace) {
  246. return createActionGroupPopup(title, actionGroup, dataContext,
  247. selectionAidMethod == ActionSelectionAid.NUMBERING || selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
  248. selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
  249. showDisabledActions,
  250. selectionAidMethod == ActionSelectionAid.MNEMONICS,
  251. null, -1, null, actionPlace);
  252. }
  253. public ListPopup createActionGroupPopup(String title,
  254. ActionGroup actionGroup,
  255. @NotNull DataContext dataContext,
  256. ActionSelectionAid selectionAidMethod,
  257. boolean showDisabledActions,
  258. Runnable disposeCallback,
  259. int maxRowCount) {
  260. return createActionGroupPopup(title, actionGroup, dataContext,
  261. selectionAidMethod == ActionSelectionAid.NUMBERING || selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
  262. selectionAidMethod == ActionSelectionAid.ALPHA_NUMBERING,
  263. showDisabledActions,
  264. selectionAidMethod == ActionSelectionAid.MNEMONICS,
  265. disposeCallback,
  266. maxRowCount);
  267. }
  268. public ListPopupStep createActionsStep(final ActionGroup actionGroup,
  269. @NotNull DataContext dataContext,
  270. final boolean showNumbers,
  271. final boolean showDisabledActions,
  272. final String title,
  273. final Component component,
  274. final boolean honorActionMnemonics) {
  275. return createActionsStep(actionGroup, dataContext, showNumbers, showDisabledActions, title, component, honorActionMnemonics, 0, false);
  276. }
  277. private static ListPopupStep createActionsStep(ActionGroup actionGroup, @NotNull DataContext dataContext,
  278. boolean showNumbers, boolean useAlphaAsNumbers, boolean showDisabledActions,
  279. String title, Component component, boolean honorActionMnemonics,
  280. final int defaultOptionIndex, final boolean autoSelectionEnabled) {
  281. final List<ActionItem> items = makeActionItemsFromActionGroup(actionGroup, dataContext, showNumbers, useAlphaAsNumbers,
  282. showDisabledActions, honorActionMnemonics);
  283. return new ActionPopupStep(items, title, component, showNumbers || honorActionMnemonics && itemsHaveMnemonics(items),
  284. new Condition<AnAction>() {
  285. @Override
  286. public boolean value(AnAction action) {
  287. return defaultOptionIndex >= 0 &&
  288. defaultOptionIndex < items.size() &&
  289. items.get(defaultOptionIndex).getAction().equals(action);
  290. }
  291. }, autoSelectionEnabled, showDisabledActions);
  292. }
  293. private static List<ActionItem> makeActionItemsFromActionGroup(ActionGroup actionGroup,
  294. DataContext dataContext,
  295. boolean showNumbers,
  296. boolean useAlphaAsNumbers,
  297. boolean showDisabledActions,
  298. boolean honorActionMnemonics) {
  299. final ActionStepBuilder builder = new ActionStepBuilder(dataContext, showNumbers, useAlphaAsNumbers, showDisabledActions,
  300. honorActionMnemonics);
  301. builder.buildGroup(actionGroup);
  302. return builder.getItems();
  303. }
  304. private static ListPopupStep createActionsStep(ActionGroup actionGroup, @NotNull DataContext dataContext,
  305. boolean showNumbers, boolean useAlphaAsNumbers, boolean showDisabledActions,
  306. String title, Component component, boolean honorActionMnemonics,
  307. Condition<AnAction> preselectActionCondition, boolean autoSelectionEnabled) {
  308. final List<ActionItem> items = makeActionItemsFromActionGroup(actionGroup, dataContext, showNumbers, useAlphaAsNumbers,
  309. showDisabledActions, honorActionMnemonics);
  310. return new ActionPopupStep(items, title, component, showNumbers || honorActionMnemonics && itemsHaveMnemonics(items), preselectActionCondition,
  311. autoSelectionEnabled, showDisabledActions);
  312. }
  313. public ListPopupStep createActionsStep(ActionGroup actionGroup, @NotNull DataContext dataContext, boolean showNumbers, boolean showDisabledActions,
  314. String title, Component component, boolean honorActionMnemonics, int defaultOptionIndex,
  315. final boolean autoSelectionEnabled) {
  316. return createActionsStep(actionGroup, dataContext, showNumbers, true, showDisabledActions, title, component, honorActionMnemonics,
  317. defaultOptionIndex, autoSelectionEnabled);
  318. }
  319. private static boolean itemsHaveMnemonics(final List<ActionItem> items) {
  320. for (ActionItem item : items) {
  321. if (item.getAction().getTemplatePresentation().getMnemonic() != 0) return true;
  322. }
  323. return false;
  324. }
  325. public ListPopup createWizardStep(PopupStep step) {
  326. return new ListPopupImpl((ListPopupStep) step);
  327. }
  328. public ListPopup createListPopup(ListPopupStep step) {
  329. return new ListPopupImpl(step);
  330. }
  331. public TreePopup createTree(JBPopup parent, TreePopupStep aStep, Object parentValue) {
  332. return new TreePopupImpl(parent, aStep, parentValue);
  333. }
  334. public TreePopup createTree(TreePopupStep aStep) {
  335. return new TreePopupImpl(aStep);
  336. }
  337. public ComponentPopupBuilder createComponentPopupBuilder(JComponent content, JComponent prefferableFocusComponent) {
  338. return new ComponentPopupBuilderImpl(content, prefferableFocusComponent);
  339. }
  340. public RelativePoint guessBestPopupLocation(@NotNull DataContext dataContext) {
  341. KeyboardFocusManager focusManager=KeyboardFocusManager.getCurrentKeyboardFocusManager();
  342. Component component = focusManager.getFocusOwner();
  343. JComponent focusOwner=component instanceof JComponent ? (JComponent)component : null;
  344. if (focusOwner == null) {
  345. Project project = PlatformDataKeys.PROJECT.getData(dataContext);
  346. IdeFrameImpl frame = project == null ? null : ((WindowManagerEx)WindowManager.getInstance()).getFrame(project);
  347. focusOwner = frame == null ? null : frame.getRootPane();
  348. if (focusOwner == null) {
  349. throw new IllegalArgumentException("focusOwner cannot be null");
  350. }
  351. }
  352. final Point point = PlatformDataKeys.CONTEXT_MENU_POINT.getData(dataContext);
  353. if (point != null) {
  354. return new RelativePoint(focusOwner, point);
  355. }
  356. Editor editor = PlatformDataKeys.EDITOR.getData(dataContext);
  357. if (editor != null && focusOwner == editor.getContentComponent()) {
  358. return guessBestPopupLocation(editor);
  359. }
  360. else {
  361. return guessBestPopupLocation(focusOwner);
  362. }
  363. }
  364. public RelativePoint guessBestPopupLocation(final JComponent component) {
  365. Point popupMenuPoint = null;
  366. final Rectangle visibleRect = component.getVisibleRect();
  367. if (component instanceof JList) { // JList
  368. JList list = (JList)component;
  369. int firstVisibleIndex = list.getFirstVisibleIndex();
  370. int lastVisibleIndex = list.getLastVisibleIndex();
  371. int[] selectedIndices = list.getSelectedIndices();
  372. for (int index : selectedIndices) {
  373. if (firstVisibleIndex <= index && index <= lastVisibleIndex) {
  374. Rectangle cellBounds = list.getCellBounds(index, index);
  375. popupMenuPoint = new Point(visibleRect.x + visibleRect.width / 4, cellBounds.y + cellBounds.height);
  376. break;
  377. }
  378. }
  379. }
  380. else if (component instanceof JTree) { // JTree
  381. JTree tree = (JTree)component;
  382. int[] selectionRows = tree.getSelectionRows();
  383. if (selectionRows != null) {
  384. Arrays.sort(selectionRows);
  385. for (int i = 0; i < selectionRows.length; i++) {
  386. int row = selectionRows[i];
  387. Rectangle rowBounds = tree.getRowBounds(row);
  388. if (visibleRect.contains(rowBounds)) {
  389. popupMenuPoint = new Point(rowBounds.x + 2, rowBounds.y + rowBounds.height - 1);
  390. break;
  391. }
  392. }
  393. if (popupMenuPoint == null) {//All selected rows are out of visible rect
  394. Point visibleCenter = new Point(visibleRect.x + visibleRect.width / 2, visibleRect.y + visibleRect.height / 2);
  395. double minDistance = Double.POSITIVE_INFINITY;
  396. int bestRow = -1;
  397. Point rowCenter;
  398. double distance;
  399. for (int i = 0; i < selectionRows.length; i++) {
  400. int row = selectionRows[i];
  401. Rectangle rowBounds = tree.getRowBounds(row);
  402. rowCenter = new Point(rowBounds.x + rowBounds.width / 2, rowBounds.y + rowBounds.height / 2);
  403. distance = visibleCenter.distance(rowCenter);
  404. if (minDistance > distance) {
  405. minDistance = distance;
  406. bestRow = row;
  407. }
  408. }
  409. if (bestRow != -1) {
  410. Rectangle rowBounds = tree.getRowBounds(bestRow);
  411. tree.scrollRectToVisible(new Rectangle(rowBounds.x, rowBounds.y, Math.min(visibleRect.width, rowBounds.width), rowBounds.height));
  412. popupMenuPoint = new Point(rowBounds.x + 2, rowBounds.y + rowBounds.height - 1);
  413. }
  414. }
  415. }
  416. } else if (component instanceof PopupOwner){
  417. popupMenuPoint = ((PopupOwner)component).getBestPopupPosition();
  418. }
  419. // TODO[vova] add usability for JTable
  420. if (popupMenuPoint == null) {
  421. popupMenuPoint = new Point(visibleRect.x + visibleRect.width / 2, visibleRect.y + visibleRect.height / 2);
  422. }
  423. return new RelativePoint(component, popupMenuPoint);
  424. }
  425. @Override
  426. public boolean isBestPopupLocationVisible(Editor editor) {
  427. return getVisibleBestPopupLocation(editor) != null;
  428. }
  429. public RelativePoint guessBestPopupLocation(Editor editor) {
  430. Point p = getVisibleBestPopupLocation(editor);
  431. if (p == null) {
  432. final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
  433. p = new Point((visibleArea.x + visibleArea.width) / 2, (visibleArea.y + visibleArea.height) / 2);
  434. }
  435. return new RelativePoint(editor.getContentComponent(), p);
  436. }
  437. @Nullable
  438. private static Point getVisibleBestPopupLocation(Editor editor) {
  439. VisualPosition visualPosition = editor.getUserData(ANCHOR_POPUP_POSITION);
  440. if (visualPosition == null) {
  441. CaretModel caretModel = editor.getCaretModel();
  442. if (caretModel.isUpToDate()) {
  443. visualPosition = caretModel.getVisualPosition();
  444. }
  445. else {
  446. visualPosition = editor.offsetToVisualPosition(caretModel.getOffset());
  447. }
  448. }
  449. Point p = editor.visualPositionToXY(new VisualPosition(visualPosition.line + 1, visualPosition.column));
  450. final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
  451. return visibleArea.contains(p) ? p : null;
  452. }
  453. public Point getCenterOf(JComponent container, JComponent content) {
  454. return AbstractPopup.getCenterOf(container, content);
  455. }
  456. public static class ActionItem {
  457. private final AnAction myAction;
  458. private final String myText;
  459. private final boolean myIsEnabled;
  460. private final Icon myIcon;
  461. private final boolean myPrependWithSeparator;
  462. private final String mySeparatorText;
  463. private ActionItem(@NotNull AnAction action, @NotNull String text, boolean enabled, Icon icon, final boolean prependWithSeparator, String separatorText) {
  464. myAction = action;
  465. myText = text;
  466. myIsEnabled = enabled;
  467. myIcon = icon;
  468. myPrependWithSeparator = prependWithSeparator;
  469. mySeparatorText = separatorText;
  470. }
  471. @NotNull
  472. public AnAction getAction() {
  473. return myAction;
  474. }
  475. @NotNull
  476. public String getText() {
  477. return myText;
  478. }
  479. public Icon getIcon() {
  480. return myIcon;
  481. }
  482. public boolean isPrependWithSeparator() {
  483. return myPrependWithSeparator;
  484. }
  485. public String getSeparatorText() {
  486. return mySeparatorText;
  487. }
  488. public boolean isEnabled() { return myIsEnabled; }
  489. }
  490. private static class ActionPopupStep implements ListPopupStep<ActionItem>, MnemonicNavigationFilter<ActionItem>, SpeedSearchFilter<ActionItem> {
  491. private final List<ActionItem> myItems;
  492. private final String myTitle;
  493. private final Component myContext;
  494. private final boolean myEnableMnemonics;
  495. private final int myDefaultOptionIndex;
  496. private final boolean myAutoSelectionEnabled;
  497. private final boolean myShowDisabledActions;
  498. private Runnable myFinalRunnable;
  499. @Nullable private final Condition<AnAction> myPreselectActionCondition;
  500. private ActionPopupStep(@NotNull final List<ActionItem> items, final String title, Component context, boolean enableMnemonics,
  501. @Nullable Condition<AnAction> preselectActionCondition, final boolean autoSelection, boolean showDisabledActions) {
  502. myItems = items;
  503. myTitle = title;
  504. myContext = context;
  505. myEnableMnemonics = enableMnemonics;
  506. myDefaultOptionIndex = getDefaultOptionIndexFromSelectCondition(preselectActionCondition, items);
  507. myPreselectActionCondition = preselectActionCondition;
  508. myAutoSelectionEnabled = autoSelection;
  509. myShowDisabledActions = showDisabledActions;
  510. }
  511. private static int getDefaultOptionIndexFromSelectCondition(@Nullable Condition<AnAction> preselectActionCondition,
  512. @NotNull List<ActionItem> items) {
  513. int defaultOptionIndex = 0;
  514. if (preselectActionCondition != null) {
  515. for (int i = 0; i < items.size(); i++) {
  516. final AnAction action = items.get(i).getAction();
  517. if (preselectActionCondition.value(action)) {
  518. defaultOptionIndex = i;
  519. break;
  520. }
  521. }
  522. }
  523. return defaultOptionIndex;
  524. }
  525. @NotNull
  526. public List<ActionItem> getValues() {
  527. return myItems;
  528. }
  529. public boolean isSelectable(final ActionItem value) {
  530. return value.isEnabled();
  531. }
  532. public int getMnemonicPos(final ActionItem value) {
  533. final String text = getTextFor(value);
  534. int i = text.indexOf(UIUtil.MNEMONIC);
  535. if (i < 0) {
  536. i = text.indexOf('&');
  537. }
  538. if (i < 0) {
  539. i = text.indexOf('_');
  540. }
  541. return i;
  542. }
  543. public Icon getIconFor(final ActionItem aValue) {
  544. return aValue.getIcon();
  545. }
  546. @NotNull
  547. public String getTextFor(final ActionItem value) {
  548. return value.getText();
  549. }
  550. public ListSeparator getSeparatorAbove(final ActionItem value) {
  551. return value.isPrependWithSeparator() ? new ListSeparator(value.getSeparatorText()) : null;
  552. }
  553. public int getDefaultOptionIndex() {
  554. return myDefaultOptionIndex;
  555. }
  556. public String getTitle() {
  557. return myTitle;
  558. }
  559. public PopupStep onChosen(final ActionItem actionChoice, final boolean finalChoice) {
  560. if (!actionChoice.isEnabled()) return FINAL_CHOICE;
  561. final AnAction action = actionChoice.getAction();
  562. DataManager mgr = DataManager.getInstance();
  563. final DataContext dataContext = myContext != null ? mgr.getDataContext(myContext) : mgr.getDataContext();
  564. if (action instanceof ActionGroup && (!finalChoice || !((ActionGroup)action).canBePerformed(dataContext))) {
  565. return createActionsStep((ActionGroup)action, dataContext, myEnableMnemonics, true, myShowDisabledActions, null, myContext, false,
  566. myPreselectActionCondition, false);
  567. }
  568. else {
  569. myFinalRunnable = new Runnable() {
  570. public void run() {
  571. action.actionPerformed(
  572. new AnActionEvent(null, dataContext, ActionPlaces.UNKNOWN, action.getTemplatePresentation().clone(),
  573. ActionManager.getInstance(), 0));
  574. }
  575. };
  576. return FINAL_CHOICE;
  577. }
  578. }
  579. public Runnable getFinalRunnable() {
  580. return myFinalRunnable;
  581. }
  582. public boolean hasSubstep(final ActionItem selectedValue) {
  583. return selectedValue != null && selectedValue.isEnabled() && selectedValue.getAction() instanceof ActionGroup;
  584. }
  585. public void canceled() {
  586. }
  587. public boolean isMnemonicsNavigationEnabled() {
  588. return myEnableMnemonics;
  589. }
  590. public MnemonicNavigationFilter<ActionItem> getMnemonicNavigationFilter() {
  591. return this;
  592. }
  593. public boolean canBeHidden(final ActionItem value) {
  594. return true;
  595. }
  596. public String getIndexedString(final ActionItem value) {
  597. return getTextFor(value);
  598. }
  599. public boolean isSpeedSearchEnabled() {
  600. return true;
  601. }
  602. public boolean isAutoSelectionEnabled() {
  603. return myAutoSelectionEnabled;
  604. }
  605. public SpeedSearchFilter<ActionItem> getSpeedSearchFilter() {
  606. return this;
  607. }
  608. }
  609. @Nullable
  610. public List<JBPopup> getChildPopups(@NotNull final Component component) {
  611. return FocusTrackback.getChildPopups(component);
  612. }
  613. @Override
  614. public boolean isPopupActive() {
  615. return IdeEventQueue.getInstance().isPopupActive();
  616. }
  617. private static class ActionStepBuilder {
  618. private final List<ActionItem> myListModel;
  619. private final DataContext myDataContext;
  620. private final boolean myShowNumbers;
  621. private final boolean myUseAlphaAsNumbers;
  622. private final boolean myShowDisabled;
  623. private final HashMap<AnAction, Presentation> myAction2presentation;
  624. private int myCurrentNumber;
  625. private boolean myPrependWithSeparator;
  626. private String mySeparatorText;
  627. private final boolean myHonorActionMnemonics;
  628. private Icon myEmptyIcon;
  629. private int myMaxIconWidth = -1;
  630. private int myMaxIconHeight = -1;
  631. @NotNull private String myActionPlace;
  632. private ActionStepBuilder(@NotNull DataContext dataContext, final boolean showNumbers, final boolean useAlphaAsNumbers,
  633. final boolean showDisabled, final boolean honorActionMnemonics)
  634. {
  635. myUseAlphaAsNumbers = useAlphaAsNumbers;
  636. myListModel = new ArrayList<ActionItem>();
  637. myDataContext = dataContext;
  638. myShowNumbers = showNumbers;
  639. myShowDisabled = showDisabled;
  640. myAction2presentation = new HashMap<AnAction, Presentation>();
  641. myCurrentNumber = 0;
  642. myPrependWithSeparator = false;
  643. mySeparatorText = null;
  644. myHonorActionMnemonics = honorActionMnemonics;
  645. myActionPlace = ActionPlaces.UNKNOWN;
  646. }
  647. public void setActionPlace(@NotNull String actionPlace) {
  648. myActionPlace = actionPlace;
  649. }
  650. public List<ActionItem> getItems() {
  651. return myListModel;
  652. }
  653. public void buildGroup(ActionGroup actionGroup) {
  654. calcMaxIconSize(actionGroup);
  655. myEmptyIcon = myMaxIconHeight != -1 && myMaxIconWidth != -1 ? new EmptyIcon(myMaxIconWidth, myMaxIconHeight) : null;
  656. appendActionsFromGroup(actionGroup);
  657. if (myListModel.isEmpty()) {
  658. myListModel.add(new ActionItem(Utils.EMPTY_MENU_FILLER, Utils.NOTHING_HERE, false, null, false, null));
  659. }
  660. }
  661. private void calcMaxIconSize(final ActionGroup actionGroup) {
  662. AnAction[] actions = actionGroup.getChildren(new AnActionEvent(null, myDataContext, myActionPlace,
  663. getPresentation(actionGroup), ActionManager.getInstance(), 0));
  664. for (AnAction action : actions) {
  665. if (action == null) continue;
  666. if (action instanceof ActionGroup) {
  667. final ActionGroup group = (ActionGroup)action;
  668. if (!group.isPopup()) {
  669. calcMaxIconSize(group);
  670. continue;
  671. }
  672. }
  673. Icon icon = action.getTemplatePresentation().getIcon();
  674. if (icon != null) {
  675. final int width = icon.getIconWidth();
  676. final int height = icon.getIconHeight();
  677. if (myMaxIconWidth < width) {
  678. myMaxIconWidth = width;
  679. }
  680. if (myMaxIconHeight < height) {
  681. myMaxIconHeight = height;
  682. }
  683. }
  684. }
  685. }
  686. private void appendActionsFromGroup(final ActionGroup actionGroup) {
  687. AnAction[] actions = actionGroup.getChildren(new AnActionEvent(null, myDataContext, myActionPlace,
  688. getPresentation(actionGroup), ActionManager.getInstance(), 0));
  689. for (AnAction action : actions) {
  690. if (action == null) {
  691. LOG.error("null action in group " + actionGroup);
  692. continue;
  693. }
  694. if (action instanceof Separator) {
  695. myPrependWithSeparator = true;
  696. mySeparatorText = ((Separator)action).getText();
  697. }
  698. else {
  699. if (action instanceof ActionGroup) {
  700. ActionGroup group = (ActionGroup)action;
  701. if (group.isPopup()) {
  702. appendAction(group);
  703. }
  704. else {
  705. appendActionsFromGroup(group);
  706. }
  707. }
  708. else {
  709. appendAction(action);
  710. }
  711. }
  712. }
  713. }
  714. private void appendAction(@NotNull AnAction action) {
  715. Presentation presentation = getPresentation(action);
  716. AnActionEvent event = new AnActionEvent(null, myDataContext, myActionPlace, presentation, ActionManager.getInstance(), 0);
  717. ActionUtil.performDumbAwareUpdate(action, event, true);
  718. if ((myShowDisabled || presentation.isEnabled()) && presentation.isVisible()) {
  719. String text = presentation.getText();
  720. if (myShowNumbers) {
  721. if (myCurrentNumber < 9) {
  722. text = "&" + (myCurrentNumber + 1) + ". " + text;
  723. }
  724. else if (myCurrentNumber == 9) {
  725. text = "&" + 0 + ". " + text;
  726. }
  727. else if (myUseAlphaAsNumbers) {
  728. text = "&" + (char)('A' + myCurrentNumber - 10) + ". " + text;
  729. }
  730. myCurrentNumber++;
  731. }
  732. else if (myHonorActionMnemonics) {
  733. text = Presentation.restoreTextWithMnemonic(text, action.getTemplatePresentation().getMnemonic());
  734. }
  735. Icon icon = presentation.getIcon();
  736. if (icon == null) {
  737. @NonNls final String actionId = ActionManager.getInstance().getId(action);
  738. icon = actionId != null && actionId.startsWith("QuickList.") ? AllIcons.Actions.QuickList : myEmptyIcon;
  739. }
  740. else {
  741. icon = new IconWrapper(icon);
  742. }
  743. boolean prependSeparator = (!myListModel.isEmpty() || mySeparatorText != null) && myPrependWithSeparator;
  744. assert text != null : action + " has no presentation";
  745. myListModel.add(new ActionItem(action, text, presentation.isEnabled(), icon, prependSeparator, mySeparatorText));
  746. myPrependWithSeparator = false;
  747. mySeparatorText = null;
  748. }
  749. }
  750. /**
  751. * Adjusts icon size to maximum, so that icons with different sizes were aligned correctly.
  752. */
  753. private class IconWrapper implements Icon {
  754. private Icon myIcon;
  755. IconWrapper(Icon icon) {
  756. myIcon = icon;
  757. }
  758. @Override
  759. public void paintIcon(Component c, Graphics g, int x, int y) {
  760. myIcon.paintIcon(c, g, x, y);
  761. }
  762. @Override
  763. public int getIconWidth() {
  764. return myMaxIconWidth;
  765. }
  766. @Override
  767. public int getIconHeight() {
  768. return myMaxIconHeight;
  769. }
  770. }
  771. private Presentation getPresentation(@NotNull AnAction action) {
  772. Presentation presentation = myAction2presentation.get(action);
  773. if (presentation == null) {
  774. presentation = action.getTemplatePresentation().clone();
  775. myAction2presentation.put(action, presentation);
  776. }
  777. return presentation;
  778. }
  779. }
  780. public BalloonBuilder createBalloonBuilder(@NotNull final JComponent content) {
  781. return new BalloonPopupBuilderImpl(myStorage, content);
  782. }
  783. @Override
  784. public BalloonBuilder createDialogBalloonBuilder(@NotNull JComponent content, String title) {
  785. final BalloonPopupBuilderImpl builder = new BalloonPopupBuilderImpl(myStorage, content);
  786. final Color bg = UIManager.getColor("Panel.background");
  787. final Color borderOriginal = Color.darkGray;
  788. final Color border = ColorUtil.toAlpha(borderOriginal, 75);
  789. builder
  790. .setDialogMode(true)
  791. .setTitle(title)
  792. .setAnimationCycle(200)
  793. .setFillColor(bg)
  794. .setBorderColor(border)
  795. .setHideOnClickOutside(false)
  796. .setHideOnKeyOutside(false)
  797. .setHideOnAction(false)
  798. .setCloseButtonEnabled(true)
  799. .setShadow(true);
  800. return builder;
  801. }
  802. public BalloonBuilder createHtmlTextBalloonBuilder(@NotNull final String htmlContent, @Nullable final Icon icon, final Color fillColor,
  803. @Nullable final HyperlinkListener listener)
  804. {
  805. JEditorPane text = IdeTooltipManager.initPane(htmlContent, new HintHint().setAwtTooltip(true), null);
  806. if (listener != null) {
  807. text.addHyperlinkListener(listener);
  808. }
  809. text.setEditable(false);
  810. NonOpaquePanel.setTransparent(text);
  811. text.setBorder(null);
  812. JLabel label = new JLabel();
  813. final JPanel content = new NonOpaquePanel(new BorderLayout((int)(label.getIconTextGap() * 1.5), (int)(label.getIconTextGap() * 1.5)));
  814. final NonOpaquePanel textWrapper = new NonOpaquePanel(new GridBagLayout());
  815. JScrollPane scrolledText = new JScrollPane(text);
  816. scrolledText.setBackground(fillColor);
  817. scrolledText.getViewport().setBackground(fillColor);
  818. scrolledText.getViewport().setBorder(null);
  819. scrolledText.setBorder(null);
  820. textWrapper.add(scrolledText);
  821. content.add(textWrapper, BorderLayout.CENTER);
  822. final NonOpaquePanel north = new NonOpaquePanel(new BorderLayout());
  823. north.add(new JLabel(icon), BorderLayout.NORTH);
  824. content.add(north, BorderLayout.WEST);
  825. content.setBorder(new EmptyBorder(2, 4, 2, 4));
  826. final BalloonBuilder builder = createBalloonBuilder(content);
  827. builder.setFillColor(fillColor);
  828. return builder;
  829. }
  830. @Override
  831. public BalloonBuilder createHtmlTextBalloonBuilder(@NotNull String htmlContent,
  832. MessageType messageType,
  833. @Nullable HyperlinkListener listener)
  834. {
  835. return createHtmlTextBalloonBuilder(htmlContent, messageType.getDefaultIcon(), messageType.getPopupBackground(), listener);
  836. }
  837. }