1 /*
   2  * Copyright 2003-2007 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  20  * CA 95054 USA or visit www.sun.com if you need additional information or
  21  * have any questions.
  22  */
  23 
  24 /*
  25 test
  26 @bug 4799136
  27 @summary Tests that type-ahead for dialog works and doesn't block program
  28 @author  area=awt.focus
  29 @run applet TestDialogTypeAhead.html
  30 */
  31 
  32 // Note there is no @ in front of test above.  This is so that the
  33 //  harness will not mistake this file as a test file.  It should
  34 //  only see the html file as a test file. (the harness runs all
  35 //  valid test files, so it would run this test twice if this file
  36 //  were valid as well as the html file.)
  37 // Also, note the area= after Your Name in the author tag.  Here, you
  38 //  should put which functional area the test falls in.  See the
  39 //  AWT-core home page -> test areas and/or -> AWT team  for a list of
  40 //  areas.
  41 // Note also the 'TestDialogTypeAhead.html' in the run tag.  This should
  42 //  be changed to the name of the test.
  43 
  44 
  45 /**
  46  * TestDialogTypeAhead.java
  47  *
  48  * summary:
  49  */
  50 
  51 import java.applet.Applet;
  52 import java.awt.*;
  53 import java.lang.reflect.InvocationTargetException;
  54 import java.awt.event.*;
  55 import java.awt.peer.DialogPeer;
  56 import java.awt.peer.ComponentPeer;
  57 import java.lang.reflect.Method;
  58 import java.lang.reflect.Proxy;
  59 import java.lang.reflect.InvocationHandler;
  60 import java.lang.reflect.InvocationTargetException;
  61 import test.java.awt.regtesthelpers.Util;
  62 
  63 //Automated tests should run as applet tests if possible because they
  64 // get their environments cleaned up, including AWT threads, any
  65 // test created threads, and any system resources used by the test
  66 // such as file descriptors.  (This is normally not a problem as
  67 // main tests usually run in a separate VM, however on some platforms
  68 // such as the Mac, separate VMs are not possible and non-applet
  69 // tests will cause problems).  Also, you don't have to worry about
  70 // synchronisation stuff in Applet tests they way you do in main
  71 // tests...
  72 
  73 
  74 public class TestDialogTypeAhead extends Applet
  75 {
  76     //Declare things used in the test, like buttons and labels here
  77     static Frame f;
  78     static Button b;
  79     static Dialog d;
  80     static Button ok;
  81     static Semaphore pressSema = new Semaphore();
  82     static Semaphore robotSema = new Semaphore();
  83     static volatile boolean gotFocus = false;
  84     static Robot robot;
  85     public void init()
  86     {
  87         //Create instructions for the user here, as well as set up
  88         // the environment -- set the layout manager, add buttons,
  89         // etc.
  90 
  91         Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
  92                 public void eventDispatched(AWTEvent e) {
  93                     System.err.println(e.toString());
  94                 }
  95             }, AWTEvent.KEY_EVENT_MASK);
  96 
  97         this.setLayout (new BorderLayout ());
  98 
  99         f = new Frame("frame");
 100         b = new Button("press");
 101         d = new TestDialog(f, "dialog", true, robotSema);
 102         ok = new Button("ok");
 103         d.add(ok);
 104         d.pack();
 105 
 106         ok.addKeyListener(new KeyAdapter() {
 107                 public void keyPressed(KeyEvent e) {
 108                     System.err.println("OK pressed");
 109                     d.dispose();
 110                     f.dispose();
 111                     // Typed-ahead key events should only be accepted if
 112                     // they arrive after FOCUS_GAINED
 113                     if (gotFocus) {
 114                         pressSema.raise();
 115                     }
 116                 }
 117             });
 118         ok.addFocusListener(new FocusAdapter() {
 119                 public void focusGained(FocusEvent e) {
 120                     gotFocus = true;
 121                     System.err.println("Ok got focus");
 122                 }
 123             });
 124         f.add(b);
 125         f.pack();
 126         b.addActionListener(new ActionListener() {
 127                 public void actionPerformed(ActionEvent e) {
 128                     System.err.println("B pressed");
 129 
 130                     EventQueue.invokeLater(new Runnable() {
 131                             public void run() {
 132                                 waitTillShown(d);
 133                                 TestDialogTypeAhead.this.d.toFront();
 134                                 TestDialogTypeAhead.this.moveMouseOver(d);
 135                             }
 136                         });
 137 
 138                     d.setVisible(true);
 139                 }
 140             });
 141 
 142     }//End  init()
 143 
 144     public void start ()
 145     {
 146         //Get things going.  Request focus, set size, et cetera
 147         setSize (200,200);
 148         setVisible(true);
 149         validate();
 150         try {
 151             robot = new Robot();
 152         } catch (Exception e) {
 153             throw new RuntimeException("Can't create robot:" + e);
 154         }
 155 
 156         f.setVisible(true);
 157         waitTillShown(b);
 158         System.err.println("b is shown");
 159         f.toFront();
 160         moveMouseOver(f);
 161         waitForIdle();
 162         makeFocused(b);
 163         waitForIdle();
 164         System.err.println("b is focused");
 165 
 166         robot.keyPress(KeyEvent.VK_SPACE);
 167         robot.keyRelease(KeyEvent.VK_SPACE);
 168         try {
 169             robotSema.doWait(1000);
 170         } catch (InterruptedException ie) {
 171             throw new RuntimeException("Interrupted!");
 172         }
 173         robot.keyPress(KeyEvent.VK_SPACE);
 174         robot.keyRelease(KeyEvent.VK_SPACE);
 175         waitForIdle();
 176         try {
 177             pressSema.doWait(3000);
 178         } catch (InterruptedException ie) {
 179             throw new RuntimeException("Interrupted!");
 180         }
 181         if (!pressSema.getState()) {
 182             throw new RuntimeException("Type-ahead doesn't work");
 183         }
 184 
 185     }// start()
 186 
 187     private void moveMouseOver(Container c) {
 188         Point p = c.getLocationOnScreen();
 189         Dimension d = c.getSize();
 190         robot.mouseMove(p.x + (int)(d.getWidth()/2), p.y + (int)(d.getHeight()/2));
 191     }
 192     private void waitForIdle() {
 193         try {
 194             Toolkit.getDefaultToolkit().sync();
 195             sun.awt.SunToolkit.flushPendingEvents();
 196             EventQueue.invokeAndWait( new Runnable() {
 197                                             public void run() {
 198                                                 // dummy implementation
 199                                             }
 200                                         } );
 201         } catch(InterruptedException ite) {
 202             System.err.println("Robot.waitForIdle, non-fatal exception caught:");
 203             ite.printStackTrace();
 204         } catch(InvocationTargetException ine) {
 205             System.err.println("Robot.waitForIdle, non-fatal exception caught:");
 206             ine.printStackTrace();
 207         }
 208     }
 209 
 210     private void waitTillShown(Component c) {
 211         while (true) {
 212             try {
 213                 Thread.sleep(100);
 214                 c.getLocationOnScreen();
 215                 break;
 216             } catch (InterruptedException ie) {
 217                 ie.printStackTrace();
 218                 break;
 219             } catch (Exception e) {
 220             }
 221         }
 222     }
 223     private void makeFocused(Component comp) {
 224         if (comp.isFocusOwner()) {
 225             return;
 226         }
 227         final Semaphore sema = new Semaphore();
 228         final FocusAdapter fa = new FocusAdapter() {
 229                 public void focusGained(FocusEvent fe) {
 230                     sema.raise();
 231                 }
 232             };
 233         comp.addFocusListener(fa);
 234         comp.requestFocusInWindow();
 235         if (comp.isFocusOwner()) {
 236             return;
 237         }
 238         try {
 239             sema.doWait(3000);
 240         } catch (InterruptedException ie) {
 241             ie.printStackTrace();
 242         }
 243         comp.removeFocusListener(fa);
 244         if (!comp.isFocusOwner()) {
 245             throw new RuntimeException("Can't make " + comp + " focused, current owner is " + KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner());
 246         }
 247     }
 248 
 249     static class Semaphore {
 250         boolean state = false;
 251         int waiting = 0;
 252         public Semaphore() {
 253         }
 254         public synchronized void doWait() throws InterruptedException {
 255             if (state) {
 256                 return;
 257             }
 258             waiting++;
 259             wait();
 260             waiting--;
 261         }
 262         public synchronized void doWait(int timeout) throws InterruptedException {
 263             if (state) {
 264                 return;
 265             }
 266             waiting++;
 267             wait(timeout);
 268             waiting--;
 269         }
 270         public synchronized void raise() {
 271             state = true;
 272             if (waiting > 0) {
 273                 notifyAll();
 274             }
 275         }
 276         public synchronized boolean getState() {
 277             return state;
 278         }
 279     }
 280 
 281     // Fix for 6446952.
 282     // In the process of showing the dialog we have to catch peer.show() call
 283     // so that to trigger key events just before it gets invoked.
 284     // We base on the fact that a modal dialog sets type-ahead markers
 285     // before it calls 'show' on the peer.
 286     // Posting the key events before dialog.setVisible(true) would be actually not
 287     // good because it would be Ok to dispatch them to the current focus owner,
 288     // not to the dialog.
 289     class TestDialog extends Dialog {
 290         ComponentPeer origDialogPeer;
 291         ComponentPeer proxyInstPeer;
 292         Semaphore trigger;
 293 
 294         TestDialog(Frame owner, String title, boolean modal, Semaphore trigger) {
 295             super(owner, title, modal);
 296             this.trigger = trigger;
 297         }
 298         public ComponentPeer getPeer() {
 299             ComponentPeer ret = super.getPeer();
 300             if (ret == proxyInstPeer) {
 301                 return origDialogPeer;
 302             } else {
 303                 return ret;
 304             }
 305         }
 306 
 307         public void addNotify() {
 308             super.addNotify();
 309             replacePeer();
 310         }
 311 
 312         void replacePeer() {
 313             origDialogPeer = getPeer();
 314 
 315             InvocationHandler handler = new InvocationHandler() {
 316                     public Object invoke(Object proxy, Method method, Object[] args) {
 317                         if (method.getName() == "show") {
 318                             trigger.raise();
 319                         }
 320 
 321                         Object ret = null;
 322                         try {
 323                             ret = method.invoke(origDialogPeer, args);
 324                         } catch (IllegalAccessException iae) {
 325                             throw new Error("Test error.", iae);
 326                         } catch (InvocationTargetException ita) {
 327                             throw new Error("Test error.", ita);
 328                         }
 329                         return ret;
 330                     }
 331                 };
 332 
 333             proxyInstPeer = (DialogPeer)Proxy.newProxyInstance(
 334                 DialogPeer.class.getClassLoader(), new Class[] {DialogPeer.class}, handler);
 335 
 336             try {
 337                 Util.getField(Component.class, "peer").set(d, proxyInstPeer);
 338             } catch (IllegalAccessException iae) {
 339                 throw new Error("Test error.", iae);
 340             }
 341         }
 342     }
 343 }// class TestDialogTypeAhead
 344 
 345 
 346 /****************************************************
 347  Standard Test Machinery
 348  DO NOT modify anything below -- it's a standard
 349   chunk of code whose purpose is to make user
 350   interaction uniform, and thereby make it simpler
 351   to read and understand someone else's test.
 352  ****************************************************/
 353 
 354 /**
 355  This is part of the standard test machinery.
 356  It creates a dialog (with the instructions), and is the interface
 357   for sending text messages to the user.
 358  To print the instructions, send an array of strings to Sysout.createDialog
 359   WithInstructions method.  Put one line of instructions per array entry.
 360  To display a message for the tester to see, simply call Sysout.println
 361   with the string to be displayed.
 362  This mimics System.out.println but works within the test harness as well
 363   as standalone.
 364  */
 365 
 366 class Sysout
 367 {
 368     private static TestDialog dialog;
 369 
 370     public static void createDialogWithInstructions( String[] instructions )
 371     {
 372         dialog = new TestDialog( new Frame(), "Instructions" );
 373         dialog.printInstructions( instructions );
 374         dialog.setVisible(true);
 375         println( "Any messages for the tester will display here." );
 376     }
 377 
 378     public static void createDialog( )
 379     {
 380         dialog = new TestDialog( new Frame(), "Instructions" );
 381         String[] defInstr = { "Instructions will appear here. ", "" } ;
 382         dialog.printInstructions( defInstr );
 383         dialog.setVisible(true);
 384         println( "Any messages for the tester will display here." );
 385     }
 386 
 387 
 388     public static void printInstructions( String[] instructions )
 389     {
 390         dialog.printInstructions( instructions );
 391     }
 392 
 393 
 394     public static void println( String messageIn )
 395     {
 396         dialog.displayMessage( messageIn );
 397     }
 398 
 399 }// Sysout  class
 400 
 401 /**
 402   This is part of the standard test machinery.  It provides a place for the
 403    test instructions to be displayed, and a place for interactive messages
 404    to the user to be displayed.
 405   To have the test instructions displayed, see Sysout.
 406   To have a message to the user be displayed, see Sysout.
 407   Do not call anything in this dialog directly.
 408   */
 409 class TestDialog extends Dialog
 410 {
 411 
 412     TextArea instructionsText;
 413     TextArea messageText;
 414     int maxStringLength = 80;
 415 
 416     //DO NOT call this directly, go through Sysout
 417     public TestDialog( Frame frame, String name )
 418     {
 419         super( frame, name );
 420         int scrollBoth = TextArea.SCROLLBARS_BOTH;
 421         instructionsText = new TextArea( "", 15, maxStringLength, scrollBoth );
 422         add( "North", instructionsText );
 423 
 424         messageText = new TextArea( "", 5, maxStringLength, scrollBoth );
 425         add("Center", messageText);
 426 
 427         pack();
 428 
 429         show();
 430     }// TestDialog()
 431 
 432     //DO NOT call this directly, go through Sysout
 433     public void printInstructions( String[] instructions )
 434     {
 435         //Clear out any current instructions
 436         instructionsText.setText( "" );
 437 
 438         //Go down array of instruction strings
 439 
 440         String printStr, remainingStr;
 441         for( int i=0; i < instructions.length; i++ )
 442         {
 443             //chop up each into pieces maxSringLength long
 444             remainingStr = instructions[ i ];
 445             while( remainingStr.length() > 0 )
 446             {
 447                 //if longer than max then chop off first max chars to print
 448                 if( remainingStr.length() >= maxStringLength )
 449                 {
 450                     //Try to chop on a word boundary
 451                     int posOfSpace = remainingStr.
 452                         lastIndexOf( ' ', maxStringLength - 1 );
 453 
 454                     if( posOfSpace <= 0 ) posOfSpace = maxStringLength - 1;
 455 
 456                     printStr = remainingStr.substring( 0, posOfSpace + 1 );
 457                     remainingStr = remainingStr.substring( posOfSpace + 1 );
 458                 }
 459                 //else just print
 460                 else
 461                 {
 462                     printStr = remainingStr;
 463                     remainingStr = "";
 464                 }
 465 
 466                 instructionsText.append( printStr + "\n" );
 467 
 468             }// while
 469 
 470         }// for
 471 
 472     }//printInstructions()
 473 
 474     //DO NOT call this directly, go through Sysout
 475     public void displayMessage( String messageIn )
 476     {
 477         messageText.append( messageIn + "\n" );
 478         System.out.println(messageIn);
 479     }
 480 
 481 }// TestDialog  class