diff --git a/.gitattributes b/.gitattributes
index c50b4c102fe..6bb27d532d2 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -13094,6 +13094,7 @@ res/defaults/gauntlet/LOCKED_DotP[!!-~]Preconstructed.dat -text
res/defaults/gauntlet/LOCKED_Swimming[!!-~]With[!!-~]Sharks.dat -text
res/defaults/home.xml svneol=native#text/xml
res/defaults/match.xml svneol=native#text/xml
+res/defaults/window.xml -text
res/draft/cube_juzamjedi.draft -text
res/draft/cube_skiera.draft -text
res/draft/rankings.txt -text
diff --git a/CHANGES.txt b/CHANGES.txt
index cbc4e1990f8..7da93ff17d0 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -8,6 +8,12 @@ Forge Beta: 0#-##-2013 ver 1.4.6
Release Notes
-------------
+- Window size/position now remembered between sessions -
+Works with multiple monitors.
+Remembers whether the window was maximized or un-maximized.
+Remembers last un-maximized size even if the window is currently maximized such that, if you un-maximize the window, it will restore to that size at the center of the current monitor.
+Window will be made accessible even if monitor setup or screen resolution is different between sessions.
+
- Zoomer Updates -
Split cards (name contains "//") are now rotated 90 degrees for easier viewing.
diff --git a/res/defaults/window.xml b/res/defaults/window.xml
new file mode 100644
index 00000000000..35ddbd4665d
--- /dev/null
+++ b/res/defaults/window.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/src/main/java/forge/control/FControl.java b/src/main/java/forge/control/FControl.java
index bc327819ad0..e270bcb3e7d 100644
--- a/src/main/java/forge/control/FControl.java
+++ b/src/main/java/forge/control/FControl.java
@@ -185,12 +185,18 @@ public enum FControl {
Singletons.getModel().getQuest().load(QuestDataIO.loadData(data));
}
- // Handles resizing in null layouts of layers in JLayeredPane.
+ // Handles resizing in null layouts of layers in JLayeredPane as well as saving window layout
Singletons.getView().getFrame().addComponentListener(new ComponentAdapter() {
- @Override
- public void componentResized(final ComponentEvent e) {
- sizeChildren();
- }
+ @Override
+ public void componentResized(final ComponentEvent e) {
+ sizeChildren();
+ SLayoutIO.saveWindowLayout();
+ }
+
+ @Override
+ public void componentMoved(final ComponentEvent e) {
+ SLayoutIO.saveWindowLayout();
+ }
});
FView.SINGLETON_INSTANCE.getLpnDocument().addMouseListener(SOverflowUtil.getHideOverflowListener());
diff --git a/src/main/java/forge/gui/framework/SDisplayUtil.java b/src/main/java/forge/gui/framework/SDisplayUtil.java
index 0d09e31f696..307864c78ac 100644
--- a/src/main/java/forge/gui/framework/SDisplayUtil.java
+++ b/src/main/java/forge/gui/framework/SDisplayUtil.java
@@ -2,7 +2,12 @@ package forge.gui.framework;
import java.awt.Color;
import java.awt.Component;
+import java.awt.GraphicsConfiguration;
+import java.awt.GraphicsDevice;
+import java.awt.GraphicsEnvironment;
import java.awt.KeyboardFocusManager;
+import java.awt.Point;
+import java.awt.Rectangle;
import java.util.Timer;
import java.util.TimerTask;
@@ -105,4 +110,18 @@ public class SDisplayUtil {
};
FThreads.invokeInEdtLater(showTabRoutine);
}
+
+ public static Rectangle getScreenBoundsForPoint(Point point) {
+ Rectangle bounds;
+ for (GraphicsDevice device : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
+ for (GraphicsConfiguration config : device.getConfigurations()) {
+ bounds = config.getBounds();
+ if (bounds.contains(point)) {
+ return bounds;
+ }
+ }
+ }
+ //return bounds of default monitor if point not on any screen
+ return GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
+ }
}
diff --git a/src/main/java/forge/gui/framework/SLayoutIO.java b/src/main/java/forge/gui/framework/SLayoutIO.java
index bb5f868f1e8..c4bb1232567 100644
--- a/src/main/java/forge/gui/framework/SLayoutIO.java
+++ b/src/main/java/forge/gui/framework/SLayoutIO.java
@@ -1,6 +1,10 @@
package forge.gui.framework;
import java.awt.BorderLayout;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Point;
+import java.awt.Rectangle;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -12,6 +16,7 @@ import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.JFrame;
import javax.swing.border.EmptyBorder;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventReader;
@@ -41,7 +46,6 @@ import forge.view.FView;
*
(S at beginning of class name denotes a static factory.)
*/
public final class SLayoutIO {
- /** Each cell must save these elements of its display. */
private static class Property {
public final static String x = "x";
public final static String y = "y";
@@ -49,11 +53,203 @@ public final class SLayoutIO {
public final static String h = "h";
public final static String sel = "sel";
public final static String doc = "doc";
+ public final static String state = "state";
}
private static final XMLEventFactory EF = XMLEventFactory.newInstance();
private static final XMLEvent NEWLINE = EF.createDTD("\n");
private static final XMLEvent TAB = EF.createDTD("\t");
+
+ private static int normalWindowWidth, normalWindowHeight;
+ private final static AtomicBoolean saveWindowRequested = new AtomicBoolean(false);
+
+ public static void saveWindowLayout() {
+ if (saveWindowRequested.getAndSet(true)) { return; }
+ FThreads.delay(500, new Runnable() {
+ @Override
+ public void run() {
+ finishSaveWindowLayout();
+ saveWindowRequested.set(false);
+ }
+ });
+ }
+
+ private synchronized static void finishSaveWindowLayout() {
+ final JFrame window = FView.SINGLETON_INSTANCE.getFrame();
+ final int state = window.getExtendedState();
+ if (state == Frame.ICONIFIED) { return; } //don't update saved layout if minimized
+
+ final Rectangle bounds = window.getBounds();
+ final int x = bounds.x;
+ final int y = bounds.y;
+ if ((state & Frame.MAXIMIZED_HORIZ) != Frame.MAXIMIZED_HORIZ) { //only modify saved width if not maximized horizontally
+ normalWindowWidth = bounds.width;
+ }
+ else if (normalWindowWidth == 0) {
+ normalWindowWidth = window.getMinimumSize().width;
+ }
+ if ((state & Frame.MAXIMIZED_VERT) != Frame.MAXIMIZED_VERT) { //only modify saved width if not maximized vertically
+ normalWindowHeight = bounds.height;
+ }
+ else if (normalWindowHeight == 0) {
+ normalWindowHeight = window.getMinimumSize().height;
+ }
+
+ final FileLocation file = NewConstants.WINDOW_LAYOUT_FILE;
+ final String fWriteTo = file.userPrefLoc;
+ final XMLOutputFactory out = XMLOutputFactory.newInstance();
+ FileOutputStream fos = null;
+ XMLEventWriter writer = null;
+ try {
+ fos = new FileOutputStream(fWriteTo);
+ writer = out.createXMLEventWriter(fos);
+
+ writer.add(EF.createStartDocument());
+ writer.add(NEWLINE);
+ writer.add(EF.createStartElement("", "", "layout"));
+ writer.add(EF.createAttribute(Property.x, String.valueOf(x)));
+ writer.add(EF.createAttribute(Property.y, String.valueOf(y)));
+ writer.add(EF.createAttribute(Property.w, String.valueOf(normalWindowWidth)));
+ writer.add(EF.createAttribute(Property.h, String.valueOf(normalWindowHeight)));
+ writer.add(EF.createAttribute(Property.state, String.valueOf(state)));
+ writer.add(EF.createEndElement("", "", "layout"));
+ writer.flush();
+ writer.add(EF.createEndDocument());
+ } catch (FileNotFoundException e) {
+ // TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
+ e.printStackTrace();
+ } catch (XMLStreamException e) {
+ // TODO Auto-generated catch block ignores the exception, but sends it to System.err and probably forge.log.
+ e.printStackTrace();
+ } finally {
+ if (writer != null ) {
+ try { writer.close(); } catch (XMLStreamException e) {}
+ }
+ if ( fos != null ) {
+ try { fos.close(); } catch (IOException e) {}
+ }
+ }
+ }
+
+ public static void loadWindowLayout() {
+ final JFrame window = FView.SINGLETON_INSTANCE.getFrame();
+ final XMLInputFactory inputFactory = XMLInputFactory.newInstance();
+ final FileLocation file = NewConstants.WINDOW_LAYOUT_FILE;
+ boolean usedCustomPrefsFile = false;
+ FileInputStream fis = null;
+ try {
+ File userSetting = new File(file.userPrefLoc);
+ if (userSetting.exists()) {
+ usedCustomPrefsFile = true;
+ fis = new FileInputStream(userSetting);
+ }
+ else {
+ fis = new FileInputStream(file.defaultLoc);
+ }
+
+ XMLEventReader reader = null;
+ try {
+ XMLEvent event;
+ StartElement element;
+ Iterator> attributes;
+ Attribute attribute;
+ reader = inputFactory.createXMLEventReader(fis);
+
+ while (reader != null && reader.hasNext()) {
+ event = reader.nextEvent();
+
+ if (event.isStartElement()) {
+ element = event.asStartElement();
+
+ if (element.getName().getLocalPart().equals("layout")) {
+ attributes = element.getAttributes();
+ Dimension minSize = window.getMinimumSize();
+ int x = 0, y = 0, w = minSize.width, h = minSize.height, state = Frame.MAXIMIZED_BOTH;
+ while (attributes.hasNext()) {
+ attribute = (Attribute) attributes.next();
+ switch (attribute.getName().toString()) {
+ case Property.x: x = Integer.parseInt(attribute.getValue()); break;
+ case Property.y: y = Integer.parseInt(attribute.getValue()); break;
+ case Property.w: w = Integer.parseInt(attribute.getValue()); break;
+ case Property.h: h = Integer.parseInt(attribute.getValue()); break;
+ case Property.state: state = Integer.parseInt(attribute.getValue()); break;
+ }
+ }
+
+ //set normal size to loaded size
+ normalWindowWidth = w;
+ normalWindowHeight = h;
+
+ //update x and y if needed such that window is centered on that axis
+ //when un-maximized if starting out maximized on that axis
+ int centerX = x + w / 2;
+ int centerY = y + h / 2;
+ Rectangle screenBounds = SDisplayUtil.getScreenBoundsForPoint(new Point(centerX, centerY));
+ if ((state & Frame.MAXIMIZED_HORIZ) == Frame.MAXIMIZED_HORIZ) {
+ x = screenBounds.x + (screenBounds.width - w) / 2;
+ }
+ else { //ensure the window is accessible
+ if (centerX < screenBounds.x) {
+ x = screenBounds.x;
+ }
+ else if (centerX > screenBounds.x + screenBounds.width) {
+ x = screenBounds.x + screenBounds.width - w;
+ if (x < screenBounds.x) {
+ x = screenBounds.x;
+ }
+ }
+ }
+ if ((state & Frame.MAXIMIZED_VERT) == Frame.MAXIMIZED_VERT) {
+ y = screenBounds.y + (screenBounds.height - h) / 2;
+ }
+ else { //ensure the window is accessible
+ if (centerY < screenBounds.y) {
+ y = screenBounds.y;
+ }
+ else if (centerY > screenBounds.y + screenBounds.height) {
+ y = screenBounds.y + screenBounds.height - h;
+ if (y < screenBounds.y) {
+ y = screenBounds.y;
+ }
+ }
+ }
+
+ window.setBounds(x, y, w, h);
+ window.setExtendedState(state);
+ }
+ }
+ }
+ }
+ catch (final Exception e) {
+ try {
+ if (reader != null) { reader.close(); };
+ }
+ catch (final XMLStreamException x) {
+ e.printStackTrace();
+ }
+ e.printStackTrace();
+ if (usedCustomPrefsFile) {
+ throw new InvalidLayoutFileException();
+ }
+ else {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ catch (FileNotFoundException e) {
+ throw new RuntimeException(e);
+ }
+ finally {
+ if (fis != null ) {
+ try {
+ fis.close();
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
/**
* Gets preferred layout file corresponding to current state of UI.
diff --git a/src/main/java/forge/properties/NewConstants.java b/src/main/java/forge/properties/NewConstants.java
index d1a6bd624ed..f3a1abdeddc 100644
--- a/src/main/java/forge/properties/NewConstants.java
+++ b/src/main/java/forge/properties/NewConstants.java
@@ -86,6 +86,7 @@ public final class NewConstants {
// data that has defaults in the program dir but overrides/additions in the user dir
private static final String _DEFAULTS_DIR = _RES_ROOT + "defaults/";
public static final FileLocation EDITOR_PREFERENCES_FILE = new FileLocation(_DEFAULTS_DIR, USER_PREFS_DIR, "editor.preferences");
+ public static final FileLocation WINDOW_LAYOUT_FILE = new FileLocation(_DEFAULTS_DIR, USER_PREFS_DIR, "window.xml");
public static final FileLocation HOME_LAYOUT_FILE = new FileLocation(_DEFAULTS_DIR, USER_PREFS_DIR, "home.xml");
public static final FileLocation MATCH_LAYOUT_FILE = new FileLocation(_DEFAULTS_DIR, USER_PREFS_DIR, "match.xml");
public static final FileLocation EDITOR_LAYOUT_FILE = new FileLocation(_DEFAULTS_DIR, USER_PREFS_DIR, "editor.xml");
diff --git a/src/main/java/forge/view/FView.java b/src/main/java/forge/view/FView.java
index a3f38796c40..b5d127ba5e9 100644
--- a/src/main/java/forge/view/FView.java
+++ b/src/main/java/forge/view/FView.java
@@ -4,7 +4,6 @@ import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
-import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -42,6 +41,7 @@ import forge.gui.deckeditor.VDeckEditorUI;
import forge.gui.framework.DragCell;
import forge.gui.framework.EDocID;
import forge.gui.framework.SLayoutConstants;
+import forge.gui.framework.SLayoutIO;
import forge.gui.home.VHomeUI;
import forge.gui.match.TargetingOverlay;
import forge.gui.match.VMatchUI;
@@ -95,7 +95,6 @@ public enum FView {
// Frame styling
frmDocument.setMinimumSize(new Dimension(800, 600));
frmDocument.setLocationRelativeTo(null);
- frmDocument.setExtendedState(frmDocument.getExtendedState() | Frame.MAXIMIZED_BOTH);
frmDocument.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frmDocument.setIconImage(FSkin.getIcon(FSkin.InterfaceIcons.ICO_FAVICON).getImage());
frmDocument.setTitle("Forge: " + BuildInfo.getVersionString());
@@ -141,9 +140,8 @@ public enum FView {
FView.this.frmSplash.dispose();
FView.this.frmSplash = null;
-
- // Allow OS to set location. Hopefully this doesn't cause issues
- frmDocument.setLocationByPlatform(true);
+
+ SLayoutIO.loadWindowLayout();
frmDocument.setVisible(true);
// remove this once our userbase has been migrated to the profile layout