001/* 002 * $Id: JXFrame.java 4158 2012-02-03 18:29:40Z kschaefe $ 003 * 004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021 022package org.jdesktop.swingx; 023 024import java.awt.AWTEvent; 025import java.awt.Component; 026import java.awt.Cursor; 027import java.awt.GraphicsConfiguration; 028import java.awt.Toolkit; 029import java.awt.event.AWTEventListener; 030import java.awt.event.ActionEvent; 031import java.awt.event.ActionListener; 032import java.awt.event.KeyEvent; 033import java.awt.event.KeyListener; 034 035import javax.swing.JButton; 036import javax.swing.JFrame; 037import javax.swing.JRootPane; 038import javax.swing.JToolBar; 039import javax.swing.Timer; 040 041import org.jdesktop.beans.JavaBean; 042import org.jdesktop.swingx.util.WindowUtils; 043 044/** 045 * <p> 046 * {@code JXFrame} is an enhanced {@link JFrame}. While {@code JXFrame} can 047 * replace any {@code JFrame}, it has features that make it particularly useful 048 * as the "main" frame for an application. 049 * </p> 050 * <h3>Additional Features</h3> 051 * <p> 052 * Root pane: {@code JXFrame} uses {@link JXRootPane} as its default root pane. 053 * The frame provide several convenience methods to provide easy access to the 054 * additional features. 055 * </p> 056 * <p> 057 * Idle: {@code JXFrame} offers an idle timer. Registering a 058 * {@link java.beans.PropertyChangeListener} for "idle" will notify when the 059 * user has not interacted with the JVM. A primary use for this type of 060 * functionality is to secure the application, blocking access and requiring the 061 * user to login again. 062 * </p> 063 * <p> 064 * Wait (busy) glass pane: The {@code JXFrame} can be configured with an 065 * alternate glass pane. Typically, this glass pane is used to notify the user 066 * that the application is busy, but the glass pane could be for any purpose. 067 * This secondary glass pane can be quickly enabled or disabled by 068 * {@linkplain #setWaitPaneVisible(boolean) setting the wait pane visible}. 069 * </p> 070 * 071 * @author unascribed from JDNC 072 */ 073@JavaBean 074@SuppressWarnings({ "nls", "serial" }) 075public class JXFrame extends JFrame { 076 /** 077 * An enumeration of {@link JXFrame} starting locations. 078 * 079 * @author unascribed from JDNC 080 */ 081 public enum StartPosition {CenterInScreen, CenterInParent, Manual} 082 083 private Component waitPane = null; 084 private Component glassPane = null; 085 private boolean waitPaneVisible = false; 086 private Cursor realCursor = null; 087 private boolean waitCursorVisible = false; 088 private boolean waiting = false; 089 private StartPosition startPosition; 090 private boolean hasBeenVisible = false; //startPosition is only used the first time the window is shown 091 private AWTEventListener keyEventListener; //for listening to KeyPreview events 092 private boolean keyPreview = false; 093 private AWTEventListener idleListener; //for listening to events. If no events happen for a specific amount of time, mark as idle 094 private Timer idleTimer; 095 private long idleThreshold = 0; 096 private boolean idle; 097 098 /** 099 * Creates a {@code JXFrame} with no title and standard closing behavior. 100 */ 101 public JXFrame() { 102 this(null, false); 103 } 104 105 /** 106 * Creates a {@code JXFrame} with the specified title and default closing 107 * behavior. 108 * 109 * @param title 110 * the frame title 111 */ 112 public JXFrame(String title) { 113 this(title, false); 114 } 115 116 /** 117 * Creates a <code>JXFrame</code> in the specified 118 * <code>GraphicsConfiguration</code> of 119 * a screen device, a blank title and default closing behaviour. 120 * <p> 121 * 122 * @param gc the <code>GraphicsConfiguration</code> that is used 123 * to construct the new <code>Frame</code>; 124 * if <code>gc</code> is <code>null</code>, the system 125 * default <code>GraphicsConfiguration</code> is assumed 126 * @exception IllegalArgumentException if <code>gc</code> is not from 127 * a screen device. This exception is always thrown when 128 * GraphicsEnvironment.isHeadless() returns true. 129 */ 130 public JXFrame(GraphicsConfiguration gc) { 131 this(null, gc, false); 132 } 133 134 135 /** 136 * Creates a <code>JXFrame</code> with the specified title, the 137 * specified <code>GraphicsConfiguration</code> of a screen device and 138 * default closing behaviour. 139 * <p> 140 * 141 * @param title the title to be displayed in the 142 * frame's border. A <code>null</code> value is treated as 143 * an empty string, "". 144 * @param gc the <code>GraphicsConfiguration</code> that is used 145 * to construct the new <code>JFrame</code> with; 146 * if <code>gc</code> is <code>null</code>, the system 147 * default <code>GraphicsConfiguration</code> is assumed 148 * @exception IllegalArgumentException if <code>gc</code> is not from 149 * a screen device. This exception is always thrown when 150 * GraphicsEnvironment.isHeadless() returns true. 151 */ 152 public JXFrame(String title, GraphicsConfiguration gc) { 153 this(title, gc, false); 154 } 155 156 /** 157 * Creates a {@code JXFrame} with the specified title and closing behavior. 158 * 159 * @param title 160 * the frame title 161 * @param exitOnClose 162 * {@code true} to override the default ({@link JFrame}) closing 163 * behavior and use {@link JFrame#EXIT_ON_CLOSE EXIT_ON_CLOSE} 164 * instead; {@code false} to use the default behavior 165 */ 166 public JXFrame(String title, boolean exitOnClose) { 167 this(title, null, exitOnClose); 168 } 169 170 /** 171 * Creates a {@code JXFrame} with the specified title, GraphicsConfiguration 172 * and closing behavior. 173 * 174 * @param title the frame title 175 * @param gc the <code>GraphicsConfiguration</code> of the target screen 176 * device. If <code>gc</code> is <code>null</code>, the system 177 * default <code>GraphicsConfiguration</code> is assumed. 178 * @param exitOnClose {@code true} to override the default ({@link JFrame}) 179 * closing behavior and use {@link JFrame#EXIT_ON_CLOSE 180 * EXIT_ON_CLOSE} instead; {@code false} to use the default behavior 181 * @exception IllegalArgumentException if <code>gc</code> is not from a 182 * screen device. 183 * 184 */ 185 public JXFrame(String title, GraphicsConfiguration gc, boolean exitOnClose) { 186 super(title, gc); 187 if (exitOnClose) { 188 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 189 } 190 191 //create the event handler for key preview functionality 192 keyEventListener = new AWTEventListener() { 193 @Override 194 public void eventDispatched(AWTEvent aWTEvent) { 195 if (aWTEvent instanceof KeyEvent) { 196 KeyEvent evt = (KeyEvent)aWTEvent; 197 for (KeyListener kl : getKeyListeners()) { 198 int id = aWTEvent.getID(); 199 switch (id) { 200 case KeyEvent.KEY_PRESSED: 201 kl.keyPressed(evt); 202 break; 203 case KeyEvent.KEY_RELEASED: 204 kl.keyReleased(evt); 205 break; 206 case KeyEvent.KEY_TYPED: 207 kl.keyTyped(evt); 208 break; 209 default: 210 System.err.println("Unhandled Key ID: " + id); 211 } 212 } 213 } 214 } 215 }; 216 217 idleTimer = new Timer(100, new ActionListener() { 218 @Override 219 public void actionPerformed(ActionEvent actionEvent) { 220 setIdle(true); 221 } 222 }); 223 224 //create the event handler for key preview functionality 225 idleListener = new AWTEventListener() { 226 @Override 227 public void eventDispatched(AWTEvent aWTEvent) { 228 //reset the timer 229 idleTimer.stop(); 230 //if the user is idle, then change to not idle 231 if (isIdle()) { 232 setIdle(false); 233 } 234 //start the timer 235 idleTimer.restart(); 236 } 237 }; 238 } 239 240 /** 241 * Sets the cancel button property on the underlying {@code JXRootPane}. 242 * 243 * @param button 244 * the {@code JButton} which is to be the cancel button 245 * @see #getCancelButton() 246 * @see JXRootPane#setCancelButton(JButton) 247 */ 248 public void setCancelButton(JButton button) { 249 getRootPaneExt().setCancelButton(button); 250 } 251 252 /** 253 * Returns the value of the cancel button property from the underlying 254 * {@code JXRootPane}. 255 * 256 * @return the {@code JButton} which is the cancel button 257 * @see #setCancelButton(JButton) 258 * @see JXRootPane#getCancelButton() 259 */ 260 public JButton getCancelButton() { 261 return getRootPaneExt().getCancelButton(); 262 } 263 264 /** 265 * Sets the default button property on the underlying {@code JRootPane}. 266 * 267 * @param button 268 * the {@code JButton} which is to be the default button 269 * @see #getDefaultButton() 270 * @see JXRootPane#setDefaultButton(JButton) 271 */ 272 public void setDefaultButton(JButton button) { 273 JButton old = getDefaultButton(); 274 getRootPane().setDefaultButton(button); 275 firePropertyChange("defaultButton", old, getDefaultButton()); 276 } 277 278 /** 279 * Returns the value of the default button property from the underlying 280 * {@code JRootPane}. 281 * 282 * @return the {@code JButton} which is the default button 283 * @see #setDefaultButton(JButton) 284 * @see JXRootPane#getDefaultButton() 285 */ 286 public JButton getDefaultButton() { 287 return getRootPane().getDefaultButton(); 288 } 289 290 /** 291 * If enabled the {@code KeyListener}s will receive a preview of the {@code 292 * KeyEvent} prior to normal viewing. 293 * 294 * @param flag {@code true} to enable previewing; {@code false} otherwise 295 * @see #getKeyPreview() 296 * @see #addKeyListener(KeyListener) 297 */ 298 public void setKeyPreview(boolean flag) { 299 Toolkit.getDefaultToolkit().removeAWTEventListener(keyEventListener); 300 if (flag) { 301 Toolkit.getDefaultToolkit().addAWTEventListener(keyEventListener, AWTEvent.KEY_EVENT_MASK); 302 } 303 boolean old = keyPreview; 304 keyPreview = flag; 305 firePropertyChange("keyPreview", old, keyPreview); 306 } 307 308 /** 309 * Returns the value for the key preview. 310 * 311 * @return if {@code true} previewing is enabled; otherwise it is not 312 * @see #setKeyPreview(boolean) 313 */ 314 public final boolean getKeyPreview() { 315 return keyPreview; 316 } 317 318 /** 319 * Sets the start position for this frame. Setting this value only has an 320 * effect is the frame has never been displayed. 321 * 322 * @param position 323 * the position to display the frame at 324 * @see #getStartPosition() 325 * @see #setVisible(boolean) 326 */ 327 public void setStartPosition(StartPosition position) { 328 StartPosition old = getStartPosition(); 329 this.startPosition = position; 330 firePropertyChange("startPosition", old, getStartPosition()); 331 } 332 333 /** 334 * Returns the start position for this frame. 335 * 336 * @return the start position of the frame 337 * @see #setStartPosition(StartPosition) 338 */ 339 public StartPosition getStartPosition() { 340 return startPosition == null ? StartPosition.Manual : startPosition; 341 } 342 343 /** 344 * Switches the display cursor to or from the wait cursor. 345 * 346 * @param flag 347 * {@code true} to enable the wait cursor; {@code false} to 348 * enable the previous cursor 349 * @see #isWaitCursorVisible() 350 * @see Cursor#WAIT_CURSOR 351 */ 352 public void setWaitCursorVisible(boolean flag) { 353 boolean old = isWaitCursorVisible(); 354 if (flag != old) { 355 waitCursorVisible = flag; 356 if (isWaitCursorVisible()) { 357 realCursor = getCursor(); 358 super.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); 359 } else { 360 super.setCursor(realCursor); 361 } 362 firePropertyChange("waitCursorVisible", old, isWaitCursorVisible()); 363 } 364 } 365 366 /** 367 * Returns the state of the wait cursor visibility. 368 * 369 * @return {@code true} if the current cursor is the wait cursor; {@code 370 * false} otherwise 371 */ 372 public boolean isWaitCursorVisible() { 373 return waitCursorVisible; 374 } 375 376 /** 377 * {@inheritDoc} 378 */ 379 @Override 380 public void setCursor(Cursor c) { 381 if (!isWaitCursorVisible()) { 382 super.setCursor(c); 383 } else { 384 this.realCursor = c; 385 } 386 } 387 388 /** 389 * Sets the component to use as a wait glass pane. This component is not 390 * part of the display hierarchy unless {@code isWaitPaneVisible() == true}. 391 * 392 * @param c 393 * the wait glass pane for this frame 394 * @see #getWaitPane() 395 * @see #setWaitPaneVisible(boolean) 396 */ 397 public void setWaitPane(Component c) { 398 Component old = getWaitPane(); 399 this.waitPane = c; 400 firePropertyChange("waitPane", old, getWaitPane()); 401 } 402 403 /** 404 * Returns the current wait pane for this frame. This component may or may 405 * not be part of the display hierarchy. 406 * 407 * @return the current wait pane 408 * @see #setWaitPane(Component) 409 */ 410 public Component getWaitPane() { 411 return waitPane; 412 } 413 414 /** 415 * Enabled or disabled the display of the normal or wait glass pane. If 416 * {@code true} the wait pane is be displayed. Altering this property alters 417 * the display hierarchy. 418 * 419 * @param flag 420 * {@code true} to display the wait glass pane; {@code false} to 421 * display the normal glass pane 422 * @see #isWaitPaneVisible() 423 * @see #setWaitPane(Component) 424 */ 425 public void setWaitPaneVisible(boolean flag) { 426 boolean old = isWaitPaneVisible(); 427 if (flag != old) { 428 this.waitPaneVisible = flag; 429 Component wp = getWaitPane(); 430 if (isWaitPaneVisible()) { 431 glassPane = getRootPane().getGlassPane(); 432 if (wp != null) { 433 getRootPane().setGlassPane(wp); 434 wp.setVisible(true); 435 } 436 } else { 437 if (wp != null) { 438 wp.setVisible(false); 439 } 440 getRootPane().setGlassPane(glassPane); 441 } 442 firePropertyChange("waitPaneVisible", old, isWaitPaneVisible()); 443 } 444 } 445 446 /** 447 * Returns the current visibility of the wait glass pane. 448 * 449 * @return {@code true} if the wait glass pane is visible; {@code false} 450 * otherwise 451 */ 452 public boolean isWaitPaneVisible() { 453 return waitPaneVisible; 454 } 455 456 /** 457 * Sets the frame into a wait state or restores the frame from a wait state. 458 * 459 * @param waiting 460 * {@code true} to place the frame in a wait state; {@code false} 461 * otherwise 462 * @see #isWaiting() 463 * @see #setWaitCursorVisible(boolean) 464 * @see #setWaitPaneVisible(boolean) 465 */ 466 public void setWaiting(boolean waiting) { 467 boolean old = isWaiting(); 468 this.waiting = waiting; 469 firePropertyChange("waiting", old, isWaiting()); 470 setWaitPaneVisible(waiting); 471 setWaitCursorVisible(waiting); 472 } 473 474 /** 475 * Determines if the frame is in a wait state or not. 476 * 477 * @return {@code true} if the frame is in the wait state; {@code false} 478 * otherwise 479 * @see #setWaiting(boolean) 480 */ 481 public boolean isWaiting() { 482 return waiting; 483 } 484 485 /** 486 * {@inheritDoc} 487 */ 488 @Override 489 public void setVisible(boolean visible) { 490 if (!hasBeenVisible && visible) { 491 //move to the proper start position 492 StartPosition pos = getStartPosition(); 493 switch (pos) { 494 case CenterInParent: 495 setLocationRelativeTo(getParent()); 496 break; 497 case CenterInScreen: 498 setLocation(WindowUtils.getPointForCentering(this)); 499 break; 500 case Manual: 501 default: 502 //nothing to do! 503 } 504 } 505 super.setVisible(visible); 506 } 507 508 public boolean isIdle() { 509 return idle; 510 } 511 512 /** 513 * Sets the frame into an idle state or restores the frame from an idle state. 514 * 515 * @param idle 516 * {@code true} to place the frame in an idle state; {@code false} 517 * otherwise 518 * @see #isIdle() 519 * @see #setIdleThreshold(long) 520 */ 521 public void setIdle(boolean idle) { 522 boolean old = isIdle(); 523 this.idle = idle; 524 firePropertyChange("idle", old, isIdle()); 525 } 526 527 /** 528 * Sets a threshold for user interaction before automatically placing the 529 * frame in an idle state. 530 * 531 * @param threshold 532 * the time (in milliseconds) to elapse before setting the frame 533 * idle 534 * @see #getIdleThreshold() 535 * @see #setIdle(boolean) 536 */ 537 public void setIdleThreshold(long threshold) { 538 long old = getIdleThreshold(); 539 this.idleThreshold = threshold; 540 firePropertyChange("idleThreshold", old, getIdleThreshold()); 541 542 threshold = getIdleThreshold(); // in case the getIdleThreshold method has been overridden 543 544 Toolkit.getDefaultToolkit().removeAWTEventListener(idleListener); 545 if (threshold > 0) { 546 Toolkit.getDefaultToolkit().addAWTEventListener(idleListener, AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_WHEEL_EVENT_MASK); 547 } 548 idleTimer.stop(); 549 idleTimer.setInitialDelay((int)threshold); 550 idleTimer.restart(); 551 } 552 553 /** 554 * Returns the amount of time that must elapse before the frame 555 * automatically enters an idle state. 556 * 557 * @return the time in milliseconds 558 */ 559 public long getIdleThreshold() { 560 return idleThreshold; 561 } 562 563 /** 564 * Sets the status bar property on the underlying {@code JXRootPane}. 565 * 566 * @param statusBar 567 * the {@code JXStatusBar} which is to be the status bar 568 * @see #getStatusBar() 569 * @see JXRootPane#setStatusBar(JXStatusBar) 570 */ 571 public void setStatusBar(JXStatusBar statusBar) { 572 getRootPaneExt().setStatusBar(statusBar); 573 } 574 575 /** 576 * Returns the value of the status bar property from the underlying 577 * {@code JXRootPane}. 578 * 579 * @return the {@code JXStatusBar} which is the current status bar 580 * @see #setStatusBar(JXStatusBar) 581 * @see JXRootPane#getStatusBar() 582 */ 583 public JXStatusBar getStatusBar() { 584 return getRootPaneExt().getStatusBar(); 585 } 586 587 /** 588 * Sets the tool bar property on the underlying {@code JXRootPane}. 589 * 590 * @param toolBar 591 * the {@code JToolBar} which is to be the tool bar 592 * @see #getToolBar() 593 * @see JXRootPane#setToolBar(JToolBar) 594 */ 595 public void setToolBar(JToolBar toolBar) { 596 getRootPaneExt().setToolBar(toolBar); 597 } 598 599 /** 600 * Returns the value of the tool bar property from the underlying 601 * {@code JXRootPane}. 602 * 603 * @return the {@code JToolBar} which is the current tool bar 604 * @see #setToolBar(JToolBar) 605 * @see JXRootPane#getToolBar() 606 */ 607 public JToolBar getToolBar() { 608 return getRootPaneExt().getToolBar(); 609 } 610 611 //---------------------------------------------------- Root Pane Methods 612 /** 613 * Overridden to create a JXRootPane. 614 */ 615 @Override 616 protected JRootPane createRootPane() { 617 return new JXRootPane(); 618 } 619 620 /** 621 * Overridden to make this public. 622 */ 623 @Override 624 public void setRootPane(JRootPane root) { 625 super.setRootPane(root); 626 } 627 628 /** 629 * Return the extended root pane. If this frame doesn't contain 630 * an extended root pane the root pane should be accessed with 631 * getRootPane(). 632 * 633 * @return the extended root pane or null. 634 */ 635 public JXRootPane getRootPaneExt() { 636 if (rootPane instanceof JXRootPane) { 637 return (JXRootPane)rootPane; 638 } 639 return null; 640 } 641} 642