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