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 }