IDEA-57733 Remove 'vcs-impl -> lang-impl' dependency introduced by the change...
[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
52   public LightweightHint(@NotNull final JComponent component) {
53     myComponent = component;
54   }
55
56   public void setForceLightweightPopup(final boolean forceLightweightPopup) {
57     myForceLightweightPopup = forceLightweightPopup;
58   }
59
60
61   public void setForceShowAsPopup(final boolean forceShowAsPopup) {
62     myForceShowAsPopup = forceShowAsPopup;
63   }
64
65   public void setTitle(final String title) {
66     myTitle = title;
67   }
68
69   public boolean isSelectingHint() {
70     return mySelectingHint;
71   }
72
73   public void setSelectingHint(final boolean selectingHint) {
74     mySelectingHint = selectingHint;
75   }
76
77   /**
78    * Shows the hint in the layered pane. Coordinates <code>x</code> and <code>y</code>
79    * are in <code>parentComponent</code> coordinate system. Note that the component
80    * appears on 250 layer.
81    */
82   public void show(@NotNull final JComponent parentComponent, final int x, final int y, final JComponent focusBackComponent) {
83     myParentComponent = parentComponent;
84
85     myFocusBackComponent = focusBackComponent;
86
87     LOG.assertTrue(myParentComponent.isShowing());
88     myEscListener = new MyEscListener();
89     myComponent.registerKeyboardAction(myEscListener, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0),
90                                        JComponent.WHEN_IN_FOCUSED_WINDOW);
91     final JLayeredPane layeredPane = parentComponent.getRootPane().getLayeredPane();
92     if (!myForceShowAsPopup &&
93         (myForceLightweightPopup || fitsLayeredPane(layeredPane, myComponent, new RelativePoint(parentComponent, new Point(x, y))))) {
94       beforeShow();
95       final Dimension preferredSize = myComponent.getPreferredSize();
96       final Point layeredPanePoint = SwingUtilities.convertPoint(parentComponent, x, y, layeredPane);
97
98       myComponent.setBounds(layeredPanePoint.x, layeredPanePoint.y, preferredSize.width, preferredSize.height);
99       layeredPane.add(myComponent, JLayeredPane.POPUP_LAYER);
100
101       myComponent.validate();
102       myComponent.repaint();
103     }
104     else {
105       myIsRealPopup = true;
106       myPopup = JBPopupFactory.getInstance().createComponentPopupBuilder(myComponent, null)
107         .setRequestFocus(false)
108         .setResizable(myForceShowAsPopup)
109         .setMovable(myForceShowAsPopup)
110         .setTitle(myTitle)
111         .setShowShadow(false)
112         .setCancelKeyEnabled(false)
113         .setCancelOnClickOutside(false)
114         .setCancelOnOtherWindowOpen(false)
115         .createPopup();
116
117       beforeShow();
118       myPopup.show(new RelativePoint(myParentComponent, new Point(x, y)));
119     }
120   }
121
122   protected void beforeShow() {
123
124   }
125
126   private static boolean fitsLayeredPane(JLayeredPane pane, JComponent component, RelativePoint desiredLocation) {
127     final Rectangle lpRect = new Rectangle(pane.getLocationOnScreen().x, pane.getLocationOnScreen().y, pane.getWidth(), pane.getHeight());
128     Rectangle componentRect = new Rectangle(desiredLocation.getScreenPoint().x,
129                                             desiredLocation.getScreenPoint().y,
130                                             component.getPreferredSize().width,
131                                             component.getPreferredSize().height);
132     return lpRect.contains(componentRect);
133   }
134
135   private void fireHintHidden() {
136     final EventListener[] listeners = myListenerList.getListeners(HintListener.class);
137     for (EventListener listener : listeners) {
138       ((HintListener)listener).hintHidden(new EventObject(this));
139     }
140   }
141
142   /**
143    * @return bounds of hint component in the layered pane.
144    */
145   public final Rectangle getBounds() {
146     return myComponent.getBounds();
147   }
148
149   public boolean isVisible() {
150     return myIsRealPopup ? myPopup != null : myComponent.isShowing();
151   }
152
153   protected final boolean isRealPopup() {
154     return myIsRealPopup;
155   }
156
157   public void hide() {
158     if (isVisible()) {
159       if (myIsRealPopup) {
160         myPopup.cancel();
161         myPopup = null;
162       }
163       else {
164         final JRootPane rootPane = myComponent.getRootPane();
165         if (rootPane != null) {
166           final Rectangle bounds = myComponent.getBounds();
167           final JLayeredPane layeredPane = rootPane.getLayeredPane();
168
169           try {
170             if (myFocusBackComponent != null) {
171               LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(myFocusBackComponent);
172             }
173             layeredPane.remove(myComponent);
174           }
175           finally {
176             LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(null);
177           }
178
179           layeredPane.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
180         }
181       }
182     }
183     if (myEscListener != null) {
184       myComponent.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0));
185     }
186     fireHintHidden();
187   }
188
189   @Override
190   public void updateBounds() {
191     updateBounds(-1, -1, false);
192   }
193
194   @Override
195   public void updateBounds(int x, int y) {
196     updateBounds(x, y, true);
197   }
198
199   private void updateBounds(int x, int y, boolean updateLocation) {
200     if (myIsRealPopup) {
201       if (myPopup == null) return;
202       if (updateLocation) ((AbstractPopup)myPopup).setLocation(new RelativePoint(myParentComponent, new Point(x, y)));
203       myPopup.setSize(myComponent.getPreferredSize());
204     }
205     else {
206       if (updateLocation) {
207         JLayeredPane layeredPane = myParentComponent.getRootPane().getLayeredPane();
208         myComponent.setLocation(SwingUtilities.convertPoint(myParentComponent, x, y, layeredPane));
209
210       }
211       Dimension preferredSize = myComponent.getPreferredSize();
212       myComponent.setSize(preferredSize.width, preferredSize.height);
213
214       myComponent.validate();
215       myComponent.repaint();
216     }
217   }
218
219   public final JComponent getComponent() {
220     return myComponent;
221   }
222
223   public final void addHintListener(final HintListener listener) {
224     myListenerList.add(HintListener.class, listener);
225   }
226
227   public final void removeHintListener(final HintListener listener) {
228     myListenerList.remove(HintListener.class, listener);
229   }
230
231   private final class MyEscListener implements ActionListener {
232     public final void actionPerformed(final ActionEvent e) {
233       hide();
234     }
235   }
236
237   @Override
238   public String toString() {
239     return getComponent().toString();
240   }
241 }