Merge remote-tracking branch 'origin/master'
[idea/community.git] / platform / platform-impl / src / com / intellij / ui / popup / MovablePopup.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.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 /**
26  * @author Sergey Malenkov
27  */
28 public class MovablePopup {
29   private static final Object CACHE = new Object();
30   private final Component myOwner;
31   private final Component myContent;
32   private Rectangle myViewBounds;
33   private Container myView;
34   private boolean myAlwaysOnTop;
35   private boolean myHeavyWeight;
36   private boolean myWindowFocusable;
37   private boolean myWindowShadow;
38
39   /**
40    * @param owner   a component to which this popup belongs
41    * @param content a component to show within this popup
42    */
43   public MovablePopup(@NotNull Component owner, @NotNull Component content) {
44     myOwner = owner;
45     myContent = content;
46     myViewBounds = new Rectangle(content.getPreferredSize());
47     myHeavyWeight = true;
48   }
49
50   /**
51    * Sets whether this popup should be always on top.
52    * This property is used by heavy weight popups only.
53    */
54   public void setAlwaysOnTop(boolean value) {
55     if (myAlwaysOnTop != value) {
56       myAlwaysOnTop = value;
57       disposeAndUpdate(true);
58     }
59   }
60
61   private static void setAlwaysOnTop(@NotNull Window window, boolean value) {
62     if (value != window.isAlwaysOnTop()) {
63       try {
64         window.setAlwaysOnTop(value);
65       }
66       catch (Exception ignored) {
67       }
68     }
69   }
70
71   /**
72    * Sets whether this popup should be a separate window.
73    * A light weight popups are painted on the layered pane.
74    */
75   public void setHeavyWeight(boolean value) {
76     if (myHeavyWeight != value) {
77       myHeavyWeight = value;
78       disposeAndUpdate(true);
79     }
80   }
81
82   /**
83    * Sets whether this popup should grab a focus.
84    * This property is used by heavy weight popups only.
85    */
86   public void setWindowFocusable(boolean value) {
87     if (myWindowFocusable != value) {
88       myWindowFocusable = value;
89       disposeAndUpdate(true);
90     }
91   }
92
93   private static void setWindowFocusable(@NotNull Window window, boolean value) {
94     if (value != window.getFocusableWindowState()) {
95       window.setFocusableWindowState(value);
96     }
97   }
98
99   /**
100    * Sets whether this popup should have a shadow.
101    * This property is used by heavy weight popups only.
102    */
103   public void setWindowShadow(boolean value) {
104     if (myWindowShadow != value) {
105       myWindowShadow = value;
106       disposeAndUpdate(true);
107     }
108   }
109
110   private static void setWindowShadow(@NotNull Window window, boolean value) {
111     JRootPane root = getRootPane(window);
112     if (root != null) {
113       root.putClientProperty("Window.shadow", value);
114     }
115   }
116
117   public void setBounds(@NotNull Rectangle bounds) {
118     setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
119   }
120
121   public void setBounds(int x, int y, int width, int height) {
122     if (myViewBounds != null) {
123       myViewBounds.setBounds(x, y, width, height);
124     }
125     else {
126       setBounds(new Point(x, y), new Dimension(width, height));
127     }
128   }
129
130   public void setLocation(@NotNull Point location) {
131     setLocation(location.x, location.y);
132   }
133
134   public void setLocation(int x, int y) {
135     if (myViewBounds != null) {
136       myViewBounds.setLocation(x, y);
137     }
138     else {
139       setBounds(new Point(x, y), null);
140     }
141   }
142
143   public void setSize(@NotNull Dimension size) {
144     setSize(size.width, size.height);
145   }
146
147   public void setSize(int width, int height) {
148     if (myViewBounds != null) {
149       myViewBounds.setSize(width, height);
150     }
151     else {
152       setBounds(null, new Dimension(width, height));
153     }
154   }
155
156   public void setVisible(boolean visible) {
157     if (!visible && myView != null) {
158       disposeAndUpdate(false);
159     }
160     else if (visible && myView == null) {
161       Window owner = UIUtil.getWindow(myOwner);
162       if (owner != null) {
163         if (myHeavyWeight) {
164           Window view = pop(owner);
165           if (view == null) {
166             view = new JWindow(owner);
167             view.setType(Window.Type.POPUP);
168           }
169           setAlwaysOnTop(view, myAlwaysOnTop);
170           setWindowFocusable(view, myWindowFocusable);
171           setWindowShadow(view, myWindowShadow);
172           myView = view;
173         }
174         else if (owner instanceof RootPaneContainer) {
175           JLayeredPane parent = ((RootPaneContainer)owner).getLayeredPane();
176           if (parent != null) {
177             JPanel view = new JPanel(new BorderLayout());
178             view.setVisible(false);
179             parent.add(view, JLayeredPane.POPUP_LAYER, 0);
180             myView = view;
181           }
182         }
183       }
184       if (myView != null) {
185         myView.add(myContent);
186         Component parent = myView instanceof Window ? null : myView.getParent();
187         if (parent != null) {
188           Point location = myViewBounds.getLocation();
189           SwingUtilities.convertPointFromScreen(location, parent);
190           myViewBounds.setLocation(location);
191         }
192         myView.setBackground(UIUtil.getLabelBackground());
193         myView.setBounds(myViewBounds);
194         myView.setVisible(true);
195         myViewBounds = null;
196       }
197     }
198   }
199
200   /**
201    * Determines whether this popup should be visible.
202    */
203   public boolean isVisible() {
204     return myView != null && myView.isVisible();
205   }
206
207   private void disposeAndUpdate(boolean update) {
208     if (myView != null) {
209       boolean visible = myView.isVisible();
210       myView.setVisible(false);
211       Container container = myContent.getParent();
212       if (container != null) {
213         container.remove(myContent);
214       }
215       if (myView instanceof Window) {
216         myViewBounds = myView.getBounds();
217         Window window = (Window)myView;
218         if (!push(UIUtil.getWindow(myOwner), window)) {
219           window.dispose();
220         }
221       }
222       else {
223         Container parent = myView.getParent();
224         if (parent == null) {
225           myViewBounds = new Rectangle(myContent.getPreferredSize());
226         }
227         else {
228           myViewBounds = new Rectangle(myView.getBounds());
229           parent.remove(myView);
230           Point point = new Point(myViewBounds.x, myViewBounds.y);
231           SwingUtilities.convertPointToScreen(point, parent);
232           myViewBounds.x = point.x;
233           myViewBounds.y = point.y;
234         }
235       }
236       myView = null;
237       if (update && visible) {
238         setVisible(true);
239       }
240     }
241   }
242
243   private void setBounds(Point location, Dimension size) {
244     if (myView != null) {
245       if (size == null) {
246         size = myView.getSize();
247       }
248       if (location == null) {
249         location = myView.getLocation();
250       }
251       else {
252         Component parent = myView instanceof Window ? null : myView.getParent();
253         if (parent != null) {
254           SwingUtilities.convertPointFromScreen(location, parent);
255         }
256       }
257       myView.setBounds(location.x, location.y, size.width, size.height);
258       if (myView.isVisible()) {
259         myView.invalidate();
260         myView.validate();
261         myView.repaint();
262       }
263     }
264   }
265
266   private static JRootPane getRootPane(Window window) {
267     if (window instanceof RootPaneContainer) {
268       RootPaneContainer container = (RootPaneContainer)window;
269       return container.getRootPane();
270     }
271     return null;
272   }
273
274   private static Window pop(Window owner) {
275     JRootPane root = getRootPane(owner);
276     if (root != null) {
277       synchronized (CACHE) {
278         @SuppressWarnings("unchecked")
279         ArrayDeque<Window> cache = (ArrayDeque<Window>)root.getClientProperty(CACHE);
280         if (cache != null && !cache.isEmpty()) {
281           return cache.pop();
282         }
283       }
284     }
285     return null;
286   }
287
288   private static boolean push(Window owner, Window window) {
289     JRootPane root = getRootPane(owner);
290     if (root != null) {
291       synchronized (CACHE) {
292         @SuppressWarnings("unchecked")
293         ArrayDeque<Window> cache = (ArrayDeque<Window>)root.getClientProperty(CACHE);
294         if (cache == null) {
295           cache = new ArrayDeque<>();
296           root.putClientProperty(CACHE, cache);
297         }
298         cache.push(window);
299         return true;
300       }
301     }
302     return false;
303   }
304 }