001/* An actor which pops up a keystroke-sensing JFrame. 002 003 Copyright (c) 1998-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.actor.lib.gui; 029 030import java.awt.BorderLayout; 031import java.awt.Component; 032import java.awt.event.ActionEvent; 033import java.awt.event.ActionListener; 034import java.awt.event.KeyEvent; 035import java.awt.event.MouseEvent; 036import java.awt.event.MouseListener; 037 038import javax.swing.JComponent; 039import javax.swing.JFrame; 040import javax.swing.JLabel; 041import javax.swing.KeyStroke; 042 043import ptolemy.actor.Director; 044import ptolemy.actor.TypedAtomicActor; 045import ptolemy.actor.TypedIOPort; 046import ptolemy.actor.gui.Tableau; 047import ptolemy.data.IntToken; 048import ptolemy.data.type.BaseType; 049import ptolemy.kernel.CompositeEntity; 050import ptolemy.kernel.util.IllegalActionException; 051import ptolemy.kernel.util.NameDuplicationException; 052 053/////////////////////////////////////////////////////////////////// 054//// ArrowKeySensor 055 056/** 057 Detect when the user presses or releases an arrow key and produce an 058 integer on the corresponding output. 059 060 <p>When this actor is preinitialized, it pops up a new JFrame window on 061 the desktop, usually in the upper left hand corner of the screen. 062 When this JFrame has the focus (such as when it has been clicked on) 063 it is capable of sensing keystrokes. 064 065 <p>This actor senses only the four non-numeric-pad arrow-key 066 keystrokes. This actor responds to key releases as well as key 067 presses. Upon each key press, the integer 1 is broadcast from the 068 corresponding output. Upon each key release, the integer 0 is output. 069 070 <p>This actor contains a private inner class which generated the JFrame. 071 The frame sets up call-backs which react to the keystrokes. When called, 072 these call the director's fireAt() method with the current 073 time as argument. This causes the director to call fire() on the actor with 074 the first valid time equal to the current time or later. The actor then broadcasts 075 tokens from one or both outputs depending on which keystroke(s) have 076 occurred since the actor was last fired. On key pressed a one will be send, on key release 077 a zero will be send. 078 079 @author Winthrop Williams, Contributor: Bert Rodiers 080 @version $Id$ 081 @since Ptolemy II 2.0 082 @Pt.ProposedRating Red (winthrop) 083 @Pt.AcceptedRating Red (winthrop) 084 */ 085public class ArrowKeySensor extends TypedAtomicActor { 086 /** Construct an actor with the given container and name. 087 * @param container The container. 088 * @param name The name of this actor. 089 * @exception IllegalActionException If the actor cannot be contained 090 * by the proposed container. 091 * @exception NameDuplicationException If the container already has an 092 * actor with this name. 093 */ 094 public ArrowKeySensor(CompositeEntity container, String name) 095 throws NameDuplicationException, IllegalActionException { 096 super(container, name); 097 098 // Outputs 099 upArrow = new TypedIOPort(this, "upArrow"); 100 upArrow.setTypeEquals(BaseType.INT); 101 upArrow.setOutput(true); 102 103 leftArrow = new TypedIOPort(this, "leftArrow"); 104 leftArrow.setTypeEquals(BaseType.INT); 105 leftArrow.setOutput(true); 106 107 rightArrow = new TypedIOPort(this, "rightArrow"); 108 rightArrow.setTypeEquals(BaseType.INT); 109 rightArrow.setOutput(true); 110 111 downArrow = new TypedIOPort(this, "downArrow"); 112 downArrow.setTypeEquals(BaseType.INT); 113 downArrow.setOutput(true); 114 } 115 116 /////////////////////////////////////////////////////////////////// 117 //// ports and parameters //// 118 119 /** Output port, which has type IntToken. */ 120 public TypedIOPort upArrow; 121 122 /** Output port, which has type IntToken. */ 123 public TypedIOPort leftArrow; 124 125 /** Output port, which has type IntToken. */ 126 public TypedIOPort rightArrow; 127 128 /** Output port, which has type IntToken. */ 129 public TypedIOPort downArrow; 130 131 /////////////////////////////////////////////////////////////////// 132 //// public methods //// 133 134 /** Broadcast the integer value 1 for each key pressed and 0 for 135 * each released. 136 */ 137 @Override 138 public void fire() throws IllegalActionException { 139 super.fire(); 140 // Broadcast key presses. 141 if (_upKeyPressed) { 142 _upKeyPressed = false; 143 upArrow.broadcast(new IntToken(1)); 144 } 145 146 if (_leftKeyPressed) { 147 _leftKeyPressed = false; 148 leftArrow.broadcast(new IntToken(1)); 149 } 150 151 if (_rightKeyPressed) { 152 _rightKeyPressed = false; 153 rightArrow.broadcast(new IntToken(1)); 154 } 155 156 if (_downKeyPressed) { 157 _downKeyPressed = false; 158 downArrow.broadcast(new IntToken(1)); 159 } 160 161 // Broadcast key releases. 162 if (_upKeyReleased) { 163 _upKeyReleased = false; 164 upArrow.broadcast(new IntToken(0)); 165 } 166 167 if (_leftKeyReleased) { 168 _leftKeyReleased = false; 169 leftArrow.broadcast(new IntToken(0)); 170 } 171 172 if (_rightKeyReleased) { 173 _rightKeyReleased = false; 174 rightArrow.broadcast(new IntToken(0)); 175 } 176 177 if (_downKeyReleased) { 178 _downKeyReleased = false; 179 downArrow.broadcast(new IntToken(0)); 180 } 181 } 182 183 /** Create the JFrame window capable of detecting the key-presses. 184 * @exception IllegalActionException If a derived class throws it. 185 */ 186 @Override 187 public void initialize() throws IllegalActionException { 188 super.initialize(); 189 _myFrame = new MyFrame(); 190 } 191 192 /** Dispose of the JFrame, causing the window to vanish. 193 * @exception IllegalActionException Not thrown in this base class. 194 */ 195 @Override 196 public void wrapup() throws IllegalActionException { 197 super.wrapup(); 198 if (_myFrame != null) { 199 _myFrame.dispose(); 200 } 201 } 202 203 /////////////////////////////////////////////////////////////////// 204 //// protected variables //// 205 206 /** A flag indicating if the down arrow key has been pressed 207 * since the last firing of the actor. <i>Pressed</i> and 208 * <i>Released</i> are are not allowed to both be true for the 209 * same key (Though both may be false). The most recent action 210 * (press or release) takes precedence. 211 */ 212 protected boolean _downKeyPressed = false; 213 214 /** A flag indicating if the down arrow key has been released 215 * since the last firing of the actor. 216 */ 217 protected boolean _downKeyReleased = false; 218 219 /** A flag indicating if the left arrow key has been released 220 * since the last firing of the actor. 221 */ 222 protected boolean _leftKeyPressed = false; 223 224 /** A flag indicating if the left arrow key has been released 225 * since the last firing of the actor. 226 */ 227 protected boolean _leftKeyReleased = false; 228 229 /** A flag indicating if the right arrow key has been pressed 230 * since the last firing of the actor. 231 */ 232 protected boolean _rightKeyPressed = false; 233 234 /** A flag indicating if the right arrow key has been released 235 * since the last firing of the actor. 236 */ 237 protected boolean _rightKeyReleased = false; 238 239 /** A flag indicating if the up arrow key has been pressed 240 * since the last firing of the actor. 241 */ 242 protected boolean _upKeyPressed = false; 243 244 /** A flag indicating if the up arrow key has been released 245 * since the last firing of the actor. 246 */ 247 protected boolean _upKeyReleased = false; 248 249 /////////////////////////////////////////////////////////////////// 250 //// private variables //// 251 252 /** The JFrame that contains the arrow keys. */ 253 private MyFrame _myFrame; 254 255 /////////////////////////////////////////////////////////////////// 256 //// private inner classes //// 257 @SuppressWarnings("serial") 258 private class MyFrame extends JFrame { 259 /** Construct a frame. After constructing this, it is 260 * necessary to call setVisible(true) to make the frame 261 * appear. This is done by calling show() at the end of this 262 * constructor. 263 * @see Tableau#show() 264 */ 265 public MyFrame() { 266 // up-arrow call-backs 267 ActionListener myUpPressedListener = new ActionListenerExceptionCatcher( 268 new ActionListener() { 269 @Override 270 public void actionPerformed(ActionEvent e) { 271 _upKeyPressed = true; 272 _upKeyReleased = false; 273 _tryCallingFireAtFirstValidTime(); 274 } 275 }); 276 277 ActionListener myUpReleasedListener = new ActionListenerExceptionCatcher( 278 new ActionListener() { 279 @Override 280 public void actionPerformed(ActionEvent e) { 281 _upKeyReleased = true; 282 _upKeyPressed = false; 283 _tryCallingFireAtFirstValidTime(); 284 } 285 }); 286 287 // left-arrow call-backs 288 ActionListener myLeftPressedListener = new ActionListenerExceptionCatcher( 289 new ActionListener() { 290 @Override 291 public void actionPerformed(ActionEvent e) { 292 _leftKeyPressed = true; 293 _leftKeyReleased = false; 294 _tryCallingFireAtFirstValidTime(); 295 } 296 }); 297 298 ActionListener myLeftReleasedListener = new ActionListenerExceptionCatcher( 299 new ActionListener() { 300 @Override 301 public void actionPerformed(ActionEvent e) { 302 _leftKeyReleased = true; 303 _leftKeyPressed = false; 304 _tryCallingFireAtFirstValidTime(); 305 } 306 }); 307 308 // right-arrow call-backs 309 ActionListener myRightPressedListener = new ActionListenerExceptionCatcher( 310 new ActionListener() { 311 @Override 312 public void actionPerformed(ActionEvent e) { 313 _rightKeyPressed = true; 314 _rightKeyReleased = false; 315 _tryCallingFireAtFirstValidTime(); 316 } 317 }); 318 319 ActionListener myRightReleasedListener = new ActionListenerExceptionCatcher( 320 new ActionListener() { 321 @Override 322 public void actionPerformed(ActionEvent e) { 323 _rightKeyReleased = true; 324 _rightKeyPressed = false; 325 _tryCallingFireAtFirstValidTime(); 326 } 327 }); 328 329 // down-arrow call-backs 330 ActionListener myDownPressedListener = new ActionListenerExceptionCatcher( 331 new ActionListener() { 332 @Override 333 public void actionPerformed(ActionEvent e) { 334 _downKeyPressed = true; 335 _downKeyReleased = false; 336 _tryCallingFireAtFirstValidTime(); 337 } 338 }); 339 340 ActionListener myDownReleasedListener = new ActionListenerExceptionCatcher( 341 new ActionListener() { 342 @Override 343 public void actionPerformed(ActionEvent e) { 344 _downKeyReleased = true; 345 _downKeyPressed = false; 346 _tryCallingFireAtFirstValidTime(); 347 } 348 }); 349 350 getContentPane().setLayout(new BorderLayout()); 351 352 JLabel label = new JLabel("This window reads Arrow Key strokes. " 353 + "It must have the focus (be on top) to do this"); 354 getContentPane().add(label); 355 356 // As of jdk1.4, the .registerKeyboardAction() method below is 357 // considered obsolete. Docs recommend using these two methods: 358 // .getInputMap().put(aKeyStroke, aCommand); 359 // .getActionMap().put(aCommand, anAction); 360 // with the String aCommand inserted to link them together. 361 // See javax.swing.Jcomponent.registerKeyboardAction(). 362 // Registration of up-arrow call-backs. 363 label.registerKeyboardAction(myUpPressedListener, "UpPressed", 364 KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), 365 JComponent.WHEN_IN_FOCUSED_WINDOW); 366 367 label.registerKeyboardAction(myUpReleasedListener, "UpReleased", 368 KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), 369 JComponent.WHEN_IN_FOCUSED_WINDOW); 370 371 // Registration of left-arrow call-backs. 372 label.registerKeyboardAction(myLeftPressedListener, "LeftPressed", 373 KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), 374 JComponent.WHEN_IN_FOCUSED_WINDOW); 375 376 label.registerKeyboardAction(myLeftReleasedListener, "LeftReleased", 377 KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), 378 JComponent.WHEN_IN_FOCUSED_WINDOW); 379 380 // Registration of right-arrow call-backs. 381 label.registerKeyboardAction(myRightPressedListener, "RightPressed", 382 KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), 383 JComponent.WHEN_IN_FOCUSED_WINDOW); 384 385 label.registerKeyboardAction(myRightReleasedListener, 386 "RightReleased", 387 KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), 388 JComponent.WHEN_IN_FOCUSED_WINDOW); 389 390 // Registration of down-arrow call-backs. 391 label.registerKeyboardAction(myDownPressedListener, "DownPressed", 392 KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), 393 JComponent.WHEN_IN_FOCUSED_WINDOW); 394 395 label.registerKeyboardAction(myDownReleasedListener, "DownReleased", 396 KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), 397 JComponent.WHEN_IN_FOCUSED_WINDOW); 398 399 label.setRequestFocusEnabled(true); 400 label.addMouseListener(new FocusMouseListener()); 401 402 // Set the default size. 403 // Note that the location is of the frame, while the size 404 // is of the scrollpane. 405 pack(); 406 setVisible(true); 407 } 408 409 /* A mouse listener that requests focus for the source of any 410 * mouse event it receives. 411 */ 412 private/*static*/class FocusMouseListener implements MouseListener { 413 // FindBugs suggests making this class static so as to decrease 414 // the size of instances and avoid dangling references. 415 // However, Java does allow inner classes of inner classes to be 416 // static. 417 418 // This is a copy of diva/gui/toolbox/FocusMouseListener.java 419 // because we don't want the dependency on diva. 420 421 /** Invoked when the mouse is released. 422 * Ignored in this listener. 423 * @param event The corresponding event. 424 */ 425 @Override 426 public void mouseReleased(MouseEvent event) { 427 } 428 429 /** Invoked when the mouse enters a component. 430 * Ignored in this listener. 431 * @param event The corresponding event. 432 */ 433 @Override 434 public void mouseEntered(MouseEvent event) { 435 } 436 437 /** Invoked when the mouse exits a component. 438 * Ignored in this listener. 439 * @param event The corresponding event. 440 */ 441 @Override 442 public void mouseExited(MouseEvent event) { 443 } 444 445 /** 446 * Grab the keyboard focus when the component that this 447 * listener is attached to is clicked on. 448 * @param event The corresponding event. 449 */ 450 @Override 451 public void mousePressed(MouseEvent event) { 452 Component component = event.getComponent(); 453 454 if (!component.hasFocus()) { 455 component.requestFocus(); 456 } 457 } 458 459 /** Invoked when the mouse is clicked (pressed and released). 460 * Ignored in this listener. 461 * @param event The corresponding event. 462 */ 463 @Override 464 public void mouseClicked(MouseEvent event) { 465 } 466 } 467 468 /////////////////////////////////////////////////////////////////// 469 //// private methods //// 470 471 /** We schedule a fire as soon as possible. 472 */ 473 private void _tryCallingFireAtFirstValidTime() { 474 Director director = getDirector(); 475 try { 476 director.fireAt(ArrowKeySensor.this, director.getModelTime()); 477 } catch (IllegalActionException ex) { 478 throw new RuntimeException(ex); 479 } 480 } 481 } 482 483 private static class ActionListenerExceptionCatcher 484 implements ActionListener { 485 /////////////////////////////////////////////////////////////////// 486 //// public methods //// 487 488 /** Construct an instance that will wrap an ActionListener, 489 * catch its exceptions and report it to the Ptolemy 490 * Message Handler. 491 * @param actionListener The actionListener. 492 */ 493 public ActionListenerExceptionCatcher(ActionListener actionListener) { 494 _actionListener = actionListener; 495 } 496 497 @Override 498 public void actionPerformed(ActionEvent e) { 499 try { 500 _actionListener.actionPerformed(e); 501 } catch (Throwable ex) { 502 ptolemy.util.MessageHandler.error( 503 ptolemy.util.MessageHandler.shortDescription(ex), ex); 504 } 505 } 506 507 /////////////////////////////////////////////////////////////////// 508 //// private variables //// 509 510 // The runnable. 511 private ActionListener _actionListener; 512 } 513 514}