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    }