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