fix completion popup crop to dialog size for single line editors (see copy class...
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / LightweightHint.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;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.ui.popup.JBPopup;
20 import com.intellij.openapi.ui.popup.JBPopupFactory;
21 import com.intellij.openapi.util.UserDataHolderBase;
22 import com.intellij.openapi.wm.ex.LayoutFocusTraversalPolicyExt;
23 import com.intellij.ui.awt.RelativePoint;
24 import com.intellij.ui.popup.AbstractPopup;
25 import org.jetbrains.annotations.NotNull;
26
27 import javax.swing.*;
28 import javax.swing.event.EventListenerList;
29 import java.awt.*;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.ActionListener;
32 import java.awt.event.KeyEvent;
33 import java.util.EventListener;
34 import java.util.EventObject;
35
36 public class LightweightHint extends UserDataHolderBase implements Hint {
37   private static final Logger LOG = Logger.getInstance("#com.intellij.ui.LightweightHint");
38
39   private final JComponent myComponent;
40   private JComponent myFocusBackComponent;
41   private final EventListenerList myListenerList = new EventListenerList();
42   private MyEscListener myEscListener;
43   private JBPopup myPopup;
44   private JComponent myParentComponent;
45   private boolean myIsRealPopup = false;
46   private boolean myForceLightweightPopup = false;
47   private boolean mySelectingHint;
48
49   private boolean myForceShowAsPopup = false;
50   private String myTitle = null;
51   private boolean myCancelOnClickOutside;
52   private boolean myCancelOnOtherWindowOpen;
53   private boolean myResizable;
54
55   public LightweightHint(@NotNull final JComponent component) {
56     myComponent = component;
57   }
58
59   public void setForceLightweightPopup(final boolean forceLightweightPopup) {
60     myForceLightweightPopup = forceLightweightPopup;
61   }
62
63
64   public void setForceShowAsPopup(final boolean forceShowAsPopup) {
65     myForceShowAsPopup = forceShowAsPopup;
66   }
67
68   public void setTitle(final String title) {
69     myTitle = title;
70   }
71
72   public boolean isSelectingHint() {
73     return mySelectingHint;
74   }
75
76   public void setSelectingHint(final boolean selectingHint) {
77     mySelectingHint = selectingHint;
78   }
79
80   public void setCancelOnClickOutside(final boolean b) {
81     myCancelOnClickOutside = b;
82   }
83
84   public void setCancelOnOtherWindowOpen(final boolean b) {
85     myCancelOnOtherWindowOpen = b;
86   }
87
88   public void setResizable(final boolean b) {
89     myResizable = b;
90   }
91
92   /**
93    * Shows the hint in the layered pane. Coordinates <code>x</code> and <code>y</code>
94    * are in <code>parentComponent</code> coordinate system. Note that the component
95    * appears on 250 layer.
96    */
97   public void show(@NotNull final JComponent parentComponent, final int x, final int y, final JComponent focusBackComponent) {
98     myParentComponent = parentComponent;
99
100     myFocusBackComponent = focusBackComponent;
101
102     LOG.assertTrue(myParentComponent.isShowing());
103     myEscListener = new MyEscListener();
104     myComponent.registerKeyboardAction(myEscListener, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
105                                        JComponent.WHEN_IN_FOCUSED_WINDOW);
106     final JLayeredPane layeredPane = parentComponent.getRootPane().getLayeredPane();
107     if (!myForceShowAsPopup &&
108         (myForceLightweightPopup || fitsLayeredPane(layeredPane, myComponent, new RelativePoint(parentComponent, new Point(x, y))))) {
109       beforeShow();
110       final Dimension preferredSize = myComponent.getPreferredSize();
111       final Point layeredPanePoint = SwingUtilities.convertPoint(parentComponent, x, y, layeredPane);
112
113       myComponent.setBounds(layeredPanePoint.x, layeredPanePoint.y, preferredSize.width, preferredSize.height);
114       layeredPane.add(myComponent, JLayeredPane.POPUP_LAYER);
115
116       myComponent.validate();
117       myComponent.repaint();
118     }
119     else {
120       myIsRealPopup = true;
121       myPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(myComponent, null)
122         .setRequestFocus(false)
123         .setResizable(myResizable)
124         .setMovable(myTitle != null)
125         .setTitle(myTitle)
126         .setShowShadow(false)
127         .setCancelKeyEnabled(false)
128         .setCancelOnClickOutside(myCancelOnClickOutside)
129         .setCancelOnOtherWindowOpen(myCancelOnOtherWindowOpen)
130         .createPopup();
131
132       beforeShow();
133       myPopup.show(new RelativePoint(myParentComponent, new Point(x, y)));
134     }
135   }
136
137   protected void beforeShow() {
138
139   }
140
141   private static boolean fitsLayeredPane(JLayeredPane pane, JComponent component, RelativePoint desiredLocation) {
142     final Rectangle lpRect = new Rectangle(pane.getLocationOnScreen().x, pane.getLocationOnScreen().y, pane.getWidth(), pane.getHeight());
143     Rectangle componentRect = new Rectangle(desiredLocation.getScreenPoint().x,
144                                             desiredLocation.getScreenPoint().y,
145                                             component.getPreferredSize().width,
146                                             component.getPreferredSize().height);
147     return lpRect.contains(componentRect);
148   }
149
150   private void fireHintHidden() {
151     final EventListener[] listeners = myListenerList.getListeners(HintListener.class);
152     for (EventListener listener : listeners) {
153       ((HintListener)listener).hintHidden(new EventObject(this));
154     }
155   }
156
157   /**
158    * @return bounds of hint component in the layered pane.
159    */
160   public final Rectangle getBounds() {
161     return myComponent.getBounds();
162   }
163
164   public boolean isVisible() {
165     return myIsRealPopup ? myPopup != null : myComponent.isShowing();
166   }
167
168   protected final boolean isRealPopup() {
169     return myIsRealPopup;
170   }
171
172   public void hide() {
173     if (isVisible()) {
174       if (myIsRealPopup) {
175         myPopup.cancel();
176         myPopup = null;
177       }
178       else {
179         final JRootPane rootPane = myComponent.getRootPane();
180         if (rootPane != null) {
181           final Rectangle bounds = myComponent.getBounds();
182           final JLayeredPane layeredPane = rootPane.getLayeredPane();
183
184           try {
185             if (myFocusBackComponent != null) {
186               LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(myFocusBackComponent);
187             }
188             layeredPane.remove(myComponent);
189           }
190           finally {
191             LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(null);
192           }
193
194           layeredPane.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
195         }
196       }
197     }
198     if (myEscListener != null) {
199       myComponent.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
200     }
201     fireHintHidden();
202   }
203
204   @Override
205   public void updateBounds() {
206     updateBounds(-1, -1, false);
207   }
208
209   @Override
210   public void updateBounds(int x, int y) {
211     updateBounds(x, y, true);
212   }
213
214   private void updateBounds(int x, int y, boolean updateLocation) {
215     if (myIsRealPopup) {
216       if (myPopup == null) return;
217       if (updateLocation) ((AbstractPopup)myPopup).setLocation(new RelativePoint(myParentComponent, new Point(x, y)));
218       myPopup.setSize(myComponent.getPreferredSize());
219     }
220     else {
221       if (updateLocation) {
222         JLayeredPane layeredPane = myParentComponent.getRootPane().getLayeredPane();
223         myComponent.setLocation(SwingUtilities.convertPoint(myParentComponent, x, y, layeredPane));
224
225       }
226       Dimension preferredSize = myComponent.getPreferredSize();
227       myComponent.setSize(preferredSize.width, preferredSize.height);
228
229       myComponent.validate();
230       myComponent.repaint();
231     }
232   }
233
234   public final JComponent getComponent() {
235     return myComponent;
236   }
237
238   public final void addHintListener(final HintListener listener) {
239     myListenerList.add(HintListener.class, listener);
240   }
241
242   public final void removeHintListener(final HintListener listener) {
243     myListenerList.remove(HintListener.class, listener);
244   }
245
246   private final class MyEscListener implements ActionListener {
247     public final void actionPerformed(final ActionEvent e) {
248       hide();
249     }
250   }
251
252   @Override
253   public String toString() {
254     return getComponent().toString();
255   }
256 }