
com.globalmentor.swing.BasicFrame Maven / Gradle / Ivy
Show all versions of globalmentor-swing Show documentation
/*
* Copyright © 1996-2009 GlobalMentor, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.globalmentor.swing;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
import java.util.prefs.*;
import javax.swing.*;
import com.globalmentor.awt.*;
import com.globalmentor.java.Objects;
import com.globalmentor.log.Log;
import com.globalmentor.model.Modifiable;
import static com.globalmentor.util.prefs.PreferencesUtilities.*;
/**
* An extended frame that has extra features beyond those in JFrame
.
*
* The default close operation in this class by default is DISPOSE_ON_CLOSE
. This class maintains its own local default close operation setting,
* and sets the parent class default close operation to DO_NOTHING_ON_CLOSE
. This allows the class listen to window events and perform consistent
* canClose()
checks for all methods of closing. This is all handled transparently—once closing should occur, the local default close
* operation setting is honored as normal.
*
*
* If the content pane implements Modifiable
, the frame's updateStatus()
method will be called when the content pane is modified.
*
*
* This class can keep track of which component should get the focus by default, and will focus that component when the frame is initially shown.
*
*
* The frame can store a preferences node to use for preference, or use the default preferences node for the frame class.
*
*
* The class automatically remembers and restores its bounds by storing them in preferences.
*
*
* The panel keeps a lazily-created manager that manages menu and tool actions. These actions will automatically be merged with whatever actions are provided by
* the content pane before creating a menu bar.
*
*
* The frame does not actually update its title unless the title text is actually changing.
*
*
* The class maintains a lazily-created property change listener that knows how to update the status when something has been modified.
*
* @author Garret Wilson
*/
public class BasicFrame extends JFrame implements DefaultFocusable, CanClosable, ActionManaged {
//the bounds preferences
/** The preference for storing the horizontal position. */
protected final String BOUNDS_X_PREFERENCE = getPreferenceName(getClass(), "bounds.x");
/** The preference for storing the vertical position. */
protected final String BOUNDS_Y_PREFERENCE = getPreferenceName(getClass(), "bounds.y");
/** The preference for storing the width. */
protected final String BOUNDS_WIDTH_PREFERENCE = getPreferenceName(getClass(), "bounds.width");
/** The preference for storing the height. */
protected final String BOUNDS_HEIGHT_PREFERENCE = getPreferenceName(getClass(), "bounds.height");
/** The preference for storing the extended state. */
protected final String EXTENDED_STATE_PREFERENCE = getPreferenceName(getClass(), "extended.state");
/**
* The preferences that should be used for this frame, or null
if the default preferences for this class should be used.
*/
private Preferences preferences = null;
/**
* @return The preferences that should be used for this frame, or the default preferences for this class if no preferences are specifically set.
* @throws SecurityException Thrown if a security manager is present and it denies RuntimePermission("preferences")
.
*/
public Preferences getPreferences() throws SecurityException {
//TODO del; stay with the old method return preferences!=null ? preferences: PreferencesUtilities.getUserNodeForClass(getClass()); //return the user preferences node for whatever class extends this one
return preferences != null ? preferences : Preferences.userNodeForPackage(getClass()); //return the user preferences node for whatever class extends this one
}
/**
* Sets the preferences to be used for this panel.
* @param preferences The preferences that should be used for this panel, or null
if the default preferences for this class should be used
*/
public void setPreferences(final Preferences preferences) {
this.preferences = preferences; //store the preferences
}
/** The lazily-created manager of menu and tool actions. */
private ActionManager actionManager;
/** @return The lazily-created manager of menu and tool actions. */
public ActionManager getActionManager() {
if(actionManager == null) { //if we haven't yet created an action manager
actionManager = new ActionManager(); //create a new action manager
}
return actionManager; //return the action manager
}
/** The action for closing the frame. */
private final Action closeAction;
/** @return The action for closing the frame. */
public Action getCloseAction() {
return closeAction;
}
/** The component that hsould get the default focus, or null
if unknown. */
private Component defaultFocusComponent;
/**
* @return The component that should get the default focus, or null
if no component should get the default focus or it is unknown which component
* should get the default focus.
*/
public Component getDefaultFocusComponent() {
return defaultFocusComponent;
}
/**
* Sets the component to get the focus by default. If this panel becomes a root focus traversal cycle, the default installed focus traversal policy will
* automatically allow this component to get the default focus.
* @param component The component to get the default focus.
*/
public void setDefaultFocusComponent(final Component component) {
defaultFocusComponent = component;
}
/** The default close operation, which defaults to DISPOSE_ON_CLOSE
. */
private int defaultCloseOperation = DISPOSE_ON_CLOSE;
/**
* @return An integer representing the operation that occurs when the user closes the frame.
* @see #setDefaultCloseOperation
*/
public int getDefaultCloseOperation() {
return defaultCloseOperation;
}
/**
* Sets the operation that will happen by default when the user closes the frame. The value is set to DISPOSE_ON_CLOSE
by default.
* @param operation The operation which should be performed when the user closes the frame
* @throws IllegalArgumentException Thrown if defaultCloseOperation value isn't a valid value.
* @see JFrame#setDefaultCloseOperation
* @throws SecurityException Thrown if EXIT_ON_CLOSE
has been specified and the SecurityManager will not allow the caller to invoke
* System.exit()
.
*/
public void setDefaultCloseOperation(final int operation) throws SecurityException {
if(operation != DO_NOTHING_ON_CLOSE && operation != HIDE_ON_CLOSE && operation != DISPOSE_ON_CLOSE && operation != EXIT_ON_CLOSE) {
throw new IllegalArgumentException("defaultCloseOperation must be one of: DO_NOTHING_ON_CLOSE, HIDE_ON_CLOSE, DISPOSE_ON_CLOSE, or EXIT_ON_CLOSE");
}
if(defaultCloseOperation != operation) { //if the operation is really changing
switch(operation) { //see which operation they want
case EXIT_ON_CLOSE: //if they want to exit on close
{
final SecurityManager securityManager = System.getSecurityManager(); //get the security manager
if(securityManager != null) //if there is a security manager
securityManager.checkExit(0); //see if we have the rights to exit
}
break;
}
final int oldOperation = defaultCloseOperation; //save the old operation
defaultCloseOperation = operation; //actually update the default close operation
firePropertyChange("defaultCloseOperation", oldOperation, operation); //notify listeners that the value changed
}
}
/** The lazily-created object that listens for resource modifications and updates the status. */
private PropertyChangeListener modifiedUpdateStatusPropertyChangeListener = null;
/** @return The lazily-created object that listens for resource modifications and updates the status. */
protected PropertyChangeListener getModifiedUpdateStatusPropertyChangeListener() {
if(modifiedUpdateStatusPropertyChangeListener == null) { //if we haven't created a property change listener, yet
modifiedUpdateStatusPropertyChangeListener = new PropertyChangeListener() { //create a property chnage listener to listen for resource modifications
public void propertyChange(final PropertyChangeEvent propertyChangeEvent) { //if the "modified" property changes in the explore panel
updateStatus(); //update the status of our actions
}
};
}
return modifiedUpdateStatusPropertyChangeListener; //return the listener
}
/**
* Sets the title for this frame to the specified string. This version does not change the title unless the specified title is actually different than the
* current title.
* @param title The title to be displayed in the frame's border. A null
value is treated as an empty string, "".
* @see JFrame#getTitle
* @see JFrame#setTitle
*/
public void setTitle(final String title) {
if(!Objects.equals(getTitle(), title)) //if the title is really changing
super.setTitle(title); //actually update the title
}
/**
* Constructs a string appropriate for showing on the title of the frame.
*
* This version returns the current title.
*
* @return A title to display on the frame.
*/
protected String constructTitle() {
return getTitle(); //return the current title
}
/**
* Sets the contentPane
property. This version installs a property listener to listen for the content pane's "modified" property being changed,
* if the content pane is Modifiable
.
* @param contentPane the contentPane
object for this frame
* @throws IllegalComponentStateException (a runtime exception) if the content pane parameter is null
.
* @see JFrame#getContentPane
* @see Modifiable
* @see Modifiable#MODIFIED_PROPERTY
* @see #getModifiedPropertyChangeListener
*/
public void setContentPane(final Container contentPane) {
final Container oldContentPane = getContentPane(); //get the current content pane
if(oldContentPane instanceof Modifiable) { //if the old content pane is modifiable
oldContentPane.removePropertyChangeListener(Modifiable.MODIFIED_PROPERTY, getModifiedUpdateStatusPropertyChangeListener()); //remove the modified property change listener from the old content pane
}
super.setContentPane(contentPane); //set the content pane normally
setDefaultFocusComponent(contentPane); //default to focusing on the content pane, if one was passed
if(contentPane instanceof Modifiable) { //if the new content pane is modifiable
contentPane.addPropertyChangeListener(Modifiable.MODIFIED_PROPERTY, getModifiedUpdateStatusPropertyChangeListener()); //add a listener to update the status when the "modified" property changes
}
}
/**
* Default constructor.
*
* Enables window events.
*
*/
public BasicFrame() {
this(true); //construct the frame and initialize it
}
/**
* Initialization constructor.
*
* Enables window events.
*
* @param initialize true
if the frame should initialize itself by calling the initialization methods.
*/
public BasicFrame(final boolean initialize) {
this("", initialize); //construct the frame with no title
}
/**
* Title constructor.
*
* Enables window events.
*
* @param title The title of the frame, or null
for no title (which will be convertd to a title of "").
*/
public BasicFrame(final String title) {
this(title, true); //construct and initialize the frame
}
/**
* Title constructor with optional initialization.
*
* Enables window events.
*
* @param title The title of the frame, or null
for no title (which will be convertd to a title of "").
* @param initialize true
if the frame should initialize itself by calling the initialization methods.
*/
public BasicFrame(final String title, final boolean initialize) {
this(title, null, initialize); //construct the class with the default content pane
}
/**
* Content pane and title constructor.
*
* Enables window events.
*
* @param contentPane The container to be used as the content pane, or null
if the default content pane should be used.
* @param title The title of the frame, or null
for no title (which will be convertd to a title of "").
*/
public BasicFrame(final String title, final Container contentPane) {
this(title, contentPane, true); //construct the frame with the given title and content pane, and initialize the frame
}
/**
* Content pane constructor with optional initialization.
*
* Enables window events.
*
* @param contentPane The container to be used as the content pane, or null
if the default content pane should be used.
* @param initialize true
if the frame should initialize itself by calling the initialization methods.
*/
public BasicFrame(final Container contentPane, final boolean initialize) {
this("", contentPane, initialize); //construct the frame with no title, initializing if requested
}
/**
* Content pane and title constructor with optional initialization.
*
* Enables window events.
*
* @param contentPane The container to be used as the content pane, or null
if the default content pane should be used.
* @param title The title of the frame, or null
for no title (which will be convertd to a title of "").
* @param initialize true
if the frame should initialize itself by calling the initialization methods.
*/
public BasicFrame(final String title, final Container contentPane, final boolean initialize) {
super(title); //construct the parent class with this title, making sure we don't pass null (JFrame sometimes allows null titles, other times it converts them to the empty string)
preferences = null; //show that we should use the default preferences for this class
actionManager = null; //default to no action manager until one is asked for
closeAction = new CloseAction(); //create the close action
//don't do anything automatically on close; we'll handle responding to close events
super.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); //tell the parent to set its default close operation TODO this implementation depends on the fact that the super class doesn't use the accessor methods---that's probably dangerous
enableEvents(AWTEvent.WINDOW_EVENT_MASK); //enable window events, so that we can respond to close events
if(contentPane != null) //if we have a content pane
setContentPane(contentPane); //set the given container as the content pane
addComponentListener(new ComponentAdapter() { //listen for the window bounds changes and save or restore the bounds in the preferences
public void componentMoved(ComponentEvent e) {
if(isVisible())
saveBoundsPreferences();
} //save the new bounds if we are visible
public void componentResized(ComponentEvent e) {
if(isVisible())
saveBoundsPreferences();
} //save the new bounds if we are visible
public void componentShown(ComponentEvent e) {
if(isVisible())
requestDefaultFocusComponentFocus(); //TODO fix; put back in windowGainedFocus() and find out why it is never called
if(isVisible())
restoreBoundsPreferences();
} //restore the new bounds if we are visible
});
//TODO actually, this window state change needs to be fixed---it isn't working correctly
//TODO del addWindowStateListener(new WindowStateListener() //listen for the window state changing so that we can save it in the preferences
//TODO del {
//TODO del public void windowStateChanged(WindowEvent e) {/*TODO fix saveStatePreferences();*/} //save the new state
//TODO del });
//TODO del defaultFocusComponent=null; //default to no default focus component
//create and install a new layout focus traversal policy that will
//automatically use the default focus component, if available
/*TODO fix
setFocusTraversalPolicy(new LayoutFocusTraversalPolicy()
{
public Component getDefaultComponent(final Container focusCycleRoot) { //if the default component is requested
//if we have a default focus component, return it; otherwise, use the value given by the parent traversal policy class
return getDefaultFocusComponent()!=null ? getDefaultFocusComponent() : super.getDefaultComponent(focusCycleRoot);
}
});
*/
/*TODO fix; move from componentShown() to here and find out why this doesn't ever get called
addWindowListener(new WindowAdapter() { //TODO testing; tidy; comment
private boolean gotFocus = false;
public void windowGainedFocus(WindowEvent we) {
// Once window gets focus, set initial focus
if (!gotFocus) {
gotFocus=requestDefaultFocusComponentFocus(); //TODO testing
}
}
});
*/
if(initialize) //if we should initialize
initialize(); //initialize the frame
}
/**
* Initializes the frame. Should only be called once per instance.
* @see #initializeUI
*/
protected void initialize() {
initializeActions(getActionManager()); //initialize actions
initializeUI(); //initialize the user interface
pack(); //set the initial size to its default
updateStatus(); //update the actions
//TODO bring back setSize(800, 600); //default to 800X600; the window can be maximized after it's shown TODO determine the initial size based upon the resolution
//TODO fix setExtendedState(MAXIMIZED_BOTH); //maximize the frame TODO get this from preferences
//TODO transfer this to WindowUtilities, maybe GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this);
//TODO fix WindowUtilities.maximize(this); //maximize the frame TODO remove this, as JDK 1.4 has a programmatic maximization
//TODO del; doesn't fix the problem getContentPane().requestFocus(); //focus on the content pane
}
/**
* Initializes actions in the action manager. Any derived class that overrides this method should call this version.
* @param actionManager The implementation that manages actions.
*/
protected void initializeActions(final ActionManager actionManager) {
}
/**
* Initializes the user interface. Any derived class that overrides this method should call this version.
*/
protected void initializeUI() {
setTitle(constructTitle()); //update the title
ActionManager actionManager = getActionManager(); //get our action manager
final Container contentPane = getContentPane(); //get the content pane
if(contentPane instanceof ActionManaged) { //if the content pane has managed actions
actionManager = actionManager.merge(((ActionManaged)contentPane).getActionManager()); //merge with the the content pane's managed actions
}
//set up the menu bar
//TODO put this in some convenience method, maybe---or maybe not, if the code is specific to frames
final Iterator menuActionIterator = actionManager.getMenuActionIterator(); //get the iterator to top-level menu actions
if(menuActionIterator.hasNext()) { //if there are top-level menu actions
final JMenuBar menuBar = new JMenuBar(); //create a menu bar
while(menuActionIterator.hasNext()) { //while there are more actions
final Action action = (Action)menuActionIterator.next(); //get the next action
final JMenuItem menuItem = createMenuItem(action, actionManager, true); //create a menu item for this top-level menu action
menuBar.add((JMenu)menuItem); //add the menu (we know it's a menu, because we specified that this is a top-level menu action) to the menu bar
}
setJMenuBar(menuBar); //set the menu bar
}
}
/**
* Creates a menu item to represent the given action. If the given action manager contains child actions for the given action, a menu will be created and the
* child actions will be recursively added to the menu.
* @param action The action to be represented with a menu or menu item.
* @param actionManager The manager that contains any children actions for the given parent.
* @param isTopLevel Whether this action represents the top level in the manu hierarchy, and thus a menu should unconditionally be created rather than a
* single menu item.
* @return A complete menu or a menu item to represent the given action and any chilren.
*/
protected JMenuItem createMenuItem(final Action action, final ActionManager actionManager, final boolean isTopLevel) {
final Iterator menuActionIterator = actionManager.getMenuActionIterator(action); //get the iterator to children, if any, of the parent action
if(isTopLevel || menuActionIterator.hasNext()) { //if the parent action has children, or if the action is a top-level action
final JMenu menu = new JMenu(action); //create a new menu from the action
Component lastComponent = null; //keep track of the last component we added
while(menuActionIterator.hasNext()) { //while there are more actions
final Action childAction = (Action)menuActionIterator.next(); //get the next child action
if(childAction instanceof ActionManager.SeparatorAction) { //if this is a separator action
//don't put two separators in a row, and don't put a separator as the first component
if(lastComponent != null && !(lastComponent instanceof JSeparator)) { //if this isn't the first component and it doesn't come before a separator
lastComponent = new JPopupMenu.Separator(); //create a menu separator
menu.add(lastComponent); //add the separator
}
} else { //if this is a normal action
final JMenuItem childMenuItem = createMenuItem(childAction, actionManager, false); //create a new menu item and/or child menu items for the action, specifying that this is not a top-level action
lastComponent = menu.add(childMenuItem); //add the child menu item
}
}
return menu; //show that we created a complete menu to represent the action
} else { //if there are no children actions
return createMenuItem(action); //just create a normal menu item from the action
}
}
/** The map of button groups keyed to action groups. */
private final Map actionGroupButtonGroupMap = new HashMap();
/**
* Retrieves a button group for the given action group. If no button group exists for the given action group, one is created.
*/
protected ButtonGroup getButtonGroup(final ActionGroup actionGroup) {
ButtonGroup buttonGroup = (ButtonGroup)actionGroupButtonGroupMap.get(actionGroup); //get a button group group for this action group
if(buttonGroup == null) { //if no button group has been created
buttonGroup = new ButtonGroup(); //create a new button group
actionGroupButtonGroupMap.put(actionGroup, buttonGroup); //associate the button group with the action group
}
return buttonGroup; //return the button group
}
/**
* Creates a single menu item—not a menu—from the given action.
* @param action The action for which a menu item should be created.
* @return A new menu item to represent the action.
*/
protected JMenuItem createMenuItem(final Action action) {
final JMenuItem menuItem;
if(action instanceof AbstractToggleAction) { //if this is a toggle action
final AbstractToggleAction toggleAction = (AbstractToggleAction)action; //cast the action to a toggle action
final ActionGroup actionGroup = toggleAction.getGroup(); //get the action's group, if any
if(actionGroup != null) { //if there is a group of mutually exclusive actions
menuItem = new JRadioButtonMenuItem(toggleAction); //create a radio button menu item
getButtonGroup(actionGroup).add(menuItem); //add this menu item to the button group
} else { //if there is no group
menuItem = new JCheckBoxMenuItem(toggleAction); //create a checkbox menu item
}
menuItem.setSelected(toggleAction.isSelected()); //set the initial selected state of the menu item
//create a property change listener to update the button in response to the action "selected" state changing
final PropertyChangeListener selectedPropertyChangeListener = Buttons.createToggleActionSelectedPropertyChangeListener(menuItem);
toggleAction.addPropertyChangeListener(selectedPropertyChangeListener); //listen for the action "selected" state changing
} else { //if we should create a normal menu item
menuItem = new JMenuItem(action); //create a normal menu item from the action
}
return menuItem; //return the menu item we created
}
/**
* Updates the states of the actions, including enabled/disabled status, proxied actions, etc.
*/
protected void updateStatus() {
setTitle(constructTitle()); //update the title
}
/**
* Requests that the default focus component should get the default.
*
* If the component is a tab in a tabbed pane, that tab in the tabbed pane is selected.
*
*
* If the default focus comonent is itself DefaultFocusable
, that component is asked to request focus for its default focus component, and so on.
*
* @return false
if the focus change request is guaranteed to fail; true
if it is likely to succeed.
* @see Component#requestFocusInWindow
*/
public boolean requestDefaultFocusComponentFocus() { //TODO put this is some common class along with the version in BasicPanel
final Component defaultFocusComponent = getDefaultFocusComponent(); //get the default focus component
if(defaultFocusComponent != null) { //if there is a default focus component, make sure its parent tabs are selected if it's in a tabbed pane
TabbedPaneUtilities.setSelectedParentTabs(defaultFocusComponent); //select the tabs of any parent tabbed panes
}
if(defaultFocusComponent instanceof DefaultFocusable //if the component is itself default focusable
&& ((DefaultFocusable)defaultFocusComponent).getDefaultFocusComponent() != defaultFocusComponent) { //and the default focus component does not reference itself (which would create an endless loop)
return ((DefaultFocusable)defaultFocusComponent).requestDefaultFocusComponentFocus(); //pass the request on to the default focus component
} else if(defaultFocusComponent != null) { //if the default focus component doesn't itself know about default focus components, but there is a default focus component
return defaultFocusComponent.requestFocusInWindow(); //tell the default focus component to request the focus
} else { //if there is no default focus component
return false; //there was nothing to focus
}
}
/**
* Closes the frame, according to the default close settings.
* @see #getDefaultCloseOperation
*/
public void close() {
if(getDefaultCloseOperation() == DO_NOTHING_ON_CLOSE || canClose()) { //if we should do something on close, make sure we can close
switch(getDefaultCloseOperation()) { //see how we should close
case HIDE_ON_CLOSE: //if we should hide
setVisible(false); //hide the frame
break;
case DISPOSE_ON_CLOSE: //if we should dispose the frame
setVisible(false); //hide the frame
dispose(); //dispose the frame
break;
case EXIT_ON_CLOSE: //if we should exit
exit(); //exit
break;
case DO_NOTHING_ON_CLOSE: //if we should do nothing
default:
break;
}
}
}
/**
* Exits the application with no status. Convenience method which calls exit(int)
.
* @see #exit(int)
*/
protected void exit() {
exit(0); //exit with no status
}
/**
* Exits the application with the given status.
* @param status The exit status.
*/
protected void exit(final int status) {
System.exit(status); //close the program with the given exit status
}
/**
* Determines whether the frame can close.
*
* If the content pane is an instance of CanClosable
, its canClose()
method is called.
*
* @return true
if the frame can close.
* @see CanClosable#canClose
*/
public boolean canClose() {
if(getContentPane() instanceof CanClosable) { //if the content pane knows how to ask about closing
return ((CanClosable)getContentPane()).canClose(); //ask if the content pane can be closed
} else { //if the content pane is not an instance of CanClosable
return true; //allow closing by default
}
}
/**
* Overrides the windows event method so that we can exit when the window closes. Instead of overriding this method, child classes are encouraged to simply
* override exit()
, making sure to call the super class after doing the pre-exit cleanup.
* @param windowEvent The window event.
*/
protected void processWindowEvent(final WindowEvent windowEvent) {
super.processWindowEvent(windowEvent); //do the default processing
if(windowEvent.getID() == WindowEvent.WINDOW_CLOSING) { //if this is a window closing event
close(); //close the window
}
}
/**
* Makes the frame visible or invisible. This version resets the bounds of the frame to the last known bounds as saved in the preferences if the window is
* being made visible.
* @param newVisible true
to make the component visible; false
to make it invisible.
*/
public void setVisible(final boolean newVisible) {
if(!isVisible() && newVisible) { //if the frame is becoming visible
restoreBoundsPreferences(); //restore the bounds (this will be done again after coming visible, because setting the bounds while hidden will not correctly set the window extended state)
}
super.setVisible(newVisible); //set the visibility normally
}
/**
* Makes the component visible or invisible. This version resets the bounds of the frame to the last known bounds as saved in the preferences.
* @param newVisible true
to make the component visible; false
to make it invisible.
*/
/*TODO del when works
public void setVisible(final boolean newVisible)
{
super.setVisible(newVisible); //update the visible status normally
if(newVisible) { //if the frame is now visible
//TODO del final Rectangle bounds=getBounds(); //get the current bounds
final Preferences preferences=getPreferences(); //get the preferences
final int x=preferences.getInt(BOUNDS_X_PREFERENCE, -1); //get the stored bounds, using invalid dimensions for defaults
final int y=preferences.getInt(BOUNDS_Y_PREFERENCE, -1);
final int width=preferences.getInt(BOUNDS_WIDTH_PREFERENCE, -1);
final int height=preferences.getInt(BOUNDS_HEIGHT_PREFERENCE, -1);
if(x>=0 && y>=0 && width>=0 && height>=0) { //if we had valid bounds stored
setBounds(x, y, width, height); //restore the bounds we had saved in preferences
}
else { //if no bounds are stored in preferences
setSize(800, 600); //set a default size, which will be saved
WindowUtilities.center(this); //center the window, which will save the new location
}
validate(); //make sure the components are all laid out correctly after was changed the size
}
}
*/
/** Saves the bounds in the preferences. */
protected void saveBoundsPreferences() {
try {
final Preferences preferences = getPreferences(); //get the preferences
final Rectangle bounds = getBounds(); //get the current bounds
final int extendedState = getExtendedState(); //get the current extended state
preferences.putInt(EXTENDED_STATE_PREFERENCE, extendedState); //store the extended state
if(extendedState != MAXIMIZED_HORIZ && extendedState != MAXIMIZED_BOTH) { //if we aren't maximized horizontally
preferences.putInt(BOUNDS_X_PREFERENCE, bounds.x); //save the horizontal bounds
preferences.putInt(BOUNDS_WIDTH_PREFERENCE, bounds.width);
}
if(extendedState != MAXIMIZED_VERT && extendedState != MAXIMIZED_BOTH) { //if we aren't maximized vertically
preferences.putInt(BOUNDS_Y_PREFERENCE, bounds.y); //save the vertical bounds
preferences.putInt(BOUNDS_HEIGHT_PREFERENCE, bounds.height);
}
} catch(SecurityException securityException) { //if we can't access preferences
Log.warn(securityException); //warn of the security problem
}
}
/** Restores the bounds from the preferences. */
public void restoreBoundsPreferences() {
boolean useDefault = true; //we'll use the defaults if we can't load the saved values
try {
final Preferences preferences = getPreferences(); //get the preferences
final int extendedState = preferences.getInt(EXTENDED_STATE_PREFERENCE, NORMAL); //get the stored extended state
final int x = preferences.getInt(BOUNDS_X_PREFERENCE, 0); //get the stored bounds, using invalid dimensions for defaults
final int y = preferences.getInt(BOUNDS_Y_PREFERENCE, 0);
final int width = preferences.getInt(BOUNDS_WIDTH_PREFERENCE, -1);
final int height = preferences.getInt(BOUNDS_HEIGHT_PREFERENCE, -1);
//TODO maybe the stuff gets changed around here---the height and width seem to be changed, but the x and y seem to be lost
//TODO there's some sort of timing issue: when debugging, the things get changed correctly, but in real time often the x and y coordinates get set before the extended state is updated to maximized
useDefault = width < 0 || height < 0; //use the default if we didn't get valid dimensions
if(!useDefault) { //if we had valid dimensions stored
setBounds(x, y, width, height); //restore the bounds we had saved in preferences
setExtendedState(extendedState); //update the extended state to match that stored
}
} catch(SecurityException securityException) { //if we can't access preferences
Log.warn(securityException); //warn of the security problem
}
if(useDefault) { //if no bounds are stored in preferences
setSize(800, 600); //set a default size, which will be saved
Windows.center(this); //center the window, which will save the new location
setExtendedState(MAXIMIZED_BOTH); //maximize the window
}
validate(); //make sure the components are all laid out correctly after was changed the size
}
public void setExtendedState(int state) {
super.setExtendedState(state); //TODO testing
}
/** Action for closing the frame. */
protected class CloseAction extends AbstractAction {
/** Default constructor. */
public CloseAction() {
super("Close"); //create the base class TODO i18n
putValue(SHORT_DESCRIPTION, "Close the window"); //set the short description TODO i18n
putValue(LONG_DESCRIPTION, "Close the window."); //set the long description TODO i18n
putValue(MNEMONIC_KEY, new Integer(KeyEvent.VK_O)); //set the mnemonic key TODO i18n
putValue(SMALL_ICON, IconResources.getIcon(IconResources.EXIT_ICON_FILENAME)); //load the correct icon
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_F4, Event.ALT_MASK)); //add the accelerator
putValue(ActionManager.MENU_ORDER_PROPERTY, new Integer(ActionManager.FILE_EXIT_MENU_ACTION_ORDER)); //set the order
}
/**
* Called when the action should be performed.
* @param actionEvent The event causing the action.
*/
public void actionPerformed(final ActionEvent actionEvent) {
close(); //close the frame
}
}
}