Author: kmorin Date: 2013-04-02 16:02:07 +0200 (Tue, 02 Apr 2013) New Revision: 2647 Url: http://nuiton.org/projects/jaxx/repository/revisions/2647 Log: fixes #2646 Move the SwingSession from nuiton-widget Added: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JSplitPaneState.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JTabbedPaneState.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JTableState.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JXTableSwingSessionState.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/State.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/SwingSession.java trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/WindowState.java Added: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JSplitPaneState.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JSplitPaneState.java (rev 0) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JSplitPaneState.java 2013-04-02 14:02:07 UTC (rev 2647) @@ -0,0 +1,75 @@ +package jaxx.runtime.swing.session; + +import javax.swing.*; + +/** + * State for JSplit + * FIXME add listener for divider move action + * + * @author poussin + * @author kmorin <kmorin@codelutin.com> + * @since 2.5.16 + */ +public class JSplitPaneState implements State { + + protected int dividerLocation = -1; + + protected int orientation = JSplitPane.HORIZONTAL_SPLIT; + + public JSplitPaneState() { + } + + public int getDividerLocation() { + return dividerLocation; + } + + public void setDividerLocation(int dividerLocation) { + this.dividerLocation = dividerLocation; + } + + public int getOrientation() { + return orientation; + } + + public void setOrientation(int orientation) { + this.orientation = orientation; + } + + protected JSplitPane checkComponent(Object o) { + if (o == null) { + throw new IllegalArgumentException("null component"); + } + if (!(o instanceof JSplitPane)) { + throw new IllegalArgumentException("invalid component"); + } + return (JSplitPane) o; + } + + @Override + public State getState(Object o) { + JSplitPane p = checkComponent(o); + + JSplitPaneState result = new JSplitPaneState(); + result.setDividerLocation(p.getUI().getDividerLocation(p)); + result.setOrientation(p.getOrientation()); + + return result; + } + + @Override + public void setState(Object o, State state) { + if (state == null) { + return; + } + JSplitPane p = checkComponent(o); + if (state instanceof JSplitPaneState) { + JSplitPaneState sps = (JSplitPaneState) state; + if (sps.getDividerLocation() != -1 + && p.getOrientation() == sps.getOrientation()) { + p.setDividerLocation(sps.getDividerLocation()); + } + } else { + throw new IllegalArgumentException("invalid state"); + } + } +} Added: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JTabbedPaneState.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JTabbedPaneState.java (rev 0) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JTabbedPaneState.java 2013-04-02 14:02:07 UTC (rev 2647) @@ -0,0 +1,75 @@ +package jaxx.runtime.swing.session; + +import javax.swing.*; + +/** + * State for the JTabbedPane + * + * @author poussin + * @author kmorin <kmorin@codelutin.com> + * @since 2.5.16 + */ +public class JTabbedPaneState implements State { + + protected int selectedIndex = -1; + + protected int tabCount; + + public JTabbedPaneState() { + } + + public int getSelectedIndex() { + return selectedIndex; + } + + public void setSelectedIndex(int selectedIndex) { + this.selectedIndex = selectedIndex; + } + + public int getTabCount() { + return tabCount; + } + + public void setTabCount(int tabCount) { + this.tabCount = tabCount; + } + + + protected JTabbedPane checkComponent(Object o) { + if (o == null) { + throw new IllegalArgumentException("null component"); + } + if (!(o instanceof JTabbedPane)) { + throw new IllegalArgumentException("invalid component"); + } + return (JTabbedPane) o; + } + + @Override + public State getState(Object o) { + JTabbedPaneState result = new JTabbedPaneState(); + + JTabbedPane p = checkComponent(o); + result.setSelectedIndex(p.getSelectedIndex()); + result.setTabCount(p.getTabCount()); + + return result; + } + + @Override + public void setState(Object o, State state) { + if (state == null) { + return; + } + if (state instanceof JTabbedPaneState) { + JTabbedPane p = checkComponent(o); + JTabbedPaneState tps = (JTabbedPaneState) state; + if (tps.getSelectedIndex() != -1 + && p.getTabCount() == tps.getTabCount()) { + p.setSelectedIndex(tps.getSelectedIndex()); + } + } else { + throw new IllegalArgumentException("invalid state"); + } + } +} Added: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JTableState.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JTableState.java (rev 0) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JTableState.java 2013-04-02 14:02:07 UTC (rev 2647) @@ -0,0 +1,83 @@ +package jaxx.runtime.swing.session; + +import javax.swing.*; +import javax.swing.table.TableColumn; + +/** + * State for JTable. + * TODO add support for column order change + * + * @author poussin + * @author kmorin <kmorin@codelutin.com> + * @since 2.5.16 + * + */ +public class JTableState implements State { + + protected int[] columnWidths = new int[0]; + + public JTableState() { + } + + public JTableState(int[] columnWidths) { + this.columnWidths = columnWidths; + } + + public int[] getColumnWidths() { + return columnWidths; + } + + public void setColumnWidths(int[] columnWidths) { + this.columnWidths = columnWidths; + } + + protected JTable checkComponent(Object o) { + if (o == null) { + throw new IllegalArgumentException("null component"); + } + if (!(o instanceof JTable)) { + throw new IllegalArgumentException("invalid component"); + } + return (JTable) o; + } + + @Override + public State getState(Object o) { + JTable table = checkComponent(o); + int[] columnWidths = new int[table.getColumnCount()]; + boolean resizableColumnExists = false; + for (int i = 0; i < columnWidths.length; i++) { + TableColumn tc = table.getColumnModel().getColumn(i); + columnWidths[i] = (tc.getResizable()) ? tc.getWidth() : -1; + if (tc.getResizable()) { + resizableColumnExists = true; + } + } + JTableState result = null; + if (resizableColumnExists) { + result = new JTableState(); + result.setColumnWidths(columnWidths); + } + return result; + } + + @Override + public void setState(Object o, State state) { + if (!(state instanceof JTableState)) { + throw new IllegalArgumentException("invalid state"); + } + JTable table = checkComponent(o); + int[] columnWidths = ((JTableState) state).getColumnWidths(); + if (columnWidths != null + && table.getColumnCount() == columnWidths.length) { + for (int i = 0; i < columnWidths.length; i++) { + if (columnWidths[i] != -1) { + TableColumn tc = table.getColumnModel().getColumn(i); + if (tc.getResizable()) { + tc.setPreferredWidth(columnWidths[i]); + } + } + } + } + } +} Added: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JXTableSwingSessionState.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JXTableSwingSessionState.java (rev 0) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/JXTableSwingSessionState.java 2013-04-02 14:02:07 UTC (rev 2647) @@ -0,0 +1,85 @@ +package jaxx.runtime.swing.session; + +import org.jdesktop.swingx.JXTable; +import org.jdesktop.swingx.table.DefaultTableColumnModelExt; +import org.jdesktop.swingx.table.TableColumnExt; + +import javax.swing.table.TableColumn; +import java.util.List; + +/** + * @author kmorin <morin@codelutin.com> + * @since 2.5.16 + */ +public class JXTableSwingSessionState extends JTableState { + + protected boolean[] hiddenColumns; + + public JXTableSwingSessionState() { + super(); + } + + public JXTableSwingSessionState(int[] columnWidths, boolean[] hiddenColumns) { + super(columnWidths); + this.hiddenColumns = hiddenColumns; + } + + public boolean[] getHiddenColumns() { + return hiddenColumns; + } + + public void setHiddenColumns(boolean[] hiddenColumns) { + this.hiddenColumns = hiddenColumns; + } + + protected JXTable checkComponent(Object o) { + if (o == null) { + throw new IllegalArgumentException("null component"); + } + if (!(o instanceof JXTable)) { + throw new IllegalArgumentException("invalid component"); + } + return (JXTable) o; + } + + @Override + public State getState(Object o) { + JXTable table = checkComponent(o); + JXTableSwingSessionState result = new JXTableSwingSessionState(); + JTableState state = (JTableState) super.getState(o); + if (state != null) { + result.setColumnWidths(state.getColumnWidths()); + } + DefaultTableColumnModelExt columnModel = + (DefaultTableColumnModelExt) table.getColumnModel(); + List<TableColumn> columns = columnModel.getColumns(true); + boolean[] hiddenColumns = new boolean[columns.size()]; + for (int i = 0; i < hiddenColumns.length; i++) { + TableColumnExt tc = (TableColumnExt) columns.get(i); + hiddenColumns[i] = !tc.isVisible(); + } + result.setHiddenColumns(hiddenColumns); + + return result; + } + + @Override + public void setState(Object o, State state) { + if (!(state instanceof JXTableSwingSessionState)) { + throw new IllegalArgumentException("invalid state"); + } + super.setState(o, state); + JXTable table = checkComponent(o); + boolean[] hiddenColumns = ((JXTableSwingSessionState) state).getHiddenColumns(); + DefaultTableColumnModelExt columnModel = + (DefaultTableColumnModelExt) table.getColumnModel(); + List<TableColumn> columns = columnModel.getColumns(true); + if (hiddenColumns != null + && columnModel.getColumnCount(true) == columns.size()) { + for (int i = 0; i < table.getColumnCount(); i++) { + TableColumnExt tc = (TableColumnExt) columns.get(i); + tc.setVisible(!hiddenColumns[i]); + } + } + } +} Added: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/State.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/State.java (rev 0) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/State.java 2013-04-02 14:02:07 UTC (rev 2647) @@ -0,0 +1,16 @@ +package jaxx.runtime.swing.session; + +/** + * get(save) and set(restore) state of object passed in argument + * + * @author poussin + * @author kmorin <kmorin@codelutin.com> + * @since 2.5.16 + */ +public interface State { + + State getState(Object o); + + void setState(Object o, State state); + +} Added: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/SwingSession.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/SwingSession.java (rev 0) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/SwingSession.java 2013-04-02 14:02:07 UTC (rev 2647) @@ -0,0 +1,339 @@ +/* + * #%L + * Graphical Widget + * + * $Id: SwingSession.java 300 2010-11-17 21:35:44Z sletellier $ + * $HeadURL: http://svn.nuiton.org/svn/nuiton-widgets/trunk/src/main/java/org/nuiton/widg... $ + * %% + * Copyright (C) 2004 - 2010 CodeLutin + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Lesser Public License for more details. + * + * You should have received a copy of the GNU General Lesser Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/lgpl-3.0.html>. + * #L% + */ +package jaxx.runtime.swing.session; + + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.jdesktop.swingx.JXTable; + +import javax.swing.*; +import javax.swing.table.AbstractTableModel; +import javax.swing.table.TableColumn; +import javax.swing.table.TableModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.beans.*; +import java.io.*; +import java.util.*; + +/** + * Use to store and restore position and size of application. Default supported widgets + * are: + * <li> java.awt.Window (and subclasses) + * <li> javax.swing.JTabbedPane (and subclasses) + * <li> javax.swing.JSplitPane (and subclasses) + * <li> org.jdesktop.swingx.JXTable (and subclasses) + * <li> javax.swing.JTable (and subclasses) + * + * + * usage: + * <li> create SwingSession object + * <li> add component that you want save + * <li> explicite call to save + * + * You can use same SwingSession for multiple window but in this case you must + * have setName for each window with different name, otherwize there are + * collision between window component and result is undetermisitic + * + * This code is partialy inspired from http://kenai.com/projects/bsaf/pages/Home + * project. This project is under LGPL v2.1 license. We can't reuse directly this + * library because to many fields and methods are private and we can't implements + * it and modify some behavior. + * + * @author poussin + * @author kmorin + * @version $Revision: 300 $ + * @since 2.5.16 + * + * Last update: $Date: 2010-11-17 22:35:44 +0100 (mer. 17 nov. 2010) $ + * by : $Author: sletellier $ + */ +public class SwingSession { + + private static final Log log = LogFactory.getLog(SwingSession.class); + + protected File file; + + protected boolean autoSave; + + protected Set<Component> registeredComponent = Sets.newIdentityHashSet(); + + /** State object registered to get and set State. + * key: class of component managed by the state; value: the state*/ + protected Map<Class, State> stateManager = Maps.newHashMap(); + + /** state of all component added with add method. + * key: path of compoenent; value: State */ + protected Map<String, State> states; + + public SwingSession(File file, boolean autoSave) { + this.file = file; + this.autoSave = autoSave; + stateManager.put(Window.class, new WindowState()); + stateManager.put(JTable.class, new JTableState()); + stateManager.put(JTabbedPane.class, new JTabbedPaneState()); + stateManager.put(JSplitPane.class, new JSplitPaneState()); + stateManager.put(JXTable.class, new JXTableSwingSessionState()); + + states = loadStates(file); + if (states == null) { + states = Maps.newHashMap(); + } + } + + @Override + protected void finalize() throws Throwable { + save(); + super.finalize(); + } + + + /* If an exception occurs in the XMLEncoder/Decoder, we want + * to throw an IOException. The exceptionThrow listener method + * doesn't throw a checked exception so we just set a flag + * here and check it when the encode/decode operation finishes + */ + static private class AbortExceptionListener implements ExceptionListener { + + public Exception exception = null; + + @Override + public void exceptionThrown(Exception e) { + if (exception == null) { + exception = e; + } + } + } + + /* There are some (old) Java classes that aren't proper beans. Rectangle + * is one of these. When running within the secure sandbox, writing a + * Rectangle with XMLEncoder causes a security exception because + * DefaultPersistenceDelegate calls Field.setAccessible(true) to gain + * access to private fields. This is a workaround for that problem. + * A bug has been filed, see JDK bug ID 4741757 + */ + private static class RectanglePD extends DefaultPersistenceDelegate { + + public RectanglePD() { + super(new String[]{"x", "y", "width", "height"}); + } + + @Override + protected Expression instantiate(Object oldInstance, Encoder out) { + Rectangle oldR = (Rectangle) oldInstance; + Object[] constructorArgs = new Object[]{ + oldR.x, oldR.y, oldR.width, oldR.height + }; + return new Expression(oldInstance, oldInstance.getClass(), "new", constructorArgs); + } + } + + public void save() { + updateState(); + AbortExceptionListener el = new AbortExceptionListener(); + ByteArrayOutputStream bst = new ByteArrayOutputStream(); + XMLEncoder e = null; + /* Buffer the XMLEncoder's output so that decoding errors don't + * cause us to trash the current version of the specified file. + */ + try { + e = new XMLEncoder(bst); + e.setPersistenceDelegate(Rectangle.class, new RectanglePD()); + e.setExceptionListener(el); + e.writeObject(states); + } finally { + if (e != null) { + e.close(); + } + } + if (el.exception != null) { + log.warn("save failed \"" + file + "\"", el.exception); + } else { + OutputStream ost = null; + try { + ost = new FileOutputStream(file); + ost.write(bst.toByteArray()); + } catch (IOException eee) { + log.warn("save failed \"" + file + "\"", eee); + } finally { + if (ost != null) { + try { + ost.close(); + } catch (IOException eee) { + log.warn("can't close properly \"" + file + "\"", eee); + } + } + } + } + } + + /** + * Loads the states from the file + */ + public Map<String, State> loadStates(File file) { + Map<String, State> result = null; + if (file.exists()) { + XMLDecoder d = null; + try { + InputStream ist = new FileInputStream(file); + d = new XMLDecoder(ist); + AbortExceptionListener eee = new AbortExceptionListener(); + d.setExceptionListener(eee); + Object bean = d.readObject(); + if (eee.exception != null) { + log.warn("load failed \"" + file + "\"", eee.exception); + } else { + result = (Map<String, State>) bean; + } + } catch (IOException eee) { + log.warn("load failed \"" + file + "\"", eee); + } finally { + if (d != null) { + d.close(); + } + } + } + return result; + } + + public void updateState() { + walkThrowComponent("", registeredComponent, + new SaveStateAction()); + } + + public void add(Component c) { + if (registeredComponent.contains(c)) { + log.warn(String.format( + "Component already added %s(%s)", c.getClass(), c.getName())); + } else { + registeredComponent.add(c); + walkThrowComponent("", Collections.singleton(c), + new RestoreStateAction()); + } + } + + /** + * Remove component from component to save + * @param c + */ + public void remove(Component c) { + registeredComponent.remove(c); + } + + protected String getComponentName(Component c) { + String name = c.getName(); + if (name == null) { + int n = c.getParent().getComponentZOrder(c); + if (n >= 0) { + Class clazz = c.getClass(); + name = clazz.getSimpleName(); + if (name.length() == 0) { + name = "Anonymous" + clazz.getSuperclass().getSimpleName(); + } + name = name + n; + } else { + // Implies that the component tree is changing + // while we're computing the path. Punt. + log.warn("Couldn't compute pathname for " + c); + } + } + return name; + } + + public State getStateManager(Class clazz) { + State result = null; + while (result == null && clazz != null) { + result = stateManager.get(clazz); + clazz = clazz.getSuperclass(); + } + return result; + } + + public void addToStateManager(Class component, State state) { + stateManager.put(component, state); + } + + public State getStates(String path) { + return states.get(path); + } + + public void setStates(String path, State state) { + this.states.put(path, state); + } + + protected void walkThrowComponent( + String path, Collection<Component> roots, Action action) { + for (Component root : roots) { + if (root != null) { + String pathname = path + "/" + getComponentName(root); + State state = getStateManager(root.getClass()); + if (state != null) { + action.doAction(this, pathname, root); + } + if (root instanceof Container) { + Component[] children = ((Container) root).getComponents(); + if ((children != null) && (children.length > 0)) { + walkThrowComponent(pathname, Arrays.asList(children), action); + } + } + if (root instanceof JFrame) { + Component[] children = ((JFrame) root).getContentPane().getComponents(); + if ((children != null) && (children.length > 0)) { + walkThrowComponent(pathname, Arrays.asList(children), action); + } + } + } + } + } + + public static interface Action { + public void doAction(SwingSession session, String path, Component c); + } + + public static class SaveStateAction implements Action { + @Override + public void doAction(SwingSession session, String path, Component c) { + State manager = session.getStateManager(c.getClass()); + State state = manager.getState(c); + session.setStates(path, state); + } + } + + public static class RestoreStateAction implements Action { + @Override + public void doAction(SwingSession session, String path, Component c) { + State manager = session.getStateManager(c.getClass()); + State state = session.getStates(path); + if (state != null) { + manager.setState(c, state); + } + } + } + +} Added: trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/WindowState.java =================================================================== --- trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/WindowState.java (rev 0) +++ trunk/jaxx-runtime/src/main/java/jaxx/runtime/swing/session/WindowState.java 2013-04-02 14:02:07 UTC (rev 2647) @@ -0,0 +1,180 @@ +package jaxx.runtime.swing.session; + +import javax.swing.*; +import java.awt.*; + +/** + * State for Window + * + * @author poussin + * @author kmorin <kmorin@codelutin.com> + * @since 2.5.16 + */ +public class WindowState implements State { + + private static final String WINDOW_STATE_NORMAL_BOUNDS = + "WindowState.normalBounds"; + + protected Rectangle bounds; + protected Rectangle gcBounds; + protected int frameState = Frame.NORMAL; + + public WindowState() { + } + + public WindowState(Rectangle bounds, Rectangle gcBounds, int frameState) { + this.bounds = new Rectangle(bounds); + this.gcBounds = new Rectangle(gcBounds); + this.frameState = frameState; + } + + public Rectangle getBounds() { + return bounds; + } + + public void setBounds(Rectangle bounds) { + this.bounds = bounds; + } + + public Rectangle getGcBounds() { + return gcBounds; + } + + public void setGcBounds(Rectangle gcBounds) { + this.gcBounds = gcBounds; + } + + public int getFrameState() { + return frameState; + } + + public void setFrameState(int frameState) { + this.frameState = frameState; + } + + protected Window checkComponent(Object o) { + if (o == null) { + throw new IllegalArgumentException("null component"); + } + if (!(o instanceof Window)) { + throw new IllegalArgumentException("invalid component"); + } + return (Window) o; + } + + /** + * Checks whether the window supports resizing + * @param window the {@code Window} to be checked + * @return true if the window supports resizing + */ + protected static boolean isResizable(Window window) { + boolean resizable = true; + if (window instanceof Frame) { + resizable = ((Frame) window).isResizable(); + } else if (window instanceof Dialog) { + resizable = ((Dialog) window).isResizable(); + } + return resizable; + } + + /** + * Gets {@code Window} bounds from the client property + * @param window the source {@code Window} + * @return bounds from the client property + */ + protected static Rectangle getWindowNormalBounds(Window window) { + Rectangle result = null; + if (window instanceof JFrame) { + Object res = ((JFrame) window).getRootPane().getClientProperty( + WINDOW_STATE_NORMAL_BOUNDS); + if (res instanceof Rectangle) { + result = (Rectangle) res; + } + } + return result; + } + + /** + * Calculates virtual graphic bounds. + * On multiscreen systems all screens are united into one virtual screen. + * @return the graphic bounds + */ + public static Rectangle computeVirtualGraphicsBounds() { + Rectangle virtualBounds = new Rectangle(); + GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice[] gs = ge.getScreenDevices(); + for (GraphicsDevice gd : gs) { + GraphicsConfiguration gc = gd.getDefaultConfiguration(); + virtualBounds = virtualBounds.union(gc.getBounds()); + } + return virtualBounds; + } + + /** + * Puts {@code Window} bounds to client property. + * @param window the target {@code Window} + * @param bounds bounds + */ + public static void putWindowNormalBounds(Window window, Rectangle bounds) { + if (window instanceof JFrame) { + ((JFrame) window).getRootPane().putClientProperty( + WINDOW_STATE_NORMAL_BOUNDS, bounds); + } + } + + @Override + public State getState(Object o) { + Window c = checkComponent(o); + int frameState = Frame.NORMAL; + if (c instanceof Frame) { + frameState = ((Frame) c).getExtendedState(); + } + GraphicsConfiguration gc = c.getGraphicsConfiguration(); + Rectangle gcBounds = (gc == null) ? null : gc.getBounds(); + Rectangle frameBounds = c.getBounds(); + + /* If this is a JFrame created by FrameView and it's been maximized, + * retrieve the frame's normal (not maximized) bounds. More info: + * see FrameStateListener#windowStateChanged in FrameView. + */ + if ((c instanceof JFrame) && (0 != (frameState & Frame.MAXIMIZED_BOTH))) { + frameBounds = getWindowNormalBounds(c); + } + + WindowState result = null; + if (frameBounds != null && !frameBounds.isEmpty()) { + result = new WindowState(); + result.setBounds(frameBounds); + result.setGcBounds(gcBounds); + result.setFrameState(frameState); + } + + return result; + } + + @Override + public void setState(Object o, State state) { + Window w = checkComponent(o); + if ((state != null) && !(state instanceof WindowState)) { + throw new IllegalArgumentException("invalid state"); + } + WindowState windowState = (WindowState) state; + if (windowState.getBounds() != null) { + putWindowNormalBounds(w, windowState.getBounds()); + if (!w.isLocationByPlatform() && (state != null)) { + + Rectangle gcBounds0 = windowState.getGcBounds(); + if (gcBounds0 != null && isResizable(w)) { + if (computeVirtualGraphicsBounds().contains(gcBounds0.getLocation())) { + w.setBounds(windowState.getBounds()); + } else { + w.setSize(windowState.getBounds().getSize()); + } + } + } + if (w instanceof Frame) { + ((Frame) w).setExtendedState(windowState.getFrameState()); + } + } + } +}