sticky documentation popup [take 1]
[fedora-idea.git] / platform / platform-impl / src / com / intellij / ui / popup / AbstractPopup.java
blob57bb88b57f0be22bb00cd9f791f6931c95bf9067
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.Processor;
44 import com.intellij.util.ui.ChildFocusWatcher;
45 import com.intellij.util.ui.EmptyIcon;
46 import com.intellij.util.ui.UIUtil;
47 import org.jetbrains.annotations.NotNull;
48 import org.jetbrains.annotations.Nullable;
50 import javax.swing.*;
51 import javax.swing.border.EmptyBorder;
52 import java.awt.*;
53 import java.awt.event.*;
54 import java.awt.image.BufferedImage;
55 import java.util.ArrayList;
56 import java.util.HashSet;
57 import java.util.List;
58 import java.util.Set;
60 import static com.intellij.openapi.ui.impl.ShadowBorderPainter.*;
62 public class AbstractPopup implements JBPopup {
63 private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.AbstractPopup");
65 private static final Image ourMacCorner = ImageLoader.loadFromResource("/general/macCorner.png");
67 private PopupComponent myPopup;
68 private MyContentPanel myContent;
69 private JComponent myPreferredFocusedComponent;
70 private boolean myRequestFocus;
71 private boolean myFocusable;
72 private boolean myForcedHeavyweight = false;
73 private boolean myLocateWithinScreen = true;
74 private boolean myResizable = false;
75 private JPanel myHeaderPanel;
76 private CaptionPanel myCaption = null;
77 private JComponent myComponent;
78 private String myDimensionServiceKey = null;
79 private Computable<Boolean> myCallBack = null;
80 private Project myProject;
81 private boolean myCancelOnClickOutside;
82 private Set<JBPopupListener> myListeners;
83 private boolean myUseDimServiceForXYLocation;
84 private MouseChecker myCancelOnMouseOutCallback;
85 private Canceller myMouseOutCanceller;
86 private boolean myCancelOnWindow;
87 private Dimension myForcedSize;
88 private Point myForcedLocation;
89 private ChildFocusWatcher myFocusWatcher;
90 private boolean myCancelKeyEnabled;
91 private boolean myLocateByContent;
92 protected FocusTrackback myFocusTrackback;
93 private Dimension myMinSize;
94 private ArrayList<Object> myUserData;
95 private boolean myShadowed;
97 private float myAlpha = 0;
98 private float myLastAlpha = 0;
100 private MaskProvider myMaskProvider;
102 private Window myWindow;
103 private boolean myInStack;
104 private MyWindowListener myWindowListener;
106 private boolean myModalContext;
108 private Component[] myFocusOwners;
109 private PopupBorder myPopupBorder;
110 private Dimension myRestoreWindowSize;
111 protected Component myOwner;
112 protected Component myRequestorComponent;
113 private boolean myHeaderAlwaysFocusable;
114 private boolean myMovable;
115 private JComponent myHeaderComponent;
117 protected InputEvent myDisposeEvent;
119 private Runnable myFinalRunnable;
121 protected final SpeedSearch mySpeedSearch = new SpeedSearch() {
122 boolean searchFieldShown = false;
123 protected void update() {
124 mySpeedSearchPatternField.setBackground(new JTextField().getBackground());
125 onSpeedSearchPatternChanged();
126 mySpeedSearchPatternField.setText(getFilter());
127 if (isHoldingFilter() && !searchFieldShown) {
128 setHeaderComponent(mySpeedSearchPatternField);
129 searchFieldShown = true;
131 else if (!isHoldingFilter() && searchFieldShown) {
132 setHeaderComponent(null);
133 searchFieldShown = false;
137 @Override
138 public void noHits() {
139 mySpeedSearchPatternField.setBackground(LightColors.RED);
143 private JTextField mySpeedSearchPatternField;
144 private boolean myNativePopup;
147 AbstractPopup() {
150 AbstractPopup init(final Project project,
151 @NotNull final JComponent component,
152 @Nullable final JComponent preferredFocusedComponent,
153 final boolean requestFocus,
154 final boolean focusable,
155 final boolean forceHeavyweight,
156 final boolean movable,
157 final String dimensionServiceKey,
158 final boolean resizable,
159 @Nullable final String caption,
160 @Nullable final Computable<Boolean> callback,
161 final boolean cancelOnClickOutside,
162 @Nullable final Set<JBPopupListener> listeners,
163 final boolean useDimServiceForXYLocation,
164 InplaceButton commandButton,
165 @Nullable final IconButton cancelButton,
166 @Nullable final MouseChecker cancelOnMouseOutCallback,
167 final boolean cancelOnWindow,
168 @Nullable final ActiveIcon titleIcon,
169 final boolean cancelKeyEnabled,
170 final boolean locateBycontent,
171 final boolean placeWithinScreenBounds,
172 @Nullable final Dimension minSize,
173 float alpha,
174 @Nullable MaskProvider maskProvider,
175 boolean inStack,
176 boolean modalContext,
177 @Nullable Component[] focusOwners,
178 @Nullable String adText,
179 final boolean headerAlwaysFocusable,
180 @NotNull List<Pair<ActionListener, KeyStroke>> keyboardActions,
181 Component settingsButtons,
182 @Nullable final Processor<JBPopup> pinCallback) {
184 if (requestFocus && !focusable) {
185 assert false : "Incorrect argument combination: requestFocus=" + requestFocus + " focusable=" + focusable;
188 myProject = project;
189 myComponent = component;
190 myPopupBorder = PopupBorder.Factory.create(true);
191 myShadowed = !movable && !resizable && Registry.is("ide.popup.dropShadow");
192 myContent = createContentPanel(resizable, myPopupBorder, isToDrawMacCorner());
194 myContent.add(component, BorderLayout.CENTER);
195 if (adText != null) {
196 myContent.add(HintUtil.createAdComponent(adText), BorderLayout.SOUTH);
199 myCancelKeyEnabled = cancelKeyEnabled;
200 myLocateByContent = locateBycontent;
201 myLocateWithinScreen = placeWithinScreenBounds;
202 myAlpha = alpha;
203 myMaskProvider = maskProvider;
204 myInStack = inStack;
205 myModalContext = modalContext;
206 myFocusOwners = focusOwners;
207 myHeaderAlwaysFocusable = headerAlwaysFocusable;
208 myMovable = movable;
210 ActiveIcon actualIcon = titleIcon == null ? new ActiveIcon(new EmptyIcon(0)) : titleIcon;
212 myHeaderPanel = new JPanel(new BorderLayout());
214 if (caption != null) {
215 if (caption.length() > 0) {
216 myCaption = new TitlePanel(actualIcon.getRegular(), actualIcon.getInactive());
217 ((TitlePanel)myCaption).setText(caption);
219 else {
220 myCaption = new CaptionPanel();
223 if (pinCallback != null) {
224 myCaption.setButtonComponent(new InplaceButton(new IconButton("Pin", IconLoader.getIcon("/general/autohideOff.png"),
225 IconLoader.getIcon("/general/autohideOff.png"),
226 IconLoader.getIcon("/general/autohideOffInactive.png")), new ActionListener() {
227 public void actionPerformed(final ActionEvent e) {
228 pinCallback.process(AbstractPopup.this);
230 }));
231 } else if (cancelButton != null) {
232 myCaption.setButtonComponent(new InplaceButton(cancelButton, new ActionListener() {
233 public void actionPerformed(final ActionEvent e) {
234 cancel();
236 }));
238 else if (commandButton != null) {
239 myCaption.setButtonComponent(commandButton);
242 else {
243 myCaption = new CaptionPanel();
244 myCaption.setBorder(null);
245 myCaption.setPreferredSize(new Dimension(0, 0));
248 setWindowActive(myHeaderAlwaysFocusable);
250 myHeaderPanel.add(myCaption, BorderLayout.NORTH);
251 myContent.add(myHeaderPanel, BorderLayout.NORTH);
253 myForcedHeavyweight = forceHeavyweight;
254 myResizable = resizable;
255 myPreferredFocusedComponent = preferredFocusedComponent;
256 myRequestFocus = requestFocus;
257 myFocusable = focusable;
258 myDimensionServiceKey = dimensionServiceKey;
259 myCallBack = callback;
260 myCancelOnClickOutside = cancelOnClickOutside;
261 myCancelOnMouseOutCallback = cancelOnMouseOutCallback;
262 myListeners = listeners == null ? new HashSet<JBPopupListener>() : listeners;
263 myUseDimServiceForXYLocation = useDimServiceForXYLocation;
264 myCancelOnWindow = cancelOnWindow;
265 myMinSize = minSize;
267 for (Pair<ActionListener, KeyStroke> pair : keyboardActions) {
268 myContent.registerKeyboardAction(pair.getFirst(), pair.getSecond(), JComponent.WHEN_IN_FOCUSED_WINDOW);
271 if (settingsButtons != null) {
272 myCaption.addSettingsComponent(settingsButtons);
275 return this;
278 private void setWindowActive(boolean active) {
279 boolean value = myHeaderAlwaysFocusable || active;
281 if (myCaption != null) {
282 myCaption.setActive(value);
284 myPopupBorder.setActive(value);
285 myContent.repaint();
289 @NotNull
290 protected MyContentPanel createContentPanel(final boolean resizable, PopupBorder border, boolean isToDrawMacCorner) {
291 return new MyContentPanel(resizable, border, isToDrawMacCorner, myShadowed);
294 public static boolean isToDrawMacCorner() {
295 return SystemInfo.isMac;
300 public String getDimensionServiceKey() {
301 return myDimensionServiceKey;
304 public void setDimensionServiceKey(final String dimensionServiceKey) {
305 myDimensionServiceKey = dimensionServiceKey;
308 public void showInCenterOf(@NotNull Component aContainer) {
309 final Point popupPoint = getCenterOf(aContainer, myContent);
310 show(aContainer, popupPoint.x, popupPoint.y, false);
313 public void setAdText(@NotNull final String s) {
314 myContent.add(HintUtil.createAdComponent(s, BorderFactory.createEmptyBorder(3, 5, 3, 5)), BorderLayout.SOUTH);
317 public static Point getCenterOf(final Component aContainer, final JComponent content) {
318 final JComponent component = getTargetComponent(aContainer);
320 Point containerScreenPoint = component.getVisibleRect().getLocation();
321 SwingUtilities.convertPointToScreen(containerScreenPoint, aContainer);
323 return UIUtil.getCenterPoint(new Rectangle(containerScreenPoint, component.getVisibleRect().getSize()), content.getPreferredSize());
326 public void showCenteredInCurrentWindow(@NotNull Project project) {
327 Window window = null;
329 Component focusedComponent = getWndManager().getFocusedComponent(project);
330 if (focusedComponent != null) {
331 if (focusedComponent instanceof Window) {
332 window = (Window)focusedComponent;
334 else {
335 window = SwingUtilities.getWindowAncestor(focusedComponent);
338 if (window == null) {
339 window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
342 if (window != null) {
343 showInCenterOf(window);
347 public void showUnderneathOf(@NotNull Component aComponent) {
348 show(new RelativePoint(aComponent, new Point(0, aComponent.getHeight())));
351 public void show(@NotNull RelativePoint aPoint) {
352 final Point screenPoint = aPoint.getScreenPoint();
353 show(aPoint.getComponent(), screenPoint.x, screenPoint.y, false);
356 public void showInScreenCoordinates(@NotNull Component owner, @NotNull Point point) {
357 show(owner, point.x, point.y, false);
360 public void showInBestPositionFor(@NotNull DataContext dataContext) {
361 final Editor editor = PlatformDataKeys.EDITOR.getData(dataContext);
362 if (editor != null) {
363 showInBestPositionFor(editor);
365 else {
366 show(relativePointByQuickSearch(dataContext));
370 public void showInFocusCenter() {
371 final Component focused = getWndManager().getFocusedComponent(myProject);
372 if (focused != null) {
373 showInCenterOf(focused);
374 } else {
375 final JFrame frame = WindowManager.getInstance().getFrame(myProject);
376 showInCenterOf(frame.getRootPane());
380 private RelativePoint relativePointByQuickSearch(final DataContext dataContext) {
381 Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(dataContext);
383 if (dominantArea != null) {
384 final Component focusedComponent = getWndManager().getFocusedComponent(myProject);
385 Window window = SwingUtilities.windowForComponent(focusedComponent);
386 JLayeredPane layeredPane;
387 if (window instanceof JFrame) {
388 layeredPane = ((JFrame)window).getLayeredPane();
390 else if (window instanceof JDialog) {
391 layeredPane = ((JDialog)window).getLayeredPane();
393 else if (window instanceof JWindow) {
394 layeredPane = ((JWindow)window).getLayeredPane();
396 else {
397 throw new IllegalStateException("cannot find parent window: project=" + myProject + "; window=" + window);
400 return relativePointWithDominantRectangle(layeredPane, dominantArea);
403 return JBPopupFactory.getInstance().guessBestPopupLocation(dataContext);
406 public void showInBestPositionFor(@NotNull Editor editor) {
407 assert editor.getComponent().isShowing() : "Editor must be showing on the screen";
409 DataContext context = ((EditorEx)editor).getDataContext();
410 Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(context);
411 if (dominantArea != null && !myRequestFocus) {
412 final JLayeredPane layeredPane = editor.getContentComponent().getRootPane().getLayeredPane();
413 show(relativePointWithDominantRectangle(layeredPane, dominantArea));
415 else {
416 show(JBPopupFactory.getInstance().guessBestPopupLocation(editor));
420 public void addPopupListener(JBPopupListener listener) {
421 myListeners.add(listener);
424 private RelativePoint relativePointWithDominantRectangle(final JLayeredPane layeredPane, final Rectangle bounds) {
425 Dimension preferredSize = getComponent().getPreferredSize();
426 if (myDimensionServiceKey != null) {
427 final Dimension dimension = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
428 if (dimension != null) {
429 preferredSize = dimension;
432 final Point leftTopCorner = new Point(bounds.x + bounds.width, bounds.y);
433 final Point leftTopCornerScreen = (Point)leftTopCorner.clone();
434 SwingUtilities.convertPointToScreen(leftTopCornerScreen, layeredPane);
435 final RelativePoint relativePoint;
436 if (!ScreenUtil.isOutsideOnTheRightOFScreen(
437 new Rectangle(leftTopCornerScreen.x, leftTopCornerScreen.y, preferredSize.width, preferredSize.height))) {
438 relativePoint = new RelativePoint(layeredPane, leftTopCorner);
440 else {
441 if (bounds.x > preferredSize.width) {
442 relativePoint = new RelativePoint(layeredPane, new Point(bounds.x - preferredSize.width, bounds.y));
444 else {
445 setDimensionServiceKey(null); // going to cut width
446 Rectangle screen = ScreenUtil.getScreenRectangle(leftTopCornerScreen.x, leftTopCornerScreen.y);
447 final int spaceOnTheLeft = bounds.x;
448 final int spaceOnTheRight = (screen.x + screen.width) - leftTopCornerScreen.x;
449 if (spaceOnTheLeft > spaceOnTheRight) {
450 relativePoint = new RelativePoint(layeredPane, new Point(0, bounds.y));
451 myComponent.setPreferredSize(new Dimension(spaceOnTheLeft, Math.max(preferredSize.height, 200)));
453 else {
454 relativePoint = new RelativePoint(layeredPane, leftTopCorner);
455 myComponent.setPreferredSize(new Dimension(spaceOnTheRight, Math.max(preferredSize.height, 200)));
459 return relativePoint;
462 public final void cancel() {
463 cancel(null);
466 public void setRequestFocus(boolean requestFocus) {
467 myRequestFocus = requestFocus;
470 public void cancel(InputEvent e) {
471 if (isDisposed()) return;
473 if (myPopup != null) {
474 if (!canClose()) {
475 return;
477 storeDimensionSize(myContent.getSize());
478 if (myUseDimServiceForXYLocation) {
479 final JRootPane root = myComponent.getRootPane();
480 if (root != null) {
481 final Container popupWindow = root.getParent();
482 if (popupWindow != null && popupWindow.isShowing()) {
483 storeLocation(popupWindow.getLocationOnScreen());
488 if (e instanceof MouseEvent) {
489 JBAwtEventQueue.getInstance().blockNextEvents(((MouseEvent)e));
492 myPopup.hide(false);
494 if (ApplicationManagerEx.getApplicationEx() != null) {
495 StackingPopupDispatcher.getInstance().onPopupHidden(this);
498 if (myInStack) {
499 myFocusTrackback.restoreFocus();
503 disposePopup();
505 if (myListeners != null) {
506 for (JBPopupListener each : myListeners) {
507 each.onClosed(new LightweightWindowEvent(this));
512 Disposer.dispose(this, false);
516 private void disposePopup() {
517 if (myPopup != null) {
518 myPopup.hide(true);
520 myPopup = null;
523 public boolean canClose() {
524 return myCallBack == null || myCallBack.compute().booleanValue();
527 public boolean isVisible() {
528 return myPopup != null;
531 public void show(final Component owner) {
532 show(owner, -1, -1, true);
535 public void show(Component owner, int aScreenX, int aScreenY, final boolean considerForcedXY) {
536 if (ApplicationManagerEx.getApplicationEx() != null && ApplicationManager.getApplication().isHeadlessEnvironment()) return;
537 if (isDisposed()) {
538 throw new IllegalStateException("Popup was already disposed. Recreate a new instance to show again");
541 assert ApplicationManager.getApplication().isDispatchThread();
544 final boolean shouldShow = beforeShow();
545 if (!shouldShow) {
546 return;
549 prepareToShow();
551 if (myInStack) {
552 myFocusTrackback = new FocusTrackback(this, owner, true);
553 myFocusTrackback.setMustBeShown(true);
557 Dimension sizeToSet = null;
559 if (myDimensionServiceKey != null) {
560 sizeToSet = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
563 if (myForcedSize != null) {
564 sizeToSet = myForcedSize;
567 if (myMinSize == null) {
568 myMinSize = myContent.getMinimumSize();
571 if (sizeToSet == null) {
572 sizeToSet = myContent.getPreferredSize();
575 if (sizeToSet != null) {
576 sizeToSet.width = Math.max(sizeToSet.width, myMinSize.width);
577 sizeToSet.height = Math.max(sizeToSet.height, myMinSize.height);
579 myContent.setSize(sizeToSet);
580 myContent.setPreferredSize(sizeToSet);
583 Point xy = new Point(aScreenX, aScreenY);
584 boolean adjustXY = true;
585 if (myDimensionServiceKey != null) {
586 final Point storedLocation = DimensionService.getInstance().getLocation(myDimensionServiceKey, myProject);
587 if (storedLocation != null) {
588 xy = storedLocation;
589 adjustXY = false;
593 if (adjustXY) {
594 final Insets insets = myContent.getInsets();
595 if (insets != null) {
596 xy.x -= insets.left;
597 xy.y -= insets.top;
601 if (considerForcedXY && myForcedLocation != null) {
602 xy = myForcedLocation;
605 if (myLocateByContent) {
606 final Dimension captionSize = myHeaderPanel.getPreferredSize();
607 xy.y -= captionSize.height;
610 Rectangle targetBounds = new Rectangle(xy, myContent.getPreferredSize());
611 Rectangle original = new Rectangle(targetBounds);
612 if (myLocateWithinScreen) {
613 ScreenUtil.moveRectangleToFitTheScreen(targetBounds);
616 if (myMouseOutCanceller != null) {
617 myMouseOutCanceller.myEverEntered = targetBounds.equals(original);
620 myOwner = IdeFrameImpl.findNearestModalComponent(owner);
621 if (myOwner == null) {
622 myOwner = owner;
625 myRequestorComponent = owner;
627 PopupComponent.Factory factory = getFactory(myForcedHeavyweight || myResizable);
628 myNativePopup = factory.isNativePopup();
629 myPopup = factory.getPopup(myOwner, myContent, targetBounds.x, targetBounds.y);
631 if (myResizable) {
632 final JRootPane root = myContent.getRootPane();
633 final IdeGlassPaneImpl glass = new IdeGlassPaneImpl(root);
634 root.setGlassPane(glass);
636 final ResizeComponentListener resizeListener = new ResizeComponentListener(this);
637 glass.addMousePreprocessor(resizeListener, this);
638 glass.addMouseMotionPreprocessor(resizeListener, this);
641 if (myCaption != null && myMovable) {
642 final MoveComponentListener moveListener = new MoveComponentListener(myCaption) {
643 public void mousePressed(final MouseEvent e) {
644 super.mousePressed(e);
645 if (e.isConsumed()) return;
647 if (UIUtil.isCloseClick(e)) {
648 if (myCaption.isWithinPanel(e)) {
649 cancel();
654 ListenerUtil.addMouseListener(myCaption, moveListener);
655 ListenerUtil.addMouseMotionListener(myCaption, moveListener);
656 final MyContentPanel saved = myContent;
657 Disposer.register(this, new Disposable() {
658 public void dispose() {
659 ListenerUtil.removeMouseListener(saved, moveListener);
660 ListenerUtil.removeMouseMotionListener(saved, moveListener);
665 for(JBPopupListener listener: myListeners) {
666 listener.beforeShown(new LightweightWindowEvent(this));
669 myPopup.show();
671 final Window window = SwingUtilities.getWindowAncestor(myContent);
673 myWindowListener = new MyWindowListener();
674 window.addWindowListener(myWindowListener);
676 if (myFocusable) {
677 window.setFocusableWindowState(true);
678 window.setFocusable(true);
679 if (myRequestFocus) {
680 window.requestFocusInWindow();
684 myWindow = updateMaskAndAlpha(window);
686 if (myWindow instanceof JWindow) {
687 ((JWindow)myWindow).getRootPane().putClientProperty(KEY, this);
690 SwingUtilities.invokeLater(new Runnable() {
691 public void run() {
692 if (isDisposed()) return;
694 if (myRequestFocus) {
695 requestFocus();
698 if (myPreferredFocusedComponent != null && myInStack) {
699 myFocusTrackback.registerFocusComponent(myPreferredFocusedComponent);
702 afterShow();
707 private void prepareToShow() {
708 final MouseAdapter mouseAdapter = new MouseAdapter() {
709 public void mousePressed(MouseEvent e) {
710 Point point = (Point)e.getPoint().clone();
711 SwingUtilities.convertPointToScreen(point, e.getComponent());
713 final Dimension dimension = myContent.getSize();
714 dimension.height += myResizable && isToDrawMacCorner() ? ourMacCorner.getHeight(myContent) : 4;
715 dimension.width += 4;
716 Point locationOnScreen = myContent.getLocationOnScreen();
717 final Rectangle bounds = new Rectangle(new Point(locationOnScreen.x - 2, locationOnScreen.y - 2), dimension);
718 if (!bounds.contains(point)) {
719 cancel();
723 myContent.addMouseListener(mouseAdapter);
724 Disposer.register(this, new Disposable() {
725 public void dispose() {
726 myContent.removeMouseListener(mouseAdapter);
730 myContent.registerKeyboardAction(new ActionListener() {
731 public void actionPerformed(ActionEvent e) {
732 if (myCancelKeyEnabled) {
733 cancel();
736 }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
739 myContent.addKeyListener(new KeyListener() {
740 public void keyTyped(final KeyEvent e) {
741 mySpeedSearch.process(e);
744 public void keyPressed(final KeyEvent e) {
745 mySpeedSearch.process(e);
748 public void keyReleased(final KeyEvent e) {
749 mySpeedSearch.process(e);
753 if (myCancelOnMouseOutCallback != null || myCancelOnWindow) {
754 myMouseOutCanceller = new Canceller();
755 Toolkit.getDefaultToolkit().addAWTEventListener(myMouseOutCanceller, AWTEvent.MOUSE_EVENT_MASK | WindowEvent.WINDOW_ACTIVATED |
756 AWTEvent.MOUSE_MOTION_EVENT_MASK);
760 myFocusWatcher = new ChildFocusWatcher(myContent) {
761 protected void onFocusGained(final FocusEvent event) {
762 setWindowActive(true);
765 protected void onFocusLost(final FocusEvent event) {
766 setWindowActive(false);
771 mySpeedSearchPatternField = new JTextField();
772 if (SystemInfo.isMac) {
773 Font f = mySpeedSearchPatternField.getFont();
774 mySpeedSearchPatternField.setFont(f.deriveFont(f.getStyle(), f.getSize() - 2));
778 private Window updateMaskAndAlpha(Window window) {
779 if (window == null) return window;
781 final WindowManagerEx wndManager = getWndManager();
782 if (wndManager == null) return window;
784 if (!wndManager.isAlphaModeEnabled(window)) return window;
786 if (myAlpha != myLastAlpha) {
787 wndManager.setAlphaModeRatio(window, myAlpha);
788 myLastAlpha = myAlpha;
791 if (myMaskProvider != null) {
792 final Dimension size = window.getSize();
793 Shape mask = myMaskProvider.getMask(size);
794 wndManager.setWindowMask(window, mask);
797 return window;
800 private static WindowManagerEx getWndManager() {
801 return ApplicationManagerEx.getApplicationEx() != null ? WindowManagerEx.getInstanceEx() : null;
804 public boolean isDisposed() {
805 return myContent == null;
808 protected boolean beforeShow() {
809 if (ApplicationManagerEx.getApplicationEx() == null) return true;
810 StackingPopupDispatcher.getInstance().onPopupShown(this, myInStack);
811 return true;
814 protected void afterShow() {
817 protected final void requestFocus() {
818 if (!myFocusable) return;
820 if (myPreferredFocusedComponent != null) {
821 if (myProject != null) {
822 getFocusManager().requestFocus(myPreferredFocusedComponent, true);
824 else {
825 myPreferredFocusedComponent.requestFocus();
830 private IdeFocusManager getFocusManager() {
831 if (myProject != null) {
832 return IdeFocusManager.getInstance(myProject);
833 } else if (myOwner != null) {
834 return IdeFocusManager.findInstanceByComponent(myOwner);
835 } else {
836 return IdeFocusManager.findInstance();
840 private static JComponent getTargetComponent(Component aComponent) {
841 if (aComponent instanceof JComponent) {
842 return (JComponent)aComponent;
844 else if (aComponent instanceof RootPaneContainer) {
845 return ((RootPaneContainer)aComponent).getRootPane();
848 LOG.error("Cannot find target for:" + aComponent);
849 return null;
852 private PopupComponent.Factory getFactory(boolean forceHeavyweight) {
853 if (isPersistent()) {
854 return new PopupComponent.Factory.Dialog();
855 } else if (forceHeavyweight || !SystemInfo.isWindows) {
856 return new PopupComponent.Factory.AwtHeavyweight();
857 } else {
858 return new PopupComponent.Factory.AwtDefault();
862 public JComponent getContent() {
863 return myContent;
866 public void setLocation(RelativePoint p) {
867 setLocation(p, myPopup, myContent);
870 private static void setLocation(final RelativePoint p, final PopupComponent popup, Component content) {
871 if (popup == null) return;
873 final Window wnd = popup.getWindow();
874 assert wnd != null;
876 wnd.setLocation(p.getScreenPoint());
879 public void pack() {
880 final Window window = SwingUtilities.getWindowAncestor(myContent);
882 window.pack();
885 public JComponent getComponent() {
886 return myComponent;
889 public void setProject(Project project) {
890 myProject = project;
894 public void dispose() {
895 Disposer.dispose(this, false);
897 assert ApplicationManager.getApplication().isDispatchThread();
899 if (myPopup != null) {
900 cancel(myDisposeEvent);
903 if (myContent != null) {
904 myContent.removeAll();
906 myContent = null;
907 myComponent = null;
908 myFocusTrackback = null;
909 myCallBack = null;
910 myListeners = null;
912 if (myMouseOutCanceller != null) {
913 final Toolkit toolkit = Toolkit.getDefaultToolkit();
914 // it may happen, but have no idea how
915 // http://www.jetbrains.net/jira/browse/IDEADEV-21265
916 if (toolkit != null) {
917 toolkit.removeAWTEventListener(myMouseOutCanceller);
920 myMouseOutCanceller = null;
922 if (myFocusWatcher != null) {
923 myFocusWatcher.dispose();
924 myFocusWatcher = null;
927 resetWindow();
929 if (myFinalRunnable != null) {
930 getFocusManager().doWhenFocusSettlesDown(myFinalRunnable);
931 myFinalRunnable = null;
935 private void resetWindow() {
936 if (myWindow != null && getWndManager() != null) {
937 getWndManager().resetWindow(myWindow);
938 if (myWindowListener != null) {
939 myWindow.removeWindowListener(myWindowListener);
942 if (myWindow instanceof JWindow) {
943 ((JWindow)myWindow).getRootPane().putClientProperty(KEY, null);
946 myWindow = null;
947 myWindowListener = null;
951 public void storeDimensionSize(final Dimension size) {
952 if (myDimensionServiceKey != null) {
953 DimensionService.getInstance().setSize(myDimensionServiceKey, size, myProject);
957 public void storeLocation(final Point xy) {
958 if (myDimensionServiceKey != null) {
959 DimensionService.getInstance().setLocation(myDimensionServiceKey, xy, myProject);
963 public static class MyContentPanel extends JPanel {
964 private final boolean myResizable;
965 private final boolean myDrawMacCorner;
966 private final boolean myShadowed;
968 public MyContentPanel(final boolean resizable, final PopupBorder border, boolean drawMacCorner) {
969 this(resizable, border, drawMacCorner, false);
972 public MyContentPanel(final boolean resizable, final PopupBorder border, boolean drawMacCorner, boolean shadowed) {
973 super(new BorderLayout());
974 myResizable = resizable;
975 myDrawMacCorner = drawMacCorner;
976 myShadowed = shadowed;
977 if (isShadowPossible()) {
978 setOpaque(false);
979 setBorder(new EmptyBorder(POPUP_TOP_SIZE, POPUP_SIDE_SIZE, POPUP_BOTTOM_SIZE, POPUP_SIDE_SIZE) {
980 @Override
981 public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
982 border.paintBorder(c, g,
983 x + POPUP_SIDE_SIZE - 1,
984 y + POPUP_TOP_SIZE - 1,
985 width - 2 * POPUP_SIDE_SIZE + 2,
986 height - POPUP_TOP_SIZE - POPUP_BOTTOM_SIZE + 2);
989 } else {
990 setBorder(border);
994 private boolean isShadowPossible() {
995 return myShadowed && !SystemInfo.isMac && !UISettings.isRemoteDesktopConnected();
998 public void paint(Graphics g) {
999 if (isShadowPossible()) {
1000 paintShadow(g);
1003 super.paint(g);
1005 if (myResizable && myDrawMacCorner) {
1006 g.drawImage(ourMacCorner,
1007 getX() + getWidth() - ourMacCorner.getWidth(this),
1008 getY() + getHeight() - ourMacCorner.getHeight(this),
1009 this);
1013 private void paintShadow(final Graphics g) {
1014 BufferedImage capture = null;
1015 try {
1016 final Point onScreen = getLocationOnScreen();
1017 capture = new Robot().createScreenCapture(
1018 new Rectangle(onScreen.x, onScreen.y, getWidth() + 2 * POPUP_SIDE_SIZE, getHeight() + POPUP_TOP_SIZE + POPUP_BOTTOM_SIZE));
1019 final BufferedImage shadow = ShadowBorderPainter.createPopupShadow(this, getWidth(), getHeight());
1020 ((Graphics2D)capture.getGraphics()).drawImage(shadow, null, null);
1022 catch (Exception e) {
1023 LOG.info(e);
1025 if (capture != null) g.drawImage(capture, 0, 0, null);
1029 public boolean isCancelOnClickOutside() {
1030 return myCancelOnClickOutside;
1033 private class Canceller implements AWTEventListener {
1035 private boolean myEverEntered = false;
1037 public void eventDispatched(final AWTEvent event) {
1038 if (event.getID() == WindowEvent.WINDOW_ACTIVATED) {
1039 if (myCancelOnWindow) {
1040 cancel();
1042 } else if (event.getID() == MouseEvent.MOUSE_ENTERED) {
1043 if (withinPopup(event)) {
1044 myEverEntered = true;
1046 } else if (event.getID() == MouseEvent.MOUSE_MOVED) {
1047 if (myCancelOnMouseOutCallback != null && myEverEntered && !withinPopup(event)) {
1048 if (myCancelOnMouseOutCallback.check((MouseEvent)event)) {
1049 cancel();
1055 private boolean withinPopup(final AWTEvent event) {
1056 if (!myContent.isShowing()) return false;
1058 final MouseEvent mouse = (MouseEvent)event;
1059 final Point point = mouse.getPoint();
1060 SwingUtilities.convertPointToScreen(point, mouse.getComponent());
1061 return new Rectangle(myContent.getLocationOnScreen(), myContent.getSize()).contains(point);
1065 public void setLocation(@NotNull final Point screenPoint) {
1066 if (myPopup == null) {
1067 myForcedLocation = screenPoint;
1068 } else {
1069 moveTo(myContent, screenPoint, myLocateByContent ? myHeaderPanel.getPreferredSize() : null);
1073 public static Window moveTo(JComponent content, Point screenPoint, final Dimension headerCorrectionSize) {
1074 setDefaultCursor(content);
1075 final Window wnd = SwingUtilities.getWindowAncestor(content);
1076 if (headerCorrectionSize != null) {
1077 screenPoint.y -= headerCorrectionSize.height;
1079 wnd.setLocation(screenPoint);
1080 return wnd;
1083 public void setSize(@NotNull final Dimension size) {
1084 if (myPopup == null) {
1085 myForcedSize = size;
1086 } else {
1087 updateMaskAndAlpha(setSize(myContent, size));
1091 public static Window setSize(JComponent content, final Dimension size) {
1092 final Window popupWindow = SwingUtilities.windowForComponent(content);
1093 final Point location = popupWindow.getLocation();
1094 popupWindow.setBounds(location.x, location.y, size.width, size.height);
1095 return popupWindow;
1098 public static void setDefaultCursor(JComponent content) {
1099 final Window wnd = SwingUtilities.getWindowAncestor(content);
1100 if (wnd != null) {
1101 wnd.setCursor(Cursor.getDefaultCursor());
1105 public void setCaption(String title) {
1106 if (myCaption instanceof TitlePanel) {
1107 ((TitlePanel)myCaption).setText(title);
1111 private class MyWindowListener extends WindowAdapter {
1112 public void windowClosed(final WindowEvent e) {
1113 resetWindow();
1117 public boolean isPersistent() {
1118 return !myCancelOnClickOutside && !myCancelOnWindow;
1121 public boolean isNativePopup() {
1122 return myNativePopup;
1125 public void setUiVisible(final boolean visible) {
1126 if (myPopup != null) {
1127 if (visible) {
1128 myPopup.show();
1129 final Window window = getPopupWindow();
1130 if (window != null && myRestoreWindowSize != null) {
1131 window.setSize(myRestoreWindowSize);
1132 myRestoreWindowSize = null;
1135 else {
1136 final Window window = getPopupWindow();
1137 if (window != null) {
1138 myRestoreWindowSize = window.getSize();
1139 window.setVisible(true);
1145 private Window getPopupWindow() {
1146 return myPopup.getWindow();
1149 public void setUserData(ArrayList<Object> userData) {
1150 myUserData = userData;
1153 public <T> T getUserData(final Class<T> userDataClass) {
1154 if (myUserData != null) {
1155 for(Object o: myUserData) {
1156 if (userDataClass.isInstance(o)) {
1157 //noinspection unchecked
1158 return (T) o;
1162 return null;
1165 public boolean isModalContext() {
1166 return myModalContext;
1169 public boolean isFocused() {
1170 if (myComponent != null && isFocused(new Component[] {SwingUtilities.getWindowAncestor(myComponent)}))
1171 return true;
1172 return isFocused(myFocusOwners);
1175 public static boolean isFocused(@Nullable Component[] components) {
1176 if (components == null) return false;
1178 Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1180 if (owner == null) return false;
1181 for (Component each : components) {
1182 if (each != null && SwingUtilities.isDescendingFrom(owner, each)) return true;
1185 return false;
1188 public boolean isCancelKeyEnabled() {
1189 return myCancelKeyEnabled;
1192 @NotNull
1193 CaptionPanel getTitle() {
1194 return myCaption;
1197 private void setHeaderComponent(JComponent c) {
1198 boolean doRevalidate = false;
1199 if (myHeaderComponent != null) {
1200 myHeaderPanel.remove(myHeaderComponent);
1201 myHeaderPanel.add(myCaption, BorderLayout.NORTH);
1202 myHeaderComponent = null;
1203 doRevalidate = true;
1206 if (c != null) {
1207 myHeaderPanel.remove(myCaption);
1208 myHeaderPanel.add(c, BorderLayout.NORTH);
1209 myHeaderComponent = c;
1211 final Dimension size = myContent.getSize();
1212 if (size.height < c.getPreferredSize().height * 2) {
1213 size.height += c.getPreferredSize().height;
1214 setSize(size);
1217 doRevalidate = true;
1220 if (doRevalidate) myContent.revalidate();
1223 public void addListener(final JBPopupListener listener) {
1224 myListeners.add(listener);
1227 public void removeListener(final JBPopupListener listener) {
1228 myListeners.remove(listener);
1231 protected void onSpeedSearchPatternChanged() {
1234 public Component getOwner() {
1235 return myRequestorComponent;
1238 public void setMinimumSize(Dimension size) {
1239 myMinSize = size;
1242 public Runnable getFinalRunnable() {
1243 return myFinalRunnable;
1246 public void setFinalRunnable(Runnable finalRunnable) {
1247 myFinalRunnable = finalRunnable;