package com.onaro.util.jfc; import java.awt.Dimension; import java.awt.GraphicsEnvironment; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.text.MessageFormat; import java.text.ParseException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.onaro.commons.prefs.PreferencesWrapper; /** * Utility for calculating windows size and position relative to the screen size. * Supports saving a window's bounds in the configuration. */ public class DefaultSizePosition { /** * The format of a window's bound asit is saved in the preferences. */ public static final MessageFormat BOUNDS_FORMAT = new MessageFormat("X:{0,number,integer} Y:{1,number} W:{2,number} H:{3,number}"); //$NON-NLS-1$ /** * The logger for this class. */ static Logger logger = LogManager.getLogger(DefaultSizePosition.class); /** * Sets the default bounds for a given window. First the preferences are * searched for previous settings. If not found, then the window is placed * in the center of the screen and its size is relative to the screen. When * the window will be closed, its bounds will be stored in the preferences. * * @param prefs The preferences where to search, save and restore window preferences * @param win the window to save and restore its bounds * @param relX default window/screen width ratio * @param relY default window/screen height ratio */ public static void restoreBounds(PreferencesWrapper prefs, Window win, double relX, double relY) { restoreBounds(new RestoreBoundsDataPrefs(relX, relY, win, prefs)); } public static void restoreBounds(PreferencesWrapper prefs, Window win, int absoluteX, int absoluteY) { restoreBounds(new RestoreBoundsDataPrefs(absoluteX, absoluteY, win, prefs)); } /** * Sets the default bounds for a given window. First the preferences are * searched for previous settings. If not found, then the window is placed * in the center of the screen and its size is relative to the screen. When * the window will be closed, its bounds will be stored in the preferences. * * @param rb The preferences where to search, save and restore window preferences */ public static void restoreBounds(RestoreBoundsData rb) { /** * Register with the window to save its bounds when it is closing. */ rb.win.addWindowListener(new SaveWindowBounds(rb, rb.win)); /** * Try getting the bounds from the preferences. */ String prefBounds = rb.get("bounds." + rb.win.getName(), null); //$NON-NLS-1$ if (prefBounds != null) { try { Object args[] = BOUNDS_FORMAT.parse(prefBounds); Rectangle bounds = new Rectangle(((Long) args[0]).intValue(), ((Long) args[1]).intValue(), ((Long) args[2]).intValue(), ((Long) args[3]).intValue()); // Ensure the new bounds fit within the display shiftOnScreen(bounds); rb.win.setBounds(bounds); return; } catch (ClassCastException e) { logger.warn("Failed restoring window bounds for '" + rb.win.getName() + "' got '" + prefBounds + "' - " + e.getMessage(), e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } catch (ParseException e) { logger.warn("Failed restoring window bounds for '" + rb.win.getName() + "' got '" + prefBounds + "' - " + e.getMessage(), e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } rb.win.setBounds(getBounds(rb.win.getPreferredSize(), rb)); } /** * Ensure the window with the given bounds can be displayed on the screen. * @param bounds the bounds of the window. This bounds object will be modified if necessary so * that the bounds will fit on the screen */ private static void shiftOnScreen(Rectangle bounds) { Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); // Ensure the position and size of the window is reasonable bounds.x = Math.max(bounds.x, 0); bounds.y = Math.max(bounds.y, 0); bounds.width = Math.max(bounds.width, 100); bounds.height = Math.max(bounds.height, 100); // If the full window width is not on screen if (bounds.x + bounds.width > screenSize.width) { // Shift the x coordinate of the window to a point that's on screen bounds.x = Math.max(0, screenSize.width - bounds.width); } // If the window width is greater than the screen width if (bounds.width > screenSize.width) { // Change the window width to match the screen width bounds.width = screenSize.width; } // If the full window height is not on screen if (bounds.y + bounds.height > screenSize.height) { // Shift the y coordinate of the window to a point that's on screen bounds.y = Math.max(0, screenSize.height - bounds.height); } // If the window height is greater than the screen height if (bounds.height > screenSize.height) { // Change the window height to match the screen height bounds.height = screenSize.height; } } /** * Saves the default bounds for a given window. * * @param win the windows to save bounds * @param prefs the preference node in which to save */ public static void saveBounds(Window win, Node prefs) { if (win == null) return; Rectangle bounds = win.getBounds(); Object args[] = {Integer.valueOf(bounds.x), Integer.valueOf(bounds.y), Integer.valueOf(bounds.width), Integer.valueOf(bounds.height)}; String boundsStr = DefaultSizePosition.BOUNDS_FORMAT.format(args); prefs.put("bounds." + win.getName(), boundsStr); //$NON-NLS-1$ } private static Rectangle getBounds(Dimension preferred, RestoreBoundsData rb) { Dimension screenSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getSize(); Rectangle bounds; if (rb.isRelative()) { bounds = new Rectangle(getRelativeDimension(preferred, rb.relX, rb.relY)); } else { bounds = new Rectangle(getAbsoluteDimension(preferred, rb.absoluteX, rb.absoluteY)); } bounds.setLocation((screenSize.width - bounds.width) / 2, (screenSize.height - bounds.height) / 2); return bounds; } public static Rectangle getBounds(Dimension preferred, double relX, double relY) { Dimension screenSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getSize(); Rectangle bounds = new Rectangle(getRelativeDimension(preferred, relX, relY)); bounds.setLocation((screenSize.width - bounds.width) / 2, (screenSize.height - bounds.height) / 2); return bounds; } private static Dimension getRelativeDimension(Dimension preferred, double relX, double relY) { Dimension screenSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getSize(); Dimension size = new Dimension(preferred); int relWidth = (int) (screenSize.width * relX); int relHeight = (int) (screenSize.height * relY); if (size.height < relHeight || size.height > screenSize.height) { size.height = relHeight; } if (size.width < relWidth || size.width > screenSize.width) { size.width = relWidth; } return size; } private static Dimension getAbsoluteDimension(Dimension preferred, int absoluteX, int absoluteY) { Dimension screenSize = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds().getSize(); Dimension size = new Dimension(preferred); size.width = Math.max(size.width, absoluteX); size.width = Math.min(size.width, screenSize.width); size.height = Math.max(size.height, absoluteY); size.height = Math.min(size.height, screenSize.height); return size; } /** * restore bounds data for PreferenceExt * Holds all the data for restoring and implements the read write interface for PreferencesWrapper preference mechanism */ private static class RestoreBoundsDataPrefs extends RestoreBoundsData { public RestoreBoundsDataPrefs(double relX, double relY, Window win, Object dat) { super(relX, relY, win, dat); } public RestoreBoundsDataPrefs(int absoluteX, int absoluteY, Window win, Object dat) { super(absoluteX, absoluteY, win, dat); } public String get(String name) { return ((PreferencesWrapper) dat).get(name); } public String get(String name, String dflt) { return ((PreferencesWrapper) dat).get(name, dflt); } public void put(String name, String val) { ((PreferencesWrapper) dat).put(name, val); } } /** * Holds all the data and require the user to implement the read write to some general preferences node mechanism */ static private abstract class RestoreBoundsData implements Node { protected final Window win; protected final double relX; protected final double relY; protected final int absoluteX; protected final int absoluteY; protected final Object dat; public RestoreBoundsData(double relX, double relY, Window win, Object dat) { this.relX = relX; this.relY = relY; this.win = win; this.dat = dat; absoluteX = -1; absoluteY = -1; } public RestoreBoundsData(int absoluteX, int absoluteY, Window win, Object dat) { this.absoluteX = absoluteX; this.absoluteY = absoluteY; this.win = win; this.dat = dat; this.relX = -1; this.relY = -1; } public boolean isRelative() { return relX != -1 && relY != -1; } public abstract void put(String name, String val); public abstract String get(String name); public abstract String get(String name, String dflt); } /** * interface for reading and writing from a general type of preference node */ public interface Node { String get(String name); String get(String name, String dflt); void put(String name, String val); } /** * A window listener saving a window's bounds when it is closing. */ private static class SaveWindowBounds extends WindowAdapter { DefaultSizePosition.Node prefs; Window win; SaveWindowBounds(DefaultSizePosition.Node prefs, Window win) { this.prefs = prefs; this.win = win; } public void windowClosing(WindowEvent e) { DefaultSizePosition.saveBounds(win, prefs); } public void windowDeactivated(WindowEvent e) { DefaultSizePosition.saveBounds(win, prefs); } } }