popups: lost data context because of invaokeLater + made setFinalRunnable() public
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ui / popup / AbstractPopup.java
blob05a275f59772d9b0fe16744bc62bc94d8a96a7e1
1 /*
2 * Copyright 2000-2009 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.ide.ui.UISettings;
20 import com.intellij.openapi.Disposable;
21 import com.intellij.openapi.actionSystem.DataContext;
22 import com.intellij.openapi.actionSystem.JBAwtEventQueue;
23 import com.intellij.openapi.actionSystem.PlatformDataKeys;
24 import com.intellij.openapi.application.ApplicationManager;
25 import com.intellij.openapi.application.ex.ApplicationManagerEx;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.editor.ex.EditorEx;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.ui.impl.ShadowBorderPainter;
31 import com.intellij.openapi.ui.popup.*;
32 import com.intellij.openapi.util.*;
33 import com.intellij.openapi.util.registry.Registry;
34 import com.intellij.openapi.wm.IdeFocusManager;
35 import com.intellij.openapi.wm.WindowManager;
36 import com.intellij.openapi.wm.ex.WindowManagerEx;
37 import com.intellij.openapi.wm.impl.IdeFrameImpl;
38 import com.intellij.openapi.wm.impl.IdeGlassPaneImpl;
39 import com.intellij.ui.*;
40 import com.intellij.ui.awt.RelativePoint;
41 import com.intellij.ui.speedSearch.SpeedSearch;
42 import com.intellij.util.ImageLoader;
43 import com.intellij.util.ui.ChildFocusWatcher;
44 import com.intellij.util.ui.EmptyIcon;
45 import com.intellij.util.ui.UIUtil;
46 import org.jetbrains.annotations.NotNull;
47 import org.jetbrains.annotations.Nullable;
49 import javax.swing.*;
50 import javax.swing.border.EmptyBorder;
51 import java.awt.*;
52 import java.awt.event.*;
53 import java.awt.image.BufferedImage;
54 import java.util.ArrayList;
55 import java.util.HashSet;
56 import java.util.List;
57 import java.util.Set;
59 import static com.intellij.openapi.ui.impl.ShadowBorderPainter.*;
61 public class AbstractPopup implements JBPopup {
62 private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.AbstractPopup");
64 private static final Image ourMacCorner = ImageLoader.loadFromResource("/general/macCorner.png");
66 private PopupComponent myPopup;
67 private MyContentPanel myContent;
68 private JComponent myPreferredFocusedComponent;
69 private boolean myRequestFocus;
70 private boolean myFocusable;
71 private boolean myForcedHeavyweight = false;
72 private boolean myLocateWithinScreen = true;
73 private boolean myResizable = false;
74 private JPanel myHeaderPanel;
75 private CaptionPanel myCaption = null;
76 private JComponent myComponent;
77 private String myDimensionServiceKey = null;
78 private Computable<Boolean> myCallBack = null;
79 private Project myProject;
80 private boolean myCancelOnClickOutside;
81 private Set<JBPopupListener> myListeners;
82 private boolean myUseDimServiceForXYLocation;
83 private MouseChecker myCancelOnMouseOutCallback;
84 private Canceller myMouseOutCanceller;
85 private boolean myCancelOnWindow;
86 private Dimension myForcedSize;
87 private Point myForcedLocation;
88 private ChildFocusWatcher myFocusWatcher;
89 private boolean myCancelKeyEnabled;
90 private boolean myLocateByContent;
91 protected FocusTrackback myFocusTrackback;
92 private Dimension myMinSize;
93 private ArrayList<Object> myUserData;
94 private boolean myShadowed;
96 private float myAlpha = 0;
97 private float myLastAlpha = 0;
99 private MaskProvider myMaskProvider;
101 private Window myWindow;
102 private boolean myInStack;
103 private MyWindowListener myWindowListener;
105 private boolean myModalContext;
107 private Component[] myFocusOwners;
108 private PopupBorder myPopupBorder;
109 private Dimension myRestoreWindowSize;
110 protected Component myOwner;
111 protected Component myRequestorComponent;
112 private boolean myHeaderAlwaysFocusable;
113 private boolean myMovable;
114 private JComponent myHeaderComponent;
116 protected InputEvent myDisposeEvent;
118 private Runnable myFinalRunnable;
120 protected final SpeedSearch mySpeedSearch = new SpeedSearch() {
121 boolean searchFieldShown = false;
122 protected void update() {
123 mySpeedSearchPatternField.setBackground(new JTextField().getBackground());
124 onSpeedSearchPatternChanged();
125 mySpeedSearchPatternField.setText(getFilter());
126 if (isHoldingFilter() && !searchFieldShown) {
127 setHeaderComponent(mySpeedSearchPatternField);
128 searchFieldShown = true;
130 else if (!isHoldingFilter() && searchFieldShown) {
131 setHeaderComponent(null);
132 searchFieldShown = false;
136 @Override
137 public void noHits() {
138 mySpeedSearchPatternField.setBackground(LightColors.RED);
142 private JTextField mySpeedSearchPatternField;
145 AbstractPopup() {
148 AbstractPopup init(final Project project,
149 @NotNull final JComponent component,
150 @Nullable final JComponent preferredFocusedComponent,
151 final boolean requestFocus,
152 final boolean focusable,
153 final boolean forceHeavyweight,
154 final boolean movable,
155 final String dimensionServiceKey,
156 final boolean resizable,
157 @Nullable final String caption,
158 @Nullable final Computable<Boolean> callback,
159 final boolean cancelOnClickOutside,
160 @Nullable final Set<JBPopupListener> listeners,
161 final boolean useDimServiceForXYLocation,
162 InplaceButton commandButton,
163 @Nullable final IconButton cancelButton,
164 @Nullable final MouseChecker cancelOnMouseOutCallback,
165 final boolean cancelOnWindow,
166 @Nullable final ActiveIcon titleIcon,
167 final boolean cancelKeyEnabled,
168 final boolean locateBycontent,
169 final boolean placeWithinScreenBounds,
170 @Nullable final Dimension minSize,
171 float alpha,
172 @Nullable MaskProvider maskProvider,
173 boolean inStack,
174 boolean modalContext,
175 @Nullable Component[] focusOwners,
176 @Nullable String adText,
177 final boolean headerAlwaysFocusable,
178 @NotNull List<Pair<ActionListener, KeyStroke>> keyboardActions,
179 Component settingsButtons) {
181 if (requestFocus && !focusable) {
182 assert false : "Incorrect argument combination: requestFocus=" + requestFocus + " focusable=" + focusable;
185 myProject = project;
186 myComponent = component;
187 myPopupBorder = PopupBorder.Factory.create(true);
188 myShadowed = !movable && !resizable && Registry.is("ide.popup.dropShadow");
189 myContent = createContentPanel(resizable, myPopupBorder, isToDrawMacCorner());
191 myContent.add(component, BorderLayout.CENTER);
192 if (adText != null) {
193 myContent.add(HintUtil.createAdComponent(adText), BorderLayout.SOUTH);
196 myCancelKeyEnabled = cancelKeyEnabled;
197 myLocateByContent = locateBycontent;
198 myLocateWithinScreen = placeWithinScreenBounds;
199 myAlpha = alpha;
200 myMaskProvider = maskProvider;
201 myInStack = inStack;
202 myModalContext = modalContext;
203 myFocusOwners = focusOwners;
204 myHeaderAlwaysFocusable = headerAlwaysFocusable;
205 myMovable = movable;
207 ActiveIcon actualIcon = titleIcon == null ? new ActiveIcon(new EmptyIcon(0)) : titleIcon;
209 myHeaderPanel = new JPanel(new BorderLayout());
211 if (caption != null) {
212 if (caption.length() > 0) {
213 myCaption = new TitlePanel(actualIcon.getRegular(), actualIcon.getInactive());
214 ((TitlePanel)myCaption).setText(caption);
216 else {
217 myCaption = new CaptionPanel();
219 if (cancelButton != null) {
220 myCaption.setButtonComponent(new InplaceButton(cancelButton, new ActionListener() {
221 public void actionPerformed(final ActionEvent e) {
222 cancel();
224 }));
226 else if (commandButton != null) {
227 myCaption.setButtonComponent(commandButton);
230 else {
231 myCaption = new CaptionPanel();
232 myCaption.setBorder(null);
233 myCaption.setPreferredSize(new Dimension(0, 0));
236 setWindowActive(myHeaderAlwaysFocusable);
238 myHeaderPanel.add(myCaption, BorderLayout.NORTH);
239 myContent.add(myHeaderPanel, BorderLayout.NORTH);
241 myForcedHeavyweight = forceHeavyweight;
242 myResizable = resizable;
243 myPreferredFocusedComponent = preferredFocusedComponent;
244 myRequestFocus = requestFocus;
245 myFocusable = focusable;
246 myDimensionServiceKey = dimensionServiceKey;
247 myCallBack = callback;
248 myCancelOnClickOutside = cancelOnClickOutside;
249 myCancelOnMouseOutCallback = cancelOnMouseOutCallback;
250 myListeners = listeners == null ? new HashSet<JBPopupListener>() : listeners;
251 myUseDimServiceForXYLocation = useDimServiceForXYLocation;
252 myCancelOnWindow = cancelOnWindow;
253 myMinSize = minSize;
255 for (Pair<ActionListener, KeyStroke> pair : keyboardActions) {
256 myContent.registerKeyboardAction(pair.getFirst(), pair.getSecond(), JComponent.WHEN_IN_FOCUSED_WINDOW);
259 if (settingsButtons != null) {
260 myCaption.addSettingsComponent(settingsButtons);
263 return this;
266 private void setWindowActive(boolean active) {
267 boolean value = myHeaderAlwaysFocusable || active;
269 if (myCaption != null) {
270 myCaption.setActive(value);
272 myPopupBorder.setActive(value);
273 myContent.repaint();
277 @NotNull
278 protected MyContentPanel createContentPanel(final boolean resizable, PopupBorder border, boolean isToDrawMacCorner) {
279 return new MyContentPanel(resizable, border, isToDrawMacCorner, myShadowed);
282 public static boolean isToDrawMacCorner() {
283 return SystemInfo.isMac;
288 public String getDimensionServiceKey() {
289 return myDimensionServiceKey;
292 public void setDimensionServiceKey(final String dimensionServiceKey) {
293 myDimensionServiceKey = dimensionServiceKey;
296 public void showInCenterOf(@NotNull Component aContainer) {
297 final Point popupPoint = getCenterOf(aContainer, myContent);
298 show(aContainer, popupPoint.x, popupPoint.y, false);
301 public void setAdText(@NotNull final String s) {
302 myContent.add(HintUtil.createAdComponent(s, BorderFactory.createEmptyBorder(3, 5, 3, 5)), BorderLayout.SOUTH);
305 public static Point getCenterOf(final Component aContainer, final JComponent content) {
306 final JComponent component = getTargetComponent(aContainer);
308 Point containerScreenPoint = component.getVisibleRect().getLocation();
309 SwingUtilities.convertPointToScreen(containerScreenPoint, aContainer);
311 return UIUtil.getCenterPoint(new Rectangle(containerScreenPoint, component.getVisibleRect().getSize()), content.getPreferredSize());
314 public void showCenteredInCurrentWindow(@NotNull Project project) {
315 Window window = null;
317 Component focusedComponent = getWndManager().getFocusedComponent(project);
318 if (focusedComponent != null) {
319 if (focusedComponent instanceof Window) {
320 window = (Window)focusedComponent;
322 else {
323 window = SwingUtilities.getWindowAncestor(focusedComponent);
326 if (window == null) {
327 window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
330 if (window != null) {
331 showInCenterOf(window);
335 public void showUnderneathOf(@NotNull Component aComponent) {
336 show(new RelativePoint(aComponent, new Point(0, aComponent.getHeight())));
339 public void show(@NotNull RelativePoint aPoint) {
340 final Point screenPoint = aPoint.getScreenPoint();
341 show(aPoint.getComponent(), screenPoint.x, screenPoint.y, false);
344 public void showInScreenCoordinates(@NotNull Component owner, @NotNull Point point) {
345 show(owner, point.x, point.y, false);
348 public void showInBestPositionFor(@NotNull DataContext dataContext) {
349 final Editor editor = PlatformDataKeys.EDITOR.getData(dataContext);
350 if (editor != null) {
351 showInBestPositionFor(editor);
353 else {
354 show(relativePointByQuickSearch(dataContext));
358 public void showInFocusCenter() {
359 final Component focused = getWndManager().getFocusedComponent(myProject);
360 if (focused != null) {
361 showInCenterOf(focused);
362 } else {
363 final JFrame frame = WindowManager.getInstance().getFrame(myProject);
364 showInCenterOf(frame.getRootPane());
368 private RelativePoint relativePointByQuickSearch(final DataContext dataContext) {
369 Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(dataContext);
371 if (dominantArea != null) {
372 final Component focusedComponent = getWndManager().getFocusedComponent(myProject);
373 Window window = SwingUtilities.windowForComponent(focusedComponent);
374 JLayeredPane layeredPane;
375 if (window instanceof JFrame) {
376 layeredPane = ((JFrame)window).getLayeredPane();
378 else if (window instanceof JDialog) {
379 layeredPane = ((JDialog)window).getLayeredPane();
381 else if (window instanceof JWindow) {
382 layeredPane = ((JWindow)window).getLayeredPane();
384 else {
385 throw new IllegalStateException("cannot find parent window: project=" + myProject + "; window=" + window);
388 return relativePointWithDominantRectangle(layeredPane, dominantArea);
391 return JBPopupFactory.getInstance().guessBestPopupLocation(dataContext);
394 public void showInBestPositionFor(@NotNull Editor editor) {
395 assert editor.getComponent().isShowing() : "Editor must be showing on the screen";
397 DataContext context = ((EditorEx)editor).getDataContext();
398 Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(context);
399 if (dominantArea != null && !myRequestFocus) {
400 final JLayeredPane layeredPane = editor.getContentComponent().getRootPane().getLayeredPane();
401 show(relativePointWithDominantRectangle(layeredPane, dominantArea));
403 else {
404 show(JBPopupFactory.getInstance().guessBestPopupLocation(editor));
408 public void addPopupListener(JBPopupListener listener) {
409 myListeners.add(listener);
412 private RelativePoint relativePointWithDominantRectangle(final JLayeredPane layeredPane, final Rectangle bounds) {
413 Dimension preferredSize = getComponent().getPreferredSize();
414 if (myDimensionServiceKey != null) {
415 final Dimension dimension = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
416 if (dimension != null) {
417 preferredSize = dimension;
420 final Point leftTopCorner = new Point(bounds.x + bounds.width, bounds.y);
421 final Point leftTopCornerScreen = (Point)leftTopCorner.clone();
422 SwingUtilities.convertPointToScreen(leftTopCornerScreen, layeredPane);
423 final RelativePoint relativePoint;
424 if (!ScreenUtil.isOutsideOnTheRightOFScreen(
425 new Rectangle(leftTopCornerScreen.x, leftTopCornerScreen.y, preferredSize.width, preferredSize.height))) {
426 relativePoint = new RelativePoint(layeredPane, leftTopCorner);
428 else {
429 if (bounds.x > preferredSize.width) {
430 relativePoint = new RelativePoint(layeredPane, new Point(bounds.x - preferredSize.width, bounds.y));
432 else {
433 setDimensionServiceKey(null); // going to cut width
434 Rectangle screen = ScreenUtil.getScreenRectangle(leftTopCornerScreen.x, leftTopCornerScreen.y);
435 final int spaceOnTheLeft = bounds.x;
436 final int spaceOnTheRight = (screen.x + screen.width) - leftTopCornerScreen.x;
437 if (spaceOnTheLeft > spaceOnTheRight) {
438 relativePoint = new RelativePoint(layeredPane, new Point(0, bounds.y));
439 myComponent.setPreferredSize(new Dimension(spaceOnTheLeft, Math.max(preferredSize.height, 200)));
441 else {
442 relativePoint = new RelativePoint(layeredPane, leftTopCorner);
443 myComponent.setPreferredSize(new Dimension(spaceOnTheRight, Math.max(preferredSize.height, 200)));
447 return relativePoint;
450 public final void cancel() {
451 cancel(null);
454 public void setRequestFocus(boolean requestFocus) {
455 myRequestFocus = requestFocus;
458 public void cancel(InputEvent e) {
459 if (isDisposed()) return;
461 final Runnable finalRunnable = myFinalRunnable;
462 final IdeFocusManager focusManager = myOwner != null ? IdeFocusManager.findInstanceByComponent(myOwner) : IdeFocusManager.findInstance();
464 if (myPopup != null) {
465 if (!canClose()) {
466 return;
468 storeDimensionSize(myContent.getSize());
469 if (myUseDimServiceForXYLocation) {
470 final JRootPane root = myComponent.getRootPane();
471 if (root != null) {
472 final Container popupWindow = root.getParent();
473 if (popupWindow != null && popupWindow.isShowing()) {
474 storeLocation(popupWindow.getLocationOnScreen());
479 if (e instanceof MouseEvent) {
480 JBAwtEventQueue.getInstance().blockNextEvents(((MouseEvent)e));
483 myPopup.hide(false);
485 if (ApplicationManagerEx.getApplicationEx() != null) {
486 StackingPopupDispatcher.getInstance().onPopupHidden(this);
489 if (myInStack) {
490 myFocusTrackback.restoreFocus();
494 disposePopup();
496 if (myListeners != null) {
497 for (JBPopupListener each : myListeners) {
498 each.onClosed(new LightweightWindowEvent(this));
503 Disposer.dispose(this, false);
505 if (finalRunnable != null) {
506 SwingUtilities.invokeLater(new Runnable() {
507 public void run() {
508 focusManager.doWhenFocusSettlesDown(finalRunnable);
515 private void disposePopup() {
516 if (myPopup != null) {
517 myPopup.hide(true);
519 myPopup = null;
522 public boolean canClose() {
523 return myCallBack == null || myCallBack.compute().booleanValue();
526 public boolean isVisible() {
527 return myPopup != null;
530 public void show(final Component owner) {
531 show(owner, -1, -1, true);
534 public void show(Component owner, int aScreenX, int aScreenY, final boolean considerForcedXY) {
535 if (ApplicationManagerEx.getApplicationEx() != null && ApplicationManager.getApplication().isHeadlessEnvironment()) return;
536 if (isDisposed()) {
537 throw new IllegalStateException("Popup was already disposed. Recreate a new instance to show again");
540 assert ApplicationManager.getApplication().isDispatchThread();
543 final boolean shouldShow = beforeShow();
544 if (!shouldShow) {
545 return;
548 prepareToShow();
550 if (myInStack) {
551 myFocusTrackback = new FocusTrackback(this, owner, true);
552 myFocusTrackback.setMustBeShown(true);
556 Dimension sizeToSet = null;
558 if (myDimensionServiceKey != null) {
559 sizeToSet = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
562 if (myForcedSize != null) {
563 sizeToSet = myForcedSize;
566 if (myMinSize == null) {
567 myMinSize = myContent.getMinimumSize();
570 if (sizeToSet == null) {
571 sizeToSet = myContent.getPreferredSize();
574 if (sizeToSet != null) {
575 sizeToSet.width = Math.max(sizeToSet.width, myMinSize.width);
576 sizeToSet.height = Math.max(sizeToSet.height, myMinSize.height);
578 myContent.setSize(sizeToSet);
579 myContent.setPreferredSize(sizeToSet);
582 Point xy = new Point(aScreenX, aScreenY);
583 boolean adjustXY = true;
584 if (myDimensionServiceKey != null) {
585 final Point storedLocation = DimensionService.getInstance().getLocation(myDimensionServiceKey, myProject);
586 if (storedLocation != null) {
587 xy = storedLocation;
588 adjustXY = false;
592 if (adjustXY) {
593 final Insets insets = myContent.getInsets();
594 if (insets != null) {
595 xy.x -= insets.left;
596 xy.y -= insets.top;
600 if (considerForcedXY && myForcedLocation != null) {
601 xy = myForcedLocation;
604 if (myLocateByContent) {
605 final Dimension captionSize = myHeaderPanel.getPreferredSize();
606 xy.y -= captionSize.height;
609 Rectangle targetBounds = new Rectangle(xy, myContent.getPreferredSize());
610 Rectangle original = new Rectangle(targetBounds);
611 if (myLocateWithinScreen) {
612 ScreenUtil.moveRectangleToFitTheScreen(targetBounds);
615 if (myMouseOutCanceller != null) {
616 myMouseOutCanceller.myEverEntered = targetBounds.equals(original);
619 myOwner = IdeFrameImpl.findNearestModalComponent(owner);
620 if (myOwner == null) {
621 myOwner = owner;
624 myRequestorComponent = owner;
626 myPopup = getFactory(myForcedHeavyweight || myResizable).getPopup(myOwner, myContent, targetBounds.x, targetBounds.y);
628 if (myResizable) {
629 final JRootPane root = myContent.getRootPane();
630 final IdeGlassPaneImpl glass = new IdeGlassPaneImpl(root);
631 root.setGlassPane(glass);
633 final ResizeComponentListener resizeListener = new ResizeComponentListener(this);
634 glass.addMousePreprocessor(resizeListener, this);
635 glass.addMouseMotionPreprocessor(resizeListener, this);
638 if (myCaption != null && myMovable) {
639 final MoveComponentListener moveListener = new MoveComponentListener(myCaption) {
640 public void mousePressed(final MouseEvent e) {
641 super.mousePressed(e);
642 if (e.isConsumed()) return;
644 if (UIUtil.isCloseClick(e)) {
645 if (myCaption.isWithinPanel(e)) {
646 cancel();
651 ListenerUtil.addMouseListener(myCaption, moveListener);
652 ListenerUtil.addMouseMotionListener(myCaption, moveListener);
653 final MyContentPanel saved = myContent;
654 Disposer.register(this, new Disposable() {
655 public void dispose() {
656 ListenerUtil.removeMouseListener(saved, moveListener);
657 ListenerUtil.removeMouseMotionListener(saved, moveListener);
662 for(JBPopupListener listener: myListeners) {
663 listener.beforeShown(new LightweightWindowEvent(this));
666 myPopup.show();
668 final Window window = SwingUtilities.getWindowAncestor(myContent);
670 myWindowListener = new MyWindowListener();
671 window.addWindowListener(myWindowListener);
673 if (myFocusable) {
674 window.setFocusableWindowState(true);
675 window.setFocusable(true);
676 if (myRequestFocus) {
677 window.requestFocusInWindow();
681 myWindow = updateMaskAndAlpha(window);
683 if (myWindow instanceof JWindow) {
684 ((JWindow)myWindow).getRootPane().putClientProperty(KEY, this);
687 SwingUtilities.invokeLater(new Runnable() {
688 public void run() {
689 if (isDisposed()) return;
691 if (myRequestFocus) {
692 requestFocus();
695 if (myPreferredFocusedComponent != null && myInStack) {
696 myFocusTrackback.registerFocusComponent(myPreferredFocusedComponent);
699 afterShow();
704 private void prepareToShow() {
705 final MouseAdapter mouseAdapter = new MouseAdapter() {
706 public void mousePressed(MouseEvent e) {
707 Point point = (Point)e.getPoint().clone();
708 SwingUtilities.convertPointToScreen(point, e.getComponent());
710 final Dimension dimension = myContent.getSize();
711 dimension.height += myResizable && isToDrawMacCorner() ? ourMacCorner.getHeight(myContent) : 4;
712 dimension.width += 4;
713 Point locationOnScreen = myContent.getLocationOnScreen();
714 final Rectangle bounds = new Rectangle(new Point(locationOnScreen.x - 2, locationOnScreen.y - 2), dimension);
715 if (!bounds.contains(point)) {
716 cancel();
720 myContent.addMouseListener(mouseAdapter);
721 Disposer.register(this, new Disposable() {
722 public void dispose() {
723 myContent.removeMouseListener(mouseAdapter);
727 myContent.registerKeyboardAction(new ActionListener() {
728 public void actionPerformed(ActionEvent e) {
729 if (myCancelKeyEnabled) {
730 cancel();
733 }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
736 myContent.addKeyListener(new KeyListener() {
737 public void keyTyped(final KeyEvent e) {
738 mySpeedSearch.process(e);
741 public void keyPressed(final KeyEvent e) {
742 mySpeedSearch.process(e);
745 public void keyReleased(final KeyEvent e) {
746 mySpeedSearch.process(e);
750 if (myCancelOnMouseOutCallback != null || myCancelOnWindow) {
751 myMouseOutCanceller = new Canceller();
752 Toolkit.getDefaultToolkit().addAWTEventListener(myMouseOutCanceller, AWTEvent.MOUSE_EVENT_MASK | WindowEvent.WINDOW_ACTIVATED |
753 AWTEvent.MOUSE_MOTION_EVENT_MASK);
757 myFocusWatcher = new ChildFocusWatcher(myContent) {
758 protected void onFocusGained(final FocusEvent event) {
759 setWindowActive(true);
762 protected void onFocusLost(final FocusEvent event) {
763 setWindowActive(false);
768 mySpeedSearchPatternField = new JTextField();
769 if (SystemInfo.isMac) {
770 Font f = mySpeedSearchPatternField.getFont();
771 mySpeedSearchPatternField.setFont(f.deriveFont(f.getStyle(), f.getSize() - 2));
775 private Window updateMaskAndAlpha(Window window) {
776 if (window == null) return window;
778 final WindowManagerEx wndManager = getWndManager();
779 if (wndManager == null) return window;
781 if (!wndManager.isAlphaModeEnabled(window)) return window;
783 if (myAlpha != myLastAlpha) {
784 wndManager.setAlphaModeRatio(window, myAlpha);
785 myLastAlpha = myAlpha;
788 if (myMaskProvider != null) {
789 final Dimension size = window.getSize();
790 Shape mask = myMaskProvider.getMask(size);
791 wndManager.setWindowMask(window, mask);
794 return window;
797 private static WindowManagerEx getWndManager() {
798 return ApplicationManagerEx.getApplicationEx() != null ? WindowManagerEx.getInstanceEx() : null;
801 public boolean isDisposed() {
802 return myContent == null;
805 protected boolean beforeShow() {
806 if (ApplicationManagerEx.getApplicationEx() == null) return true;
807 StackingPopupDispatcher.getInstance().onPopupShown(this, myInStack);
808 return true;
811 protected void afterShow() {
814 protected final void requestFocus() {
815 if (!myFocusable) return;
817 if (myPreferredFocusedComponent != null) {
818 if (myProject != null) {
819 getFocusManager().requestFocus(myPreferredFocusedComponent, true);
821 else {
822 myPreferredFocusedComponent.requestFocus();
827 private IdeFocusManager getFocusManager() {
828 return IdeFocusManager.getInstance(myProject);
831 private static JComponent getTargetComponent(Component aComponent) {
832 if (aComponent instanceof JComponent) {
833 return (JComponent)aComponent;
835 else if (aComponent instanceof RootPaneContainer) {
836 return ((RootPaneContainer)aComponent).getRootPane();
839 LOG.error("Cannot find target for:" + aComponent);
840 return null;
843 private PopupComponent.Factory getFactory(boolean forceHeavyweight) {
844 if (isPersistent()) {
845 return new PopupComponent.Factory.Dialog();
846 } else if (forceHeavyweight || !SystemInfo.isWindows) {
847 return new PopupComponent.Factory.AwtHeavyweight();
848 } else {
849 return new PopupComponent.Factory.AwtDefault();
853 public JComponent getContent() {
854 return myContent;
857 public void setLocation(RelativePoint p) {
858 setLocation(p, myPopup, myContent);
861 private static void setLocation(final RelativePoint p, final PopupComponent popup, Component content) {
862 if (popup == null) return;
864 final Window wnd = popup.getWindow();
865 assert wnd != null;
867 wnd.pack();
868 wnd.setLocation(p.getScreenPoint());
871 public void pack() {
872 final Window window = SwingUtilities.getWindowAncestor(myContent);
874 window.pack();
877 public JComponent getComponent() {
878 return myComponent;
881 public void setProject(Project project) {
882 myProject = project;
886 public void dispose() {
887 Disposer.dispose(this, false);
889 assert ApplicationManager.getApplication().isDispatchThread();
891 if (myPopup != null) {
892 cancel(myDisposeEvent);
895 if (myContent != null) {
896 myContent.removeAll();
898 myContent = null;
899 myComponent = null;
900 myFocusTrackback = null;
901 myCallBack = null;
902 myListeners = null;
904 if (myMouseOutCanceller != null) {
905 final Toolkit toolkit = Toolkit.getDefaultToolkit();
906 // it may happen, but have no idea how
907 // http://www.jetbrains.net/jira/browse/IDEADEV-21265
908 if (toolkit != null) {
909 toolkit.removeAWTEventListener(myMouseOutCanceller);
912 myMouseOutCanceller = null;
914 if (myFocusWatcher != null) {
915 myFocusWatcher.dispose();
916 myFocusWatcher = null;
919 resetWindow();
923 private void resetWindow() {
924 if (myWindow != null && getWndManager() != null) {
925 getWndManager().resetWindow(myWindow);
926 if (myWindowListener != null) {
927 myWindow.removeWindowListener(myWindowListener);
930 if (myWindow instanceof JWindow) {
931 ((JWindow)myWindow).getRootPane().putClientProperty(KEY, null);
934 myWindow = null;
935 myWindowListener = null;
939 public void storeDimensionSize(final Dimension size) {
940 if (myDimensionServiceKey != null) {
941 DimensionService.getInstance().setSize(myDimensionServiceKey, size, myProject);
945 public void storeLocation(final Point xy) {
946 if (myDimensionServiceKey != null) {
947 DimensionService.getInstance().setLocation(myDimensionServiceKey, xy, myProject);
951 public static class MyContentPanel extends JPanel {
952 private final boolean myResizable;
953 private final boolean myDrawMacCorner;
954 private final boolean myShadowed;
956 public MyContentPanel(final boolean resizable, final PopupBorder border, boolean drawMacCorner) {
957 this(resizable, border, drawMacCorner, false);
960 public MyContentPanel(final boolean resizable, final PopupBorder border, boolean drawMacCorner, boolean shadowed) {
961 super(new BorderLayout());
962 myResizable = resizable;
963 myDrawMacCorner = drawMacCorner;
964 myShadowed = shadowed;
965 if (isShadowPossible()) {
966 setOpaque(false);
967 setBorder(new EmptyBorder(POPUP_TOP_SIZE, POPUP_SIDE_SIZE, POPUP_BOTTOM_SIZE, POPUP_SIDE_SIZE) {
968 @Override
969 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
970 border.paintBorder(c, g,
971 x + POPUP_SIDE_SIZE - 1,
972 y + POPUP_TOP_SIZE - 1,
973 width - 2 * POPUP_SIDE_SIZE + 2,
974 height - POPUP_TOP_SIZE - POPUP_BOTTOM_SIZE + 2);
977 } else {
978 setBorder(border);
982 private boolean isShadowPossible() {
983 return myShadowed && !SystemInfo.isMac && !UISettings.isRemoteDesktopConnected();
986 public void paint(Graphics g) {
987 if (isShadowPossible()) {
988 paintShadow(g);
991 super.paint(g);
993 if (myResizable && myDrawMacCorner) {
994 g.drawImage(ourMacCorner,
995 getX() + getWidth() - ourMacCorner.getWidth(this),
996 getY() + getHeight() - ourMacCorner.getHeight(this),
997 this);
1001 private void paintShadow(final Graphics g) {
1002 BufferedImage capture = null;
1003 try {
1004 final Point onScreen = getLocationOnScreen();
1005 capture = new Robot().createScreenCapture(
1006 new Rectangle(onScreen.x, onScreen.y, getWidth() + 2 * POPUP_SIDE_SIZE, getHeight() + POPUP_TOP_SIZE + POPUP_BOTTOM_SIZE));
1007 final BufferedImage shadow = ShadowBorderPainter.createPopupShadow(this, getWidth(), getHeight());
1008 ((Graphics2D)capture.getGraphics()).drawImage(shadow, null, null);
1010 catch (Exception e) {
1011 e.printStackTrace();
1013 if (capture != null) g.drawImage(capture, 0, 0, null);
1017 public boolean isCancelOnClickOutside() {
1018 return myCancelOnClickOutside;
1021 private class Canceller implements AWTEventListener {
1023 private boolean myEverEntered = false;
1025 public void eventDispatched(final AWTEvent event) {
1026 if (event.getID() == WindowEvent.WINDOW_ACTIVATED) {
1027 if (myCancelOnWindow) {
1028 cancel();
1030 } else if (event.getID() == MouseEvent.MOUSE_ENTERED) {
1031 if (withinPopup(event)) {
1032 myEverEntered = true;
1034 } else if (event.getID() == MouseEvent.MOUSE_MOVED) {
1035 if (myCancelOnMouseOutCallback != null && myEverEntered && !withinPopup(event)) {
1036 if (myCancelOnMouseOutCallback.check((MouseEvent)event)) {
1037 cancel();
1043 private boolean withinPopup(final AWTEvent event) {
1044 if (!myContent.isShowing()) return false;
1046 final MouseEvent mouse = (MouseEvent)event;
1047 final Point point = mouse.getPoint();
1048 SwingUtilities.convertPointToScreen(point, mouse.getComponent());
1049 return new Rectangle(myContent.getLocationOnScreen(), myContent.getSize()).contains(point);
1053 public void setLocation(@NotNull final Point screenPoint) {
1054 if (myPopup == null) {
1055 myForcedLocation = screenPoint;
1056 } else {
1057 moveTo(myContent, screenPoint, myLocateByContent ? myHeaderPanel.getPreferredSize() : null);
1061 public static Window moveTo(JComponent content, Point screenPoint, final Dimension headerCorrectionSize) {
1062 setDefaultCursor(content);
1063 final Window wnd = SwingUtilities.getWindowAncestor(content);
1064 if (headerCorrectionSize != null) {
1065 screenPoint.y -= headerCorrectionSize.height;
1067 wnd.setLocation(screenPoint);
1068 return wnd;
1071 public void setSize(@NotNull final Dimension size) {
1072 if (myPopup == null) {
1073 myForcedSize = size;
1074 } else {
1075 updateMaskAndAlpha(setSize(myContent, size));
1079 public static Window setSize(JComponent content, final Dimension size) {
1080 final Window popupWindow = SwingUtilities.windowForComponent(content);
1081 final Point location = popupWindow.getLocation();
1082 popupWindow.setBounds(location.x, location.y, size.width, size.height);
1083 return popupWindow;
1086 public static void setDefaultCursor(JComponent content) {
1087 final Window wnd = SwingUtilities.getWindowAncestor(content);
1088 if (wnd != null) {
1089 wnd.setCursor(Cursor.getDefaultCursor());
1093 public void setCaption(String title) {
1094 if (myCaption instanceof TitlePanel) {
1095 ((TitlePanel)myCaption).setText(title);
1099 private class MyWindowListener extends WindowAdapter {
1100 public void windowClosed(final WindowEvent e) {
1101 resetWindow();
1105 public boolean isPersistent() {
1106 return !myCancelOnClickOutside && !myCancelOnWindow;
1109 public void setUiVisible(final boolean visible) {
1110 if (myPopup != null) {
1111 if (visible) {
1112 myPopup.show();
1113 final Window window = getPopupWindow();
1114 if (window != null && myRestoreWindowSize != null) {
1115 window.setSize(myRestoreWindowSize);
1116 myRestoreWindowSize = null;
1119 else {
1120 final Window window = getPopupWindow();
1121 if (window != null) {
1122 myRestoreWindowSize = window.getSize();
1123 window.setVisible(true);
1129 private Window getPopupWindow() {
1130 return myPopup.getWindow();
1133 public void setUserData(ArrayList<Object> userData) {
1134 myUserData = userData;
1137 public <T> T getUserData(final Class<T> userDataClass) {
1138 if (myUserData != null) {
1139 for(Object o: myUserData) {
1140 if (userDataClass.isInstance(o)) {
1141 //noinspection unchecked
1142 return (T) o;
1146 return null;
1149 public boolean isModalContext() {
1150 return myModalContext;
1153 public boolean isFocused() {
1154 if (myComponent != null && isFocused(new Component[] {SwingUtilities.getWindowAncestor(myComponent)}))
1155 return true;
1156 return isFocused(myFocusOwners);
1159 public static boolean isFocused(@Nullable Component[] components) {
1160 if (components == null) return false;
1162 Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1164 if (owner == null) return false;
1165 for (Component each : components) {
1166 if (each != null && SwingUtilities.isDescendingFrom(owner, each)) return true;
1169 return false;
1172 public boolean isCancelKeyEnabled() {
1173 return myCancelKeyEnabled;
1176 @NotNull
1177 CaptionPanel getTitle() {
1178 return myCaption;
1181 private void setHeaderComponent(JComponent c) {
1182 boolean doRevalidate = false;
1183 if (myHeaderComponent != null) {
1184 myHeaderPanel.remove(myHeaderComponent);
1185 myHeaderPanel.add(myCaption, BorderLayout.NORTH);
1186 myHeaderComponent = null;
1187 doRevalidate = true;
1190 if (c != null) {
1191 myHeaderPanel.remove(myCaption);
1192 myHeaderPanel.add(c, BorderLayout.NORTH);
1193 myHeaderComponent = c;
1195 final Dimension size = myContent.getSize();
1196 if (size.height < c.getPreferredSize().height * 2) {
1197 size.height += c.getPreferredSize().height;
1198 setSize(size);
1201 doRevalidate = true;
1204 if (doRevalidate) myContent.revalidate();
1207 public void addListener(final JBPopupListener listener) {
1208 myListeners.add(listener);
1211 public void removeListener(final JBPopupListener listener) {
1212 myListeners.remove(listener);
1215 protected void onSpeedSearchPatternChanged() {
1218 public Component getOwner() {
1219 return myRequestorComponent;
1222 public void setMinimumSize(Dimension size) {
1223 myMinSize = size;
1226 public Runnable getFinalRunnable() {
1227 return myFinalRunnable;
1230 public void setFinalRunnable(Runnable finalRunnable) {
1231 myFinalRunnable = finalRunnable;