Let toolbar buttons do their work in Find popup & do not blink too much
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / popup / AbstractPopup.java
1 /*
2  * Copyright 2000-2015 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.icons.AllIcons;
20 import com.intellij.ide.DataManager;
21 import com.intellij.ide.IdeEventQueue;
22 import com.intellij.ide.UiActivity;
23 import com.intellij.ide.UiActivityMonitor;
24 import com.intellij.openapi.Disposable;
25 import com.intellij.openapi.actionSystem.CommonDataKeys;
26 import com.intellij.openapi.actionSystem.DataContext;
27 import com.intellij.openapi.actionSystem.DataProvider;
28 import com.intellij.openapi.actionSystem.PlatformDataKeys;
29 import com.intellij.openapi.application.ApplicationManager;
30 import com.intellij.openapi.application.ex.ApplicationManagerEx;
31 import com.intellij.openapi.diagnostic.Logger;
32 import com.intellij.openapi.editor.Editor;
33 import com.intellij.openapi.editor.ex.EditorEx;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.ui.popup.*;
36 import com.intellij.openapi.util.*;
37 import com.intellij.openapi.util.registry.Registry;
38 import com.intellij.openapi.wm.*;
39 import com.intellij.openapi.wm.ex.WindowManagerEx;
40 import com.intellij.openapi.wm.impl.IdeFrameImpl;
41 import com.intellij.openapi.wm.impl.IdeGlassPaneImpl;
42 import com.intellij.ui.*;
43 import com.intellij.ui.awt.RelativePoint;
44 import com.intellij.ui.components.JBLabel;
45 import com.intellij.ui.speedSearch.SpeedSearch;
46 import com.intellij.util.Alarm;
47 import com.intellij.util.BooleanFunction;
48 import com.intellij.util.IJSwingUtilities;
49 import com.intellij.util.Processor;
50 import com.intellij.util.ui.ChildFocusWatcher;
51 import com.intellij.util.ui.EmptyIcon;
52 import com.intellij.util.ui.JBUI;
53 import com.intellij.util.ui.UIUtil;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import javax.swing.*;
59 import javax.swing.text.JTextComponent;
60 import java.awt.*;
61 import java.awt.event.*;
62 import java.util.HashSet;
63 import java.util.List;
64 import java.util.Set;
65
66 public class AbstractPopup implements JBPopup {
67   public static final String SHOW_HINTS = "ShowHints";
68
69   private static final Logger LOG = Logger.getInstance("#com.intellij.ui.popup.AbstractPopup");
70
71   private static final Object SUPPRESS_MAC_CORNER = new Object();
72
73   // X server sometimes focuses focusable popups upon appearance, ignoring the fact that we didn't ask to focus them (IDEA-94683)
74   private static final boolean X_WINDOW_FOCUS_BUG = SystemInfo.isXWindow;
75
76   private PopupComponent myPopup;
77   private MyContentPanel myContent;
78   private JComponent     myPreferredFocusedComponent;
79   private boolean        myRequestFocus;
80   private boolean        myFocusable;
81   private boolean        myForcedHeavyweight;
82   private boolean        myLocateWithinScreen;
83   private boolean myResizable = false;
84   private WindowResizeListener myResizeListener;
85   private WindowMoveListener myMoveListener;
86   private JPanel myHeaderPanel;
87   private CaptionPanel myCaption = null;
88   private JComponent myComponent;
89   private String              myDimensionServiceKey = null;
90   private Computable<Boolean> myCallBack            = null;
91   private Project              myProject;
92   private boolean              myCancelOnClickOutside;
93   private Set<JBPopupListener> myListeners;
94   private boolean              myUseDimServiceForXYLocation;
95   private MouseChecker         myCancelOnMouseOutCallback;
96   private Canceller            myMouseOutCanceller;
97   private boolean              myCancelOnWindow;
98   private boolean myCancelOnWindowDeactivation = true;
99   private   Dimension         myForcedSize;
100   private   Point             myForcedLocation;
101   private   boolean           myCancelKeyEnabled;
102   private   boolean           myLocateByContent;
103   protected FocusTrackback    myFocusTrackback;
104   private   Dimension         myMinSize;
105   private   List<Object>      myUserData;
106   private   boolean           myShadowed;
107
108   private float myAlpha     = 0;
109   private float myLastAlpha = 0;
110
111   private MaskProvider myMaskProvider;
112
113   private Window           myWindow;
114   private boolean          myInStack;
115   private MyWindowListener myWindowListener;
116
117   private boolean myModalContext;
118
119   private   Component[] myFocusOwners;
120   private   PopupBorder myPopupBorder;
121   private   Dimension   myRestoreWindowSize;
122   protected Component   myOwner;
123   protected Component   myRequestorComponent;
124   private   boolean     myHeaderAlwaysFocusable;
125   private   boolean     myMovable;
126   private   JComponent  myHeaderComponent;
127
128   protected InputEvent myDisposeEvent;
129
130   private Runnable myFinalRunnable;
131   @Nullable private BooleanFunction<KeyEvent> myKeyEventHandler;
132
133   protected boolean myOk;
134
135   protected final SpeedSearch mySpeedSearch = new SpeedSearch() {
136     boolean searchFieldShown = false;
137
138     @Override
139     public void update() {
140       mySpeedSearchPatternField.setBackground(new JTextField().getBackground());
141       onSpeedSearchPatternChanged();
142       mySpeedSearchPatternField.setText(getFilter());
143       if (isHoldingFilter() && !searchFieldShown) {
144         setHeaderComponent(mySpeedSearchPatternField);
145         searchFieldShown = true;
146       }
147       else if (!isHoldingFilter() && searchFieldShown) {
148         setHeaderComponent(null);
149         searchFieldShown = false;
150       }
151     }
152
153     @Override
154     public void noHits() {
155       mySpeedSearchPatternField.setBackground(LightColors.RED);
156     }
157   };
158
159   private JTextField mySpeedSearchPatternField;
160   private boolean myNativePopup;
161   private boolean myMayBeParent;
162   private AbstractPopup.SpeedSearchKeyListener mySearchKeyListener;
163   private JLabel myAdComponent;
164   private boolean myDisposed;
165
166   private UiActivity myActivityKey;
167   private Disposable myProjectDisposable;
168
169   private volatile State myState = State.NEW;
170
171   private enum State {NEW, INIT, SHOWING, SHOWN, CANCEL, DISPOSE}
172
173   private void debugState(String message, State... states) {
174     if (LOG.isDebugEnabled()) {
175       LOG.debug(hashCode() + " - " + message);
176       if (!ApplicationManager.getApplication().isDispatchThread()) {
177         LOG.debug("unexpected thread");
178       }
179       for (State state : states) {
180         if (state == myState) {
181           return;
182         }
183       }
184       LOG.debug(new IllegalStateException("myState=" + myState));
185     }
186   }
187
188   AbstractPopup() { }
189
190   AbstractPopup init(Project project,
191                      @NotNull JComponent component,
192                      @Nullable JComponent preferredFocusedComponent,
193                      boolean requestFocus,
194                      boolean focusable,
195                      boolean movable,
196                      String dimensionServiceKey,
197                      boolean resizable,
198                      @Nullable String caption,
199                      @Nullable Computable<Boolean> callback,
200                      boolean cancelOnClickOutside,
201                      @Nullable Set<JBPopupListener> listeners,
202                      boolean useDimServiceForXYLocation,
203                      ActiveComponent commandButton,
204                      @Nullable IconButton cancelButton,
205                      @Nullable MouseChecker cancelOnMouseOutCallback,
206                      boolean cancelOnWindow,
207                      @Nullable ActiveIcon titleIcon,
208                      boolean cancelKeyEnabled,
209                      boolean locateByContent,
210                      boolean placeWithinScreenBounds,
211                      @Nullable Dimension minSize,
212                      float alpha,
213                      @Nullable MaskProvider maskProvider,
214                      boolean inStack,
215                      boolean modalContext,
216                      @Nullable Component[] focusOwners,
217                      @Nullable String adText,
218                      int adTextAlignment,
219                      boolean headerAlwaysFocusable,
220                      @NotNull List<Pair<ActionListener, KeyStroke>> keyboardActions,
221                      Component settingsButtons,
222                      @Nullable final Processor<JBPopup> pinCallback,
223                      boolean mayBeParent,
224                      boolean showShadow,
225                      boolean showBorder,
226                      boolean cancelOnWindowDeactivation,
227                      @Nullable BooleanFunction<KeyEvent> keyEventHandler)
228   {
229     if (requestFocus && !focusable) {
230       assert false : "Incorrect argument combination: requestFocus=true focusable=false";
231     }
232
233     myActivityKey = new UiActivity.Focus("Popup:" + this);
234     myProject = project;
235     myComponent = component;
236     myPopupBorder = showBorder ? PopupBorder.Factory.create(true, showShadow) : PopupBorder.Factory.createEmpty();
237     myShadowed = showShadow;
238     myContent = createContentPanel(resizable, myPopupBorder, isToDrawMacCorner() && resizable);
239     myMayBeParent = mayBeParent;
240     myCancelOnWindowDeactivation = cancelOnWindowDeactivation;
241
242     myContent.add(component, BorderLayout.CENTER);
243     if (adText != null) {
244       setAdText(adText, adTextAlignment);
245     }
246
247     myCancelKeyEnabled = cancelKeyEnabled;
248     myLocateByContent = locateByContent;
249     myLocateWithinScreen = placeWithinScreenBounds;
250     myAlpha = alpha;
251     myMaskProvider = maskProvider;
252     myInStack = inStack;
253     myModalContext = modalContext;
254     myFocusOwners = focusOwners;
255     myHeaderAlwaysFocusable = headerAlwaysFocusable;
256     myMovable = movable;
257
258     ActiveIcon actualIcon = titleIcon == null ? new ActiveIcon(EmptyIcon.ICON_0) : titleIcon;
259
260     myHeaderPanel = new JPanel(new BorderLayout());
261
262     if (caption != null) {
263       if (!caption.isEmpty()) {
264         myCaption = new TitlePanel(actualIcon.getRegular(), actualIcon.getInactive());
265         ((TitlePanel)myCaption).setText(caption);
266       }
267       else {
268         myCaption = new CaptionPanel();
269       }
270
271       if (pinCallback != null) {
272         myCaption.setButtonComponent(new InplaceButton(
273           new IconButton("Pin", AllIcons.General.AutohideOff, AllIcons.General.AutohideOff, AllIcons.General.AutohideOffInactive),
274           new ActionListener() {
275             @Override
276             public void actionPerformed(final ActionEvent e) {
277               pinCallback.process(AbstractPopup.this);
278             }
279           }
280         ));
281       }
282       else if (cancelButton != null) {
283         myCaption.setButtonComponent(new InplaceButton(cancelButton, new ActionListener() {
284           @Override
285           public void actionPerformed(final ActionEvent e) {
286             cancel();
287           }
288         }));
289       }
290       else if (commandButton != null) {
291         myCaption.setButtonComponent(commandButton);
292       }
293     }
294     else {
295       myCaption = new CaptionPanel();
296       myCaption.setBorder(null);
297       myCaption.setPreferredSize(JBUI.emptySize());
298     }
299
300     setWindowActive(myHeaderAlwaysFocusable);
301
302     myHeaderPanel.add(myCaption, BorderLayout.NORTH);
303     myContent.add(myHeaderPanel, BorderLayout.NORTH);
304
305     myForcedHeavyweight = true;
306     myResizable = resizable;
307     myPreferredFocusedComponent = preferredFocusedComponent;
308     myRequestFocus = requestFocus;
309     myFocusable = focusable;
310     myDimensionServiceKey = dimensionServiceKey;
311     myCallBack = callback;
312     myCancelOnClickOutside = cancelOnClickOutside;
313     myCancelOnMouseOutCallback = cancelOnMouseOutCallback;
314     myListeners = listeners == null ? new HashSet<JBPopupListener>() : listeners;
315     myUseDimServiceForXYLocation = useDimServiceForXYLocation;
316     myCancelOnWindow = cancelOnWindow;
317     myMinSize = minSize;
318
319     for (Pair<ActionListener, KeyStroke> pair : keyboardActions) {
320       myContent.registerKeyboardAction(pair.getFirst(), pair.getSecond(), JComponent.WHEN_IN_FOCUSED_WINDOW);
321     }
322
323     if (settingsButtons != null) {
324       myCaption.addSettingsComponent(settingsButtons);
325     }
326
327     myKeyEventHandler = keyEventHandler;
328     debugState("popup initialized", State.NEW);
329     myState = State.INIT;
330     return this;
331   }
332
333   private void setWindowActive(boolean active) {
334     boolean value = myHeaderAlwaysFocusable || active;
335
336     if (myCaption != null) {
337       myCaption.setActive(value);
338     }
339     myPopupBorder.setActive(value);
340     myContent.repaint();
341   }
342
343
344   @NotNull
345   protected MyContentPanel createContentPanel(final boolean resizable, PopupBorder border, boolean isToDrawMacCorner) {
346     return new MyContentPanel(resizable, border, isToDrawMacCorner);
347   }
348
349   public boolean isToDrawMacCorner() {
350     if (!SystemInfo.isMac || myComponent.getComponentCount() <= 0) {
351       return false;
352     }
353
354     if (myComponent.getComponentCount() > 0) {
355       Component component = myComponent.getComponent(0);
356       if (component instanceof JComponent && Boolean.TRUE.equals(((JComponent)component).getClientProperty(SUPPRESS_MAC_CORNER))) {
357         return false;
358       }
359     }
360
361     return true;
362   }
363
364   public void setShowHints(boolean show) {
365     final Window ancestor = getContentWindow(myComponent);
366     if (ancestor instanceof RootPaneContainer) {
367       final JRootPane rootPane = ((RootPaneContainer)ancestor).getRootPane();
368       if (rootPane != null) {
369         rootPane.putClientProperty(SHOW_HINTS, Boolean.valueOf(show));
370       }
371     }
372   }
373
374   public static void suppressMacCornerFor(JComponent popupComponent) {
375     popupComponent.putClientProperty(SUPPRESS_MAC_CORNER, Boolean.TRUE);
376   }
377
378
379   public String getDimensionServiceKey() {
380     return myDimensionServiceKey;
381   }
382
383   public void setDimensionServiceKey(@Nullable final String dimensionServiceKey) {
384     myDimensionServiceKey = dimensionServiceKey;
385   }
386
387   @Override
388   public void showInCenterOf(@NotNull Component aContainer) {
389     final Point popupPoint = getCenterOf(aContainer, myContent);
390     show(aContainer, popupPoint.x, popupPoint.y, false);
391   }
392
393   public void setAdText(@NotNull final String s) {
394     setAdText(s, SwingConstants.LEFT);
395   }
396
397   @Override
398   public void setAdText(@NotNull final String s, int alignment) {
399     if (myAdComponent == null) {
400       myAdComponent = HintUtil.createAdComponent(s, JBUI.Borders.empty(1, 5), alignment);
401       JPanel wrapper = new JPanel(new BorderLayout()) {
402         @Override
403         protected void paintComponent(Graphics g) {
404           g.setColor(Gray._135);
405           g.drawLine(0, 0, getWidth(), 0);
406           super.paintComponent(g);
407         }
408       };
409       wrapper.setOpaque(false);
410       wrapper.setBorder(JBUI.Borders.emptyTop(1));
411       wrapper.add(myAdComponent, BorderLayout.CENTER);
412       myContent.add(wrapper, BorderLayout.SOUTH);
413       pack(false, true);
414     } else {
415       myAdComponent.setText(s);
416       myAdComponent.setHorizontalAlignment(alignment);
417     }
418   }
419
420   public static Point getCenterOf(final Component aContainer, final JComponent content) {
421     final JComponent component = getTargetComponent(aContainer);
422
423     Point containerScreenPoint = component.getVisibleRect().getLocation();
424     SwingUtilities.convertPointToScreen(containerScreenPoint, aContainer);
425
426     return UIUtil.getCenterPoint(new Rectangle(containerScreenPoint, component.getVisibleRect().getSize()), content.getPreferredSize());
427   }
428
429   @Override
430   public void showCenteredInCurrentWindow(@NotNull Project project) {
431     Window window = null;
432
433     Component focusedComponent = getWndManager().getFocusedComponent(project);
434     if (focusedComponent != null) {
435       Component parent = UIUtil.findUltimateParent(focusedComponent);
436       if (parent instanceof Window) {
437         window = (Window)parent;
438       }
439     }
440     if (window == null) {
441       window = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusedWindow();
442     }
443
444     if (window != null) {
445       showInCenterOf(window);
446     }
447   }
448
449   @Override
450   public void showUnderneathOf(@NotNull Component aComponent) {
451     show(new RelativePoint(aComponent, new Point(0, aComponent.getHeight())));
452   }
453
454   @Override
455   public void show(@NotNull RelativePoint aPoint) {
456     final Point screenPoint = aPoint.getScreenPoint();
457     show(aPoint.getComponent(), screenPoint.x, screenPoint.y, false);
458   }
459
460   @Override
461   public void showInScreenCoordinates(@NotNull Component owner, @NotNull Point point) {
462     show(owner, point.x, point.y, false);
463   }
464
465   @Override
466   public void showInBestPositionFor(@NotNull DataContext dataContext) {
467     final Editor editor = CommonDataKeys.EDITOR.getData(dataContext);
468     if (editor != null) {
469       showInBestPositionFor(editor);
470     }
471     else {
472       show(relativePointByQuickSearch(dataContext));
473     }
474   }
475
476   @Override
477   public void showInFocusCenter() {
478     final Component focused = getWndManager().getFocusedComponent(myProject);
479     if (focused != null) {
480       showInCenterOf(focused);
481     }
482     else {
483       final WindowManager manager = WindowManager.getInstance();
484       final JFrame frame = myProject != null ? manager.getFrame(myProject) : manager.findVisibleFrame();
485       showInCenterOf(frame.getRootPane());
486     }
487   }
488
489   private RelativePoint relativePointByQuickSearch(final DataContext dataContext) {
490     Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(dataContext);
491
492     if (dominantArea != null) {
493       final Component focusedComponent = getWndManager().getFocusedComponent(myProject);
494       if (focusedComponent != null) {
495         Window window = SwingUtilities.windowForComponent(focusedComponent);
496         JLayeredPane layeredPane;
497         if (window instanceof JFrame) {
498           layeredPane = ((JFrame)window).getLayeredPane();
499         }
500         else if (window instanceof JDialog) {
501           layeredPane = ((JDialog)window).getLayeredPane();
502         }
503         else if (window instanceof JWindow) {
504           layeredPane = ((JWindow)window).getLayeredPane();
505         }
506         else {
507           throw new IllegalStateException("cannot find parent window: project=" + myProject + "; window=" + window);
508         }
509
510         return relativePointWithDominantRectangle(layeredPane, dominantArea);
511       }
512     }
513
514     return JBPopupFactory.getInstance().guessBestPopupLocation(dataContext);
515   }
516
517   @Override
518   public void showInBestPositionFor(@NotNull Editor editor) {
519     assert editor.getComponent().isShowing() : "Editor must be showing on the screen";
520
521     DataContext context = ((EditorEx)editor).getDataContext();
522     Rectangle dominantArea = PlatformDataKeys.DOMINANT_HINT_AREA_RECTANGLE.getData(context);
523     if (dominantArea != null && !myRequestFocus) {
524       final JLayeredPane layeredPane = editor.getContentComponent().getRootPane().getLayeredPane();
525       show(relativePointWithDominantRectangle(layeredPane, dominantArea));
526     }
527     else {
528       show(guessBestPopupLocation(editor));
529     }
530   }
531
532   @NotNull
533   private RelativePoint guessBestPopupLocation(@NotNull Editor editor) {
534     RelativePoint preferredLocation = JBPopupFactory.getInstance().guessBestPopupLocation(editor);
535     if (myDimensionServiceKey == null) {
536       return preferredLocation;
537     }
538     Dimension preferredSize = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
539     if (preferredSize == null) {
540       return preferredLocation;
541     }
542     Rectangle preferredBounds = new Rectangle(preferredLocation.getScreenPoint(), preferredSize);
543     Rectangle adjustedBounds = new Rectangle(preferredBounds);
544     ScreenUtil.moveRectangleToFitTheScreen(adjustedBounds);
545     if (preferredBounds.y - adjustedBounds.y <= 0) {
546       return preferredLocation;
547     }
548     int adjustedY = preferredBounds.y - editor.getLineHeight() * 3 / 2 - preferredSize.height;
549     return adjustedY >= 0 ? RelativePoint.fromScreen(new Point(preferredBounds.x, adjustedY)) : preferredLocation;
550   }
551
552   public void addPopupListener(JBPopupListener listener) {
553     myListeners.add(listener);
554   }
555
556   private RelativePoint relativePointWithDominantRectangle(final JLayeredPane layeredPane, final Rectangle bounds) {
557     Dimension preferredSize = getComponent().getPreferredSize();
558     if (myDimensionServiceKey != null) {
559       final Dimension dimension = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
560       if (dimension != null) {
561         preferredSize = dimension;
562       }
563     }
564     final Point leftTopCorner = new Point(bounds.x + bounds.width, bounds.y);
565     final Point leftTopCornerScreen = (Point)leftTopCorner.clone();
566     SwingUtilities.convertPointToScreen(leftTopCornerScreen, layeredPane);
567     final RelativePoint relativePoint;
568     if (!ScreenUtil.isOutsideOnTheRightOFScreen(
569       new Rectangle(leftTopCornerScreen.x, leftTopCornerScreen.y, preferredSize.width, preferredSize.height))) {
570       relativePoint = new RelativePoint(layeredPane, leftTopCorner);
571     }
572     else {
573       if (bounds.x > preferredSize.width) {
574         relativePoint = new RelativePoint(layeredPane, new Point(bounds.x - preferredSize.width, bounds.y));
575       }
576       else {
577         setDimensionServiceKey(null); // going to cut width
578         Rectangle screen = ScreenUtil.getScreenRectangle(leftTopCornerScreen.x, leftTopCornerScreen.y);
579         final int spaceOnTheLeft = bounds.x;
580         final int spaceOnTheRight = screen.x + screen.width - leftTopCornerScreen.x;
581         if (spaceOnTheLeft > spaceOnTheRight) {
582           relativePoint = new RelativePoint(layeredPane, new Point(0, bounds.y));
583           myComponent.setPreferredSize(new Dimension(spaceOnTheLeft, Math.max(preferredSize.height, JBUI.scale(200))));
584         }
585         else {
586           relativePoint = new RelativePoint(layeredPane, leftTopCorner);
587           myComponent.setPreferredSize(new Dimension(spaceOnTheRight, Math.max(preferredSize.height, JBUI.scale(200))));
588         }
589       }
590     }
591     return relativePoint;
592   }
593
594   @Override
595   public final void closeOk(@Nullable InputEvent e) {
596     setOk(true);
597     cancel(e);
598   }
599
600   @Override
601   public final void cancel() {
602     cancel(null);
603   }
604
605   @Override
606   public void setRequestFocus(boolean requestFocus) {
607     myRequestFocus = requestFocus;
608   }
609
610   @Override
611   public void cancel(InputEvent e) {
612     if (myState == State.CANCEL || myState == State.DISPOSE) {
613       return;
614     }
615     debugState("cancel popup", State.SHOWN);
616     myState = State.CANCEL;
617
618     if (isDisposed()) return;
619
620     if (myPopup != null) {
621       if (!canClose()) {
622         debugState("cannot cancel popup", State.CANCEL);
623         myState = State.SHOWN;
624         return;
625       }
626       storeDimensionSize(myContent.getSize());
627       if (myUseDimServiceForXYLocation) {
628         final JRootPane root = myComponent.getRootPane();
629         if (root != null) {
630           final Container popupWindow = root.getParent();
631           if (popupWindow != null && popupWindow.isShowing()) {
632             storeLocation(popupWindow.getLocationOnScreen());
633           }
634         }
635       }
636
637       if (e instanceof MouseEvent) {
638         IdeEventQueue.getInstance().blockNextEvents((MouseEvent)e);
639       }
640
641       myPopup.hide(false);
642
643       if (ApplicationManagerEx.getApplicationEx() != null) {
644         StackingPopupDispatcher.getInstance().onPopupHidden(this);
645       }
646
647       if (myInStack) {
648         if (myFocusTrackback != null) {
649           myFocusTrackback.setForcedRestore(!myOk && myFocusable);
650           myFocusTrackback.restoreFocus();
651         }
652         else if (LOG.isDebugEnabled()) {
653           LOG.debug("cancel before show @ " + Thread.currentThread());
654         }
655       }
656
657
658       disposePopup();
659
660       if (myListeners != null) {
661         for (JBPopupListener each : myListeners) {
662           each.onClosed(new LightweightWindowEvent(this, myOk));
663         }
664       }
665     }
666
667     Disposer.dispose(this, false);
668     if (myProjectDisposable != null) {
669       Disposer.dispose(myProjectDisposable);
670     }
671   }
672
673   public FocusTrackback getFocusTrackback() {
674     return myFocusTrackback;
675   }
676
677   private void disposePopup() {
678     if (myPopup != null) {
679       myPopup.hide(true);
680     }
681     myPopup = null;
682   }
683
684   @Override
685   public boolean canClose() {
686     return myCallBack == null || myCallBack.compute().booleanValue();
687   }
688
689   @Override
690   public boolean isVisible() {
691     return myPopup != null;
692   }
693
694   @Override
695   public void show(final Component owner) {
696     show(owner, -1, -1, true);
697   }
698
699   public void show(Component owner, int aScreenX, int aScreenY, final boolean considerForcedXY) {
700     if (ApplicationManagerEx.getApplicationEx() != null && ApplicationManager.getApplication().isHeadlessEnvironment()) return;
701     if (isDisposed()) {
702       throw new IllegalStateException("Popup was already disposed. Recreate a new instance to show again");
703     }
704
705     assert ApplicationManager.getApplication().isDispatchThread();
706     assert myState == State.INIT : "Popup was already shown. Recreate a new instance to show again.";
707
708     debugState("show popup", State.INIT);
709     myState = State.SHOWING;
710
711     installWindowHook(this);
712     installProjectDisposer();
713     addActivity();
714
715     final Component prevOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
716
717     final boolean shouldShow = beforeShow();
718     if (!shouldShow) {
719       removeActivity();
720       debugState("rejected to show popup", State.SHOWING);
721       myState = State.INIT;
722       return;
723     }
724
725     prepareToShow();
726
727     if (myInStack) {
728       myFocusTrackback = new FocusTrackback(this, owner, true);
729       myFocusTrackback.setMustBeShown(true);
730     }
731
732
733     Dimension sizeToSet = null;
734
735     if (myDimensionServiceKey != null) {
736       sizeToSet = DimensionService.getInstance().getSize(myDimensionServiceKey, myProject);
737     }
738
739     if (myForcedSize != null) {
740       sizeToSet = myForcedSize;
741     }
742
743     if (sizeToSet != null) {
744       sizeToSet.width = Math.max(sizeToSet.width, myContent.getMinimumSize().width);
745       sizeToSet.height = Math.max(sizeToSet.height, myContent.getMinimumSize().height);
746
747       myContent.setSize(sizeToSet);
748       myContent.setPreferredSize(sizeToSet);
749     }
750
751     Point xy = new Point(aScreenX, aScreenY);
752     boolean adjustXY = true;
753     if (myUseDimServiceForXYLocation && myDimensionServiceKey != null) {
754       final Point storedLocation = DimensionService.getInstance().getLocation(myDimensionServiceKey, myProject);
755       if (storedLocation != null) {
756         xy = storedLocation;
757         adjustXY = false;
758       }
759     }
760
761     if (adjustXY) {
762       final Insets insets = myContent.getInsets();
763       if (insets != null) {
764         xy.x -= insets.left;
765         xy.y -= insets.top;
766       }
767     }
768
769     if (considerForcedXY && myForcedLocation != null) {
770       xy = myForcedLocation;
771     }
772
773     if (myLocateByContent) {
774       final Dimension captionSize = myHeaderPanel.getPreferredSize();
775       xy.y -= captionSize.height;
776     }
777
778     Rectangle targetBounds = new Rectangle(xy, myContent.getPreferredSize());
779     Rectangle original = new Rectangle(targetBounds);
780     if (myLocateWithinScreen) {
781       if (myMovable) {
782         ScreenUtil.moveToFit(targetBounds, ScreenUtil.getScreenRectangle(aScreenX, aScreenY), null);
783       }
784     }
785
786     if (myMouseOutCanceller != null) {
787       myMouseOutCanceller.myEverEntered = targetBounds.equals(original);
788     }
789
790     myOwner = IdeFrameImpl.findNearestModalComponent(owner);
791     if (myOwner == null) {
792       myOwner = owner;
793     }
794
795     myRequestorComponent = owner;
796
797     boolean forcedDialog = myMayBeParent
798       || SystemInfo.isMac && !(myOwner instanceof IdeFrame) && myOwner != null && myOwner.isShowing();
799
800     PopupComponent.Factory factory = getFactory(myForcedHeavyweight || myResizable, forcedDialog);
801     myNativePopup = factory.isNativePopup();
802     Component popupOwner = myOwner;
803     if (popupOwner instanceof RootPaneContainer && !(popupOwner instanceof IdeFrame && !Registry.is("popup.fix.ide.frame.owner"))) {
804       // JDK uses cached heavyweight popup for a window ancestor
805       RootPaneContainer root = (RootPaneContainer)popupOwner;
806       popupOwner = root.getRootPane();
807       LOG.debug("popup owner fixed for JDK cache");
808     }
809     if (LOG.isDebugEnabled()) {
810       LOG.debug("expected preferred size: " + myContent.getPreferredSize());
811     }
812     myPopup = factory.getPopup(popupOwner, myContent, targetBounds.x, targetBounds.y, this);
813     if (LOG.isDebugEnabled()) {
814       LOG.debug("  actual preferred size: " + myContent.getPreferredSize());
815     }
816     if ((targetBounds.width != myContent.getWidth()) || (targetBounds.height != myContent.getHeight())) {
817       // JDK uses cached heavyweight popup that is not initialized properly
818       LOG.debug("the expected size is not equal to the actual size");
819       Window popup = myPopup.getWindow();
820       if (popup != null) {
821         popup.setSize(targetBounds.width, targetBounds.height);
822         if (myContent.getParent().getComponentCount() != 1) {
823           LOG.debug("unexpected count of components in heavy-weight popup");
824         }
825       }
826       else {
827         LOG.debug("cannot fix size for non-heavy-weight popup");
828       }
829     }
830
831     if (myResizable) {
832       final JRootPane root = myContent.getRootPane();
833       final IdeGlassPaneImpl glass = new IdeGlassPaneImpl(root);
834       root.setGlassPane(glass);
835
836       int i = Registry.intValue("ide.popup.resizable.border.sensitivity", 4);
837       WindowResizeListener resizeListener = new WindowResizeListener(
838         myContent,
839         myMovable ? new Insets(i, i, i, i) : new Insets(0, 0, i, i),
840         isToDrawMacCorner() ? AllIcons.General.MacCorner : null) {
841         @Override
842         protected void setCursor(Component content, Cursor cursor) {
843           glass.setCursor(cursor, this);
844         }
845       };
846       glass.addMousePreprocessor(resizeListener, this);
847       glass.addMouseMotionPreprocessor(resizeListener, this);
848       myResizeListener = resizeListener;
849     }
850
851     if (myCaption != null && myMovable) {
852       final WindowMoveListener moveListener = new WindowMoveListener(myCaption) {
853         @Override
854         public void mousePressed(final MouseEvent e) {
855           if (e.isConsumed()) return;
856           if (UIUtil.isCloseClick(e) && myCaption.isWithinPanel(e)) {
857             cancel();
858           }
859           else {
860             super.mousePressed(e);
861           }
862         }
863       };
864       myCaption.addMouseListener(moveListener);
865       myCaption.addMouseMotionListener(moveListener);
866       final MyContentPanel saved = myContent;
867       Disposer.register(this, new Disposable() {
868         @Override
869         public void dispose() {
870           ListenerUtil.removeMouseListener(saved, moveListener);
871           ListenerUtil.removeMouseMotionListener(saved, moveListener);
872         }
873       });
874       myMoveListener = moveListener;
875     }
876
877     for (JBPopupListener listener : myListeners) {
878       listener.beforeShown(new LightweightWindowEvent(this));
879     }
880
881     myPopup.setRequestFocus(myRequestFocus);
882     myPopup.show();
883
884     final Window window = getContentWindow(myContent);
885
886     myWindow = window;
887
888     myWindowListener = new MyWindowListener();
889     window.addWindowListener(myWindowListener);
890
891     if (myFocusable) {
892       window.setFocusableWindowState(true);
893       window.setFocusable(true);
894     }
895
896     if (myWindow != null) {
897       // dialogwrapper-based popups do this internally through peer,
898       // for other popups like jdialog-based we should exclude them manually, but
899       // we still have to be able to use IdeFrame as parent
900       if (!myMayBeParent && !(myWindow instanceof Frame)) {
901         WindowManager.getInstance().doNotSuggestAsParent(myWindow);
902       }
903     }
904
905     setMinimumSize(myMinSize);
906
907     final Runnable afterShow = new Runnable() {
908       @Override
909       public void run() {
910         if (myPreferredFocusedComponent != null && myInStack && myFocusable) {
911           myFocusTrackback.registerFocusComponent(myPreferredFocusedComponent);
912           if (myPreferredFocusedComponent instanceof JTextComponent) {
913             IJSwingUtilities.moveMousePointerOn(myPreferredFocusedComponent);
914           }
915         }
916
917         removeActivity();
918
919         afterShow();
920
921       }
922     };
923
924     if (myRequestFocus) {
925       getFocusManager().requestFocus(new FocusCommand() {
926         @NotNull
927         @Override
928         public ActionCallback run() {
929           if (isDisposed()) {
930             removeActivity();
931             return new ActionCallback.Done();
932           }
933
934           _requestFocus();
935
936           final ActionCallback result = new ActionCallback();
937
938           final Runnable afterShowRunnable = new Runnable() {
939             @Override
940             public void run() {
941               afterShow.run();
942               result.setDone();
943             }
944           };
945           if (myNativePopup) {
946             final FocusRequestor furtherRequestor = getFocusManager().getFurtherRequestor();
947             //noinspection SSBasedInspection
948             SwingUtilities.invokeLater(new Runnable() {
949               @Override
950               public void run() {
951                 if (isDisposed()) {
952                   result.setRejected();
953                   return;
954                 }
955
956                 furtherRequestor.requestFocus(new FocusCommand() {
957                   @NotNull
958                   @Override
959                   public ActionCallback run() {
960                     if (isDisposed()) {
961                       return new ActionCallback.Rejected();
962                     }
963
964                     _requestFocus();
965
966                     afterShowRunnable.run();
967
968                     return new ActionCallback.Done();
969                   }
970                 }, true).notify(result).doWhenProcessed(new Runnable() {
971                   @Override
972                   public void run() {
973                     removeActivity();
974                   }
975                 });
976               }
977             });
978           } else {
979             afterShowRunnable.run();
980           }
981
982           return result;
983         }
984       }, true).doWhenRejected(new Runnable() {
985         @Override
986         public void run() {
987           afterShow.run();
988         }
989       });
990     } else {
991       //noinspection SSBasedInspection
992       SwingUtilities.invokeLater(new Runnable() {
993         @Override
994         public void run() {
995           if (isDisposed()) {
996             removeActivity();
997             return;
998           }
999
1000           if (X_WINDOW_FOCUS_BUG && !myRequestFocus && prevOwner != null &&
1001               Registry.is("actionSystem.xWindow.remove.focus.from.nonFocusable.popups")) {
1002             new Alarm().addRequest(new Runnable() {
1003               @Override
1004               public void run() {
1005                 if (isFocused()) {
1006                   IdeFocusManager.getInstance(myProject).requestFocus(prevOwner, false);
1007                 }
1008               }
1009             }, Registry.intValue("actionSystem.xWindow.remove.focus.from.nonFocusable.popups.delay"));
1010           }
1011
1012           afterShow.run();
1013         }
1014       });
1015     }
1016     debugState("popup shown", State.SHOWING);
1017     myState = State.SHOWN;
1018   }
1019
1020   public void focusPreferredComponent() {
1021     _requestFocus();
1022   }
1023
1024   private void installProjectDisposer() {
1025     final Component c = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1026     if (c != null) {
1027       final DataContext context = DataManager.getInstance().getDataContext(c);
1028       final Project project = CommonDataKeys.PROJECT.getData(context);
1029       if (project != null) {
1030         myProjectDisposable = new Disposable() {
1031
1032           @Override
1033           public void dispose() {
1034             if (!AbstractPopup.this.isDisposed()) {
1035               Disposer.dispose(AbstractPopup.this);
1036             }
1037           }
1038         };
1039         Disposer.register(project, myProjectDisposable);
1040       }
1041     }
1042   }
1043
1044   //Sometimes just after popup was shown the WINDOW_ACTIVATED cancels it
1045   private static void installWindowHook(final AbstractPopup popup) {
1046     if (popup.myCancelOnWindow) {
1047       popup.myCancelOnWindow = false;
1048       new Alarm(popup).addRequest(new Runnable() {
1049         @Override
1050         public void run() {
1051           popup.myCancelOnWindow = true;
1052         }
1053       }, 100);
1054     }
1055   }
1056
1057   private void addActivity() {
1058     UiActivityMonitor.getInstance().addActivity(myActivityKey);
1059   }
1060
1061   private void removeActivity() {
1062     UiActivityMonitor.getInstance().removeActivity(myActivityKey);
1063   }
1064
1065   private void prepareToShow() {
1066     final MouseAdapter mouseAdapter = new MouseAdapter() {
1067       @Override
1068       public void mousePressed(MouseEvent e) {
1069         Point point = (Point)e.getPoint().clone();
1070         SwingUtilities.convertPointToScreen(point, e.getComponent());
1071
1072         final Dimension dimension = myContent.getSize();
1073         dimension.height += myResizable && isToDrawMacCorner() ? AllIcons.General.MacCorner.getIconHeight() : 4;
1074         dimension.width += 4;
1075         Point locationOnScreen = myContent.getLocationOnScreen();
1076         final Rectangle bounds = new Rectangle(new Point(locationOnScreen.x - 2, locationOnScreen.y - 2), dimension);
1077         if (!bounds.contains(point)) {
1078           cancel();
1079         }
1080       }
1081     };
1082     myContent.addMouseListener(mouseAdapter);
1083     Disposer.register(this, new Disposable() {
1084       @Override
1085       public void dispose() {
1086         myContent.removeMouseListener(mouseAdapter);
1087       }
1088     });
1089
1090     myContent.registerKeyboardAction(new ActionListener() {
1091       @Override
1092       public void actionPerformed(ActionEvent e) {
1093         if (myCancelKeyEnabled) {
1094           cancel();
1095         }
1096       }
1097     }, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_IN_FOCUSED_WINDOW);
1098
1099
1100     mySearchKeyListener = new SpeedSearchKeyListener();
1101     myContent.addKeyListener(mySearchKeyListener);
1102
1103     if (myCancelOnMouseOutCallback != null || myCancelOnWindow) {
1104       myMouseOutCanceller = new Canceller();
1105       Toolkit.getDefaultToolkit().addAWTEventListener(myMouseOutCanceller, AWTEvent.MOUSE_EVENT_MASK | WindowEvent.WINDOW_ACTIVATED |
1106                                                                            AWTEvent.MOUSE_MOTION_EVENT_MASK);
1107     }
1108
1109
1110     ChildFocusWatcher focusWatcher = new ChildFocusWatcher(myContent) {
1111       @Override
1112       protected void onFocusGained(final FocusEvent event) {
1113         setWindowActive(true);
1114       }
1115
1116       @Override
1117       protected void onFocusLost(final FocusEvent event) {
1118         setWindowActive(false);
1119       }
1120     };
1121     Disposer.register(this, focusWatcher);
1122
1123     mySpeedSearchPatternField = new JTextField();
1124     if (SystemInfo.isMac) {
1125       Font f = mySpeedSearchPatternField.getFont();
1126       mySpeedSearchPatternField.setFont(f.deriveFont(f.getStyle(), f.getSize() - 2));
1127     }
1128   }
1129
1130   private Window updateMaskAndAlpha(Window window) {
1131     if (window == null) return null;
1132
1133     final WindowManagerEx wndManager = getWndManager();
1134     if (wndManager == null) return window;
1135
1136     if (!wndManager.isAlphaModeEnabled(window)) return window;
1137
1138     if (myAlpha != myLastAlpha) {
1139       wndManager.setAlphaModeRatio(window, myAlpha);
1140       myLastAlpha = myAlpha;
1141     }
1142
1143     if (myMaskProvider != null) {
1144       final Dimension size = window.getSize();
1145       Shape mask = myMaskProvider.getMask(size);
1146       wndManager.setWindowMask(window, mask);
1147     }
1148
1149     WindowManagerEx.WindowShadowMode mode =
1150       myShadowed ? WindowManagerEx.WindowShadowMode.NORMAL : WindowManagerEx.WindowShadowMode.DISABLED;
1151     WindowManagerEx.getInstanceEx().setWindowShadow(window, mode);
1152
1153     return window;
1154   }
1155
1156   private static WindowManagerEx getWndManager() {
1157     return ApplicationManagerEx.getApplicationEx() != null ? WindowManagerEx.getInstanceEx() : null;
1158   }
1159
1160   @Override
1161   public boolean isDisposed() {
1162     return myContent == null;
1163   }
1164
1165   protected boolean beforeShow() {
1166     if (ApplicationManagerEx.getApplicationEx() == null) return true;
1167     StackingPopupDispatcher.getInstance().onPopupShown(this, myInStack);
1168     return true;
1169   }
1170
1171   protected void afterShow() {
1172   }
1173
1174   protected final boolean requestFocus() {
1175     if (!myFocusable) return false;
1176
1177     getFocusManager().requestFocus(new FocusCommand() {
1178       @NotNull
1179       @Override
1180       public ActionCallback run() {
1181         _requestFocus();
1182         return new ActionCallback.Done();
1183       }
1184     }, true);
1185
1186     return true;
1187   }
1188
1189   private void _requestFocus() {
1190     if (!myFocusable) return;
1191
1192     if (myPreferredFocusedComponent != null) {
1193       myPreferredFocusedComponent.requestFocus();
1194     }
1195   }
1196
1197   private IdeFocusManager getFocusManager() {
1198     if (myProject != null) {
1199       return IdeFocusManager.getInstance(myProject);
1200     }
1201     if (myOwner != null) {
1202       return IdeFocusManager.findInstanceByComponent(myOwner);
1203     }
1204     return IdeFocusManager.findInstance();
1205   }
1206
1207   private static JComponent getTargetComponent(Component aComponent) {
1208     if (aComponent instanceof JComponent) {
1209       return (JComponent)aComponent;
1210     }
1211     if (aComponent instanceof RootPaneContainer) {
1212       return ((RootPaneContainer)aComponent).getRootPane();
1213     }
1214
1215     LOG.error("Cannot find target for:" + aComponent);
1216     return null;
1217   }
1218
1219   private PopupComponent.Factory getFactory(boolean forceHeavyweight, boolean forceDialog) {
1220     if (Registry.is("allow.dialog.based.popups")) {
1221       boolean noFocus = !myFocusable || !myRequestFocus;
1222       boolean cannotBeDialog = noFocus && SystemInfo.isXWindow;
1223
1224       if (!cannotBeDialog && (isPersistent() || forceDialog)) {
1225         return new PopupComponent.Factory.Dialog();
1226       }
1227     }
1228     if (forceHeavyweight) {
1229       return new PopupComponent.Factory.AwtHeavyweight();
1230     }
1231     return new PopupComponent.Factory.AwtDefault();
1232   }
1233
1234   @Override
1235   public JComponent getContent() {
1236     return myContent;
1237   }
1238
1239   public void setLocation(RelativePoint p) {
1240     if (isBusy()) return;
1241
1242     setLocation(p, myPopup);
1243   }
1244
1245   private static void setLocation(final RelativePoint p, final PopupComponent popup) {
1246     if (popup == null) return;
1247
1248     final Window wnd = popup.getWindow();
1249     assert wnd != null;
1250
1251     wnd.setLocation(p.getScreenPoint());
1252   }
1253
1254   @Override
1255   public void pack(boolean width, boolean height) {
1256     if (!isVisible() || !width && !height || isBusy()) return;
1257
1258     Dimension size = getSize();
1259     Dimension prefSize = myContent.computePreferredSize();
1260
1261     if (width) {
1262       size.width = prefSize.width;
1263     }
1264
1265     if (height) {
1266       size.height = prefSize.height;
1267     }
1268
1269     size = computeWindowSize(size);
1270
1271     final Window window = getContentWindow(myContent);
1272     if (window != null) {
1273       window.setSize(size);
1274     }
1275   }
1276
1277   public void pack() {
1278     if (isBusy()) return;
1279
1280     final Window window = getContentWindow(myContent);
1281     if (window != null) {
1282       window.pack();
1283     }
1284   }
1285
1286   public JComponent getComponent() {
1287     return myComponent;
1288   }
1289
1290   public void setProject(Project project) {
1291     myProject = project;
1292   }
1293
1294
1295   @Override
1296   public void dispose() {
1297     if (myState == State.SHOWN) {
1298       LOG.debug("shown popup must be cancelled");
1299       cancel();
1300     }
1301     if (myState == State.DISPOSE) {
1302       return;
1303     }
1304     debugState("dispose popup", State.INIT, State.CANCEL);
1305     myState = State.DISPOSE;
1306
1307     if (myDisposed) {
1308       return;
1309     }
1310     myDisposed = true;
1311
1312     if (LOG.isDebugEnabled()) {
1313       LOG.debug("start disposing " + myContent);
1314     }
1315
1316     Disposer.dispose(this, false);
1317
1318     ApplicationManager.getApplication().assertIsDispatchThread();
1319
1320     if (myPopup != null) {
1321       cancel(myDisposeEvent);
1322     }
1323
1324     if (myContent != null) {
1325       myContent.removeAll();
1326       myContent.removeKeyListener(mySearchKeyListener);
1327     }
1328     myContent = null;
1329     myPreferredFocusedComponent = null;
1330     myComponent = null;
1331     myFocusTrackback = null;
1332     myCallBack = null;
1333     myListeners = null;
1334
1335     if (myMouseOutCanceller != null) {
1336       final Toolkit toolkit = Toolkit.getDefaultToolkit();
1337       // it may happen, but have no idea how
1338       // http://www.jetbrains.net/jira/browse/IDEADEV-21265
1339       if (toolkit != null) {
1340         toolkit.removeAWTEventListener(myMouseOutCanceller);
1341       }
1342     }
1343     myMouseOutCanceller = null;
1344
1345     resetWindow();
1346
1347     if (myFinalRunnable != null) {
1348       final ActionCallback typeAheadDone = new ActionCallback();
1349       Runnable runFinal = new Runnable() {
1350         @Override
1351         public void run() {
1352           //noinspection SSBasedInspection
1353           SwingUtilities.invokeLater(myFinalRunnable);
1354           //noinspection SSBasedInspection
1355           SwingUtilities.invokeLater(typeAheadDone.createSetDoneRunnable());
1356           myFinalRunnable = null;
1357         }
1358       };
1359
1360       IdeFocusManager.getInstance(myProject).typeAheadUntil(typeAheadDone);
1361       getFocusManager().doWhenFocusSettlesDown(runFinal);
1362     }
1363
1364     if (LOG.isDebugEnabled()) {
1365       LOG.debug("stop disposing content");
1366     }
1367   }
1368
1369   private void resetWindow() {
1370     if (myWindow != null && getWndManager() != null) {
1371       getWndManager().resetWindow(myWindow);
1372       if (myWindowListener != null) {
1373         myWindow.removeWindowListener(myWindowListener);
1374       }
1375
1376       if (myWindow instanceof JWindow) {
1377         ((JWindow)myWindow).getRootPane().putClientProperty(KEY, null);
1378       }
1379
1380       myWindow = null;
1381       myWindowListener = null;
1382     }
1383   }
1384
1385   public void storeDimensionSize(final Dimension size) {
1386     if (myDimensionServiceKey != null) {
1387       DimensionService.getInstance().setSize(myDimensionServiceKey, size, myProject);
1388     }
1389   }
1390
1391   public void storeLocation(final Point xy) {
1392     if (myDimensionServiceKey != null) {
1393       DimensionService.getInstance().setLocation(myDimensionServiceKey, xy, myProject);
1394     }
1395   }
1396
1397   public static class MyContentPanel extends JPanel implements DataProvider {
1398     private final boolean myResizable;
1399     private final boolean myDrawMacCorner;
1400     @Nullable private DataProvider myDataProvider;
1401
1402     public MyContentPanel(final boolean resizable, final PopupBorder border, boolean drawMacCorner) {
1403       super(new BorderLayout());
1404       myResizable = resizable;
1405       myDrawMacCorner = drawMacCorner;
1406       setBorder(border);
1407     }
1408
1409     @Override
1410     public void paint(Graphics g) {
1411       super.paint(g);
1412
1413       if (myResizable && myDrawMacCorner) {
1414         AllIcons.General.MacCorner.paintIcon(this, g,
1415                                              getX() + getWidth() - AllIcons.General.MacCorner.getIconWidth(),
1416                                              getY() + getHeight() - AllIcons.General.MacCorner.getIconHeight());
1417       }
1418     }
1419
1420     public Dimension computePreferredSize() {
1421       if (isPreferredSizeSet()) {
1422         Dimension setSize = getPreferredSize();
1423         setPreferredSize(null);
1424         Dimension result = getPreferredSize();
1425         setPreferredSize(setSize);
1426         return result;
1427       }
1428       return getPreferredSize();
1429     }
1430
1431     @Nullable
1432     @Override
1433     public Object getData(@NonNls String dataId) {
1434       return myDataProvider != null ? myDataProvider.getData(dataId) : null;
1435     }
1436
1437     public void setDataProvider(@Nullable DataProvider dataProvider) {
1438       myDataProvider = dataProvider;
1439     }
1440   }
1441
1442   public boolean isCancelOnClickOutside() {
1443     return myCancelOnClickOutside;
1444   }
1445
1446   public boolean isCancelOnWindowDeactivation() {
1447     return myCancelOnWindowDeactivation;
1448   }
1449
1450   private class Canceller implements AWTEventListener {
1451     private boolean myEverEntered = false;
1452
1453     @Override
1454     public void eventDispatched(final AWTEvent event) {
1455       if (event.getID() == WindowEvent.WINDOW_ACTIVATED) {
1456         if (myCancelOnWindow && myPopup != null && !myPopup.isPopupWindow(((WindowEvent)event).getWindow())) {
1457           cancel();
1458         }
1459       }
1460       else if (event.getID() == MouseEvent.MOUSE_ENTERED) {
1461         if (withinPopup(event)) {
1462           myEverEntered = true;
1463         }
1464       }
1465       else if (event.getID() == MouseEvent.MOUSE_MOVED) {
1466         if (myCancelOnMouseOutCallback != null && myEverEntered && !withinPopup(event)) {
1467           if (myCancelOnMouseOutCallback.check((MouseEvent)event)) {
1468             cancel();
1469           }
1470         }
1471       }
1472     }
1473
1474     private boolean withinPopup(final AWTEvent event) {
1475       if (!myContent.isShowing()) return false;
1476
1477       final MouseEvent mouse = (MouseEvent)event;
1478       final Point point = mouse.getPoint();
1479       SwingUtilities.convertPointToScreen(point, mouse.getComponent());
1480       return new Rectangle(myContent.getLocationOnScreen(), myContent.getSize()).contains(point);
1481     }
1482   }
1483
1484   @Override
1485   public void setLocation(@NotNull final Point screenPoint) {
1486     if (myPopup == null) {
1487       myForcedLocation = screenPoint;
1488     }
1489     else if (!isBusy()) {
1490       moveTo(myContent, screenPoint, myLocateByContent ? myHeaderPanel.getPreferredSize() : null);
1491     }
1492   }
1493
1494   public static Window moveTo(JComponent content, Point screenPoint, final Dimension headerCorrectionSize) {
1495     final Window wnd = getContentWindow(content);
1496     if (wnd != null) {
1497       wnd.setCursor(Cursor.getDefaultCursor());
1498       if (headerCorrectionSize != null) {
1499         screenPoint.y -= headerCorrectionSize.height;
1500       }
1501       wnd.setLocation(screenPoint);
1502     }
1503     return wnd;
1504   }
1505
1506   private static Window getContentWindow(Component content) {
1507     Window window = SwingUtilities.getWindowAncestor(content);
1508     if (window == null) {
1509       if (LOG.isDebugEnabled()) {
1510         LOG.debug("no window ancestor for " + content);
1511       }
1512     }
1513     return window;
1514   }
1515
1516   @Override
1517   public Point getLocationOnScreen() {
1518     Dimension headerCorrectionSize = myLocateByContent ? myHeaderPanel.getPreferredSize() : null;
1519     Point screenPoint = myContent.getLocationOnScreen();
1520     if (headerCorrectionSize != null) {
1521       screenPoint.y -= headerCorrectionSize.height;
1522     }
1523
1524     return screenPoint;
1525   }
1526
1527
1528   @Override
1529   public void setSize(@NotNull final Dimension size) {
1530     setSize(size, true);
1531   }
1532
1533   private void setSize(Dimension size, boolean adjustByContent) {
1534     if (isBusy()) return;
1535
1536     Dimension toSet = size;
1537     if (myPopup == null) {
1538       myForcedSize = toSet;
1539     }
1540     else {
1541       if (adjustByContent) {
1542         toSet = computeWindowSize(toSet);
1543       }
1544       updateMaskAndAlpha(setSize(myContent, toSet));
1545     }
1546   }
1547
1548   private Dimension computeWindowSize(Dimension size) {
1549     if (myAdComponent != null && myAdComponent.isShowing()) {
1550       size.height += myAdComponent.getPreferredSize().height + 1;
1551     }
1552     return size;
1553   }
1554
1555   @Override
1556   public Dimension getSize() {
1557     if (myPopup != null) {
1558       final Window popupWindow = getContentWindow(myContent);
1559       return (popupWindow == null) ? myForcedSize : popupWindow.getSize();
1560     } else {
1561       return myForcedSize;
1562     }
1563   }
1564
1565   @Override
1566   public void moveToFitScreen() {
1567     if (myPopup == null || isBusy()) return;
1568
1569     final Window popupWindow = getContentWindow(myContent);
1570     if (popupWindow == null) return;
1571     Rectangle bounds = popupWindow.getBounds();
1572
1573     ScreenUtil.moveRectangleToFitTheScreen(bounds);
1574     setLocation(bounds.getLocation());
1575     setSize(bounds.getSize(), false);
1576   }
1577
1578
1579   public static Window setSize(JComponent content, final Dimension size) {
1580     final Window popupWindow = getContentWindow(content);
1581     if (popupWindow == null) return null;
1582     Insets insets = content.getInsets();
1583     if (insets != null) {
1584       size.width += insets.left + insets.right;
1585       size.height += insets.top + insets.bottom;
1586     }
1587     content.setPreferredSize(size);
1588     popupWindow.pack();
1589     return popupWindow;
1590   }
1591
1592   public void setCaption(String title) {
1593     if (myCaption instanceof TitlePanel) {
1594       ((TitlePanel)myCaption).setText(title);
1595     }
1596   }
1597
1598   private class MyWindowListener extends WindowAdapter {
1599
1600     @Override
1601     public void windowOpened(WindowEvent e) {
1602       updateMaskAndAlpha(myWindow);
1603     }
1604
1605     @Override
1606     public void windowClosing(final WindowEvent e) {
1607       resetWindow();
1608       cancel();
1609     }
1610   }
1611
1612   @Override
1613   public boolean isPersistent() {
1614     return !myCancelOnClickOutside && !myCancelOnWindow;
1615   }
1616
1617   @Override
1618   public boolean isNativePopup() {
1619     return myNativePopup;
1620   }
1621
1622   @Override
1623   public void setUiVisible(final boolean visible) {
1624     if (myPopup != null) {
1625       if (visible) {
1626         myPopup.show();
1627         final Window window = getPopupWindow();
1628         if (window != null && myRestoreWindowSize != null) {
1629           window.setSize(myRestoreWindowSize);
1630           myRestoreWindowSize = null;
1631         }
1632       }
1633       else {
1634         final Window window = getPopupWindow();
1635         if (window != null) {
1636           myRestoreWindowSize = window.getSize();
1637           window.setVisible(true);
1638         }
1639       }
1640     }
1641   }
1642
1643   public Window getPopupWindow() {
1644     return myPopup.getWindow();
1645   }
1646
1647   public void setUserData(List<Object> userData) {
1648     myUserData = userData;
1649   }
1650
1651   @Override
1652   public <T> T getUserData(final Class<T> userDataClass) {
1653     if (myUserData != null) {
1654       for (Object o : myUserData) {
1655         if (userDataClass.isInstance(o)) {
1656           @SuppressWarnings("unchecked") T t = (T)o;
1657           return t;
1658         }
1659       }
1660     }
1661     return null;
1662   }
1663
1664   @Override
1665   public boolean isModalContext() {
1666     return myModalContext;
1667   }
1668
1669   @Override
1670   public boolean isFocused() {
1671     if (myComponent != null && isFocused(new Component[]{SwingUtilities.getWindowAncestor(myComponent)})) {
1672       return true;
1673     }
1674     return isFocused(myFocusOwners);
1675   }
1676
1677   public static boolean isFocused(@Nullable Component[] components) {
1678     if (components == null) return false;
1679
1680     Component owner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
1681
1682     if (owner == null) return false;
1683
1684     Window wnd = UIUtil.getWindow(owner);
1685
1686     for (Component each : components) {
1687       if (each != null && SwingUtilities.isDescendingFrom(owner, each)) {
1688         Window eachWindow = UIUtil.getWindow(each);
1689         if (eachWindow == wnd) {
1690           return true;
1691         }
1692       }
1693     }
1694
1695     return false;
1696   }
1697
1698   @Override
1699   public boolean isCancelKeyEnabled() {
1700     return myCancelKeyEnabled;
1701   }
1702
1703   @NotNull
1704   CaptionPanel getTitle() {
1705     return myCaption;
1706   }
1707
1708   private void setHeaderComponent(JComponent c) {
1709     boolean doRevalidate = false;
1710     if (myHeaderComponent != null) {
1711       myHeaderPanel.remove(myHeaderComponent);
1712       myHeaderPanel.add(myCaption, BorderLayout.NORTH);
1713       myHeaderComponent = null;
1714       doRevalidate = true;
1715     }
1716
1717     if (c != null) {
1718       myHeaderPanel.remove(myCaption);
1719       myHeaderPanel.add(c, BorderLayout.NORTH);
1720       myHeaderComponent = c;
1721
1722       final Dimension size = myContent.getSize();
1723       if (size.height < c.getPreferredSize().height * 2) {
1724         size.height += c.getPreferredSize().height;
1725         setSize(size);
1726       }
1727
1728       doRevalidate = true;
1729     }
1730
1731     if (doRevalidate) myContent.revalidate();
1732   }
1733
1734   public void setWarning(@NotNull String text) {
1735     JBLabel label = new JBLabel(text, UIUtil.getBalloonWarningIcon(), SwingConstants.CENTER);
1736     label.setOpaque(true);
1737     Color color = HintUtil.INFORMATION_COLOR;
1738     label.setBackground(color);
1739     label.setBorder(BorderFactory.createLineBorder(color, 3));
1740     myHeaderPanel.add(label, BorderLayout.SOUTH);
1741   }
1742
1743   @Override
1744   public void addListener(final JBPopupListener listener) {
1745     myListeners.add(listener);
1746   }
1747
1748   @Override
1749   public void removeListener(final JBPopupListener listener) {
1750     myListeners.remove(listener);
1751   }
1752
1753   protected void onSpeedSearchPatternChanged() {
1754   }
1755
1756   @Override
1757   public Component getOwner() {
1758     return myRequestorComponent;
1759   }
1760
1761   @Override
1762   public void setMinimumSize(Dimension size) {
1763     //todo: consider changing only the caption panel minimum size
1764     Dimension sizeFromHeader = myHeaderPanel.getPreferredSize();
1765
1766     if (sizeFromHeader == null) {
1767       sizeFromHeader = myHeaderPanel.getMinimumSize();
1768     }
1769
1770     if (sizeFromHeader == null) {
1771       int minimumSize = myWindow.getGraphics().getFontMetrics(myHeaderPanel.getFont()).getHeight();
1772       sizeFromHeader = new Dimension(minimumSize, minimumSize);
1773     }
1774
1775     if (size == null) {
1776       myMinSize = sizeFromHeader;
1777     } else {
1778       final int width = Math.max(size.width, sizeFromHeader.width);
1779       final int height = Math.max(size.height, sizeFromHeader.height);
1780       myMinSize = new Dimension(width, height);
1781     }
1782
1783     if (myWindow != null) {
1784       myWindow.setMinimumSize(myMinSize);
1785     }
1786   }
1787
1788   @Override
1789   public void setFinalRunnable(Runnable finalRunnable) {
1790     myFinalRunnable = finalRunnable;
1791   }
1792
1793   public void setOk(boolean ok) {
1794     myOk = ok;
1795   }
1796
1797   @Override
1798   public void setDataProvider(@NotNull DataProvider dataProvider) {
1799     if (myContent != null) {
1800       myContent.setDataProvider(dataProvider);
1801     }
1802   }
1803
1804   @Override
1805   public boolean dispatchKeyEvent(@NotNull KeyEvent e) {
1806     BooleanFunction<KeyEvent> handler = myKeyEventHandler;
1807     if (handler != null) {
1808       return handler.fun(e);
1809     }
1810     else {
1811       if (isCloseRequest(e) && myCancelKeyEnabled) {
1812         cancel(e);
1813         return true;
1814       }
1815     }
1816     return false;
1817   }
1818
1819   private class SpeedSearchKeyListener implements KeyListener {
1820     @Override
1821     public void keyTyped(final KeyEvent e) {
1822       mySpeedSearch.process(e);
1823     }
1824
1825     @Override
1826     public void keyPressed(final KeyEvent e) {
1827       mySpeedSearch.process(e);
1828     }
1829
1830     @Override
1831     public void keyReleased(final KeyEvent e) {
1832       mySpeedSearch.process(e);
1833     }
1834   }
1835
1836   @NotNull
1837   public Dimension getHeaderPreferredSize() {
1838     return myHeaderPanel.getPreferredSize();
1839   }
1840   @NotNull
1841   public Dimension getFooterPreferredSize() {
1842     return myAdComponent == null ? new Dimension(0,0) : myAdComponent.getPreferredSize();
1843   }
1844
1845   public static boolean isCloseRequest(KeyEvent e) {
1846     return e != null && e.getID() == KeyEvent.KEY_PRESSED && e.getKeyCode() == KeyEvent.VK_ESCAPE && e.getModifiers() == 0;
1847   }
1848
1849   private boolean isBusy() {
1850     return myResizeListener != null && myResizeListener.isBusy() || myMoveListener != null && myMoveListener.isBusy();
1851   }
1852 }