001 /*
002 * Created on Jan 27, 2008
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005 * the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011 * specific language governing permissions and limitations under the License.
012 *
013 * Copyright @2008-2010 the original author or authors.
014 */
015 package org.fest.swing.awt;
016
017 import static java.awt.event.InputEvent.BUTTON3_MASK;
018 import static org.fest.reflect.core.Reflection.staticMethod;
019 import static org.fest.swing.edt.GuiActionRunner.execute;
020 import static org.fest.swing.util.Platform.isWindows;
021 import static org.fest.util.Strings.concat;
022
023 import java.awt.Component;
024 import java.awt.Container;
025 import java.awt.Dialog;
026 import java.awt.Dimension;
027 import java.awt.Frame;
028 import java.awt.Insets;
029 import java.awt.Point;
030 import java.awt.Rectangle;
031 import java.awt.Toolkit;
032 import java.awt.Window;
033 import java.awt.event.InputEvent;
034
035 import javax.swing.JComponent;
036 import javax.swing.JOptionPane;
037 import javax.swing.JPopupMenu;
038 import javax.swing.SwingUtilities;
039
040 import org.fest.swing.annotation.RunsInCurrentThread;
041 import org.fest.swing.annotation.RunsInEDT;
042 import org.fest.swing.edt.GuiQuery;
043
044 /**
045 * Understands utility methods related to AWT.
046 *
047 * @author Alex Ruiz
048 */
049 public class AWT {
050
051 private static final String APPLET_APPLET_VIEWER_CLASS = "sun.applet.AppletViewer";
052 private static final String ROOT_FRAME_CLASSNAME = concat(SwingUtilities.class.getName(), "$");
053
054 /**
055 * Indicates whether the given point, relative to the given <code>JComponent</code>, is inside the screen boundaries.
056 * @param c the given <code>JComponent</code>.
057 * @param p the point to verify.
058 * @return <code>true</code> if the point is inside the screen boundaries; <code>false</code> otherwise.
059 * @since 1.2
060 */
061 public static boolean isPointInScreenBoundaries(JComponent c, Point p) {
062 Point where = translate(c, p.x, p.y);
063 Rectangle screen = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
064 return screen.contains(where);
065 }
066
067 /**
068 * Indicates whether the given point is inside the screen boundaries.
069 * @param p the point to verify.
070 * @return <code>true</code> if the point is inside the screen boundaries; <code>false</code> otherwise.
071 * @since 1.2
072 */
073 public static boolean isPointInScreenBoundaries(Point p) {
074 Rectangle screen = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
075 return screen.contains(p);
076 }
077
078 /**
079 * Returns an array of all <code>{@link Window}</code>s that have no owner. They include <code>{@link Frame}</code>s
080 * and ownerless <code>{@link Dialog}</code>s and <code>{@link Window}</code>s.
081 * <p>
082 * This method only works when using JDK 1.6 or later. For JDK 1.5, this method returns an empty array.
083 * </p>
084 * @return an array of all <code>{@link Window}</code>s that have no owner.
085 * @since 1.2
086 */
087 public static Window[] ownerLessWindows() {
088 try {
089 // Java 1.6 code
090 return staticMethod("getOwnerlessWindows").withReturnType(Window[].class).in(Window.class).invoke();
091 } catch (RuntimeException e) {
092 return new Window[0];
093 }
094 }
095
096 /**
097 * Translates the given coordinates to the location on screen of the given <code>{@link Component}</code>.
098 * <p>
099 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
100 * responsible for calling this method from the EDT.
101 * </p>
102 * @param c the given <code>Component</code>.
103 * @param x X coordinate.
104 * @param y Y coordinate.
105 * @return the translated coordinates.
106 * @since 1.1
107 */
108 @RunsInCurrentThread
109 public static Point translate(Component c, int x, int y) {
110 Point p = locationOnScreenOf(c);
111 if (p == null) return null;
112 p.translate(x, y);
113 return p;
114 }
115
116
117 /**
118 * Returns a point at the center of the visible area of the given <code>{@link Component}</code>.
119 * @param c the given <code>Component</code>.
120 * @return a point at the center of the visible area of the given <code>Component</code>.
121 */
122 @RunsInEDT
123 public static Point visibleCenterOf(final Component c) {
124 return execute(new GuiQuery<Point>() {
125 protected Point executeInEDT() {
126 if (c instanceof JComponent) return centerOfVisibleRect((JComponent)c);
127 return centerOf(c);
128 }
129 });
130 }
131
132 /**
133 * Returns a point at the center of the given <code>{@link Component}</code>.
134 * <p>
135 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
136 * responsible for calling this method from the EDT.
137 * </p>
138 * @param c the given <code>Component</code>.
139 * @return a point at the center of the given <code>Component</code>.
140 */
141 @RunsInCurrentThread
142 public static Point centerOf(Component c) {
143 Dimension size = c.getSize();
144 return new Point(size.width / 2, size.height / 2);
145 }
146
147 /**
148 * Returns a point at the center of the visible rectangle of the given <code>{@link JComponent}</code>.
149 * <p>
150 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
151 * responsible for calling this method from the EDT.
152 * </p>
153 * @param c the given <code>JComponent</code>.
154 * @return a point at the center of the visible rectangle of the given <code>JComponent</code>.
155 */
156 @RunsInCurrentThread
157 public static Point centerOfVisibleRect(JComponent c) {
158 Rectangle r = c.getVisibleRect();
159 return centerOf(r);
160 }
161
162 /**
163 * Returns a point at the center of the given <code>{@link Rectangle}</code>.
164 * <p>
165 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
166 * responsible for calling this method from the EDT.
167 * </p>
168 * @param r the given <code>Rectangle</code>.
169 * @return a point at the center of the given <code>Rectangle</code>.
170 */
171 @RunsInCurrentThread
172 public static Point centerOf(Rectangle r) {
173 return new Point((r.x + (r.width / 2)), (r.y + (r.height / 2)));
174 }
175
176 /**
177 * Returns the insets of the given <code>{@link Container}</code>, or an empty one if no insets can be found.
178 * <p>
179 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
180 * responsible for calling this method from the EDT.
181 * </p>
182 * @param c the given <code>Container</code>.
183 * @return the insets of the given <code>Container</code>, or an empty one if no insets can be found.
184 */
185 @RunsInCurrentThread
186 public static Insets insetsFrom(Container c) {
187 try {
188 Insets insets = c.getInsets();
189 if (insets != null) return insets;
190 } catch (Exception e) {}
191 return new Insets(0, 0, 0, 0);
192 }
193
194 /**
195 * Returns <code>true</code> if the given component is an Applet viewer.
196 * @param c the component to check.
197 * @return <code>true</code> if the given component is an Applet viewer, <code>false</code> otherwise.
198 */
199 public static boolean isAppletViewer(Component c) {
200 return c != null && APPLET_APPLET_VIEWER_CLASS.equals(c.getClass().getName());
201 }
202
203 /**
204 * Returns whether the given component is the default Swing hidden frame.
205 * @param c the component to check.
206 * @return <code>true</code> if the given component is the default hidden frame, <code>false</code> otherwise.
207 */
208 public static boolean isSharedInvisibleFrame(Component c) {
209 if (c == null) return false;
210 // Must perform an additional check, since applets may have their own version in their AppContext
211 return c instanceof Frame
212 && (c == JOptionPane.getRootFrame() || c.getClass().getName().startsWith(ROOT_FRAME_CLASSNAME));
213 }
214
215 /**
216 * Returns whether the given <code>Component</code> is a heavy-weight pop-up, that is, a container for a
217 * <code>JPopupMenu</code> that is implemented with a heavy-weight component (usually a <code>Window</code>).
218 * <p>
219 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
220 * responsible for calling this method from the EDT.
221 * </p>
222 * @param c the given <code>Component</code>.
223 * @return <code>true</code> if the given <code>Component</code> is a heavy-weight pop-up; <code>false</code>
224 * otherwise.
225 * @since 1.2
226 */
227 @RunsInCurrentThread
228 public static boolean isHeavyWeightPopup(Component c) {
229 if (!(c instanceof Window) || c instanceof Dialog || c instanceof Frame) return false;
230 String name = obtainNameSafely(c);
231 if ("###overrideRedirect###".equals(name) || "###focusableSwingPopup###".equals(name)) return true;
232 String typeName = c.getClass().getName();
233 return typeName.indexOf("PopupFactory$WindowPopup") != -1 || typeName.indexOf("HeavyWeightWindow") != -1;
234 }
235
236 @RunsInCurrentThread
237 private static String obtainNameSafely(Component c) {
238 // Work around some components throwing exceptions if getName is called prematurely
239 try {
240 return c.getName();
241 } catch (Throwable e) {
242 return null;
243 }
244 }
245
246 /**
247 * Returns the invoker, if any, of the given <code>{@link Component}</code>; or <code>null</code>, if the
248 * <code>Component</code> is not on a pop-up of any sort.
249 * <p>
250 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
251 * responsible for calling this method from the EDT.
252 * </p>
253 * @param c the given <code>Component</code>.
254 * @return the invoker, if any, of the given <code>Component</code>; or <code>null</code>, if the
255 * <code>Component</code> is not on a pop-up of any sort.
256 */
257 @RunsInCurrentThread
258 public static Component invokerOf(final Component c) {
259 if (c instanceof JPopupMenu) return ((JPopupMenu)c).getInvoker();
260 Container parent = c.getParent();
261 return parent != null ? invokerOf(parent) : null;
262 }
263
264 /**
265 * Safe version of <code>{@link Component#getLocationOnScreen}</code>, which avoids lockup if an AWT pop-up menu is
266 * showing. The AWT pop-up holds the AWT tree lock when showing, which lock is required by
267 * <code>getLocationOnScreen</code>.
268 * <p>
269 * <b>Note:</b> This method is <b>not</b> guaranteed to be executed in the event dispatch thread (EDT.) Clients are
270 * responsible for calling this method from the EDT.
271 * </p>
272 * @param c the given <code>Component</code>.
273 * @return the a point specifying the <code>Component</code>'s top-left corner in the screen's coordinate space, or
274 * <code>null</code>, if the <code>Component</code> is not showing on the screen.
275 */
276 @RunsInCurrentThread
277 public static Point locationOnScreenOf(Component c) {
278 if (!isAWTTreeLockHeld()) return new Point(c.getLocationOnScreen());
279 if (!c.isShowing()) return null;
280 Point location = new Point(c.getLocation());
281 if (c instanceof Window) return location;
282 Container parent = c.getParent();
283 if (parent == null) return null;
284 Point parentLocation = locationOnScreenOf(parent);
285 location.translate(parentLocation.x, parentLocation.y);
286 return location;
287 }
288
289 /**
290 * Returns whether the platform registers a pop-up on mouse press.
291 * @return <code>true</code> if the platform registers a pop-up on mouse press, <code>false</code> otherwise.
292 */
293 public static boolean popupOnPress() {
294 // Only w32 is pop-up on release
295 return !isWindows();
296 }
297
298 /**
299 * Returns the <code>{@link InputEvent}</code> mask for the pop-up trigger button.
300 * @return the <code>InputEvent</code> mask for the pop-up trigger button.
301 */
302 public static int popupMask() {
303 return BUTTON3_MASK;
304 }
305
306 /**
307 * Indicates whether the AWT Tree Lock is currently held.
308 * @return <code>true</code> if the AWT Tree Lock is currently held, <code>false</code> otherwise.
309 */
310 public static boolean isAWTTreeLockHeld() {
311 Frame[] frames = Frame.getFrames();
312 if (frames.length == 0) return false;
313 // From Abbot: Hack based on 1.4.2 java.awt.PopupMenu implementation, which blocks the event dispatch thread while
314 // the pop-up is visible, while holding the AWT tree lock.
315 // Start another thread which attempts to get the tree lock.
316 // If it can't get the tree lock, then there is a pop-up active in the current tree.
317 // Any component can provide the tree lock.
318 ThreadStateChecker checker = new ThreadStateChecker(frames[0].getTreeLock());
319 try {
320 checker.start();
321 // wait a little bit for the checker to finish
322 if (checker.isAlive()) checker.join(100);
323 return checker.isAlive();
324 } catch (InterruptedException e) {
325 return false;
326 }
327 }
328
329 // Try to lock the AWT tree lock; returns immediately if it can
330 private static class ThreadStateChecker extends Thread {
331 private final Object lock;
332
333 public ThreadStateChecker(Object lock) {
334 super("Thread state checker");
335 setDaemon(true);
336 this.lock = lock;
337 }
338
339 @Override public synchronized void start() {
340 super.start();
341 try {
342 wait(30000);
343 } catch (InterruptedException e) {}
344 }
345
346 @Override public void run() {
347 synchronized (this) {
348 notifyAll();
349 }
350 synchronized (lock) {
351 setName(super.getName()); // dummy operation
352 }
353 }
354 }
355
356 private AWT() {}
357 }