
org.jdesktop.application.SingleFrameApplication Maven / Gradle / Ivy
Show all versions of bsaf Show documentation
/*
* Copyright (C) 2006 Sun Microsystems, Inc. All rights reserved.
* Copyright (C) 2010 Illya Yalovyy. All rights reserved.
* Use is subject to license terms.
*/
package org.jdesktop.application;
import org.jdesktop.application.utils.SwingHelper;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* An application base class for simple GUIs with one primary JFrame.
*
* This class takes care of component property injection, exit processing,
* and saving/restoring session state in a way that's appropriate for
* simple single-frame applications. The application's JFrame is created
* automatically, with a WindowListener that calls exit() when the
* window is closed. Session state is stored when the application
* shuts down, and restored when the GUI is shown.
*
* To use {@code SingleFrameApplication}, one need only override
* {@code startup}, create the GUI's main panel, and apply
* {@code show} to that. Here's an example:
*
*class MyApplication extends SingleFrameApplication {
* @Override protected void startup() {
* show(new JLabel("Hello World"));
* }
*}
*
* The call to {@code show} in this example creates a JFrame (named
* "mainFrame"), that contains the "Hello World" JLabel. Before the
* frame is made visible, the properties of all of the components in
* the hierarchy are initialized with
* {@link ResourceMap#injectComponents ResourceMap.injectComponents}
* and then restored from saved session state (if any) with
* {@link SessionStorage#restore SessionStorage.restore}.
* When the application shuts down, session state is saved.
*
* A more realistic tiny example would rely on a ResourceBundle for
* the JLabel's string and the main frame's title. The automatic
* injection step only initializes the properties of named
* components, so:
*
* class MyApplication extends SingleFrameApplication {
* @Override protected void startup() {
* JLabel label = new JLabel();
* label.setName("label");
* show(label);
* }
* }
*
* The ResourceBundle should contain definitions for all of the
* standard Application resources, as well the main frame's title
* and the label's text. Note that the JFrame that's implicitly
* created by the {@code show} method is named "mainFrame".
*
* # resources/MyApplication.properties
* Application.id = MyApplication
* Application.title = My Hello World Application
* Application.version = 1.0
* Application.vendor = Illya Yalovyy
* Application.vendorId = Etf
* Application.homepage = http://kenai.com/projects/bsaf
* Application.description = An example of SingleFrameApplication
* Application.lookAndFeel = system
* Application.icon=app_icon.png
*
* mainFrame.title = ${Application.title} ${Application.version}
* label.text = Hello World
*
*/
public abstract class SingleFrameApplication extends Application {
private static final String INITIALIZATION_MARKER="SingleFrameApplication.initRootPaneContainer";
private static final Logger logger = Logger.getLogger(SingleFrameApplication.class.getName());
/**
* Return the JFrame used to show this application.
*
* The frame's name is set to "mainFrame", its title is
* initialized with the value of the {@code Application.title}
* resource and a {@code WindowListener} is added that calls
* {@code exit} when the user attempts to close the frame.
*
*
* This method may be called at any time; the JFrame is created lazily
* and cached. For example:
*
* protected void startup() {
* getMainFrame().setJMenuBar(createMenuBar());
* show(createMainPanel());
* }
*
*
* @return this application's main frame
* @see #setMainFrame
* @see #show
* @see JFrame#setName
* @see JFrame#setTitle
* @see JFrame#addWindowListener
*/
public final JFrame getMainFrame() {
return getMainView().getFrame();
}
/**
* Sets the JFrame use to show this application.
*
* This method should be called from the startup method by a
* subclass that wants to construct and initialize the main frame
* itself. Most applications can rely on the fact that {code
* getMainFrame} lazily constructs the main frame and initializes
* the {@code mainFrame} property.
*
* If the main frame property was already initialized, either
* implicitly through a call to {@code getMainFrame} or by
* explicitly calling this method, an IllegalStateException is
* thrown. If {@code mainFrame} is null, an IllegalArgumentException
* is thrown.
*
* This property is bound.
*
* @param mainFrame the new value of the mainFrame property
* @see #getMainFrame
*/
protected final void setMainFrame(JFrame mainFrame) {
getMainView().setFrame(mainFrame);
}
private String sessionFilename(Window window) {
if (window == null) {
return null;
} else {
String name = window.getName();
return (name == null) ? null : name + ".session.xml";
}
}
/**
* Initialize the hierarchy with the specified root by
* injecting resources.
*
* By default the {@code show} methods
* {@link ResourceMap#injectComponents inject resources} before
* initializing the JFrame or JDialog's size, location,
* and restoring the window's session state. If the application
* is showing a window whose resources have already been injected,
* or that shouldn't be initialized via resource injection,
* this method can be overridden to defeat the default
* behavior.
*
* @param root the root of the component hierarchy
* @see ResourceMap#injectComponents
* @see #show(JComponent)
* @see #show(JFrame)
* @see #show(JDialog)
*/
protected void configureWindow(Window root) {
getContext().getResourceMap().injectComponents(root);
}
private void initRootPaneContainer(RootPaneContainer c) {
JComponent rootPane = c.getRootPane();
// These initializations are only done once
if (rootPane.getClientProperty(INITIALIZATION_MARKER) != null) {
return;
}
rootPane.putClientProperty(INITIALIZATION_MARKER, Boolean.TRUE);
// Inject resources
Container root = rootPane.getParent();
if (root instanceof Window) {
configureWindow((Window) root);
}
// If this is the mainFrame, then close == exit
JFrame mainFrame = getMainFrame();
if (c == mainFrame) {
mainFrame.addWindowListener(new MainFrameListener());
mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
} else if (root instanceof Window) { // close == save session state
Window window = (Window) root;
window.addHierarchyListener(new SecondaryWindowListener());
}
// If this is a JFrame monitor "normal" (not maximized) bounds
if (root instanceof JFrame) {
root.addComponentListener(new FrameBoundsListener());
}
if (root instanceof Window) {
Window window = (Window) root;
// If the window's bounds don't appear to have been set, do it
if (!root.isValid() || (root.getWidth() == 0) || (root.getHeight() == 0)) {
window.pack();
}
// Restore session state
String filename = sessionFilename((Window) root);
if (filename != null) {
try {
getContext().getSessionStorage().restore(root, filename);
} catch (Exception e) {
String msg = String.format("couldn't restore session [%s]", filename);
logger.log(Level.WARNING, msg, e);
}
}
// If window location is default and size is not too big
// the window should be centered
Point defaultLocation = SwingHelper.defaultLocation(window);
if (!window.isLocationByPlatform() &&
(root.getX() == defaultLocation.getX()) &&
(root.getY() == defaultLocation.getY())) {
Dimension screenSize = window.getToolkit().getScreenSize();
Dimension windowSIze = window.getSize();
if (screenSize.getWidth()/windowSIze.getWidth()>1.25 &&
screenSize.getHeight()/windowSIze.getHeight()>1.25) {
Component owner = window.getOwner();
if (owner == null) {
owner = (window != mainFrame) ? mainFrame : null;
}
window.setLocationRelativeTo(owner); // center the window
}
}
}
}
/**
* Show the specified component in the {@link #getMainFrame main frame}.
* Typical applications will call this method after constructing their
* main GUI panel in the {@code startup} method.
*
* Before the main frame is made visible, the properties of all of
* the components in the hierarchy are initialized with {@link
* ResourceMap#injectComponents ResourceMap.injectComponents} and
* then restored from saved session state (if any) with {@link
* SessionStorage#restore SessionStorage.restore}. When the
* application shuts down, session state is saved.
*
* Note that the name of the lazily created main frame (see
* {@link #getMainFrame getMainFrame}) is set by default.
* Session state is only saved for top level windows with
* a valid name and then only for component descendants
* that are named.
*
* Throws an IllegalArgumentException if {@code c} is null
*
* @param c the main frame's contentPane child
*/
protected void show(JComponent c) {
if (c == null) {
throw new IllegalArgumentException("null JComponent");
}
JFrame f = getMainFrame();
f.getContentPane().add(c, BorderLayout.CENTER);
initRootPaneContainer(f);
f.setVisible(true);
}
/**
* Initialize and show the JDialog.
*
* This method is intended for showing "secondary" windows, like
* message dialogs, about boxes, and so on. Unlike the {@code mainFrame},
* dismissing a secondary window will not exit the application.
*
* Session state is only automatically saved if the specified
* JDialog has a name, and then only for component descendants
* that are named.
*
* Throws an IllegalArgumentException if {@code c} is null
*
* @param c the main frame's contentPane child
* @see #show(JComponent)
* @see #show(JFrame)
* @see #configureWindow
*/
public void show(JDialog c) {
if (c == null) {
throw new IllegalArgumentException("null JDialog");
}
initRootPaneContainer(c);
c.setVisible(true);
}
/**
* Initialize and show the secondary JFrame.
*
* This method is intended for showing "secondary" windows, like
* message dialogs, about boxes, and so on. Unlike the {@code mainFrame},
* dismissing a secondary window will not exit the application.
*
* Session state is only automatically saved if the specified
* JFrame has a name, and then only for component descendants
* that are named.
*
* Throws an IllegalArgumentException if {@code c} is null
*
* @param c
* @see #show(JComponent)
* @see #show(JDialog)
* @see #configureWindow
*/
public void show(JFrame c) {
if (c == null) {
throw new IllegalArgumentException("null JFrame");
}
initRootPaneContainer(c);
c.setVisible(true);
}
private void saveSession(Window window) {
String filename = sessionFilename(window);
if (filename != null) {
try {
getContext().getSessionStorage().save(window, filename);
} catch (IOException e) {
logger.log(Level.WARNING, "couldn't save session", e);
}
}
}
private boolean isVisibleWindow(Window w) {
return w.isVisible() &&
((w instanceof JFrame) || (w instanceof JDialog) || (w instanceof JWindow));
}
/**
* Return all of the visible JWindows, JDialogs, and JFrames per
* Window.getWindows() on Java SE 6
*/
private List getVisibleSecondaryWindows() {
List rv = new ArrayList();
for (Window window : Window.getWindows()) {
if (isVisibleWindow(window)) {
rv.add(window);
}
}
return rv;
}
/**
* Save session state for the component hierarchy rooted by
* the mainFrame. SingleFrameApplication subclasses that override
* shutdown need to remember call {@code super.shutdown()}.
*/
@Override
protected void shutdown() {
if (isReady()) {
for (Window window : getVisibleSecondaryWindows()) {
saveSession(window);
}
}
}
private class MainFrameListener extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
exit(e);
}
}
/* Although it would have been simpler to listen for changes in
* the secondary window's visibility per either a
* PropertyChangeEvent on the "visible" property or a change in
* visibility per ComponentListener, neither listener is notified
* if the secondary window is disposed.
* HierarchyEvent.SHOWING_CHANGED does report the change in all
* cases, so we use that.
*/
private class SecondaryWindowListener implements HierarchyListener {
@Override
public void hierarchyChanged(HierarchyEvent e) {
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
if (e.getSource() instanceof Window) {
Window secondaryWindow = (Window) e.getSource();
if (!secondaryWindow.isShowing()) {
saveSession(secondaryWindow);
}
}
}
}
}
/* In order to properly restore a maximized JFrame, we need to
* record it's normal (not maximized) bounds. They're recorded
* under a rootPane client property here, so that they've can be
* session-saved by WindowProperty#getSessionState().
*/
private static class FrameBoundsListener implements ComponentListener {
private void maybeSaveFrameSize(ComponentEvent e) {
if (e.getComponent() instanceof JFrame) {
JFrame f = (JFrame) e.getComponent();
if ((f.getExtendedState() & Frame.MAXIMIZED_BOTH) == 0) {
SwingHelper.putWindowNormalBounds(f, f.getBounds());
}
}
}
@Override
public void componentResized(ComponentEvent e) {
maybeSaveFrameSize(e);
}
@Override
public void componentMoved(ComponentEvent e) { /* maybeSaveFrameSize(e); */ }
@Override
public void componentHidden(ComponentEvent e) {
}
@Override
public void componentShown(ComponentEvent e) {
}
}
/* Prototype support for the View type */
private FrameView mainView = null;
/**
* Gets the main view of the application
* @return the main view of the application
*/
public FrameView getMainView() {
if (mainView == null) {
mainView = new FrameView(this);
}
return mainView;
}
@Override
public void show(View view) {
if ((mainView == null) && (view instanceof FrameView)) {
mainView = (FrameView) view;
}
RootPaneContainer c = (RootPaneContainer) view.getRootPane().getParent();
initRootPaneContainer(c);
((Window) c).setVisible(true);
}
}