db179130e98b17ff91d3767ecc8b0d77dc97025f
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / popup / AbstractPopup.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ui.popup;
17
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;
49
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;
59
60 import static com.intellij.openapi.ui.impl.ShadowBorderPainter.*;
61
62 public class AbstractPopup implements JBPopup {
63   private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.AbstractPopup");
64
65   private static final Image ourMacCorner = ImageLoader.loadFromResource("/general/macCorner.png");
66
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;
96   private boolean myPaintShadow;
97
98   private float myAlpha = 0;
99   private float myLastAlpha = 0;
100
101   private MaskProvider myMaskProvider;
102
103   private Window myWindow;
104   private boolean myInStack;
105   private MyWindowListener myWindowListener;
106
107   private boolean myModalContext;
108
109   private Component[] myFocusOwners;
110   private PopupBorder myPopupBorder;
111   private Dimension myRestoreWindowSize;
112   protected Component myOwner;
113   protected Component myRequestorComponent;
114   private boolean myHeaderAlwaysFocusable;
115   private boolean myMovable;
116   private JComponent myHeaderComponent;
117
118   protected InputEvent myDisposeEvent;
119
120   private Runnable myFinalRunnable;
121
122   protected boolean myOk;
123
124   protected final SpeedSearch mySpeedSearch = new SpeedSearch() {
125     boolean searchFieldShown = false;
126
127     protected void update() {
128       mySpeedSearchPatternField.setBackground(new JTextField().getBackground());
129       onSpeedSearchPatternChanged();
130       mySpeedSearchPatternField.setText(getFilter());
131       if (isHoldingFilter() && !searchFieldShown) {
132         setHeaderComponent(mySpeedSearchPatternField);
133         searchFieldShown = true;
134       }
135       else if (!isHoldingFilter() && searchFieldShown) {
136         setHeaderComponent(null);
137         searchFieldShown = false;
138       }
139     }
140
141     @Override
142     public void noHits() {
143       mySpeedSearchPatternField.setBackground(LightColors.RED);
144     }
145   };
146
147   private JTextField mySpeedSearchPatternField;
148   private boolean myNativePopup;
149   private boolean myMayBeParent;
150
151
152   AbstractPopup() {
153   }
154
155   AbstractPopup init(final Project project,
156                      @NotNull final JComponent component,
157                      @Nullable final JComponent preferredFocusedComponent,
158                      final boolean requestFocus,
159                      final boolean focusable,
160                      final boolean forceHeavyweight,
161                      final boolean movable,
162                      final String dimensionServiceKey,
163                      final boolean resizable,
164                      @Nullable final String caption,
165                      @Nullable final Computable<Boolean> callback,
166                      final boolean cancelOnClickOutside,
167                      @Nullable final Set<JBPopupListener> listeners,
168                      final boolean useDimServiceForXYLocation,
169                      InplaceButton commandButton,
170                      @Nullable final IconButton cancelButton,
171                      @Nullable final MouseChecker cancelOnMouseOutCallback,
172                      final boolean cancelOnWindow,
173                      @Nullable final ActiveIcon titleIcon,
174                      final boolean cancelKeyEnabled,
175                      final boolean locateBycontent,
176                      final boolean placeWithinScreenBounds,
177                      @Nullable final Dimension minSize,
178                      float alpha,
179                      @Nullable MaskProvider maskProvider,
180                      boolean inStack,
181                      boolean modalContext,
182                      @Nullable Component[] focusOwners,
183                      @Nullable String adText,
184                      final boolean headerAlwaysFocusable,
185                      @NotNull List<Pair<ActionListener, KeyStroke>> keyboardActions,
186                      Component settingsButtons,
187                      @Nullable final Processor<JBPopup> pinCallback,
188                      boolean mayBeParent,
189                      boolean showShadow) {
190
191     if (requestFocus && !focusable) {
192       assert false : "Incorrect argument combination: requestFocus=" + requestFocus + " focusable=" + focusable;
193     }
194
195     myProject = project;
196     myComponent = component;
197     myPopupBorder = PopupBorder.Factory.create(true);
198     myShadowed = showShadow;
199     myPaintShadow = showShadow && !SystemInfo.isMac && !movable && !resizable && Registry.is("ide.popup.dropShadow");
200     myContent = createContentPanel(resizable, myPopupBorder, isToDrawMacCorner());
201     myMayBeParent = mayBeParent;
202
203     myContent.add(component, BorderLayout.CENTER);
204     if (adText != null) {
205       myContent.add(HintUtil.createAdComponent(adText), BorderLayout.SOUTH);
206     }
207
208     myCancelKeyEnabled = cancelKeyEnabled;
209     myLocateByContent = locateBycontent;
210     myLocateWithinScreen = placeWithinScreenBounds;
211     myAlpha = alpha;
212     myMaskProvider = maskProvider;
213     myInStack = inStack;
214     myModalContext = modalContext;
215     myFocusOwners = focusOwners;
216     myHeaderAlwaysFocusable = headerAlwaysFocusable;
217     myMovable = movable;
218
219     ActiveIcon actualIcon = titleIcon == null ? new ActiveIcon(new EmptyIcon(0)) : titleIcon;
220
221     myHeaderPanel = new JPanel(new BorderLayout());
222
223     if (caption != null) {
224       if (caption.length() > 0) {
225         myCaption = new TitlePanel(actualIcon.getRegular(), actualIcon.getInactive());
226         ((TitlePanel)myCaption).setText(caption);
227       }
228       else {
229         myCaption = new CaptionPanel();
230       }
231
232       if (pinCallback != null) {
233         myCaption.setButtonComponent(new InplaceButton(new IconButton("Pin", IconLoader.getIcon("/general/autohideOff.png"),
234                                                                       IconLoader.getIcon("/general/autohideOff.png"),
235                                                                       IconLoader.getIcon("/general/autohideOffInactive.png")),
236                                                        new ActionListener() {
237                                                          public void actionPerformed(final ActionEvent e) {
238                                                            pinCallback.process(AbstractPopup.this);
239                                                          }
240                                                        }));
241       }
242       else if (cancelButton != null) {
243         myCaption.setButtonComponent(new InplaceButton(cancelButton, new ActionListener() {
244           public void actionPerformed(final ActionEvent e) {
245             cancel();
246           }
247         }));
248       }
249       else if (commandButton != null) {
250         myCaption.setButtonComponent(commandButton);
251       }
252     }
253     else {
254       myCaption = new CaptionPanel();
255       myCaption.setBorder(null);
256       myCaption.setPreferredSize(new Dimension(0, 0));
257     }
258
259     setWindowActive(myHeaderAlwaysFocusable);
260
261     myHeaderPanel.add(myCaption, BorderLayout.NORTH);
262     myContent.add(myHeaderPanel, BorderLayout.NORTH);
263
264     myForcedHeavyweight = forceHeavyweight;
265     myResizable = resizable;
266     myPreferredFocusedComponent = preferredFocusedComponent;
267     myRequestFocus = requestFocus;
268     myFocusable = focusable;
269     myDimensionServiceKey = dimensionServiceKey;
270     myCallBack = callback;
271     myCancelOnClickOutside = cancelOnClickOutside;
272     myCancelOnMouseOutCallback = cancelOnMouseOutCallback;
273     myListeners = listeners == null ? new HashSet<JBPopupListener>() : listeners;
274     myUseDimServiceForXYLocation = useDimServiceForXYLocation;
275     myCancelOnWindow = cancelOnWindow;
276     myMinSize = minSize;
277
278     for (Pair<ActionListener, KeyStroke> pair : keyboardActions) {
279       myContent.registerKeyboardAction(pair.getFirst(), pair.getSecond(), JComponent.WHEN_IN_FOCUSED_WINDOW);
280     }
281
282     if (settingsButtons != null) {
283       myCaption.addSettingsComponent(settingsButtons);
284     }
285
286     return this;
287   }
288
289   private void setWindowActive(boolean active) {
290     boolean value = myHeaderAlwaysFocusable || active;
291
292     if (myCaption != null) {
293       myCaption.setActive(value);
294     }
295     myPopupBorder.setActive(value);
296     myContent.repaint();
297   }
298
299
300   @NotNull
301   protected MyContentPanel createContentPanel(final boolean resizable, PopupBorder border, boolean isToDrawMacCorner) {
302     return new MyContentPanel(resizable, border, isToDrawMacCorner, myPaintShadow);
303   }
304
305   public static boolean isToDrawMacCorner() {
306     return SystemInfo.isMac;
307   }
308
309
310   public String getDimensionServiceKey() {
311     return myDimensionServiceKey;
312   }
313
314   public void setDimensionServiceKey(final String dimensionServiceKey) {
315     myDimensionServiceKey = dimensionServiceKey;
316   }
317
318   public void showInCenterOf(@NotNull Component aContainer) {
319     final Point popupPoint = getCenterOf(aContainer, myContent);
320     show(aContainer, popupPoint.x, popupPoint.y, false);
321   }
322
323   public void setAdText(@NotNull final String s) {
324     myContent.add(HintUtil.createAdComponent(s, BorderFactory.createEmptyBorder(3, 5, 3, 5)), BorderLayout.SOUTH);
325   }
326
327   public static Point getCenterOf(final Component aContainer, final JComponent content) {
328     final JComponent component = getTargetComponent(aContainer);
329
330     Point containerScreenPoint = component.getVisibleRect().getLocation();
331     SwingUtilities.convertPointToScreen(containerScreenPoint, aContainer);
332
333     return UIUtil.getCenterPoint(new Rectangle(containerScreenPoint, component.getVisibleRect().getSize()), content.getPreferredSize());
334   }
335
336   public void showCenteredInCurrentWindow(@NotNull Project project) {
337     Window window = null;
338
339     Component focusedComponent = getWndManager().getFocusedComponent(project);
340     if (focusedComponent != null) {
341       Component parent = UIUtil.findUltimateParent(focusedComponent);
342       if (parent instanceof Window) {
343         window = (Window)parent;
344       }
345     }
346     if (window == null) {
347       window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
348     }
349
350     if (window != null) {
351       showInCenterOf(window);
352     }
353   }
354
355   public void showUnderneathOf(@NotNull Component aComponent) {
356     show(new RelativePoint(aComponent, new Point(0, aComponent.getHeight())));
357   }
358
359   public void show(@NotNull RelativePoint aPoint) {
360     final Point screenPoint = aPoint.getScreenPoint();
361     show(aPoint.getComponent(), screenPoint.x, screenPoint.y, false);
362   }
363
364   public void showInScreenCoordinates(@NotNull Component owner, @NotNull Point point) {
365     show(owner, point.x, point.y, false);
366   }
367
368   public void showInBestPositionFor(@NotNull DataContext dataContext) {
369     final Editor editor = PlatformDataKeys.EDITOR.getData(dataContext);
370     if (editor != null) {
371       showInBestPositionFor(editor);
372     }
373     else {
374       show(relativePointByQuickSearch(dataContext));
375     }
376   }
377
378   public void showInFocusCenter() {
379     final Component focused = getWndManager().getFocusedComponent(myProject);
380     if (focused != null) {
381       showInCenterOf(focused);
382     }
383     else {
384       final JFrame frame = WindowManager.getInstance().getFrame(myProject);
385       showInCenterOf(frame.getRootPane());
386     }
387   }
388
389   private RelativePoint relativePointByQuickSearch(final DataContext dataContext) {
390     Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(dataContext);
391
392     if (dominantArea != null) {
393       final Component focusedComponent = getWndManager().getFocusedComponent(myProject);
394       Window window = SwingUtilities.windowForComponent(focusedComponent);
395       JLayeredPane layeredPane;
396       if (window instanceof JFrame) {
397         layeredPane = ((JFrame)window).getLayeredPane();
398       }
399       else if (window instanceof JDialog) {
400         layeredPane = ((JDialog)window).getLayeredPane();
401       }
402       else if (window instanceof JWindow) {
403         layeredPane = ((JWindow)window).getLayeredPane();
404       }
405       else {
406         throw new IllegalStateException("cannot find parent window: project=" + myProject + "; window=" + window);
407       }
408
409       return relativePointWithDominantRectangle(layeredPane, dominantArea);
410     }
411
412     return JBPopupFactory.getInstance().guessBestPopupLocation(dataContext);
413   }
414
415   public void showInBestPositionFor(@NotNull Editor editor) {
416     assert editor.getComponent().isShowing() : "Editor must be showing on the screen";
417
418     DataContext context = ((EditorEx)editor).getDataContext();
419     Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(context);
420     if (dominantArea != null && !myRequestFocus) {
421       final JLayeredPane layeredPane = editor.getContentComponent().getRootPane().getLayeredPane();
422       show(relativePointWithDominantRectangle(layeredPane, dominantArea));
423     }
424     else {
425       show(JBPopupFactory.getInstance().guessBestPopupLocation(editor));
426     }
427   }
428
429   public void addPopupListener(JBPopupListener listener) {
430     myListeners.add(listener);
431   }
432
433   private RelativePoint relativePointWithDominantRectangle(final JLayeredPane layeredPane, final Rectangle bounds) {
434     Dimension preferredSize = getComponent().getPreferredSize();
435     if (myDimensionServiceKey != null) {
436       final Dimension dimension = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
437       if (dimension != null) {
438         preferredSize = dimension;
439       }
440     }
441     final Point leftTopCorner = new Point(bounds.x + bounds.width, bounds.y);
442     final Point leftTopCornerScreen = (Point)leftTopCorner.clone();
443     SwingUtilities.convertPointToScreen(leftTopCornerScreen, layeredPane);
444     final RelativePoint relativePoint;
445     if (!ScreenUtil.isOutsideOnTheRightOFScreen(
446       new Rectangle(leftTopCornerScreen.x, leftTopCornerScreen.y, preferredSize.width, preferredSize.height))) {
447       relativePoint = new RelativePoint(layeredPane, leftTopCorner);
448     }
449     else {
450       if (bounds.x > preferredSize.width) {
451         relativePoint = new RelativePoint(layeredPane, new Point(bounds.x - preferredSize.width, bounds.y));
452       }
453       else {
454         setDimensionServiceKey(null); // going to cut width
455         Rectangle screen = ScreenUtil.getScreenRectangle(leftTopCornerScreen.x, leftTopCornerScreen.y);
456         final int spaceOnTheLeft = bounds.x;
457         final int spaceOnTheRight = (screen.x + screen.width) - leftTopCornerScreen.x;
458         if (spaceOnTheLeft > spaceOnTheRight) {
459           relativePoint = new RelativePoint(layeredPane, new Point(0, bounds.y));
460           myComponent.setPreferredSize(new Dimension(spaceOnTheLeft, Math.max(preferredSize.height, 200)));
461         }
462         else {
463           relativePoint = new RelativePoint(layeredPane, leftTopCorner);
464           myComponent.setPreferredSize(new Dimension(spaceOnTheRight, Math.max(preferredSize.height, 200)));
465         }
466       }
467     }
468     return relativePoint;
469   }
470
471   public final void closeOk(@Nullable InputEvent e) {
472     setOk(true);
473     cancel(e);
474   }
475
476   public final void cancel() {
477     cancel(null);
478   }
479
480   public void setRequestFocus(boolean requestFocus) {
481     myRequestFocus = requestFocus;
482   }
483
484   public void cancel(InputEvent e) {
485     if (isDisposed()) return;
486
487     if (myPopup != null) {
488       if (!canClose()) {
489         return;
490       }
491       storeDimensionSize(myContent.getSize());
492       if (myUseDimServiceForXYLocation) {
493         final JRootPane root = myComponent.getRootPane();
494         if (root != null) {
495           final Container popupWindow = root.getParent();
496           if (popupWindow != null && popupWindow.isShowing()) {
497             storeLocation(popupWindow.getLocationOnScreen());
498           }
499         }
500       }
501
502       if (e instanceof MouseEvent) {
503         JBAwtEventQueue.getInstance().blockNextEvents(((MouseEvent)e));
504       }
505
506       myPopup.hide(false);
507
508       if (ApplicationManagerEx.getApplicationEx() != null) {
509         StackingPopupDispatcher.getInstance().onPopupHidden(this);
510       }
511
512       if (myInStack) {
513         myFocusTrackback.restoreFocus();
514       }
515
516
517       disposePopup();
518
519       if (myListeners != null) {
520         for (JBPopupListener each : myListeners) {
521           each.onClosed(new LightweightWindowEvent(this, myOk));
522         }
523       }
524     }
525
526     Disposer.dispose(this, false);
527   }
528
529
530   private void disposePopup() {
531     if (myPopup != null) {
532       myPopup.hide(true);
533     }
534     myPopup = null;
535   }
536
537   public boolean canClose() {
538     return myCallBack == null || myCallBack.compute().booleanValue();
539   }
540
541   public boolean isVisible() {
542     return myPopup != null;
543   }
544
545   public void show(final Component owner) {
546     show(owner, -1, -1, true);
547   }
548
549   public void show(Component owner, int aScreenX, int aScreenY, final boolean considerForcedXY) {
550     if (ApplicationManagerEx.getApplicationEx() != null && ApplicationManager.getApplication().isHeadlessEnvironment()) return;
551     if (isDisposed()) {
552       throw new IllegalStateException("Popup was already disposed. Recreate a new instance to show again");
553     }
554
555     assert ApplicationManager.getApplication().isDispatchThread();
556
557
558     final boolean shouldShow = beforeShow();
559     if (!shouldShow) {
560       return;
561     }
562
563     prepareToShow();
564
565     if (myInStack) {
566       myFocusTrackback = new FocusTrackback(this, owner, true);
567       myFocusTrackback.setMustBeShown(true);
568     }
569
570
571     Dimension sizeToSet = null;
572
573     if (myDimensionServiceKey != null) {
574       sizeToSet = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
575     }
576
577     if (myForcedSize != null) {
578       sizeToSet = myForcedSize;
579     }
580
581     if (myMinSize == null) {
582       myMinSize = myContent.getMinimumSize();
583     }
584
585     if (sizeToSet == null) {
586       sizeToSet = myContent.getPreferredSize();
587     }
588
589     if (sizeToSet != null) {
590       sizeToSet.width = Math.max(sizeToSet.width, myMinSize.width);
591       sizeToSet.height = Math.max(sizeToSet.height, myMinSize.height);
592
593       myContent.setSize(sizeToSet);
594       myContent.setPreferredSize(sizeToSet);
595     }
596
597     Point xy = new Point(aScreenX, aScreenY);
598     boolean adjustXY = true;
599     if (myDimensionServiceKey != null) {
600       final Point storedLocation = DimensionService.getInstance().getLocation(myDimensionServiceKey, myProject);
601       if (storedLocation != null) {
602         xy = storedLocation;
603         adjustXY = false;
604       }
605     }
606
607     if (adjustXY) {
608       final Insets insets = myContent.getInsets();
609       if (insets != null) {
610         xy.x -= insets.left;
611         xy.y -= insets.top;
612       }
613     }
614
615     if (considerForcedXY && myForcedLocation != null) {
616       xy = myForcedLocation;
617     }
618
619     if (myLocateByContent) {
620       final Dimension captionSize = myHeaderPanel.getPreferredSize();
621       xy.y -= captionSize.height;
622     }
623
624     Rectangle targetBounds = new Rectangle(xy, myContent.getPreferredSize());
625     Rectangle original = new Rectangle(targetBounds);
626     if (myLocateWithinScreen) {
627       ScreenUtil.moveRectangleToFitTheScreen(targetBounds);
628     }
629
630     if (myMouseOutCanceller != null) {
631       myMouseOutCanceller.myEverEntered = targetBounds.equals(original);
632     }
633
634     myOwner = IdeFrameImpl.findNearestModalComponent(owner);
635     if (myOwner == null) {
636       myOwner = owner;
637     }
638
639     myRequestorComponent = owner;
640
641     PopupComponent.Factory factory = getFactory(myForcedHeavyweight || myResizable);
642     myNativePopup = factory.isNativePopup();
643     myPopup = factory.getPopup(myOwner, myContent, targetBounds.x, targetBounds.y);
644
645     if (myResizable) {
646       final JRootPane root = myContent.getRootPane();
647       final IdeGlassPaneImpl glass = new IdeGlassPaneImpl(root);
648       root.setGlassPane(glass);
649
650       final ResizeComponentListener resizeListener = new ResizeComponentListener(this);
651       glass.addMousePreprocessor(resizeListener, this);
652       glass.addMouseMotionPreprocessor(resizeListener, this);
653     }
654
655     if (myCaption != null && myMovable) {
656       final MoveComponentListener moveListener = new MoveComponentListener(myCaption) {
657         public void mousePressed(final MouseEvent e) {
658           super.mousePressed(e);
659           if (e.isConsumed()) return;
660
661           if (UIUtil.isCloseClick(e)) {
662             if (myCaption.isWithinPanel(e)) {
663               cancel();
664             }
665           }
666         }
667       };
668       ListenerUtil.addMouseListener(myCaption, moveListener);
669       ListenerUtil.addMouseMotionListener(myCaption, moveListener);
670       final MyContentPanel saved = myContent;
671       Disposer.register(this, new Disposable() {
672         public void dispose() {
673           ListenerUtil.removeMouseListener(saved, moveListener);
674           ListenerUtil.removeMouseMotionListener(saved, moveListener);
675         }
676       });
677     }
678
679     for (JBPopupListener listener : myListeners) {
680       listener.beforeShown(new LightweightWindowEvent(this));
681     }
682
683     Window w = myPopup.getWindow();
684     if (w != null) {
685       WindowManagerEx.WindowShadowMode mode =
686         myShadowed ? WindowManagerEx.WindowShadowMode.NORMAL : WindowManagerEx.WindowShadowMode.DISABLED;
687       WindowManagerEx.getInstanceEx().setWindowShadow(myWindow, mode);
688     }
689
690     myPopup.setRequestFocus(myRequestFocus);
691     myPopup.show();
692
693     final Window window = SwingUtilities.getWindowAncestor(myContent);
694
695     myWindowListener = new MyWindowListener();
696     window.addWindowListener(myWindowListener);
697
698     if (myFocusable) {
699       window.setFocusableWindowState(true);
700       window.setFocusable(true);
701       if (myRequestFocus) {
702         window.requestFocusInWindow();
703       }
704     }
705
706     myWindow = updateMaskAndAlpha(window);
707
708     if (myWindow instanceof JWindow) {
709       ((JWindow)myWindow).getRootPane().putClientProperty(KEY, this);
710     }
711
712     if (myWindow != null) {
713       // dialogwrapper-based popups do this internally through peer,
714       // for other popups like jdialog-based we should exclude them manually, but
715       // we still have to be able to use IdeFrame as parent
716       if (!myMayBeParent && !(myWindow instanceof Frame)) {
717         WindowManager.getInstance().doNotSuggestAsParent(myWindow);
718       }
719     }
720
721     SwingUtilities.invokeLater(new Runnable() {
722       public void run() {
723         if (isDisposed()) return;
724
725         if (myRequestFocus) {
726           requestFocus();
727         }
728
729         if (myPreferredFocusedComponent != null && myInStack) {
730           myFocusTrackback.registerFocusComponent(myPreferredFocusedComponent);
731         }
732
733         afterShow();
734       }
735     });
736   }
737
738   private void prepareToShow() {
739     final MouseAdapter mouseAdapter = new MouseAdapter() {
740       public void mousePressed(MouseEvent e) {
741         Point point = (Point)e.getPoint().clone();
742         SwingUtilities.convertPointToScreen(point, e.getComponent());
743
744         final Dimension dimension = myContent.getSize();
745         dimension.height += myResizable && isToDrawMacCorner() ? ourMacCorner.getHeight(myContent) : 4;
746         dimension.width += 4;
747         Point locationOnScreen = myContent.getLocationOnScreen();
748         final Rectangle bounds = new Rectangle(new Point(locationOnScreen.x - 2, locationOnScreen.y - 2), dimension);
749         if (!bounds.contains(point)) {
750           cancel();
751         }
752       }
753     };
754     myContent.addMouseListener(mouseAdapter);
755     Disposer.register(this, new Disposable() {
756       public void dispose() {
757         myContent.removeMouseListener(mouseAdapter);
758       }
759     });
760
761     myContent.registerKeyboardAction(new ActionListener() {
762       public void actionPerformed(ActionEvent e) {
763         if (myCancelKeyEnabled) {
764           cancel();
765         }
766       }
767     }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
768
769
770     myContent.addKeyListener(new KeyListener() {
771       public void keyTyped(final KeyEvent e) {
772         mySpeedSearch.process(e);
773       }
774
775       public void keyPressed(final KeyEvent e) {
776         mySpeedSearch.process(e);
777       }
778
779       public void keyReleased(final KeyEvent e) {
780         mySpeedSearch.process(e);
781       }
782     });
783
784     if (myCancelOnMouseOutCallback != null || myCancelOnWindow) {
785       myMouseOutCanceller = new Canceller();
786       Toolkit.getDefaultToolkit().addAWTEventListener(myMouseOutCanceller, AWTEvent.MOUSE_EVENT_MASK | WindowEvent.WINDOW_ACTIVATED |
787                                                                            AWTEvent.MOUSE_MOTION_EVENT_MASK);
788     }
789
790
791     myFocusWatcher = new ChildFocusWatcher(myContent) {
792       protected void onFocusGained(final FocusEvent event) {
793         setWindowActive(true);
794       }
795
796       protected void onFocusLost(final FocusEvent event) {
797         setWindowActive(false);
798       }
799
800     };
801
802     mySpeedSearchPatternField = new JTextField();
803     if (SystemInfo.isMac) {
804       Font f = mySpeedSearchPatternField.getFont();
805       mySpeedSearchPatternField.setFont(f.deriveFont(f.getStyle(), f.getSize() - 2));
806     }
807   }
808
809   private Window updateMaskAndAlpha(Window window) {
810     if (window == null) return window;
811
812     final WindowManagerEx wndManager = getWndManager();
813     if (wndManager == null) return window;
814
815     if (!wndManager.isAlphaModeEnabled(window)) return window;
816
817     if (myAlpha != myLastAlpha) {
818       wndManager.setAlphaModeRatio(window, myAlpha);
819       myLastAlpha = myAlpha;
820     }
821
822     if (myMaskProvider != null) {
823       final Dimension size = window.getSize();
824       Shape mask = myMaskProvider.getMask(size);
825       wndManager.setWindowMask(window, mask);
826     }
827
828     return window;
829   }
830
831   private static WindowManagerEx getWndManager() {
832     return ApplicationManagerEx.getApplicationEx() != null ? WindowManagerEx.getInstanceEx() : null;
833   }
834
835   public boolean isDisposed() {
836     return myContent == null;
837   }
838
839   protected boolean beforeShow() {
840     if (ApplicationManagerEx.getApplicationEx() == null) return true;
841     StackingPopupDispatcher.getInstance().onPopupShown(this, myInStack);
842     return true;
843   }
844
845   protected void afterShow() {
846   }
847
848   protected final void requestFocus() {
849     if (!myFocusable) return;
850
851     if (myPreferredFocusedComponent != null) {
852       if (myProject != null) {
853         getFocusManager().requestFocus(myPreferredFocusedComponent, true);
854       }
855       else {
856         myPreferredFocusedComponent.requestFocus();
857       }
858     }
859   }
860
861   private IdeFocusManager getFocusManager() {
862     if (myProject != null) {
863       return IdeFocusManager.getInstance(myProject);
864     }
865     else if (myOwner != null) {
866       return IdeFocusManager.findInstanceByComponent(myOwner);
867     }
868     else {
869       return IdeFocusManager.findInstance();
870     }
871   }
872
873   private static JComponent getTargetComponent(Component aComponent) {
874     if (aComponent instanceof JComponent) {
875       return (JComponent)aComponent;
876     }
877     else if (aComponent instanceof RootPaneContainer) {
878       return ((RootPaneContainer)aComponent).getRootPane();
879     }
880
881     LOG.error("Cannot find target for:" + aComponent);
882     return null;
883   }
884
885   private PopupComponent.Factory getFactory(boolean forceHeavyweight) {
886     if (isPersistent()) {
887       return new PopupComponent.Factory.Dialog();
888     }
889     else if (forceHeavyweight || !SystemInfo.isWindows) {
890       return new PopupComponent.Factory.AwtHeavyweight();
891     }
892     else {
893       return new PopupComponent.Factory.AwtDefault();
894     }
895   }
896
897   public JComponent getContent() {
898     return myContent;
899   }
900
901   public void setLocation(RelativePoint p) {
902     setLocation(p, myPopup, myContent);
903   }
904
905   private static void setLocation(final RelativePoint p, final PopupComponent popup, Component content) {
906     if (popup == null) return;
907
908     final Window wnd = popup.getWindow();
909     assert wnd != null;
910
911     wnd.setLocation(p.getScreenPoint());
912   }
913
914   public void pack() {
915     final Window window = SwingUtilities.getWindowAncestor(myContent);
916
917     window.pack();
918   }
919
920   public JComponent getComponent() {
921     return myComponent;
922   }
923
924   public void setProject(Project project) {
925     myProject = project;
926   }
927
928
929   public void dispose() {
930     Disposer.dispose(this, false);
931
932     assert ApplicationManager.getApplication().isDispatchThread();
933
934     if (myPopup != null) {
935       cancel(myDisposeEvent);
936     }
937
938     if (myContent != null) {
939       myContent.removeAll();
940     }
941     myContent = null;
942     myComponent = null;
943     myFocusTrackback = null;
944     myCallBack = null;
945     myListeners = null;
946
947     if (myMouseOutCanceller != null) {
948       final Toolkit toolkit = Toolkit.getDefaultToolkit();
949       // it may happen, but have no idea how
950       // http://www.jetbrains.net/jira/browse/IDEADEV-21265
951       if (toolkit != null) {
952         toolkit.removeAWTEventListener(myMouseOutCanceller);
953       }
954     }
955     myMouseOutCanceller = null;
956
957     if (myFocusWatcher != null) {
958       myFocusWatcher.dispose();
959       myFocusWatcher = null;
960     }
961
962     resetWindow();
963
964     if (myFinalRunnable != null) {
965       getFocusManager().doWhenFocusSettlesDown(myFinalRunnable);
966       myFinalRunnable = null;
967     }
968   }
969
970   private void resetWindow() {
971     if (myWindow != null && getWndManager() != null) {
972       getWndManager().resetWindow(myWindow);
973       if (myWindowListener != null) {
974         myWindow.removeWindowListener(myWindowListener);
975       }
976
977       if (myWindow instanceof JWindow) {
978         ((JWindow)myWindow).getRootPane().putClientProperty(KEY, null);
979       }
980
981       myWindow = null;
982       myWindowListener = null;
983     }
984   }
985
986   public void storeDimensionSize(final Dimension size) {
987     if (myDimensionServiceKey != null) {
988       DimensionService.getInstance().setSize(myDimensionServiceKey, size, myProject);
989     }
990   }
991
992   public void storeLocation(final Point xy) {
993     if (myDimensionServiceKey != null) {
994       DimensionService.getInstance().setLocation(myDimensionServiceKey, xy, myProject);
995     }
996   }
997
998   public static class MyContentPanel extends JPanel {
999     private final boolean myResizable;
1000     private final boolean myDrawMacCorner;
1001     private final boolean myPaintShadow;
1002
1003     public MyContentPanel(final boolean resizable, final PopupBorder border, boolean drawMacCorner) {
1004       this(resizable, border, drawMacCorner, false);
1005     }
1006
1007     public MyContentPanel(final boolean resizable, final PopupBorder border, boolean drawMacCorner, boolean shadowed) {
1008       super(new BorderLayout());
1009       myResizable = resizable;
1010       myDrawMacCorner = drawMacCorner;
1011       myPaintShadow = shadowed && !UISettings.isRemoteDesktopConnected();
1012       if (myPaintShadow) {
1013         setOpaque(false);
1014         setBorder(new EmptyBorder(POPUP_TOP_SIZE, POPUP_SIDE_SIZE, POPUP_BOTTOM_SIZE, POPUP_SIDE_SIZE) {
1015           @Override
1016           public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
1017             border.paintBorder(c, g,
1018                                x + POPUP_SIDE_SIZE - 1,
1019                                y + POPUP_TOP_SIZE - 1,
1020                                width - 2 * POPUP_SIDE_SIZE + 2,
1021                                height - POPUP_TOP_SIZE - POPUP_BOTTOM_SIZE + 2);
1022           }
1023         });
1024       }
1025       else {
1026         setBorder(border);
1027       }
1028     }
1029
1030     public void paint(Graphics g) {
1031       if (myPaintShadow) {
1032         paintShadow(g);
1033       }
1034
1035       super.paint(g);
1036
1037       if (myResizable && myDrawMacCorner) {
1038         g.drawImage(ourMacCorner,
1039                     getX() + getWidth() - ourMacCorner.getWidth(this),
1040                     getY() + getHeight() - ourMacCorner.getHeight(this),
1041                     this);
1042       }
1043     }
1044
1045     private void paintShadow(final Graphics g) {
1046       BufferedImage capture = null;
1047       try {
1048         final Point onScreen = getLocationOnScreen();
1049         capture = new Robot().createScreenCapture(
1050           new Rectangle(onScreen.x, onScreen.y, getWidth() + 2 * POPUP_SIDE_SIZE, getHeight() + POPUP_TOP_SIZE + POPUP_BOTTOM_SIZE));
1051         final BufferedImage shadow = ShadowBorderPainter.createPopupShadow(this, getWidth(), getHeight());
1052         ((Graphics2D)capture.getGraphics()).drawImage(shadow, null, null);
1053       }
1054       catch (Exception e) {
1055         LOG.info(e);
1056       }
1057       if (capture != null) g.drawImage(capture, 0, 0, null);
1058     }
1059   }
1060
1061   public boolean isCancelOnClickOutside() {
1062     return myCancelOnClickOutside;
1063   }
1064
1065   private class Canceller implements AWTEventListener {
1066
1067     private boolean myEverEntered = false;
1068
1069     public void eventDispatched(final AWTEvent event) {
1070       if (event.getID() == WindowEvent.WINDOW_ACTIVATED) {
1071         if (myCancelOnWindow) {
1072           cancel();
1073         }
1074       }
1075       else if (event.getID() == MouseEvent.MOUSE_ENTERED) {
1076         if (withinPopup(event)) {
1077           myEverEntered = true;
1078         }
1079       }
1080       else if (event.getID() == MouseEvent.MOUSE_MOVED) {
1081         if (myCancelOnMouseOutCallback != null && myEverEntered && !withinPopup(event)) {
1082           if (myCancelOnMouseOutCallback.check((MouseEvent)event)) {
1083             cancel();
1084           }
1085         }
1086       }
1087     }
1088
1089     private boolean withinPopup(final AWTEvent event) {
1090       if (!myContent.isShowing()) return false;
1091
1092       final MouseEvent mouse = (MouseEvent)event;
1093       final Point point = mouse.getPoint();
1094       SwingUtilities.convertPointToScreen(point, mouse.getComponent());
1095       return new Rectangle(myContent.getLocationOnScreen(), myContent.getSize()).contains(point);
1096     }
1097   }
1098
1099   public void setLocation(@NotNull final Point screenPoint) {
1100     if (myPopup == null) {
1101       myForcedLocation = screenPoint;
1102     }
1103     else {
1104       moveTo(myContent, screenPoint, myLocateByContent ? myHeaderPanel.getPreferredSize() : null);
1105     }
1106   }
1107
1108   public static Window moveTo(JComponent content, Point screenPoint, final Dimension headerCorrectionSize) {
1109     setDefaultCursor(content);
1110     final Window wnd = SwingUtilities.getWindowAncestor(content);
1111     if (headerCorrectionSize != null) {
1112       screenPoint.y -= headerCorrectionSize.height;
1113     }
1114     wnd.setLocation(screenPoint);
1115     return wnd;
1116   }
1117
1118   public void setSize(@NotNull final Dimension size) {
1119     if (myPopup == null) {
1120       myForcedSize = size;
1121     }
1122     else {
1123       updateMaskAndAlpha(setSize(myContent, size));
1124     }
1125   }
1126
1127   @Override
1128   public Dimension getSize() {
1129     if (myPopup != null) {
1130       final Window popupWindow = SwingUtilities.windowForComponent(myContent);
1131       return popupWindow.getSize();
1132     } else {
1133       return myForcedSize;
1134     }
1135   }
1136
1137   @Override
1138   public void moveToFitScreen() {
1139     if (myPopup == null) return;
1140     
1141     final Window popupWindow = SwingUtilities.windowForComponent(myContent);
1142     Rectangle bounds = popupWindow.getBounds();
1143
1144     ScreenUtil.moveRectangleToFitTheScreen(bounds);
1145     setLocation(bounds.getLocation());
1146     setSize(bounds.getSize());
1147   }
1148
1149
1150   public static Window setSize(JComponent content, final Dimension size) {
1151     final Window popupWindow = SwingUtilities.windowForComponent(content);
1152     final Point location = popupWindow.getLocation();
1153     popupWindow.setBounds(location.x, location.y, size.width, size.height);
1154     return popupWindow;
1155   }
1156
1157   public static void setDefaultCursor(JComponent content) {
1158     final Window wnd = SwingUtilities.getWindowAncestor(content);
1159     if (wnd != null) {
1160       wnd.setCursor(Cursor.getDefaultCursor());
1161     }
1162   }
1163
1164   public void setCaption(String title) {
1165     if (myCaption instanceof TitlePanel) {
1166       ((TitlePanel)myCaption).setText(title);
1167     }
1168   }
1169
1170   private class MyWindowListener extends WindowAdapter {
1171     public void windowClosed(final WindowEvent e) {
1172       resetWindow();
1173     }
1174   }
1175
1176   public boolean isPersistent() {
1177     return !myCancelOnClickOutside && !myCancelOnWindow;
1178   }
1179
1180   public boolean isNativePopup() {
1181     return myNativePopup;
1182   }
1183
1184   public void setUiVisible(final boolean visible) {
1185     if (myPopup != null) {
1186       if (visible) {
1187         myPopup.show();
1188         final Window window = getPopupWindow();
1189         if (window != null && myRestoreWindowSize != null) {
1190           window.setSize(myRestoreWindowSize);
1191           myRestoreWindowSize = null;
1192         }
1193       }
1194       else {
1195         final Window window = getPopupWindow();
1196         if (window != null) {
1197           myRestoreWindowSize = window.getSize();
1198           window.setVisible(true);
1199         }
1200       }
1201     }
1202   }
1203
1204   private Window getPopupWindow() {
1205     return myPopup.getWindow();
1206   }
1207
1208   public void setUserData(ArrayList<Object> userData) {
1209     myUserData = userData;
1210   }
1211
1212   public <T> T getUserData(final Class<T> userDataClass) {
1213     if (myUserData != null) {
1214       for (Object o : myUserData) {
1215         if (userDataClass.isInstance(o)) {
1216           //noinspection unchecked
1217           return (T)o;
1218         }
1219       }
1220     }
1221     return null;
1222   }
1223
1224   public boolean isModalContext() {
1225     return myModalContext;
1226   }
1227
1228   public boolean isFocused() {
1229     if (myComponent != null && isFocused(new Component[]{SwingUtilities.getWindowAncestor(myComponent)})) {
1230       return true;
1231     }
1232     return isFocused(myFocusOwners);
1233   }
1234
1235   public static boolean isFocused(@Nullable Component[] components) {
1236     if (components == null) return false;
1237
1238     Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1239
1240     if (owner == null) return false;
1241     for (Component each : components) {
1242       if (each != null && SwingUtilities.isDescendingFrom(owner, each)) return true;
1243     }
1244
1245     return false;
1246   }
1247
1248   public boolean isCancelKeyEnabled() {
1249     return myCancelKeyEnabled;
1250   }
1251
1252   @NotNull
1253   CaptionPanel getTitle() {
1254     return myCaption;
1255   }
1256
1257   private void setHeaderComponent(JComponent c) {
1258     boolean doRevalidate = false;
1259     if (myHeaderComponent != null) {
1260       myHeaderPanel.remove(myHeaderComponent);
1261       myHeaderPanel.add(myCaption, BorderLayout.NORTH);
1262       myHeaderComponent = null;
1263       doRevalidate = true;
1264     }
1265
1266     if (c != null) {
1267       myHeaderPanel.remove(myCaption);
1268       myHeaderPanel.add(c, BorderLayout.NORTH);
1269       myHeaderComponent = c;
1270
1271       final Dimension size = myContent.getSize();
1272       if (size.height < c.getPreferredSize().height * 2) {
1273         size.height += c.getPreferredSize().height;
1274         setSize(size);
1275       }
1276
1277       doRevalidate = true;
1278     }
1279
1280     if (doRevalidate) myContent.revalidate();
1281   }
1282
1283   public void addListener(final JBPopupListener listener) {
1284     myListeners.add(listener);
1285   }
1286
1287   public void removeListener(final JBPopupListener listener) {
1288     myListeners.remove(listener);
1289   }
1290
1291   protected void onSpeedSearchPatternChanged() {
1292   }
1293
1294   public Component getOwner() {
1295     return myRequestorComponent;
1296   }
1297
1298   public void setMinimumSize(Dimension size) {
1299     myMinSize = size;
1300   }
1301
1302   public Runnable getFinalRunnable() {
1303     return myFinalRunnable;
1304   }
1305
1306   public void setFinalRunnable(Runnable finalRunnable) {
1307     myFinalRunnable = finalRunnable;
1308   }
1309
1310   public void setOk(boolean ok) {
1311     myOk = ok;
1312   }
1313 }