Merge branch 'db/javac-ast'
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / popup / MovablePopup.java
1 /*
2  * Copyright 2000-2015 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.ui.popup;
17
18 import com.intellij.util.ui.UIUtil;
19 import org.jetbrains.annotations.NotNull;
20
21 import javax.swing.*;
22 import java.awt.*;
23 import java.util.ArrayDeque;
24
25 import static com.intellij.Patches.USE_REFLECTION_TO_ACCESS_JDK7;
26
27 /**
28  * @author Sergey Malenkov
29  */
30 public class MovablePopup {
31   private static final Object CACHE = new Object();
32   private final Component myOwner;
33   private final Component myContent;
34   private Rectangle myViewBounds;
35   private Container myView;
36   private boolean myAlwaysOnTop;
37   private boolean myHeavyWeight;
38   private boolean myWindowFocusable;
39   private boolean myWindowShadow;
40
41   /**
42    * @param owner   a component to which this popup belongs
43    * @param content a component to show within this popup
44    */
45   public MovablePopup(@NotNull Component owner, @NotNull Component content) {
46     myOwner = owner;
47     myContent = content;
48     myViewBounds = new Rectangle(content.getPreferredSize());
49     myHeavyWeight = true;
50   }
51
52   /**
53    * Sets whether this popup should be always on top.
54    * This property is used by heavy weight popups only.
55    */
56   public void setAlwaysOnTop(boolean value) {
57     if (myAlwaysOnTop != value) {
58       myAlwaysOnTop = value;
59       disposeAndUpdate(true);
60     }
61   }
62
63   private static void setAlwaysOnTop(@NotNull Window window, boolean value) {
64     if (value != window.isAlwaysOnTop()) {
65       try {
66         window.setAlwaysOnTop(value);
67       }
68       catch (Exception ignored) {
69       }
70     }
71   }
72
73   /**
74    * Sets whether this popup should be a separate window.
75    * A light weight popups are painted on the layered pane.
76    */
77   public void setHeavyWeight(boolean value) {
78     if (myHeavyWeight != value) {
79       myHeavyWeight = value;
80       disposeAndUpdate(true);
81     }
82   }
83
84   /**
85    * Sets whether this popup should grab a focus.
86    * This property is used by heavy weight popups only.
87    */
88   public void setWindowFocusable(boolean value) {
89     if (myWindowFocusable != value) {
90       myWindowFocusable = value;
91       disposeAndUpdate(true);
92     }
93   }
94
95   private static void setWindowFocusable(@NotNull Window window, boolean value) {
96     if (value != window.getFocusableWindowState()) {
97       window.setFocusableWindowState(value);
98     }
99   }
100
101   /**
102    * Sets whether this popup should have a shadow.
103    * This property is used by heavy weight popups only.
104    */
105   public void setWindowShadow(boolean value) {
106     if (myWindowShadow != value) {
107       myWindowShadow = value;
108       disposeAndUpdate(true);
109     }
110   }
111
112   private static void setWindowShadow(@NotNull Window window, boolean value) {
113     JRootPane root = getRootPane(window);
114     if (root != null) {
115       root.putClientProperty("Window.shadow", value);
116     }
117   }
118
119   public void setBounds(@NotNull Rectangle bounds) {
120     setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
121   }
122
123   public void setBounds(int x, int y, int width, int height) {
124     if (myViewBounds != null) {
125       myViewBounds.setBounds(x, y, width, height);
126     }
127     else {
128       setBounds(new Point(x, y), new Dimension(width, height));
129     }
130   }
131
132   public void setLocation(@NotNull Point location) {
133     setLocation(location.x, location.y);
134   }
135
136   public void setLocation(int x, int y) {
137     if (myViewBounds != null) {
138       myViewBounds.setLocation(x, y);
139     }
140     else {
141       setBounds(new Point(x, y), null);
142     }
143   }
144
145   public void setSize(@NotNull Dimension size) {
146     setSize(size.width, size.height);
147   }
148
149   public void setSize(int width, int height) {
150     if (myViewBounds != null) {
151       myViewBounds.setSize(width, height);
152     }
153     else {
154       setBounds(null, new Dimension(width, height));
155     }
156   }
157
158   public void setVisible(boolean visible) {
159     if (!visible && myView != null) {
160       disposeAndUpdate(false);
161     }
162     else if (visible && myView == null) {
163       Window owner = UIUtil.getWindow(myOwner);
164       if (owner != null) {
165         if (myHeavyWeight) {
166           Window view = pop(owner);
167           if (view == null) {
168             view = new JWindow(owner);
169             setPopupType(view);
170           }
171           setAlwaysOnTop(view, myAlwaysOnTop);
172           setWindowFocusable(view, myWindowFocusable);
173           setWindowShadow(view, myWindowShadow);
174           myView = view;
175         }
176         else if (owner instanceof RootPaneContainer) {
177           JLayeredPane parent = ((RootPaneContainer)owner).getLayeredPane();
178           if (parent != null) {
179             JPanel view = new JPanel(new BorderLayout());
180             view.setVisible(false);
181             parent.add(view, JLayeredPane.POPUP_LAYER, 0);
182             myView = view;
183           }
184         }
185       }
186       if (myView != null) {
187         myView.add(myContent);
188         Component parent = myView instanceof Window ? null : myView.getParent();
189         if (parent != null) {
190           Point location = myViewBounds.getLocation();
191           SwingUtilities.convertPointFromScreen(location, parent);
192           myViewBounds.setLocation(location);
193         }
194         myView.setBackground(UIUtil.getLabelBackground());
195         myView.setBounds(myViewBounds);
196         myView.setVisible(true);
197         myViewBounds = null;
198       }
199     }
200   }
201
202   /**
203    * Determines whether this popup should be visible.
204    */
205   public boolean isVisible() {
206     return myView != null && myView.isVisible();
207   }
208
209   private void disposeAndUpdate(boolean update) {
210     if (myView != null) {
211       boolean visible = myView.isVisible();
212       myView.setVisible(false);
213       Container container = myContent.getParent();
214       if (container != null) {
215         container.remove(myContent);
216       }
217       if (myView instanceof Window) {
218         myViewBounds = myView.getBounds();
219         Window window = (Window)myView;
220         if (!push(UIUtil.getWindow(myOwner), window)) {
221           window.dispose();
222         }
223       }
224       else {
225         Container parent = myView.getParent();
226         if (parent == null) {
227           myViewBounds = new Rectangle(myContent.getPreferredSize());
228         }
229         else {
230           myViewBounds = new Rectangle(myView.getBounds());
231           parent.remove(myView);
232           Point point = new Point(myViewBounds.x, myViewBounds.y);
233           SwingUtilities.convertPointToScreen(point, parent);
234           myViewBounds.x = point.x;
235           myViewBounds.y = point.y;
236         }
237       }
238       myView = null;
239       if (update && visible) {
240         setVisible(true);
241       }
242     }
243   }
244
245   private void setBounds(Point location, Dimension size) {
246     if (myView != null) {
247       if (size == null) {
248         size = myView.getSize();
249       }
250       if (location == null) {
251         location = myView.getLocation();
252       }
253       else {
254         Component parent = myView instanceof Window ? null : myView.getParent();
255         if (parent != null) {
256           SwingUtilities.convertPointFromScreen(location, parent);
257         }
258       }
259       myView.setBounds(location.x, location.y, size.width, size.height);
260       if (myView.isVisible()) {
261         myView.invalidate();
262         myView.validate();
263         myView.repaint();
264       }
265     }
266   }
267
268   // TODO: HACK because of Java7 required:
269   // replace later with window.setType(Window.Type.POPUP)
270   private static void setPopupType(@NotNull Window window) {
271     //noinspection ConstantConditions,ConstantAssertCondition
272     assert USE_REFLECTION_TO_ACCESS_JDK7;
273     try {
274       @SuppressWarnings("unchecked")
275       Class<? extends Enum> type = (Class<? extends Enum>)Class.forName("java.awt.Window$Type");
276       Object value = Enum.valueOf(type, "POPUP");
277       Window.class.getMethod("setType", type).invoke(window, value);
278     }
279     catch (Exception ignored) {
280     }
281   }
282
283   private static JRootPane getRootPane(Window window) {
284     if (window instanceof RootPaneContainer) {
285       RootPaneContainer container = (RootPaneContainer)window;
286       return container.getRootPane();
287     }
288     return null;
289   }
290
291   private static Window pop(Window owner) {
292     JRootPane root = getRootPane(owner);
293     if (root != null) {
294       synchronized (CACHE) {
295         @SuppressWarnings("unchecked")
296         ArrayDeque<Window> cache = (ArrayDeque<Window>)root.getClientProperty(CACHE);
297         if (cache != null && !cache.isEmpty()) {
298           return cache.pop();
299         }
300       }
301     }
302     return null;
303   }
304
305   private static boolean push(Window owner, Window window) {
306     JRootPane root = getRootPane(owner);
307     if (root != null) {
308       synchronized (CACHE) {
309         @SuppressWarnings("unchecked")
310         ArrayDeque<Window> cache = (ArrayDeque<Window>)root.getClientProperty(CACHE);
311         if (cache == null) {
312           cache = new ArrayDeque<>();
313           root.putClientProperty(CACHE, cache);
314         }
315         cache.push(window);
316         return true;
317       }
318     }
319     return false;
320   }
321 }