1 /*
   2  * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.awt.Component;
  25 import java.awt.Frame;
  26 import java.awt.Dialog;
  27 import java.awt.Window;
  28 import java.awt.Button;
  29 import java.awt.Point;
  30 import java.awt.Dimension;
  31 import java.awt.Rectangle;
  32 import java.awt.Robot;
  33 import java.awt.Toolkit;
  34 import java.awt.IllegalComponentStateException;
  35 import java.awt.AWTException;
  36 import java.awt.AWTEvent;
  37 
  38 import java.awt.event.InputEvent;
  39 import java.awt.event.WindowAdapter;
  40 import java.awt.event.WindowEvent;
  41 import java.awt.event.ActionEvent;
  42 import java.awt.event.FocusEvent;
  43 import java.awt.event.WindowListener;
  44 import java.awt.event.WindowFocusListener;
  45 import java.awt.event.FocusListener;
  46 import java.awt.event.ActionListener;
  47 
  48 import java.awt.peer.FramePeer;
  49 
  50 import java.lang.reflect.Constructor;
  51 import java.lang.reflect.Field;
  52 import java.lang.reflect.InvocationTargetException;
  53 import java.lang.reflect.Method;
  54 
  55 import java.security.PrivilegedAction;
  56 import java.security.AccessController;
  57 
  58 import java.util.concurrent.atomic.AtomicBoolean;
  59 
  60 /**
  61  * <p>This class contains utilities useful for regression testing.
  62  * <p>When using jtreg you would include this class into the build
  63  * list via something like:
  64  * <pre>
  65      &amp;library ../../../../share/lib/AWT_Mixing/src/regtesthelpers/
  66      &amp;build Util
  67      &amp;run main YourTest
  68    </pre>
  69  * Note that if you are about to create a test based on
  70  * Applet-template, then put those lines into html-file, not in java-file.
  71  * <p> And put an
  72  * import regtesthelpers.Util;
  73  * into the java source of test.
  74 */
  75 public final class Util {
  76     private Util() {} // this is a helper class with static methods :)
  77 
  78     /*
  79      * @throws RuntimeException when creation failed
  80      */
  81     public static Robot createRobot() {
  82         try {
  83             return new Robot();
  84         } catch (AWTException e) {
  85             throw new RuntimeException("Error: unable to create robot", e);
  86         }
  87     }
  88 
  89     public static Frame createEmbeddedFrame(final Frame embedder)
  90         throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException,
  91                InstantiationException, InvocationTargetException
  92     {
  93         Toolkit tk = Toolkit.getDefaultToolkit();
  94         FramePeer frame_peer = (FramePeer) embedder.getPeer();
  95         System.out.println("frame's peer = " + frame_peer);
  96         if ("sun.awt.windows.WToolkit".equals(tk.getClass().getName())) {
  97             Class comp_peer_class =
  98                 Class.forName("sun.awt.windows.WComponentPeer");
  99             System.out.println("comp peer class = " + comp_peer_class);
 100             Field hwnd_field = comp_peer_class.getDeclaredField("hwnd");
 101             hwnd_field.setAccessible(true);
 102             System.out.println("hwnd_field =" + hwnd_field);
 103             long hwnd = hwnd_field.getLong(frame_peer);
 104             System.out.println("hwnd = " + hwnd);
 105 
 106             Class clazz = Class.forName("sun.awt.windows.WEmbeddedFrame");
 107             Constructor constructor = clazz.getConstructor (new Class [] {Long.TYPE});
 108             return (Frame) constructor.newInstance (new Object[] {hwnd});
 109         } else if ("sun.awt.X11.XToolkit".equals(tk.getClass().getName())) {
 110             Class x_base_window_class = Class.forName("sun.awt.X11.XBaseWindow");
 111             System.out.println("x_base_window_class = " + x_base_window_class);
 112             Method get_window = x_base_window_class.getMethod("getWindow", new Class[0]);
 113             System.out.println("get_window = " + get_window);
 114             long window = (Long) get_window.invoke(frame_peer, new Object[0]);
 115             System.out.println("window = " + window);
 116             Class clazz = Class.forName("sun.awt.X11.XEmbeddedFrame");
 117             Constructor constructor = clazz.getConstructor (new Class [] {Long.TYPE, Boolean.TYPE});
 118             return (Frame) constructor.newInstance (new Object[] {window, true});
 119         }
 120 
 121         throw new RuntimeException("Unexpected toolkit - " + tk);
 122     }
 123 
 124     /**
 125      * Makes the window visible and waits until it's shown.
 126      */
 127     public static void showWindowWait(Window win) {
 128         win.setVisible(true);
 129         waitTillShown(win);
 130     }
 131 
 132     /**
 133      * Moves mouse pointer in the center of given {@code comp} component
 134      * using {@code robot} parameter.
 135      */
 136     public static void pointOnComp(final Component comp, final Robot robot) {
 137         Rectangle bounds = new Rectangle(comp.getLocationOnScreen(), comp.getSize());
 138         robot.mouseMove(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2);
 139     }
 140 
 141     /**
 142      * Moves mouse pointer in the center of a given {@code comp} component
 143      * and performs a left mouse button click using the {@code robot} parameter
 144      * with the {@code delay} delay between press and release.
 145      */
 146     public static void clickOnComp(final Component comp, final Robot robot, int delay) {
 147         pointOnComp(comp, robot);
 148         robot.delay(delay);
 149         robot.mousePress(InputEvent.BUTTON1_MASK);
 150         robot.delay(delay);
 151         robot.mouseRelease(InputEvent.BUTTON1_MASK);
 152     }
 153 
 154     /**
 155      * Moves mouse pointer in the center of a given {@code comp} component
 156      * and performs a left mouse button click using the {@code robot} parameter
 157      * with the default delay between press and release.
 158      */
 159     public static void clickOnComp(final Component comp, final Robot robot) {
 160         clickOnComp(comp, robot, 50);
 161     }
 162 
 163     /*
 164      * Clicks on a title of Frame/Dialog.
 165      * WARNING: it may fail on some platforms when the window is not wide enough.
 166      */
 167     public static void clickOnTitle(final Window decoratedWindow, final Robot robot) {
 168         Point p = decoratedWindow.getLocationOnScreen();
 169         Dimension d = decoratedWindow.getSize();
 170 
 171         if (decoratedWindow instanceof Frame || decoratedWindow instanceof Dialog) {
 172             robot.mouseMove(p.x + (int)(d.getWidth()/2), p.y + (int)decoratedWindow.getInsets().top/2);
 173             robot.delay(50);
 174             robot.mousePress(InputEvent.BUTTON1_MASK);
 175             robot.delay(50);
 176             robot.mouseRelease(InputEvent.BUTTON1_MASK);
 177         }
 178     }
 179 
 180     public static void waitForIdle(final Robot robot) {
 181         // we do not use robot for now, use SunToolkit.realSync() instead
 182         ((sun.awt.SunToolkit)Toolkit.getDefaultToolkit()).realSync();
 183     }
 184 
 185     public static Field getField(final Class klass, final String fieldName) {
 186         return AccessController.doPrivileged(new PrivilegedAction<Field>() {
 187             public Field run() {
 188                 try {
 189                     Field field = klass.getDeclaredField(fieldName);
 190                     assert (field != null);
 191                     field.setAccessible(true);
 192                     return field;
 193                 } catch (SecurityException se) {
 194                     throw new RuntimeException("Error: unexpected exception caught!", se);
 195                 } catch (NoSuchFieldException nsfe) {
 196                     throw new RuntimeException("Error: unexpected exception caught!", nsfe);
 197                 }
 198             }
 199         });
 200     }
 201 
 202     /*
 203      * Waits for a notification and for a boolean condition to become true.
 204      * The method returns when the above conditions are fullfilled or when the timeout
 205      * occurs.
 206      *
 207      * @param condition the object to be notified and the booelan condition to wait for
 208      * @param timeout the maximum time to wait in milliseconds
 209      * @param catchExceptions if {@code true} the method catches InterruptedException
 210      * @return the final boolean value of the {@code condition}
 211      * @throws InterruptedException if the awaiting proccess has been interrupted
 212      */
 213     public static boolean waitForConditionEx(final AtomicBoolean condition, long timeout)
 214       throws InterruptedException
 215         {
 216             synchronized (condition) {
 217                 long startTime = System.currentTimeMillis();
 218                 while (!condition.get()) {
 219                     condition.wait(timeout);
 220                     if (System.currentTimeMillis() - startTime >= timeout ) {
 221                         break;
 222                     }
 223                 }
 224             }
 225             return condition.get();
 226         }
 227 
 228     /*
 229      * The same as {@code waitForConditionEx(AtomicBoolean, long)} except that it
 230      * doesn't throw InterruptedException.
 231      */
 232     public static boolean waitForCondition(final AtomicBoolean condition, long timeout) {
 233         try {
 234             return waitForConditionEx(condition, timeout);
 235         } catch (InterruptedException e) {
 236             throw new RuntimeException("Error: unexpected exception caught!", e);
 237         }
 238     }
 239 
 240     /*
 241      * The same as {@code waitForConditionEx(AtomicBoolean, long)} but without a timeout.
 242      */
 243     public static void waitForConditionEx(final AtomicBoolean condition)
 244       throws InterruptedException
 245         {
 246             synchronized (condition) {
 247                 while (!condition.get()) {
 248                     condition.wait();
 249                 }
 250             }
 251         }
 252 
 253     /*
 254      * The same as {@code waitForConditionEx(AtomicBoolean)} except that it
 255      * doesn't throw InterruptedException.
 256      */
 257     public static void waitForCondition(final AtomicBoolean condition) {
 258         try {
 259             waitForConditionEx(condition);
 260         } catch (InterruptedException e) {
 261             throw new RuntimeException("Error: unexpected exception caught!", e);
 262         }
 263     }
 264 
 265     public static void waitTillShownEx(final Component comp) throws InterruptedException {
 266         while (true) {
 267             try {
 268                 Thread.sleep(100);
 269                 comp.getLocationOnScreen();
 270                 break;
 271             } catch (IllegalComponentStateException e) {}
 272         }
 273     }
 274     public static void waitTillShown(final Component comp) {
 275         try {
 276             waitTillShownEx(comp);
 277         } catch (InterruptedException e) {
 278             throw new RuntimeException("Error: unexpected exception caught!", e);
 279         }
 280     }
 281 
 282     /**
 283      * Drags from one point to another with the specified mouse button pressed.
 284      *
 285      * @param robot a robot to use for moving the mouse, etc.
 286      * @param startPoint a start point of the drag
 287      * @param endPoint an end point of the drag
 288      * @param button one of {@code InputEvent.BUTTON1_MASK},
 289      *     {@code InputEvent.BUTTON2_MASK}, {@code InputEvent.BUTTON3_MASK}
 290      *
 291      * @throws IllegalArgumentException if {@code button} is not one of
 292      *     {@code InputEvent.BUTTON1_MASK}, {@code InputEvent.BUTTON2_MASK},
 293      *     {@code InputEvent.BUTTON3_MASK}
 294      */
 295     public static void drag(Robot robot, Point startPoint, Point endPoint, int button) {
 296         if (!(button == InputEvent.BUTTON1_MASK || button == InputEvent.BUTTON2_MASK
 297                 || button == InputEvent.BUTTON3_MASK))
 298         {
 299             throw new IllegalArgumentException("invalid mouse button");
 300         }
 301 
 302         robot.mouseMove(startPoint.x, startPoint.y);
 303         robot.mousePress(button);
 304         try {
 305             mouseMove(robot, startPoint, endPoint);
 306         } finally {
 307             robot.mouseRelease(button);
 308         }
 309     }
 310 
 311     /**
 312      * Moves the mouse pointer from one point to another.
 313      * Uses Bresenham's algorithm.
 314      *
 315      * @param robot a robot to use for moving the mouse
 316      * @param startPoint a start point of the drag
 317      * @param endPoint an end point of the drag
 318      */
 319     public static void mouseMove(Robot robot, Point startPoint, Point endPoint) {
 320         int dx = endPoint.x - startPoint.x;
 321         int dy = endPoint.y - startPoint.y;
 322 
 323         int ax = Math.abs(dx) * 2;
 324         int ay = Math.abs(dy) * 2;
 325 
 326         int sx = signWOZero(dx);
 327         int sy = signWOZero(dy);
 328 
 329         int x = startPoint.x;
 330         int y = startPoint.y;
 331 
 332         int d = 0;
 333 
 334         if (ax > ay) {
 335             d = ay - ax/2;
 336             while (true){
 337                 robot.mouseMove(x, y);
 338                 robot.delay(50);
 339 
 340                 if (x == endPoint.x){
 341                     return;
 342                 }
 343                 if (d >= 0){
 344                     y = y + sy;
 345                     d = d - ax;
 346                 }
 347                 x = x + sx;
 348                 d = d + ay;
 349             }
 350         } else {
 351             d = ax - ay/2;
 352             while (true){
 353                 robot.mouseMove(x, y);
 354                 robot.delay(50);
 355 
 356                 if (y == endPoint.y){
 357                     return;
 358                 }
 359                 if (d >= 0){
 360                     x = x + sx;
 361                     d = d - ay;
 362                 }
 363                 y = y + sy;
 364                 d = d + ax;
 365             }
 366         }
 367     }
 368 
 369     private static int signWOZero(int i){
 370         return (i > 0)? 1: -1;
 371     }
 372 
 373     private static int sign(int n) {
 374         return n < 0 ? -1 : n == 0 ? 0 : 1;
 375     }
 376 
 377     /** Returns {@code WindowListener} instance that diposes {@code Window} on
 378      *  "window closing" event.
 379      *
 380      * @return    the {@code WindowListener} instance that could be set
 381      *            on a {@code Window}. After that
 382      *            the {@code Window} is disposed when "window closed"
 383      *            event is sent to the {@code Window}
 384      */
 385     public static WindowListener getClosingWindowAdapter() {
 386         return new WindowAdapter () {
 387             public void windowClosing(WindowEvent e) {
 388                 e.getWindow().dispose();
 389             }
 390         };
 391     }
 392 
 393     /*
 394      * The values directly map to the ones of
 395      * sun.awt.X11.XWM & sun.awt.motif.MToolkit classes.
 396      */
 397     public final static int
 398         UNDETERMINED_WM = 1,
 399         NO_WM = 2,
 400         OTHER_WM = 3,
 401         OPENLOOK_WM = 4,
 402         MOTIF_WM = 5,
 403         CDE_WM = 6,
 404         ENLIGHTEN_WM = 7,
 405         KDE2_WM = 8,
 406         SAWFISH_WM = 9,
 407         ICE_WM = 10,
 408         METACITY_WM = 11,
 409         COMPIZ_WM = 12,
 410         LG3D_WM = 13;
 411 
 412     /*
 413      * Returns -1 in case of not X Window or any problems.
 414      */
 415     public static int getWMID() {
 416         Class clazz = null;
 417         try {
 418             if ("sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
 419                 clazz = Class.forName("sun.awt.X11.XWM");
 420             } else if ("sun.awt.motif.MToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName())) {
 421                 clazz = Class.forName("sun.awt.motif.MToolkit");
 422             }
 423         } catch (ClassNotFoundException cnfe) {
 424             cnfe.printStackTrace();
 425         }
 426         if (clazz == null) {
 427             return -1;
 428         }
 429 
 430         try {
 431             final Class _clazz = clazz;
 432             Method m_getWMID = (Method)AccessController.doPrivileged(new PrivilegedAction() {
 433                     public Object run() {
 434                         try {
 435                             Method method = _clazz.getDeclaredMethod("getWMID", new Class[] {});
 436                             if (method != null) {
 437                                 method.setAccessible(true);
 438                             }
 439                             return method;
 440                         } catch (NoSuchMethodException e) {
 441                             assert false;
 442                         } catch (SecurityException e) {
 443                             assert false;
 444                         }
 445                         return null;
 446                     }
 447                 });
 448             return ((Integer)m_getWMID.invoke(null, new Object[] {})).intValue();
 449         } catch (IllegalAccessException iae) {
 450             iae.printStackTrace();
 451         } catch (InvocationTargetException ite) {
 452             ite.printStackTrace();
 453         }
 454         return -1;
 455     }
 456 
 457 
 458     ////////////////////////////
 459     // Some stuff to test focus.
 460     ////////////////////////////
 461 
 462     private static WindowGainedFocusListener wgfListener = new WindowGainedFocusListener();
 463     private static FocusGainedListener fgListener = new FocusGainedListener();
 464     private static ActionPerformedListener apListener = new ActionPerformedListener();
 465 
 466     private abstract static class EventListener {
 467         AtomicBoolean notifier = new AtomicBoolean(false);
 468         Component comp;
 469         boolean printEvent;
 470 
 471         public void listen(Component comp, boolean printEvent) {
 472             this.comp = comp;
 473             this.printEvent = printEvent;
 474             notifier.set(false);
 475             setListener(comp);
 476         }
 477 
 478         public AtomicBoolean getNotifier() {
 479             return notifier;
 480         }
 481 
 482         abstract void setListener(Component comp);
 483 
 484         void printAndNotify(AWTEvent e) {
 485             if (printEvent) {
 486                 System.err.println(e);
 487             }
 488             synchronized (notifier) {
 489                 notifier.set(true);
 490                 notifier.notifyAll();
 491             }
 492         }
 493     }
 494 
 495     private static class WindowGainedFocusListener extends EventListener implements WindowFocusListener {
 496 
 497         void setListener(Component comp) {
 498             ((Window)comp).addWindowFocusListener(this);
 499         }
 500 
 501         public void windowGainedFocus(WindowEvent e) {
 502 
 503             ((Window)comp).removeWindowFocusListener(this);
 504             printAndNotify(e);
 505         }
 506 
 507         public void windowLostFocus(WindowEvent e) {}
 508     }
 509 
 510     private static class FocusGainedListener extends EventListener implements FocusListener {
 511 
 512         void setListener(Component comp) {
 513             comp.addFocusListener(this);
 514         }
 515 
 516         public void focusGained(FocusEvent e) {
 517             comp.removeFocusListener(this);
 518             printAndNotify(e);
 519         }
 520 
 521         public void focusLost(FocusEvent e) {}
 522     }
 523 
 524     private static class ActionPerformedListener extends EventListener implements ActionListener {
 525 
 526         void setListener(Component comp) {
 527             ((Button)comp).addActionListener(this);
 528         }
 529 
 530         public void actionPerformed(ActionEvent e) {
 531             ((Button)comp).removeActionListener(this);
 532             printAndNotify(e);
 533         }
 534     }
 535 
 536     private static boolean trackEvent(int eventID, Component comp, Runnable action, int time, boolean printEvent) {
 537         EventListener listener = null;
 538 
 539         switch (eventID) {
 540         case WindowEvent.WINDOW_GAINED_FOCUS:
 541             listener = wgfListener;
 542             break;
 543         case FocusEvent.FOCUS_GAINED:
 544             listener = fgListener;
 545             break;
 546         case ActionEvent.ACTION_PERFORMED:
 547             listener = apListener;
 548             break;
 549         }
 550 
 551         listener.listen(comp, printEvent);
 552         action.run();
 553         return Util.waitForCondition(listener.getNotifier(), time);
 554     }
 555 
 556     /*
 557      * Tracks WINDOW_GAINED_FOCUS event for a window caused by an action.
 558      * @param window the window to track the event for
 559      * @param action the action to perform
 560      * @param time the max time to wait for the event
 561      * @param printEvent should the event received be printed or doesn't
 562      * @return true if the event has been received, otherwise false
 563      */
 564     public static boolean trackWindowGainedFocus(Window window, Runnable action, int time, boolean printEvent) {
 565         return trackEvent(WindowEvent.WINDOW_GAINED_FOCUS, window, action, time, printEvent);
 566     }
 567 
 568     /*
 569      * Tracks FOCUS_GAINED event for a component caused by an action.
 570      * @see #trackWindowGainedFocus
 571      */
 572     public static boolean trackFocusGained(Component comp, Runnable action, int time, boolean printEvent) {
 573         return trackEvent(FocusEvent.FOCUS_GAINED, comp, action, time, printEvent);
 574     }
 575 
 576     /*
 577      * Tracks ACTION_PERFORMED event for a button caused by an action.
 578      * @see #trackWindowGainedFocus
 579      */
 580     public static boolean trackActionPerformed(Button button, Runnable action, int time, boolean printEvent) {
 581         return trackEvent(ActionEvent.ACTION_PERFORMED, button, action, time, printEvent);
 582     }
 583 
 584     /*
 585      * Requests focus on the component provided and waits for the result.
 586      * @return true if the component has been focused, false otherwise.
 587      */
 588     public static boolean focusComponent(Component comp, int time) {
 589         return focusComponent(comp, time, false);
 590     }
 591     public static boolean focusComponent(final Component comp, int time, boolean printEvent) {
 592         return trackFocusGained(comp,
 593                                 new Runnable() {
 594                                     public void run() {
 595                                         comp.requestFocus();
 596                                     }
 597                                 },
 598                                 time, printEvent);
 599 
 600     }
 601 }