2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.ui.popup;
18 import com.intellij.codeInsight.hint.HintUtil;
19 import com.intellij.icons.AllIcons;
20 import com.intellij.ide.DataManager;
21 import com.intellij.ide.IdeEventQueue;
22 import com.intellij.ide.UiActivity;
23 import com.intellij.ide.UiActivityMonitor;
24 import com.intellij.openapi.Disposable;
25 import com.intellij.openapi.actionSystem.CommonDataKeys;
26 import com.intellij.openapi.actionSystem.DataContext;
27 import com.intellij.openapi.actionSystem.DataProvider;
28 import com.intellij.openapi.actionSystem.PlatformDataKeys;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.application.ModalityState;
31 import com.intellij.openapi.application.TransactionGuard;
32 import com.intellij.openapi.application.TransactionGuardImpl;
33 import com.intellij.openapi.application.ex.ApplicationManagerEx;
34 import com.intellij.openapi.diagnostic.Logger;
35 import com.intellij.openapi.editor.Editor;
36 import com.intellij.openapi.editor.ex.EditorEx;
37 import com.intellij.openapi.project.Project;
38 import com.intellij.openapi.ui.popup.*;
39 import com.intellij.openapi.util.*;
40 import com.intellij.openapi.util.registry.Registry;
41 import com.intellij.openapi.wm.*;
42 import com.intellij.openapi.wm.ex.WindowManagerEx;
43 import com.intellij.openapi.wm.impl.IdeGlassPaneImpl;
44 import com.intellij.ui.*;
45 import com.intellij.ui.awt.RelativePoint;
46 import com.intellij.ui.components.JBLabel;
47 import com.intellij.ui.speedSearch.SpeedSearch;
48 import com.intellij.util.Alarm;
49 import com.intellij.util.BooleanFunction;
50 import com.intellij.util.IJSwingUtilities;
51 import com.intellij.util.Processor;
52 import com.intellij.util.ui.*;
53 import com.intellij.util.ui.accessibility.AccessibleContextUtil;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
59 import javax.swing.text.JTextComponent;
61 import java.awt.event.*;
62 import java.util.HashSet;
63 import java.util.List;
66 public class AbstractPopup implements JBPopup {
67 public static final String SHOW_HINTS = "ShowHints";
69 private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.AbstractPopup");
71 private static final Object SUPPRESS_MAC_CORNER = new Object();
73 // X server sometimes focuses focusable popups upon appearance, ignoring the fact that we didn't ask to focus them (IDEA-94683)
74 private static final boolean X_WINDOW_FOCUS_BUG = SystemInfo.isXWindow;
76 private PopupComponent myPopup;
77 private MyContentPanel myContent;
78 private JComponent myPreferredFocusedComponent;
79 private boolean myRequestFocus;
80 private boolean myFocusable;
81 private boolean myForcedHeavyweight;
82 private boolean myLocateWithinScreen;
83 private boolean myResizable = false;
84 private WindowResizeListener myResizeListener;
85 private WindowMoveListener myMoveListener;
86 private JPanel myHeaderPanel;
87 private CaptionPanel myCaption = null;
88 private JComponent myComponent;
89 private String myDimensionServiceKey = null;
90 private Computable<Boolean> myCallBack = null;
91 private Project myProject;
92 private boolean myCancelOnClickOutside;
93 private Set<JBPopupListener> myListeners;
94 private boolean myUseDimServiceForXYLocation;
95 private MouseChecker myCancelOnMouseOutCallback;
96 private Canceller myMouseOutCanceller;
97 private boolean myCancelOnWindow;
98 private boolean myCancelOnWindowDeactivation = true;
99 private Dimension myForcedSize;
100 private Point myForcedLocation;
101 private boolean myCancelKeyEnabled;
102 private boolean myLocateByContent;
103 protected FocusTrackback myFocusTrackback;
104 private Dimension myMinSize;
105 private List<Object> myUserData;
106 private boolean myShadowed;
108 private float myAlpha = 0;
109 private float myLastAlpha = 0;
111 private MaskProvider myMaskProvider;
113 private Window myWindow;
114 private boolean myInStack;
115 private MyWindowListener myWindowListener;
117 private boolean myModalContext;
119 private Component[] myFocusOwners;
120 private PopupBorder myPopupBorder;
121 private Dimension myRestoreWindowSize;
122 protected Component myOwner;
123 protected Component myRequestorComponent;
124 private boolean myHeaderAlwaysFocusable;
125 private boolean myMovable;
126 private JComponent myHeaderComponent;
128 protected InputEvent myDisposeEvent;
130 private Runnable myFinalRunnable;
131 @Nullable private BooleanFunction<KeyEvent> myKeyEventHandler;
133 protected boolean myOk;
135 protected final SpeedSearch mySpeedSearch = new SpeedSearch() {
136 boolean searchFieldShown = false;
139 public void update() {
140 mySpeedSearchPatternField.setBackground(new JTextField().getBackground());
141 onSpeedSearchPatternChanged();
142 mySpeedSearchPatternField.setText(getFilter());
143 if (isHoldingFilter() && !searchFieldShown) {
144 setHeaderComponent(mySpeedSearchPatternField);
145 searchFieldShown = true;
147 else if (!isHoldingFilter() && searchFieldShown) {
148 setHeaderComponent(null);
149 searchFieldShown = false;
154 public void noHits() {
155 mySpeedSearchPatternField.setBackground(LightColors.RED);
159 private JTextField mySpeedSearchPatternField;
160 private boolean myNativePopup;
161 private boolean myMayBeParent;
162 private AbstractPopup.SpeedSearchKeyListener mySearchKeyListener;
163 private JLabel myAdComponent;
164 private boolean myDisposed;
166 private UiActivity myActivityKey;
167 private Disposable myProjectDisposable;
169 private volatile State myState = State.NEW;
171 private enum State {NEW, INIT, SHOWING, SHOWN, CANCEL, DISPOSE}
173 private void debugState(String message, State... states) {
174 if (LOG.isDebugEnabled()) {
175 LOG.debug(hashCode() + " - " + message);
176 if (!ApplicationManager.getApplication().isDispatchThread()) {
177 LOG.debug("unexpected thread");
179 for (State state : states) {
180 if (state == myState) {
184 LOG.debug(new IllegalStateException("myState=" + myState));
190 AbstractPopup init(Project project,
191 @NotNull JComponent component,
192 @Nullable JComponent preferredFocusedComponent,
193 boolean requestFocus,
196 String dimensionServiceKey,
198 @Nullable String caption,
199 @Nullable Computable<Boolean> callback,
200 boolean cancelOnClickOutside,
201 @Nullable Set<JBPopupListener> listeners,
202 boolean useDimServiceForXYLocation,
203 ActiveComponent commandButton,
204 @Nullable IconButton cancelButton,
205 @Nullable MouseChecker cancelOnMouseOutCallback,
206 boolean cancelOnWindow,
207 @Nullable ActiveIcon titleIcon,
208 boolean cancelKeyEnabled,
209 boolean locateByContent,
210 boolean placeWithinScreenBounds,
211 @Nullable Dimension minSize,
213 @Nullable MaskProvider maskProvider,
215 boolean modalContext,
216 @Nullable Component[] focusOwners,
217 @Nullable String adText,
219 boolean headerAlwaysFocusable,
220 @NotNull List<Pair<ActionListener, KeyStroke>> keyboardActions,
221 Component settingsButtons,
222 @Nullable final Processor<JBPopup> pinCallback,
226 boolean cancelOnWindowDeactivation,
227 @Nullable BooleanFunction<KeyEvent> keyEventHandler)
229 if (requestFocus && !focusable) {
230 assert false : "Incorrect argument combination: requestFocus=true focusable=false";
233 myActivityKey = new UiActivity.Focus("Popup:" + this);
235 myComponent = component;
236 myPopupBorder = showBorder ? PopupBorder.Factory.create(true, showShadow) : PopupBorder.Factory.createEmpty();
237 myShadowed = showShadow;
238 myContent = createContentPanel(resizable, myPopupBorder, isToDrawMacCorner() && resizable);
239 myMayBeParent = mayBeParent;
240 myCancelOnWindowDeactivation = cancelOnWindowDeactivation;
242 myContent.add(component, BorderLayout.CENTER);
243 if (adText != null) {
244 setAdText(adText, adTextAlignment);
247 myCancelKeyEnabled = cancelKeyEnabled;
248 myLocateByContent = locateByContent;
249 myLocateWithinScreen = placeWithinScreenBounds;
251 myMaskProvider = maskProvider;
253 myModalContext = modalContext;
254 myFocusOwners = focusOwners;
255 myHeaderAlwaysFocusable = headerAlwaysFocusable;
258 ActiveIcon actualIcon = titleIcon == null ? new ActiveIcon(EmptyIcon.ICON_0) : titleIcon;
260 myHeaderPanel = new JPanel(new BorderLayout());
262 if (caption != null) {
263 if (!caption.isEmpty()) {
264 myCaption = new TitlePanel(actualIcon.getRegular(), actualIcon.getInactive());
265 ((TitlePanel)myCaption).setText(caption);
268 myCaption = new CaptionPanel();
271 if (pinCallback != null) {
272 myCaption.setButtonComponent(new InplaceButton(
273 new IconButton("Open as Tool Window",
274 AllIcons.General.AutohideOff, AllIcons.General.AutohideOff, AllIcons.General.AutohideOffInactive),
275 e -> pinCallback.process(this)
276 ), JBUI.Borders.empty(4));
278 else if (cancelButton != null) {
279 myCaption.setButtonComponent(new InplaceButton(cancelButton, e -> cancel()), JBUI.Borders.empty(4));
281 else if (commandButton != null) {
282 myCaption.setButtonComponent(commandButton, null);
286 myCaption = new CaptionPanel();
287 myCaption.setBorder(null);
288 myCaption.setPreferredSize(JBUI.emptySize());
291 setWindowActive(myHeaderAlwaysFocusable);
293 myHeaderPanel.add(myCaption, BorderLayout.NORTH);
294 myContent.add(myHeaderPanel, BorderLayout.NORTH);
296 myForcedHeavyweight = true;
297 myResizable = resizable;
298 myPreferredFocusedComponent = preferredFocusedComponent;
299 myRequestFocus = requestFocus;
300 myFocusable = focusable;
301 myDimensionServiceKey = dimensionServiceKey;
302 myCallBack = callback;
303 myCancelOnClickOutside = cancelOnClickOutside;
304 myCancelOnMouseOutCallback = cancelOnMouseOutCallback;
305 myListeners = listeners == null ? new HashSet<JBPopupListener>() : listeners;
306 myUseDimServiceForXYLocation = useDimServiceForXYLocation;
307 myCancelOnWindow = cancelOnWindow;
310 for (Pair<ActionListener, KeyStroke> pair : keyboardActions) {
311 myContent.registerKeyboardAction(pair.getFirst(), pair.getSecond(), JComponent.WHEN_IN_FOCUSED_WINDOW);
314 if (settingsButtons != null) {
315 myCaption.addSettingsComponent(settingsButtons);
318 myKeyEventHandler = keyEventHandler;
319 debugState("popup initialized", State.NEW);
320 myState = State.INIT;
324 private void setWindowActive(boolean active) {
325 boolean value = myHeaderAlwaysFocusable || active;
327 if (myCaption != null) {
328 myCaption.setActive(value);
330 myPopupBorder.setActive(value);
336 protected MyContentPanel createContentPanel(final boolean resizable, PopupBorder border, boolean isToDrawMacCorner) {
337 return new MyContentPanel(resizable, border, isToDrawMacCorner);
340 public boolean isToDrawMacCorner() {
341 if (!SystemInfo.isMac || myComponent.getComponentCount() <= 0) {
345 if (SystemInfo.isMacOSYosemite) {
349 if (myComponent.getComponentCount() > 0) {
350 Component component = myComponent.getComponent(0);
351 if (component instanceof JComponent && Boolean.TRUE.equals(((JComponent)component).getClientProperty(SUPPRESS_MAC_CORNER))) {
359 public void setShowHints(boolean show) {
360 final Window ancestor = getContentWindow(myComponent);
361 if (ancestor instanceof RootPaneContainer) {
362 final JRootPane rootPane = ((RootPaneContainer)ancestor).getRootPane();
363 if (rootPane != null) {
364 rootPane.putClientProperty(SHOW_HINTS, Boolean.valueOf(show));
369 public static void suppressMacCornerFor(JComponent popupComponent) {
370 popupComponent.putClientProperty(SUPPRESS_MAC_CORNER, Boolean.TRUE);
374 public String getDimensionServiceKey() {
375 return myDimensionServiceKey;
378 public void setDimensionServiceKey(@Nullable final String dimensionServiceKey) {
379 myDimensionServiceKey = dimensionServiceKey;
383 public void showInCenterOf(@NotNull Component aContainer) {
384 final Point popupPoint = getCenterOf(aContainer, myContent);
385 show(aContainer, popupPoint.x, popupPoint.y, false);
388 public void setAdText(@NotNull final String s) {
389 setAdText(s, SwingConstants.LEFT);
393 public PopupBorder getPopupBorder() {
394 return myPopupBorder;
398 public void setAdText(@NotNull final String s, int alignment) {
399 if (myAdComponent == null) {
400 myAdComponent = HintUtil.createAdComponent(s, JBUI.Borders.empty(1, 5), alignment);
401 JPanel wrapper = new JPanel(new BorderLayout()) {
403 protected void paintComponent(Graphics g) {
404 g.setColor(Gray._135);
405 g.drawLine(0, 0, getWidth(), 0);
406 super.paintComponent(g);
409 wrapper.setOpaque(false);
410 wrapper.setBorder(JBUI.Borders.emptyTop(1));
411 wrapper.add(myAdComponent, BorderLayout.CENTER);
412 myContent.add(wrapper, BorderLayout.SOUTH);
415 myAdComponent.setText(s);
416 myAdComponent.setHorizontalAlignment(alignment);
420 public static Point getCenterOf(final Component aContainer, final JComponent content) {
421 final JComponent component = getTargetComponent(aContainer);
423 Rectangle visibleBounds = component != null
424 ? component.getVisibleRect()
425 : new Rectangle(aContainer.getSize());
427 Point containerScreenPoint = visibleBounds.getLocation();
428 SwingUtilities.convertPointToScreen(containerScreenPoint, aContainer);
429 visibleBounds.setLocation(containerScreenPoint);
430 return UIUtil.getCenterPoint(visibleBounds, content.getPreferredSize());
434 public void showCenteredInCurrentWindow(@NotNull Project project) {
435 Window window = null;
437 Component focusedComponent = getWndManager().getFocusedComponent(project);
438 if (focusedComponent != null) {
439 Component parent = UIUtil.findUltimateParent(focusedComponent);
440 if (parent instanceof Window) {
441 window = (Window)parent;
444 if (window == null) {
445 window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
448 if (window != null && window.isShowing()) {
449 showInCenterOf(window);
454 public void showUnderneathOf(@NotNull Component aComponent) {
455 show(new RelativePoint(aComponent, new Point(0, aComponent.getHeight())));
459 public void show(@NotNull RelativePoint aPoint) {
460 final Point screenPoint = aPoint.getScreenPoint();
461 show(aPoint.getComponent(), screenPoint.x, screenPoint.y, false);
465 public void showInScreenCoordinates(@NotNull Component owner, @NotNull Point point) {
466 show(owner, point.x, point.y, false);
470 public void showInBestPositionFor(@NotNull DataContext dataContext) {
471 final Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
472 if (editor != null) {
473 showInBestPositionFor(editor);
476 show(relativePointByQuickSearch(dataContext));
481 public void showInFocusCenter() {
482 final Component focused = getWndManager().getFocusedComponent(myProject);
483 if (focused != null) {
484 showInCenterOf(focused);
487 final WindowManager manager = WindowManager.getInstance();
488 final JFrame frame = myProject != null ? manager.getFrame(myProject) : manager.findVisibleFrame();
489 showInCenterOf(frame.getRootPane());
493 private RelativePoint relativePointByQuickSearch(final DataContext dataContext) {
494 Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(dataContext);
496 if (dominantArea != null) {
497 final Component focusedComponent = getWndManager().getFocusedComponent(myProject);
498 if (focusedComponent != null) {
499 Window window = SwingUtilities.windowForComponent(focusedComponent);
500 JLayeredPane layeredPane;
501 if (window instanceof JFrame) {
502 layeredPane = ((JFrame)window).getLayeredPane();
504 else if (window instanceof JDialog) {
505 layeredPane = ((JDialog)window).getLayeredPane();
507 else if (window instanceof JWindow) {
508 layeredPane = ((JWindow)window).getLayeredPane();
511 throw new IllegalStateException("cannot find parent window: project=" + myProject + "; window=" + window);
514 return relativePointWithDominantRectangle(layeredPane, dominantArea);
518 return JBPopupFactory.getInstance().guessBestPopupLocation(dataContext);
522 public void showInBestPositionFor(@NotNull Editor editor) {
523 assert editor.getComponent().isShowing() : "Editor must be showing on the screen";
525 // Set the accessible parent so that screen readers don't announce
526 // a window context change -- the tooltip is "logically" hosted
527 // inside the component (e.g. editor) it appears on top of.
528 AccessibleContextUtil.setParent(myComponent, editor.getContentComponent());
529 DataContext context = ((EditorEx)editor).getDataContext();
530 Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(context);
531 if (dominantArea != null && !myRequestFocus) {
532 final JLayeredPane layeredPane = editor.getContentComponent().getRootPane().getLayeredPane();
533 show(relativePointWithDominantRectangle(layeredPane, dominantArea));
536 show(guessBestPopupLocation(editor));
541 private RelativePoint guessBestPopupLocation(@NotNull Editor editor) {
542 RelativePoint preferredLocation = JBPopupFactory.getInstance().guessBestPopupLocation(editor);
543 if (myDimensionServiceKey == null) {
544 return preferredLocation;
546 Dimension preferredSize = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
547 if (preferredSize == null) {
548 return preferredLocation;
550 Rectangle preferredBounds = new Rectangle(preferredLocation.getScreenPoint(), preferredSize);
551 Rectangle adjustedBounds = new Rectangle(preferredBounds);
552 ScreenUtil.moveRectangleToFitTheScreen(adjustedBounds);
553 if (preferredBounds.y - adjustedBounds.y <= 0) {
554 return preferredLocation;
556 int adjustedY = preferredBounds.y - editor.getLineHeight() - preferredSize.height;
558 return preferredLocation;
560 Point point = new Point(preferredBounds.x, adjustedY);
561 Component component = preferredLocation.getComponent();
562 if (component == null) {
563 return RelativePoint.fromScreen(point);
565 SwingUtilities.convertPointFromScreen(point, component);
566 return new RelativePoint(component, point);
569 public void addPopupListener(JBPopupListener listener) {
570 myListeners.add(listener);
573 private RelativePoint relativePointWithDominantRectangle(final JLayeredPane layeredPane, final Rectangle bounds) {
574 Dimension preferredSize = getComponent().getPreferredSize();
575 if (myDimensionServiceKey != null) {
576 final Dimension dimension = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
577 if (dimension != null) {
578 preferredSize = dimension;
581 final Point leftTopCorner = new Point(bounds.x + bounds.width, bounds.y);
582 final Point leftTopCornerScreen = (Point)leftTopCorner.clone();
583 SwingUtilities.convertPointToScreen(leftTopCornerScreen, layeredPane);
584 final RelativePoint relativePoint;
585 if (!ScreenUtil.isOutsideOnTheRightOFScreen(
586 new Rectangle(leftTopCornerScreen.x, leftTopCornerScreen.y, preferredSize.width, preferredSize.height))) {
587 relativePoint = new RelativePoint(layeredPane, leftTopCorner);
590 if (bounds.x > preferredSize.width) {
591 relativePoint = new RelativePoint(layeredPane, new Point(bounds.x - preferredSize.width, bounds.y));
594 setDimensionServiceKey(null); // going to cut width
595 Rectangle screen = ScreenUtil.getScreenRectangle(leftTopCornerScreen.x, leftTopCornerScreen.y);
596 final int spaceOnTheLeft = bounds.x;
597 final int spaceOnTheRight = screen.x + screen.width - leftTopCornerScreen.x;
598 if (spaceOnTheLeft > spaceOnTheRight) {
599 relativePoint = new RelativePoint(layeredPane, new Point(0, bounds.y));
600 myComponent.setPreferredSize(new Dimension(spaceOnTheLeft, Math.max(preferredSize.height, JBUI.scale(200))));
603 relativePoint = new RelativePoint(layeredPane, leftTopCorner);
604 myComponent.setPreferredSize(new Dimension(spaceOnTheRight, Math.max(preferredSize.height, JBUI.scale(200))));
608 return relativePoint;
612 public final void closeOk(@Nullable InputEvent e) {
618 public final void cancel() {
623 public void setRequestFocus(boolean requestFocus) {
624 myRequestFocus = requestFocus;
628 public void cancel(InputEvent e) {
629 if (myState == State.CANCEL || myState == State.DISPOSE) {
632 debugState("cancel popup", State.SHOWN);
633 myState = State.CANCEL;
635 if (isDisposed()) return;
637 if (myPopup != null) {
639 debugState("cannot cancel popup", State.CANCEL);
640 myState = State.SHOWN;
643 storeDimensionSize(myContent.getSize());
644 if (myUseDimServiceForXYLocation) {
645 final JRootPane root = myComponent.getRootPane();
647 final Container popupWindow = root.getParent();
648 if (popupWindow != null && popupWindow.isShowing()) {
649 storeLocation(popupWindow.getLocationOnScreen());
654 if (e instanceof MouseEvent) {
655 IdeEventQueue.getInstance().blockNextEvents((MouseEvent)e);
660 if (ApplicationManagerEx.getApplicationEx() != null) {
661 StackingPopupDispatcher.getInstance().onPopupHidden(this);
665 if (myFocusTrackback != null) {
666 myFocusTrackback.setForcedRestore(!myOk && myFocusable);
667 myFocusTrackback.restoreFocus();
669 else if (LOG.isDebugEnabled()) {
670 LOG.debug("cancel before show @ " + Thread.currentThread());
677 if (myListeners != null) {
678 for (JBPopupListener each : myListeners) {
679 each.onClosed(new LightweightWindowEvent(this, myOk));
684 Disposer.dispose(this, false);
685 if (myProjectDisposable != null) {
686 Disposer.dispose(myProjectDisposable);
690 public FocusTrackback getFocusTrackback() {
691 return myFocusTrackback;
694 private void disposePopup() {
695 if (myPopup != null) {
702 public boolean canClose() {
703 return myCallBack == null || myCallBack.compute().booleanValue();
707 public boolean isVisible() {
708 if (myPopup == null) return false;
709 Window window = myPopup.getWindow();
710 if (window != null && window.isShowing()) return true;
711 if (LOG.isDebugEnabled()) LOG.debug("window hidden, popup's state: " + myState);
716 public void show(final Component owner) {
717 show(owner, -1, -1, true);
720 public void show(Component owner, int aScreenX, int aScreenY, final boolean considerForcedXY) {
721 if (ApplicationManagerEx.getApplicationEx() != null && ApplicationManager.getApplication().isHeadlessEnvironment()) return;
723 throw new IllegalStateException("Popup was already disposed. Recreate a new instance to show again");
726 assert ApplicationManager.getApplication().isDispatchThread();
727 assert myState == State.INIT : "Popup was already shown. Recreate a new instance to show again.";
729 debugState("show popup", State.INIT);
730 myState = State.SHOWING;
732 installWindowHook(this);
733 installProjectDisposer();
736 final Component prevOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
738 final boolean shouldShow = beforeShow();
741 debugState("rejected to show popup", State.SHOWING);
742 myState = State.INIT;
749 myFocusTrackback = new FocusTrackback(this, owner, true);
750 myFocusTrackback.setMustBeShown(true);
754 Dimension sizeToSet = null;
756 if (myDimensionServiceKey != null) {
757 sizeToSet = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
760 if (myForcedSize != null) {
761 sizeToSet = myForcedSize;
764 if (myLocateWithinScreen) {
765 Dimension size = sizeToSet != null ? sizeToSet : myContent.getPreferredSize();
766 Rectangle screen = ScreenUtil.getScreenRectangle(aScreenX, aScreenY);
767 if (size.width > screen.width) {
768 size.width = screen.width;
771 if (size.height > screen.height) {
772 size.height = screen.height;
777 if (sizeToSet != null) {
778 sizeToSet.width = Math.max(sizeToSet.width, myContent.getMinimumSize().width);
779 sizeToSet.height = Math.max(sizeToSet.height, myContent.getMinimumSize().height);
781 myContent.setSize(sizeToSet);
782 myContent.setPreferredSize(sizeToSet);
785 Point xy = new Point(aScreenX, aScreenY);
786 boolean adjustXY = true;
787 if (myUseDimServiceForXYLocation && myDimensionServiceKey != null) {
788 final Point storedLocation = DimensionService.getInstance().getLocation(myDimensionServiceKey, myProject);
789 if (storedLocation != null) {
796 final Insets insets = myContent.getInsets();
797 if (insets != null) {
803 if (considerForcedXY && myForcedLocation != null) {
804 xy = myForcedLocation;
807 if (myLocateByContent) {
808 final Dimension captionSize = myHeaderPanel.getPreferredSize();
809 xy.y -= captionSize.height;
812 Rectangle targetBounds = new Rectangle(xy, myContent.getPreferredSize());
813 Rectangle original = new Rectangle(targetBounds);
814 if (myLocateWithinScreen) {
815 ScreenUtil.moveToFit(targetBounds, ScreenUtil.getScreenRectangle(aScreenX, aScreenY), null);
818 if (myMouseOutCanceller != null) {
819 myMouseOutCanceller.myEverEntered = targetBounds.equals(original);
822 myOwner = getFrameOrDialog(owner); // use correct popup owner for non-modal dialogs too
823 if (myOwner == null) {
827 myRequestorComponent = owner;
829 boolean forcedDialog = myMayBeParent
830 || SystemInfo.isMac && !(myOwner instanceof IdeFrame) && myOwner != null && myOwner.isShowing();
832 PopupComponent.Factory factory = getFactory(myForcedHeavyweight || myResizable, forcedDialog);
833 myNativePopup = factory.isNativePopup();
834 Component popupOwner = myOwner;
835 if (popupOwner instanceof RootPaneContainer && !(popupOwner instanceof IdeFrame && !Registry.is("popup.fix.ide.frame.owner"))) {
836 // JDK uses cached heavyweight popup for a window ancestor
837 RootPaneContainer root = (RootPaneContainer)popupOwner;
838 popupOwner = root.getRootPane();
839 LOG.debug("popup owner fixed for JDK cache");
841 if (LOG.isDebugEnabled()) {
842 LOG.debug("expected preferred size: " + myContent.getPreferredSize());
844 myPopup = factory.getPopup(popupOwner, myContent, targetBounds.x, targetBounds.y, this);
845 if (LOG.isDebugEnabled()) {
846 LOG.debug(" actual preferred size: " + myContent.getPreferredSize());
848 if ((targetBounds.width != myContent.getWidth()) || (targetBounds.height != myContent.getHeight())) {
849 // JDK uses cached heavyweight popup that is not initialized properly
850 LOG.debug("the expected size is not equal to the actual size");
851 Window popup = myPopup.getWindow();
853 popup.setSize(targetBounds.width, targetBounds.height);
854 if (myContent.getParent().getComponentCount() != 1) {
855 LOG.debug("unexpected count of components in heavy-weight popup");
859 LOG.debug("cannot fix size for non-heavy-weight popup");
864 final JRootPane root = myContent.getRootPane();
865 final IdeGlassPaneImpl glass = new IdeGlassPaneImpl(root);
866 root.setGlassPane(glass);
868 int i = Registry.intValue("ide.popup.resizable.border.sensitivity", 4);
869 WindowResizeListener resizeListener = new WindowResizeListener(
871 myMovable ? new Insets(i, i, i, i) : new Insets(0, 0, i, i),
872 isToDrawMacCorner() ? AllIcons.General.MacCorner : null) {
873 private Cursor myCursor;
876 protected void setCursor(Component content, Cursor cursor) {
877 if (myCursor != cursor || myCursor != Cursor.getDefaultCursor()) {
878 glass.setCursor(cursor, this);
883 glass.addMousePreprocessor(resizeListener, this);
884 glass.addMouseMotionPreprocessor(resizeListener, this);
885 myResizeListener = resizeListener;
888 if (myCaption != null && myMovable) {
889 final WindowMoveListener moveListener = new WindowMoveListener(myCaption) {
891 public void mousePressed(final MouseEvent e) {
892 if (e.isConsumed()) return;
893 if (UIUtil.isCloseClick(e) && myCaption.isWithinPanel(e)) {
897 super.mousePressed(e);
901 myCaption.addMouseListener(moveListener);
902 myCaption.addMouseMotionListener(moveListener);
903 final MyContentPanel saved = myContent;
904 Disposer.register(this, new Disposable() {
906 public void dispose() {
907 ListenerUtil.removeMouseListener(saved, moveListener);
908 ListenerUtil.removeMouseMotionListener(saved, moveListener);
911 myMoveListener = moveListener;
914 for (JBPopupListener listener : myListeners) {
915 listener.beforeShown(new LightweightWindowEvent(this));
918 myPopup.setRequestFocus(myRequestFocus);
921 final Window window = getContentWindow(myContent);
925 myWindowListener = new MyWindowListener();
926 window.addWindowListener(myWindowListener);
929 window.setFocusableWindowState(true);
930 window.setFocusable(true);
933 if (myWindow != null) {
934 // dialogwrapper-based popups do this internally through peer,
935 // for other popups like jdialog-based we should exclude them manually, but
936 // we still have to be able to use IdeFrame as parent
937 if (!myMayBeParent && !(myWindow instanceof Frame)) {
938 WindowManager.getInstance().doNotSuggestAsParent(myWindow);
942 setMinimumSize(myMinSize);
944 final Runnable afterShow = () -> {
945 if (myPreferredFocusedComponent != null && myInStack && myFocusable) {
946 myFocusTrackback.registerFocusComponent(myPreferredFocusedComponent);
947 if (myPreferredFocusedComponent instanceof JTextComponent) {
948 IJSwingUtilities.moveMousePointerOn(myPreferredFocusedComponent);
958 if (myRequestFocus) {
959 getFocusManager().requestFocus(new FocusCommand() {
962 public ActionCallback run() {
965 return ActionCallback.DONE;
970 final ActionCallback result = new ActionCallback();
972 final Runnable afterShowRunnable = () -> {
977 final FocusRequestor furtherRequestor = getFocusManager().getFurtherRequestor();
978 //noinspection SSBasedInspection
979 SwingUtilities.invokeLater(() -> {
981 result.setRejected();
985 furtherRequestor.requestFocus(new FocusCommand() {
988 public ActionCallback run() {
990 return ActionCallback.REJECTED;
995 afterShowRunnable.run();
997 return ActionCallback.DONE;
999 }, true).notify(result).doWhenProcessed(() -> removeActivity());
1002 afterShowRunnable.run();
1007 }, true).doWhenRejected(() -> afterShow.run());
1009 //noinspection SSBasedInspection
1010 SwingUtilities.invokeLater(() -> {
1016 if (X_WINDOW_FOCUS_BUG && !myRequestFocus && prevOwner != null &&
1017 Registry.is("actionSystem.xWindow.remove.focus.from.nonFocusable.popups")) {
1018 new Alarm().addRequest(() -> {
1020 IdeFocusManager.getInstance(myProject).requestFocus(prevOwner, false);
1022 }, Registry.intValue("actionSystem.xWindow.remove.focus.from.nonFocusable.popups.delay"));
1028 debugState("popup shown", State.SHOWING);
1029 myState = State.SHOWN;
1032 public void focusPreferredComponent() {
1036 private void installProjectDisposer() {
1037 final Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1039 final DataContext context = DataManager.getInstance().getDataContext(c);
1040 final Project project = CommonDataKeys.PROJECT.getData(context);
1041 if (project != null) {
1042 myProjectDisposable = new Disposable() {
1045 public void dispose() {
1046 if (!AbstractPopup.this.isDisposed()) {
1047 Disposer.dispose(AbstractPopup.this);
1051 Disposer.register(project, myProjectDisposable);
1056 //Sometimes just after popup was shown the WINDOW_ACTIVATED cancels it
1057 private static void installWindowHook(final AbstractPopup popup) {
1058 if (popup.myCancelOnWindow) {
1059 popup.myCancelOnWindow = false;
1060 new Alarm(popup).addRequest(() -> popup.myCancelOnWindow = true, 100);
1064 private void addActivity() {
1065 UiActivityMonitor.getInstance().addActivity(myActivityKey);
1068 private void removeActivity() {
1069 UiActivityMonitor.getInstance().removeActivity(myActivityKey);
1072 private void prepareToShow() {
1073 final MouseAdapter mouseAdapter = new MouseAdapter() {
1075 public void mousePressed(MouseEvent e) {
1076 Point point = (Point)e.getPoint().clone();
1077 SwingUtilities.convertPointToScreen(point, e.getComponent());
1079 final Dimension dimension = myContent.getSize();
1080 dimension.height += myResizable && isToDrawMacCorner() ? AllIcons.General.MacCorner.getIconHeight() : 4;
1081 dimension.width += 4;
1082 Point locationOnScreen = myContent.getLocationOnScreen();
1083 final Rectangle bounds = new Rectangle(new Point(locationOnScreen.x - 2, locationOnScreen.y - 2), dimension);
1084 if (!bounds.contains(point)) {
1089 myContent.addMouseListener(mouseAdapter);
1090 Disposer.register(this, new Disposable() {
1092 public void dispose() {
1093 myContent.removeMouseListener(mouseAdapter);
1097 myContent.registerKeyboardAction(new ActionListener() {
1099 public void actionPerformed(ActionEvent e) {
1100 if (myCancelKeyEnabled) {
1104 }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
1107 mySearchKeyListener = new SpeedSearchKeyListener();
1108 myContent.addKeyListener(mySearchKeyListener);
1110 if (myCancelOnMouseOutCallback != null || myCancelOnWindow) {
1111 myMouseOutCanceller = new Canceller();
1112 Toolkit.getDefaultToolkit().addAWTEventListener(myMouseOutCanceller, AWTEvent.MOUSE_EVENT_MASK | WindowEvent.WINDOW_ACTIVATED |
1113 AWTEvent.MOUSE_MOTION_EVENT_MASK);
1117 ChildFocusWatcher focusWatcher = new ChildFocusWatcher(myContent) {
1119 protected void onFocusGained(final FocusEvent event) {
1120 setWindowActive(true);
1124 protected void onFocusLost(final FocusEvent event) {
1125 setWindowActive(false);
1128 Disposer.register(this, focusWatcher);
1130 mySpeedSearchPatternField = new JTextField();
1131 mySpeedSearchPatternField.setFocusable(false);
1132 if (SystemInfo.isMac) {
1133 RelativeFont.TINY.install(mySpeedSearchPatternField);
1137 private Window updateMaskAndAlpha(Window window) {
1138 if (window == null) return null;
1140 if (!window.isDisplayable() || !window.isShowing()) return window;
1142 final WindowManagerEx wndManager = getWndManager();
1143 if (wndManager == null) return window;
1145 if (!wndManager.isAlphaModeEnabled(window)) return window;
1147 if (myAlpha != myLastAlpha) {
1148 wndManager.setAlphaModeRatio(window, myAlpha);
1149 myLastAlpha = myAlpha;
1152 if (myMaskProvider != null) {
1153 final Dimension size = window.getSize();
1154 Shape mask = myMaskProvider.getMask(size);
1155 wndManager.setWindowMask(window, mask);
1158 WindowManagerEx.WindowShadowMode mode =
1159 myShadowed ? WindowManagerEx.WindowShadowMode.NORMAL : WindowManagerEx.WindowShadowMode.DISABLED;
1160 WindowManagerEx.getInstanceEx().setWindowShadow(window, mode);
1165 private static WindowManagerEx getWndManager() {
1166 return ApplicationManagerEx.getApplicationEx() != null ? WindowManagerEx.getInstanceEx() : null;
1170 public boolean isDisposed() {
1171 return myContent == null;
1174 protected boolean beforeShow() {
1175 if (ApplicationManagerEx.getApplicationEx() == null) return true;
1176 StackingPopupDispatcher.getInstance().onPopupShown(this, myInStack);
1180 protected void afterShow() {
1183 protected final boolean requestFocus() {
1184 if (!myFocusable) return false;
1186 getFocusManager().requestFocus(new FocusCommand() {
1189 public ActionCallback run() {
1191 return ActionCallback.DONE;
1198 private void _requestFocus() {
1199 if (!myFocusable) return;
1201 if (myPreferredFocusedComponent != null) {
1202 myPreferredFocusedComponent.requestFocus();
1206 private IdeFocusManager getFocusManager() {
1207 if (myProject != null) {
1208 return IdeFocusManager.getInstance(myProject);
1210 if (myOwner != null) {
1211 return IdeFocusManager.findInstanceByComponent(myOwner);
1213 return IdeFocusManager.findInstance();
1216 private static JComponent getTargetComponent(Component aComponent) {
1217 if (aComponent instanceof JComponent) {
1218 return (JComponent)aComponent;
1220 if (aComponent instanceof RootPaneContainer) {
1221 return ((RootPaneContainer)aComponent).getRootPane();
1224 LOG.error("Cannot find target for:" + aComponent);
1228 private PopupComponent.Factory getFactory(boolean forceHeavyweight, boolean forceDialog) {
1229 if (Registry.is("allow.dialog.based.popups")) {
1230 boolean noFocus = !myFocusable || !myRequestFocus;
1231 boolean cannotBeDialog = noFocus && SystemInfo.isXWindow;
1233 if (!cannotBeDialog && (isPersistent() || forceDialog)) {
1234 return new PopupComponent.Factory.Dialog();
1237 if (forceHeavyweight) {
1238 return new PopupComponent.Factory.AwtHeavyweight();
1240 return new PopupComponent.Factory.AwtDefault();
1244 public JComponent getContent() {
1248 public void setLocation(RelativePoint p) {
1249 if (isBusy()) return;
1251 setLocation(p, myPopup);
1254 private static void setLocation(final RelativePoint p, final PopupComponent popup) {
1255 if (popup == null) return;
1257 final Window wnd = popup.getWindow();
1260 wnd.setLocation(p.getScreenPoint());
1264 public void pack(boolean width, boolean height) {
1265 if (!isVisible() || !width && !height || isBusy()) return;
1267 Dimension size = getSize();
1268 Dimension prefSize = myContent.computePreferredSize();
1269 Point location = !myLocateWithinScreen ? null : getLocationOnScreen();
1270 Rectangle screen = location == null ? null : ScreenUtil.getScreenRectangle(location);
1273 size.width = prefSize.width;
1274 if (screen != null) {
1275 int delta = screen.width + screen.x - location.x;
1276 if (size.width > delta) {
1278 // we shrank horizontally - need to increase height to fit the horizontal scrollbar
1279 JScrollPane scrollPane = ScrollUtil.findScrollPane(myContent);
1280 if (scrollPane != null && scrollPane.getHorizontalScrollBarPolicy() != ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER) {
1281 JScrollBar scrollBar = scrollPane.getHorizontalScrollBar();
1282 if (scrollBar != null) {
1283 prefSize.height += scrollBar.getPreferredSize().height;
1291 size.height = prefSize.height;
1292 if (screen != null) {
1293 int delta = screen.height + screen.y - location.y;
1294 if (size.height > delta) {
1295 size.height = delta;
1300 size = computeWindowSize(size);
1302 final Window window = getContentWindow(myContent);
1303 if (window != null) {
1304 window.setSize(size);
1309 public void pack() {
1310 if (isBusy()) return;
1312 final Window window = getContentWindow(myContent);
1313 if (window != null) {
1318 public JComponent getComponent() {
1322 public void setProject(Project project) {
1323 myProject = project;
1328 public void dispose() {
1329 if (myState == State.SHOWN) {
1330 LOG.debug("shown popup must be cancelled");
1333 if (myState == State.DISPOSE) {
1336 debugState("dispose popup", State.INIT, State.CANCEL);
1337 myState = State.DISPOSE;
1344 if (LOG.isDebugEnabled()) {
1345 LOG.debug("start disposing " + myContent);
1348 Disposer.dispose(this, false);
1350 ApplicationManager.getApplication().assertIsDispatchThread();
1352 if (myPopup != null) {
1353 cancel(myDisposeEvent);
1356 if (myContent != null) {
1357 myContent.removeAll();
1358 myContent.removeKeyListener(mySearchKeyListener);
1361 myPreferredFocusedComponent = null;
1363 myFocusTrackback = null;
1367 if (myMouseOutCanceller != null) {
1368 final Toolkit toolkit = Toolkit.getDefaultToolkit();
1369 // it may happen, but have no idea how
1370 // http://www.jetbrains.net/jira/browse/IDEADEV-21265
1371 if (toolkit != null) {
1372 toolkit.removeAWTEventListener(myMouseOutCanceller);
1375 myMouseOutCanceller = null;
1379 if (myFinalRunnable != null) {
1380 final ActionCallback typeAheadDone = new ActionCallback();
1381 IdeFocusManager.getInstance(myProject).typeAheadUntil(typeAheadDone);
1383 ModalityState modalityState = ModalityState.current();
1384 Runnable finalRunnable = myFinalRunnable;
1386 getFocusManager().doWhenFocusSettlesDown(() -> {
1387 //noinspection SSBasedInspection
1388 SwingUtilities.invokeLater(() -> {
1389 if (ModalityState.current().equals(modalityState)) {
1390 ((TransactionGuardImpl)TransactionGuard.getInstance()).performUserActivity(finalRunnable);
1392 // Otherwise the UI has changed unexpectedly and the action is likely not applicable.
1393 // And we don't want finalRunnable to perform potentially destructive actions
1394 // in the context of a suddenly appeared modal dialog.
1396 //noinspection SSBasedInspection
1397 SwingUtilities.invokeLater(typeAheadDone.createSetDoneRunnable());
1398 myFinalRunnable = null;
1402 if (LOG.isDebugEnabled()) {
1403 LOG.debug("stop disposing content");
1407 private void resetWindow() {
1408 if (myWindow != null && getWndManager() != null) {
1409 getWndManager().resetWindow(myWindow);
1410 if (myWindowListener != null) {
1411 myWindow.removeWindowListener(myWindowListener);
1414 if (myWindow instanceof JWindow) {
1415 ((JWindow)myWindow).getRootPane().putClientProperty(KEY, null);
1419 myWindowListener = null;
1423 public void storeDimensionSize(final Dimension size) {
1424 if (myDimensionServiceKey != null) {
1425 DimensionService.getInstance().setSize(myDimensionServiceKey, size, myProject);
1429 public void storeLocation(final Point xy) {
1430 if (myDimensionServiceKey != null) {
1431 DimensionService.getInstance().setLocation(myDimensionServiceKey, xy, myProject);
1435 public static class MyContentPanel extends JPanel implements DataProvider {
1436 private final boolean myResizable;
1437 private final boolean myDrawMacCorner;
1438 @Nullable private DataProvider myDataProvider;
1440 public MyContentPanel(final boolean resizable, final PopupBorder border, boolean drawMacCorner) {
1441 super(new BorderLayout());
1442 myResizable = resizable;
1443 myDrawMacCorner = drawMacCorner;
1448 public void paint(Graphics g) {
1451 if (myResizable && myDrawMacCorner) {
1452 AllIcons.General.MacCorner.paintIcon(this, g,
1453 getX() + getWidth() - AllIcons.General.MacCorner.getIconWidth(),
1454 getY() + getHeight() - AllIcons.General.MacCorner.getIconHeight());
1458 public Dimension computePreferredSize() {
1459 if (isPreferredSizeSet()) {
1460 Dimension setSize = getPreferredSize();
1461 setPreferredSize(null);
1462 Dimension result = getPreferredSize();
1463 setPreferredSize(setSize);
1466 return getPreferredSize();
1471 public Object getData(@NonNls String dataId) {
1472 return myDataProvider != null ? myDataProvider.getData(dataId) : null;
1475 public void setDataProvider(@Nullable DataProvider dataProvider) {
1476 myDataProvider = dataProvider;
1480 public boolean isCancelOnClickOutside() {
1481 return myCancelOnClickOutside;
1484 public boolean isCancelOnWindowDeactivation() {
1485 return myCancelOnWindowDeactivation;
1488 private class Canceller implements AWTEventListener {
1489 private boolean myEverEntered = false;
1492 public void eventDispatched(final AWTEvent event) {
1493 if (event.getID() == WindowEvent.WINDOW_ACTIVATED) {
1494 if (myCancelOnWindow && myPopup != null && !myPopup.isPopupWindow(((WindowEvent)event).getWindow())) {
1498 else if (event.getID() == MouseEvent.MOUSE_ENTERED) {
1499 if (withinPopup(event)) {
1500 myEverEntered = true;
1503 else if (event.getID() == MouseEvent.MOUSE_MOVED) {
1504 if (myCancelOnMouseOutCallback != null && myEverEntered && !withinPopup(event)) {
1505 if (myCancelOnMouseOutCallback.check((MouseEvent)event)) {
1512 private boolean withinPopup(final AWTEvent event) {
1513 if (!myContent.isShowing()) return false;
1515 final MouseEvent mouse = (MouseEvent)event;
1516 final Point point = mouse.getPoint();
1517 SwingUtilities.convertPointToScreen(point, mouse.getComponent());
1518 return new Rectangle(myContent.getLocationOnScreen(), myContent.getSize()).contains(point);
1523 public void setLocation(@NotNull final Point screenPoint) {
1524 if (myPopup == null) {
1525 myForcedLocation = screenPoint;
1527 else if (!isBusy()) {
1528 moveTo(myContent, screenPoint, myLocateByContent ? myHeaderPanel.getPreferredSize() : null);
1532 public static Window moveTo(JComponent content, Point screenPoint, final Dimension headerCorrectionSize) {
1533 final Window wnd = getContentWindow(content);
1535 wnd.setCursor(Cursor.getDefaultCursor());
1536 if (headerCorrectionSize != null) {
1537 screenPoint.y -= headerCorrectionSize.height;
1539 wnd.setLocation(screenPoint);
1544 private static Window getContentWindow(Component content) {
1545 Window window = SwingUtilities.getWindowAncestor(content);
1546 if (window == null) {
1547 if (LOG.isDebugEnabled()) {
1548 LOG.debug("no window ancestor for " + content);
1555 public Point getLocationOnScreen() {
1556 Dimension headerCorrectionSize = myLocateByContent ? myHeaderPanel.getPreferredSize() : null;
1557 Point screenPoint = myContent.getLocation();
1558 SwingUtilities.convertPointToScreen(screenPoint, myContent);
1559 if (headerCorrectionSize != null) {
1560 screenPoint.y -= headerCorrectionSize.height;
1568 public void setSize(@NotNull final Dimension size) {
1569 setSize(size, true);
1572 private void setSize(Dimension size, boolean adjustByContent) {
1573 if (isBusy()) return;
1575 Dimension toSet = size;
1576 if (myPopup == null) {
1577 myForcedSize = toSet;
1580 if (adjustByContent) {
1581 toSet = computeWindowSize(toSet);
1583 updateMaskAndAlpha(setSize(myContent, toSet));
1587 private Dimension computeWindowSize(Dimension size) {
1588 if (myAdComponent != null && myAdComponent.isShowing()) {
1589 size.height += myAdComponent.getPreferredSize().height + 1;
1595 public Dimension getSize() {
1596 if (myPopup != null) {
1597 final Window popupWindow = getContentWindow(myContent);
1598 return (popupWindow == null) ? myForcedSize : popupWindow.getSize();
1600 return myForcedSize;
1605 public void moveToFitScreen() {
1606 if (myPopup == null || isBusy()) return;
1608 final Window popupWindow = getContentWindow(myContent);
1609 if (popupWindow == null) return;
1610 Rectangle bounds = popupWindow.getBounds();
1612 ScreenUtil.moveRectangleToFitTheScreen(bounds);
1613 setLocation(bounds.getLocation());
1614 setSize(bounds.getSize(), false);
1618 public static Window setSize(JComponent content, final Dimension size) {
1619 final Window popupWindow = getContentWindow(content);
1620 if (popupWindow == null) return null;
1621 JBInsets.addTo(size, content.getInsets());
1622 content.setPreferredSize(size);
1627 public void setCaption(String title) {
1628 if (myCaption instanceof TitlePanel) {
1629 ((TitlePanel)myCaption).setText(title);
1633 private class MyWindowListener extends WindowAdapter {
1636 public void windowOpened(WindowEvent e) {
1637 updateMaskAndAlpha(myWindow);
1641 public void windowClosing(final WindowEvent e) {
1648 public boolean isPersistent() {
1649 return !myCancelOnClickOutside && !myCancelOnWindow;
1653 public boolean isNativePopup() {
1654 return myNativePopup;
1658 public void setUiVisible(final boolean visible) {
1659 if (myPopup != null) {
1662 final Window window = getPopupWindow();
1663 if (window != null && myRestoreWindowSize != null) {
1664 window.setSize(myRestoreWindowSize);
1665 myRestoreWindowSize = null;
1669 final Window window = getPopupWindow();
1670 if (window != null) {
1671 myRestoreWindowSize = window.getSize();
1672 window.setVisible(true);
1678 public Window getPopupWindow() {
1679 return myPopup.getWindow();
1682 public void setUserData(List<Object> userData) {
1683 myUserData = userData;
1687 public <T> T getUserData(final Class<T> userDataClass) {
1688 if (myUserData != null) {
1689 for (Object o : myUserData) {
1690 if (userDataClass.isInstance(o)) {
1691 @SuppressWarnings("unchecked") T t = (T)o;
1700 public boolean isModalContext() {
1701 return myModalContext;
1705 public boolean isFocused() {
1706 if (myComponent != null && isFocused(new Component[]{SwingUtilities.getWindowAncestor(myComponent)})) {
1709 return isFocused(myFocusOwners);
1712 public static boolean isFocused(@Nullable Component[] components) {
1713 if (components == null) return false;
1715 Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1717 if (owner == null) return false;
1719 Window wnd = UIUtil.getWindow(owner);
1721 for (Component each : components) {
1722 if (each != null && SwingUtilities.isDescendingFrom(owner, each)) {
1723 Window eachWindow = UIUtil.getWindow(each);
1724 if (eachWindow == wnd) {
1734 public boolean isCancelKeyEnabled() {
1735 return myCancelKeyEnabled;
1739 CaptionPanel getTitle() {
1743 private void setHeaderComponent(JComponent c) {
1744 boolean doRevalidate = false;
1745 if (myHeaderComponent != null) {
1746 myHeaderPanel.remove(myHeaderComponent);
1747 myHeaderComponent = null;
1748 doRevalidate = true;
1752 myHeaderPanel.add(c, BorderLayout.CENTER);
1753 myHeaderComponent = c;
1755 final Dimension size = myContent.getSize();
1756 if (size.height < c.getPreferredSize().height * 2) {
1757 size.height += c.getPreferredSize().height;
1761 doRevalidate = true;
1764 if (doRevalidate) myContent.revalidate();
1767 public void setWarning(@NotNull String text) {
1768 JBLabel label = new JBLabel(text, UIUtil.getBalloonWarningIcon(), SwingConstants.CENTER);
1769 label.setOpaque(true);
1770 Color color = HintUtil.INFORMATION_COLOR;
1771 label.setBackground(color);
1772 label.setBorder(BorderFactory.createLineBorder(color, 3));
1773 myHeaderPanel.add(label, BorderLayout.SOUTH);
1777 public void addListener(final JBPopupListener listener) {
1778 myListeners.add(listener);
1782 public void removeListener(final JBPopupListener listener) {
1783 myListeners.remove(listener);
1786 protected void onSpeedSearchPatternChanged() {
1790 public Component getOwner() {
1791 return myRequestorComponent;
1795 public void setMinimumSize(Dimension size) {
1796 //todo: consider changing only the caption panel minimum size
1797 Dimension sizeFromHeader = myHeaderPanel.getPreferredSize();
1799 if (sizeFromHeader == null) {
1800 sizeFromHeader = myHeaderPanel.getMinimumSize();
1803 if (sizeFromHeader == null) {
1804 int minimumSize = myWindow.getGraphics().getFontMetrics(myHeaderPanel.getFont()).getHeight();
1805 sizeFromHeader = new Dimension(minimumSize, minimumSize);
1809 myMinSize = sizeFromHeader;
1811 final int width = Math.max(size.width, sizeFromHeader.width);
1812 final int height = Math.max(size.height, sizeFromHeader.height);
1813 myMinSize = new Dimension(width, height);
1816 if (myWindow != null) {
1817 Rectangle screenRectangle = ScreenUtil.getScreenRectangle(myWindow.getLocation());
1818 int width = Math.min(screenRectangle.width, myMinSize.width);
1819 int height = Math.min(screenRectangle.height, myMinSize.height);
1820 myWindow.setMinimumSize(new Dimension(width, height));
1825 public void setFinalRunnable(Runnable finalRunnable) {
1826 myFinalRunnable = finalRunnable;
1829 public void setOk(boolean ok) {
1834 public void setDataProvider(@NotNull DataProvider dataProvider) {
1835 if (myContent != null) {
1836 myContent.setDataProvider(dataProvider);
1841 public boolean dispatchKeyEvent(@NotNull KeyEvent e) {
1842 BooleanFunction<KeyEvent> handler = myKeyEventHandler;
1843 if (handler != null) {
1844 return handler.fun(e);
1847 if (isCloseRequest(e) && myCancelKeyEnabled) {
1855 private class SpeedSearchKeyListener implements KeyListener {
1857 public void keyTyped(final KeyEvent e) {
1858 mySpeedSearch.process(e);
1862 public void keyPressed(final KeyEvent e) {
1863 mySpeedSearch.process(e);
1867 public void keyReleased(final KeyEvent e) {
1868 mySpeedSearch.process(e);
1873 public Dimension getHeaderPreferredSize() {
1874 return myHeaderPanel.getPreferredSize();
1877 public Dimension getFooterPreferredSize() {
1878 return myAdComponent == null ? new Dimension(0,0) : myAdComponent.getPreferredSize();
1881 public static boolean isCloseRequest(KeyEvent e) {
1882 return e != null && e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_ESCAPE && e.getModifiers() == 0;
1885 private boolean isBusy() {
1886 return myResizeListener != null && myResizeListener.isBusy() || myMoveListener != null && myMoveListener.isBusy();
1890 * Returns the first frame (or dialog) ancestor of the component.
1891 * Note that this method returns the component itself if it is a frame (or dialog).
1893 * @param component the component used to find corresponding frame (or dialog)
1894 * @return the first frame (or dialog) ancestor of the component; or {@code null}
1895 * if the component is not a frame (or dialog) and is not contained inside a frame (or dialog)
1897 * @see UIUtil#getWindow
1899 private static Component getFrameOrDialog(Component component) {
1900 while (component != null) {
1901 if (component instanceof Frame || component instanceof Dialog) return component;
1902 component = component.getParent();