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