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