package chatty.gui; import chatty.Helper; import chatty.Helper.IntegerPair; import chatty.util.settings.Settings; import java.awt.Dimension; import java.awt.Point; import java.awt.Window; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.logging.Logger; /** * Allows you to add windows that you can then save the position and size for * (usually at the end of the program session), which can be reloaded later, * based on the restore settings. Also attaches the added windows to the * default parent, moving them along if the parent is moved (if enabled). * * The window attributes are saved to the settings. * * The position/size of the window is restored just once at the start (even if * it isn't shown yet) and then just not changed if it's set to restore. If it's * set to not restore, then the position is set everytime it's opened. * * @author tduva */ public class WindowStateManager { private static final Logger LOGGER = Logger.getLogger(WindowStateManager.class.getName()); private static final String SETTING = "windows"; private static final String MODE_SETTING = "restoreMode"; private static final String CHECK_ON_SCREEN_SETTING = "restoreOnlyIfOnScreen"; public static final int DONT_RESTORE = 0; public static final int RESTORE_MAIN = 1; public static final int RESTORE_ALL = 2; public static final int RESTORE_ON_START = 3; public static final int REOPEN_ON_START = 4; private final HashMap<Window, StateItem> windows = new HashMap<>(); /** * Saves which windows have had their position set already in this session. */ private final Set<Window> locationSet = new HashSet<>(); private final Settings settings; /** * The default parent is the one that the windows are centered on, if they * are opened and their position hasn't been set yet or their position * should not be restored. */ private final Window defaultParent; /** * The primary window is special, because it can be set to be restored even * if the others aren't ({@code RESTORE_MAIN}). */ private Window primaryWindow; private final AttachedWindowManager attachedWindowManager; public WindowStateManager(Window defaultParent, Settings settings) { this.settings = settings; this.defaultParent = defaultParent; attachedWindowManager = new AttachedWindowManager(defaultParent); } /** * Adds a window to be managed. * * @param window The {@code Window} object * @param id The id of the window, which is used when saving * @param saveSize Whether to save (or rather restore) the size * @param reopen Whether to reopen the window on start */ public void addWindow(Window window, String id, boolean saveSize, boolean reopen) { windows.put(window, new StateItem(id, saveSize, reopen)); attachedWindowManager.attach(window); } public Set<Window> getWindows() { return new HashSet<>(windows.keySet()); } /** * Sets the primary window, which can be restored even if the other windows * are set to not restore. * * @param window */ public void setPrimaryWindow(Window window) { this.primaryWindow = window; } public void setAttachedWindowsEnabled(boolean enabled) { attachedWindowManager.setEnabled(enabled); } /** * Saves the attributes for all managed windows. */ public void saveWindowStates() { for (Window window : windows.keySet()) { saveWindowState(window); } } /** * Loads the window attributes for all managed windows. */ public void loadWindowStates() { for (Window window : windows.keySet()) { loadWindowState(window); } } /** * Save the window attributes for a single window. * * @param window The window to save the attributes for */ private void saveWindowState(Window window) { // Only save state when window state was actually set during this // this session at least once if (!locationSet.contains(window) && window != primaryWindow) { return; } // Should still save whether it was open StateItem item = windows.get(window); Point location = window.getLocation(); String state = location.x+","+location.y; Dimension size = window.getSize(); state += ";"+size.width+","+size.height; state += ";"+(window.isVisible() ? "1" : "0"); settings.mapPut(SETTING, item.id, state); } /** * Try to load the window attributes for the given {@literal Window} and sets * the location and size for the window (if it's set to restore). * * The attributes are loaded from a map in the settings, with the window id * as the key. The item has to be a {@literal String} in the format: * x,y;width,height;wasOpen * * @param window */ private void loadWindowState(Window window) { StateItem item = windows.get(window); String state = (String)settings.mapGet(SETTING, item.id); if (state == null) { return; } String[] states = state.split(";"); if (states.length < 3) { return; } if (mode() >= RESTORE_ON_START || (mode() >= RESTORE_MAIN && window == primaryWindow)) { // Set position and size // Set size first, so width is set already for checking position IntegerPair sizeTemp = Helper.getNumbersFromString(states[1]); if (sizeTemp != null && item.saveSize) { Dimension size = new Dimension(sizeTemp.a, sizeTemp.b); window.setSize(size); } // Set position IntegerPair locationTemp = Helper.getNumbersFromString(states[0]); if (locationTemp != null) { Point location = new Point(locationTemp.a, locationTemp.b); if (!settings.getBoolean(CHECK_ON_SCREEN_SETTING) || isOnScreen(location, window.getWidth())) { // If this is the window the others are attached to, ignore // this movement. if (window == defaultParent) { attachedWindowManager.ignoreLocationOnce(location); } window.setLocation(location); locationSet.add(window); } else { LOGGER.info("Location for "+item.id+" ["+location+"] not restored (not on screen)"); } } } item.wasOpen = states[2].equals("1"); } private boolean isOnScreen(Point location, int width) { return GuiUtil.isPointOnScreen(location, 100) || GuiUtil.isPointOnScreen(location, width - 100); } /** * Sets the location to the default one (centered on the default parent), * unless the mode is set to restore and the position was set before, in * which case nothing is done, so the window just keeps the location it * already has. * * @param window The window to set the position for * @param parent The window to center the window on, can be null in which * case it centers on the default parent */ public void setWindowPosition(Window window, Window parent) { boolean setLocationBefore = locationSet.contains(window); if (mode() < RESTORE_ALL || !setLocationBefore) { if (parent == null) { parent = defaultParent; } window.setLocationRelativeTo(parent); locationSet.add(window); } } public void setWindowPosition(Window window) { setWindowPosition(window, null); } /** * Returns whether the window was open in the last session. * * @param window The window to check. * @return {@code true} if the window was open last session, {@code false} * if it wasn't or there is no data saved */ public boolean wasOpen(Window window) { StateItem item = windows.get(window); return item != null && item.wasOpen; } /** * Returns whether the window should be reopened on start. * * @param window * @return {@code true} if it should be reopened, {@code false} otherwise */ public boolean shouldReopen(Window window) { StateItem item = windows.get(window); return mode() >= REOPEN_ON_START && item != null && item.reopen && item.wasOpen; } /** * Gets the current restore mode setting. * * @return The mode as an integer */ private int mode() { return (int)settings.getLong(MODE_SETTING); } /** * Saves some stuff for a single window. */ private static class StateItem { /** * Whether to save (or rather restore) the size. */ public final boolean saveSize; /** * The id of this window, which is used when saving the settings. */ public final String id; /** * Whether this window should be reopened (if the restore setting allows * it). */ public final boolean reopen; /** * Saves whether the window was open when the program was closed last * session. */ public boolean wasOpen; StateItem(String id, boolean saveSize, boolean reopen) { this.saveSize = saveSize; this.reopen = reopen; this.id = id; } } }