2 * Copyright 2000-2015 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.ui.popup;
18 import com.intellij.util.ui.UIUtil;
19 import org.jetbrains.annotations.NotNull;
23 import java.util.ArrayDeque;
25 import static com.intellij.Patches.USE_REFLECTION_TO_ACCESS_JDK7;
28 * @author Sergey Malenkov
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;
42 * @param owner a component to which this popup belongs
43 * @param content a component to show within this popup
45 public MovablePopup(@NotNull Component owner, @NotNull Component content) {
48 myViewBounds = new Rectangle(content.getPreferredSize());
53 * Sets whether this popup should be always on top.
54 * This property is used by heavy weight popups only.
56 public void setAlwaysOnTop(boolean value) {
57 if (myAlwaysOnTop != value) {
58 myAlwaysOnTop = value;
59 disposeAndUpdate(true);
63 private static void setAlwaysOnTop(@NotNull Window window, boolean value) {
64 if (value != window.isAlwaysOnTop()) {
66 window.setAlwaysOnTop(value);
68 catch (Exception ignored) {
74 * Sets whether this popup should be a separate window.
75 * A light weight popups are painted on the layered pane.
77 public void setHeavyWeight(boolean value) {
78 if (myHeavyWeight != value) {
79 myHeavyWeight = value;
80 disposeAndUpdate(true);
85 * Sets whether this popup should grab a focus.
86 * This property is used by heavy weight popups only.
88 public void setWindowFocusable(boolean value) {
89 if (myWindowFocusable != value) {
90 myWindowFocusable = value;
91 disposeAndUpdate(true);
95 private static void setWindowFocusable(@NotNull Window window, boolean value) {
96 if (value != window.getFocusableWindowState()) {
97 window.setFocusableWindowState(value);
102 * Sets whether this popup should have a shadow.
103 * This property is used by heavy weight popups only.
105 public void setWindowShadow(boolean value) {
106 if (myWindowShadow != value) {
107 myWindowShadow = value;
108 disposeAndUpdate(true);
112 private static void setWindowShadow(@NotNull Window window, boolean value) {
113 JRootPane root = getRootPane(window);
115 root.putClientProperty("Window.shadow", value);
119 public void setBounds(@NotNull Rectangle bounds) {
120 setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
123 public void setBounds(int x, int y, int width, int height) {
124 if (myViewBounds != null) {
125 myViewBounds.setBounds(x, y, width, height);
128 setBounds(new Point(x, y), new Dimension(width, height));
132 public void setLocation(@NotNull Point location) {
133 setLocation(location.x, location.y);
136 public void setLocation(int x, int y) {
137 if (myViewBounds != null) {
138 myViewBounds.setLocation(x, y);
141 setBounds(new Point(x, y), null);
145 public void setSize(@NotNull Dimension size) {
146 setSize(size.width, size.height);
149 public void setSize(int width, int height) {
150 if (myViewBounds != null) {
151 myViewBounds.setSize(width, height);
154 setBounds(null, new Dimension(width, height));
158 public void setVisible(boolean visible) {
159 if (!visible && myView != null) {
160 disposeAndUpdate(false);
162 else if (visible && myView == null) {
163 Window owner = UIUtil.getWindow(myOwner);
166 Window view = pop(owner);
168 view = new JWindow(owner);
171 setAlwaysOnTop(view, myAlwaysOnTop);
172 setWindowFocusable(view, myWindowFocusable);
173 setWindowShadow(view, myWindowShadow);
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);
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);
194 myView.setBackground(UIUtil.getLabelBackground());
195 myView.setBounds(myViewBounds);
196 myView.setVisible(true);
203 * Determines whether this popup should be visible.
205 public boolean isVisible() {
206 return myView != null && myView.isVisible();
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);
217 if (myView instanceof Window) {
218 myViewBounds = myView.getBounds();
219 Window window = (Window)myView;
220 if (!push(UIUtil.getWindow(myOwner), window)) {
225 Container parent = myView.getParent();
226 if (parent == null) {
227 myViewBounds = new Rectangle(myContent.getPreferredSize());
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;
239 if (update && visible) {
245 private void setBounds(Point location, Dimension size) {
246 if (myView != null) {
248 size = myView.getSize();
250 if (location == null) {
251 location = myView.getLocation();
254 Component parent = myView instanceof Window ? null : myView.getParent();
255 if (parent != null) {
256 SwingUtilities.convertPointFromScreen(location, parent);
259 myView.setBounds(location.x, location.y, size.width, size.height);
260 if (myView.isVisible()) {
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;
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);
279 catch (Exception ignored) {
283 private static JRootPane getRootPane(Window window) {
284 if (window instanceof RootPaneContainer) {
285 RootPaneContainer container = (RootPaneContainer)window;
286 return container.getRootPane();
291 private static Window pop(Window owner) {
292 JRootPane root = getRootPane(owner);
294 synchronized (CACHE) {
295 @SuppressWarnings("unchecked")
296 ArrayDeque<Window> cache = (ArrayDeque<Window>)root.getClientProperty(CACHE);
297 if (cache != null && !cache.isEmpty()) {
305 private static boolean push(Window owner, Window window) {
306 JRootPane root = getRootPane(owner);
308 synchronized (CACHE) {
309 @SuppressWarnings("unchecked")
310 ArrayDeque<Window> cache = (ArrayDeque<Window>)root.getClientProperty(CACHE);
312 cache = new ArrayDeque<>();
313 root.putClientProperty(CACHE, cache);