diff --git a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java index e24c30f36e..4caf92e5b8 100644 --- a/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java +++ b/CodenameOne/src/com/codename1/impl/CodenameOneImplementation.java @@ -174,6 +174,7 @@ public abstract class CodenameOneImplementation { */ private final Rectangle paintDirtyTmpRect = new Rectangle(); private BrowserComponent sharedJavascriptContext; + private Dimension initialWindowSizeHintPercent; static void setOnCurrentFormChange(Runnable on) { onCurrentFormChange = on; @@ -467,6 +468,54 @@ public int getActualDisplayHeight() { return getDisplayHeight(); } + /** + * Returns the size of the desktop area hosting the application window when running on a desktop + * platform. Implementations that do not support windows may return {@code null}. + * + * @return the desktop size or {@code null} + */ + public Dimension getDesktopSize() { + return null; + } + + /** + * Returns the bounds of the application window when running on a desktop platform. + * + * @return the window bounds, defaults to the current display size + */ + public Rectangle getWindowBounds() { + return new Rectangle(0, 0, getDisplayWidth(), getDisplayHeight()); + } + + /** + * Requests a resize of the application window when supported by the platform. + * + * @param width the desired window width in pixels + * @param height the desired window height in pixels + */ + public void setWindowSize(int width, int height) { + } + + /** + * Stores an optional window size hint (in percent values) for desktop environments. Implementations + * that do not support windows may ignore this value. + * + * @param hint a {@link Dimension} whose width/height represent percentages of the desktop to use for + * the initial window size, or {@code null} to clear a previously stored hint + */ + public void setInitialWindowSizeHintPercent(Dimension hint) { + initialWindowSizeHintPercent = hint; + } + + /** + * Returns the optional desktop window size hint provided by the first form. + * + * @return the stored hint or {@code null} + */ + public Dimension getInitialWindowSizeHintPercent() { + return initialWindowSizeHintPercent; + } + /** * Invoked when an exception occurs on the EDT, allows the implementation to * take control of the device to produce testing information. diff --git a/CodenameOne/src/com/codename1/ui/CN.java b/CodenameOne/src/com/codename1/ui/CN.java index fafbdc2fad..41d50661db 100644 --- a/CodenameOne/src/com/codename1/ui/CN.java +++ b/CodenameOne/src/com/codename1/ui/CN.java @@ -33,6 +33,8 @@ import com.codename1.plugin.PluginSupport; import com.codename1.ui.events.ActionListener; import com.codename1.ui.events.MessageEvent; +import com.codename1.ui.events.WindowEvent; +import com.codename1.ui.geom.Dimension; import com.codename1.ui.geom.Rectangle; import com.codename1.ui.plaf.Style; import com.codename1.util.RunnableWithResultSync; @@ -813,6 +815,78 @@ public static boolean isDesktop() { return Display.impl.isDesktop(); } + /** + * Returns the size of the desktop hosting the application window when running on a desktop platform. + * + * @return the desktop size + */ + public static Dimension getDesktopSize() { + return Display.getInstance().getDesktopSize(); + } + + /** + * Returns the number of monitors attached to the desktop environment when available. + * + * @return the number of monitors + */ + /** + * Returns the current bounds of the application window when supported by the platform. + * + * @return the window bounds + */ + public static Rectangle getWindowBounds() { + return Display.getInstance().getWindowBounds(); + } + + /** + * Requests a resize of the application window when supported by the platform. + * + * @param width the desired window width + * @param height the desired window height + */ + public static void setWindowSize(int width, int height) { + Display.getInstance().setWindowSize(width, height); + } + + /** + * Returns the initial desktop window size hint provided by the first shown form, when available. + * + * @return the stored hint or {@code null} + */ + public static Dimension getInitialWindowSizeHintPercent() { + return Display.getInstance().getInitialWindowSizeHintPercent(); + } + + /** + * Sets the initial desktop window size hint (percent of the desktop) that should be used when the + * first form is shown. This is primarily useful for desktop environments where the Codename One + * application is hosted in a window rather than full-screen. + * + * @param hint a {@link Dimension} whose width/height represent percentages of the desktop to use for + * the initial window size, or {@code null} to clear a previously stored hint + */ + public static void setInitialWindowSizeHintPercent(Dimension hint) { + Display.getInstance().setInitialWindowSizeHintPercent(hint); + } + + /** + * Adds a listener for window events such as resize or move. + * + * @param l the listener to add + */ + public static void addWindowListener(ActionListener l) { + Display.getInstance().addWindowListener(l); + } + + /** + * Removes a previously registered window listener. + * + * @param l the listener to remove + */ + public static void removeWindowListener(ActionListener l) { + Display.getInstance().removeWindowListener(l); + } + /** * Returns true if the device has dialing capabilities * diff --git a/CodenameOne/src/com/codename1/ui/Display.java b/CodenameOne/src/com/codename1/ui/Display.java index b0025a08ec..e290455e27 100644 --- a/CodenameOne/src/com/codename1/ui/Display.java +++ b/CodenameOne/src/com/codename1/ui/Display.java @@ -55,6 +55,7 @@ import com.codename1.ui.events.ActionEvent; import com.codename1.ui.events.ActionListener; import com.codename1.ui.events.MessageEvent; +import com.codename1.ui.events.WindowEvent; import com.codename1.ui.geom.Dimension; import com.codename1.ui.geom.Rectangle; import com.codename1.ui.plaf.Style; @@ -261,6 +262,12 @@ public final class Display extends CN1Constants { * Indicates that commands should try to add themselves to the native menus */ public static final int COMMAND_BEHAVIOR_NATIVE = 10; + /** + * Client property key used on the first shown {@link Form} to indicate the desired initial + * window size as a percentage of the available desktop. The value should be a {@link com.codename1.ui.geom.Dimension} + * whose width and height represent percentages. + */ + public static final String WINDOW_SIZE_HINT_PERCENT = "cn1.windowSizePercent"; static final Display INSTANCE = new Display(); static final Object lock = new Object(); private static final int POINTER_PRESSED = 1; @@ -293,6 +300,11 @@ public final class Display extends CN1Constants { private boolean inNativeUI; private Runnable bookmark; private EventDispatcher messageListeners; + private EventDispatcher windowListeners; + /** + * Tracks whether the initial window size hint has already been consumed for the first shown form. + */ + private boolean initialWindowSizeApplied; private boolean disableInvokeAndBlock; /** * Enable Async stack traces. This is disabled by default, but will cause @@ -430,6 +442,7 @@ private Display() { public static void init(Object m) { if (!INSTANCE.codenameOneRunning) { INSTANCE.codenameOneRunning = true; + INSTANCE.initialWindowSizeApplied = false; INSTANCE.pluginSupport = new PluginSupport(); INSTANCE.displayInitTime = System.currentTimeMillis(); @@ -1648,6 +1661,9 @@ void setCurrentForm(Form newForm) { } else { forceShow = true; } + if (!initialWindowSizeApplied) { + initialWindowSizeApplied = applyInitialWindowSize(newForm); + } keyRepeatCharged = false; longPressCharged = false; longPointerCharged = false; @@ -1669,6 +1685,18 @@ void setCurrentForm(Form newForm) { newForm.onShowCompletedImpl(); } + private boolean applyInitialWindowSize(Form form) { + if (form == null) { + return false; + } + Object hint = form.getClientProperty(WINDOW_SIZE_HINT_PERCENT); + if (!(hint instanceof Dimension)) { + return false; + } + impl.setInitialWindowSizeHintPercent((Dimension) hint); + return true; + } + /** * Indicates whether a delay should exist between calls to flush graphics during * transition. In some devices flushGraphics is asynchronious causing it to be @@ -2552,6 +2580,63 @@ public int getDisplayHeight() { return impl.getDisplayHeight(); } + /** + * Returns the size of the desktop hosting the application window when running on a desktop platform. + * + * @return the desktop size or the current display size if not supported + */ + public Dimension getDesktopSize() { + Dimension desktopSize = impl.getDesktopSize(); + if (desktopSize != null) { + return desktopSize; + } + return new Dimension(getDisplayWidth(), getDisplayHeight()); + } + + /** + * Returns the current window bounds when running on a desktop platform. + * + * @return the bounds of the application window + */ + public Rectangle getWindowBounds() { + Rectangle bounds = impl.getWindowBounds(); + if (bounds == null) { + return new Rectangle(0, 0, getDisplayWidth(), getDisplayHeight()); + } + return bounds; + } + + /** + * Requests a resize of the application window when supported by the platform. + * + * @param width the desired window width + * @param height the desired window height + */ + public void setWindowSize(int width, int height) { + impl.setWindowSize(width, height); + } + + /** + * Returns the initial desktop window size hint provided by the first shown form, when available. + * + * @return the stored hint or {@code null} + */ + public Dimension getInitialWindowSizeHintPercent() { + return impl.getInitialWindowSizeHintPercent(); + } + + /** + * Sets the initial desktop window size hint (percent of the desktop) that should be used when the + * first form is shown. This is primarily useful for desktop environments where the Codename One + * application is hosted in a window rather than full-screen. + * + * @param hint a {@link Dimension} whose width/height represent percentages of the desktop to use for + * the initial window size, or {@code null} to clear a previously stored hint + */ + public void setInitialWindowSizeHintPercent(Dimension hint) { + impl.setInitialWindowSizeHintPercent(hint); + } + /** * Causes the given component to repaint, used internally by Form * @@ -3320,6 +3405,54 @@ public void dispatchMessage(MessageEvent evt) { } } + /** + * Adds a listener to receive notifications about native window changes such as resize or movement. + * + * @param l the listener to add + */ + public synchronized void addWindowListener(ActionListener l) { + if (windowListeners == null) { + windowListeners = new EventDispatcher(); + } + windowListeners.addListener(l); + } + + /** + * Removes a previously registered window listener. + * + * @param l the listener to remove + */ + public synchronized void removeWindowListener(ActionListener l) { + if (windowListeners != null) { + windowListeners.removeListener(l); + } + } + + /** + * Dispatches a window change event to registered listeners. This method is intended to be invoked by + * platform implementations. + * + * @param evt the window event to dispatch + */ + public void fireWindowEvent(WindowEvent evt) { + if (evt == null || windowListeners == null || !windowListeners.hasListeners()) { + return; + } + if (isEdt()) { + windowListeners.fireActionEvent(evt); + } else { + final WindowEvent windowEvent = evt; + callSerially(new Runnable() { + @Override + public void run() { + if (windowListeners != null && windowListeners.hasListeners()) { + windowListeners.fireActionEvent(windowEvent); + } + } + }); + } + } + /** * Returns the property from the underlying platform deployment or the default * value if no deployment values are supported. This is equivalent to the diff --git a/CodenameOne/src/com/codename1/ui/events/WindowEvent.java b/CodenameOne/src/com/codename1/ui/events/WindowEvent.java new file mode 100644 index 0000000000..73b516e678 --- /dev/null +++ b/CodenameOne/src/com/codename1/ui/events/WindowEvent.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code 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 Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores + * CA 94065 USA or visit www.oracle.com if you need additional information or + * have any questions. + */ +package com.codename1.ui.events; + +import com.codename1.ui.Display; +import com.codename1.ui.geom.Rectangle; + +/** + * Describes a change in the native window hosting the Codename One display on + * desktop platforms. + */ +public class WindowEvent extends ActionEvent { + + /** + * The type of window change that occurred. + */ + public enum Type { + /** + * The window became visible. + */ + Shown, + /** + * The window is no longer visible. + */ + Hidden, + /** + * The window was minimized/iconified. + */ + Minimized, + /** + * The window was restored from a minimized/iconified state. + */ + Restored, + /** + * The window was resized. + */ + Resized, + /** + * The window was moved. + */ + Moved + } + + private final Type type; + private final Rectangle bounds; + + /** + * Creates a new window event instance. + * + * @param source the display that generated the event + * @param type the type of the window event + * @param bounds the bounds of the window, if known + */ + public WindowEvent(Display source, Type type, Rectangle bounds) { + super(source, ActionEvent.Type.Other); + this.type = type; + this.bounds = bounds; + } + + /** + * The type of window event. + * + * @return the event type + */ + public Type getType() { + return type; + } + + /** + * Returns the window bounds associated with this event. + * + * @return the window bounds or {@code null} if not provided + */ + public Rectangle getBounds() { + return bounds; + } +} diff --git a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java index 68a86ccf07..2a1bda2f9b 100644 --- a/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java +++ b/Ports/JavaSE/src/com/codename1/impl/javase/JavaSEPort.java @@ -227,6 +227,14 @@ private JFrame findTopFrame() { */ } + private void fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type type) { + if (!isDesktop() || !Display.isInitialized()) { + return; + } + Display display = Display.getInstance(); + display.fireWindowEvent(new com.codename1.ui.events.WindowEvent(display, type, getWindowBounds())); + } + @Override public boolean isFullScreenSupported() { Preferences pref = Preferences.userNodeForPackage(JavaSEPort.class); @@ -5532,6 +5540,7 @@ public void componentResized(ComponentEvent e) { window.addWindowListener(new WindowListener() { public void windowOpened(WindowEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Shown); } public void windowClosing(WindowEvent e) { @@ -5539,12 +5548,15 @@ public void windowClosing(WindowEvent e) { } public void windowClosed(WindowEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Hidden); } public void windowIconified(WindowEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Minimized); } public void windowDeiconified(WindowEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Restored); } public void windowActivated(WindowEvent e) { @@ -5569,15 +5581,27 @@ private void saveBounds(ComponentEvent e) { @Override public void componentResized(ComponentEvent e) { saveBounds(e); + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Resized); } @Override public void componentMoved(ComponentEvent e) { saveBounds(e); + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Moved); } - - - + + @Override + public void componentShown(ComponentEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Shown); + } + + @Override + public void componentHidden(ComponentEvent e) { + fireDesktopWindowEvent(com.codename1.ui.events.WindowEvent.Type.Hidden); + } + + + }); window.setLocationByPlatform(true); @@ -5616,7 +5640,13 @@ public void componentMoved(ComponentEvent e) { window.setUndecorated(true); window.setExtendedState(JFrame.MAXIMIZED_BOTH); } - window.pack(); + java.awt.Dimension initialSize = resolveInitialWindowSizeFromHint(); + if (initialSize != null) { + window.setSize(initialSize); + window.validate(); + } else { + window.pack(); + } if (getSkin() != null && !scrollableSkin) { zoomLevel = zoomLevel(); } @@ -5745,6 +5775,58 @@ private int getDisplayHeightImpl() { return Math.max(h, 100); } + @Override + public com.codename1.ui.geom.Dimension getDesktopSize() { + if (!isDesktop()) { + return super.getDesktopSize(); + } + java.awt.Dimension size = Toolkit.getDefaultToolkit().getScreenSize(); + return new com.codename1.ui.geom.Dimension(size.width, size.height); + } + + private java.awt.Dimension resolveInitialWindowSizeFromHint() { + com.codename1.ui.geom.Dimension percent = getInitialWindowSizeHintPercent(); + com.codename1.ui.geom.Dimension desktopSize = getDesktopSize(); + if (percent == null || desktopSize == null) { + return null; + } + int width = Math.min(desktopSize.getWidth(), Math.max(1, Math.round(desktopSize.getWidth() * (percent.getWidth() / 100f)))); + int height = Math.min(desktopSize.getHeight(), Math.max(1, Math.round(desktopSize.getHeight() * (percent.getHeight() / 100f)))); + setInitialWindowSizeHintPercent(null); + return new java.awt.Dimension(width, height); + } + + @Override + public com.codename1.ui.geom.Rectangle getWindowBounds() { + if (!isDesktop()) { + return super.getWindowBounds(); + } + JFrame frame = findTopFrame(); + if (frame == null) { + return super.getWindowBounds(); + } + java.awt.Rectangle bounds = frame.getBounds(); + return new com.codename1.ui.geom.Rectangle(bounds.x, bounds.y, bounds.width, bounds.height); + } + + @Override + public void setWindowSize(int width, int height) { + if (!isDesktop()) { + return; + } + final JFrame frame = findTopFrame(); + if (frame != null) { + final java.awt.Dimension size = new java.awt.Dimension(width, height); + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + frame.setSize(size); + frame.validate(); + } + }); + } + } + /** * Creates a soft/weak reference to an object that allows it to be collected * yet caches it. This method is in the porting layer since CLDC only diff --git a/maven/core-unittests/src/test/java/com/codename1/junit/UITestBase.java b/maven/core-unittests/src/test/java/com/codename1/junit/UITestBase.java index 8bd54ad99c..c1fbf95b5b 100644 --- a/maven/core-unittests/src/test/java/com/codename1/junit/UITestBase.java +++ b/maven/core-unittests/src/test/java/com/codename1/junit/UITestBase.java @@ -25,12 +25,16 @@ public abstract class UITestBase { @BeforeEach protected void setUpDisplay() throws Exception { - if(!Display.isInitialized() && TestCodenameOneImplementation.getInstance() == null) { - implementation = new TestCodenameOneImplementation(); + if (!Display.isInitialized()) { + implementation = TestCodenameOneImplementation.getInstance(); + if (implementation == null) { + implementation = new TestCodenameOneImplementation(); + } + final TestCodenameOneImplementation implRef = implementation; ImplementationFactory.setInstance(new ImplementationFactory() { @Override public Object createImplementation() { - return implementation; + return implRef; } }); Display.init(null); diff --git a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java index e0b33dbf69..b6310ebfc0 100644 --- a/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java +++ b/maven/core-unittests/src/test/java/com/codename1/testing/TestCodenameOneImplementation.java @@ -87,6 +87,9 @@ public class TestCodenameOneImplementation extends CodenameOneImplementation { private final TestFont defaultFont = new TestFont(8, 16); private int displayWidth = 1080; private int displayHeight = 1920; + private Dimension desktopSize = new Dimension(displayWidth, displayHeight); + private Dimension lastWindowSize; + private Rectangle windowBounds = new Rectangle(0, 0, displayWidth, displayHeight); private int deviceDensity = Display.DENSITY_MEDIUM; private boolean portrait = true; private boolean touchDevice = true; @@ -1048,6 +1051,49 @@ public int getDisplayHeight() { return displayHeight; } + @Override + public Dimension getDesktopSize() { + return desktopSize; + } + + public void setDesktopSize(Dimension desktopSize) { + this.desktopSize = desktopSize; + } + + @Override + public Rectangle getWindowBounds() { + if (windowBounds == null) { + return new Rectangle(0, 0, displayWidth, displayHeight); + } + return new Rectangle(windowBounds); + } + + public void setWindowBounds(Rectangle windowBounds) { + this.windowBounds = windowBounds; + } + + @Override + public void setInitialWindowSizeHintPercent(Dimension hint) { + super.setInitialWindowSizeHintPercent(hint); + if (hint != null && desktopSize != null) { + int width = Math.min(desktopSize.getWidth(), Math.max(1, Math.round(desktopSize.getWidth() * (hint.getWidth() / 100f)))); + int height = Math.min(desktopSize.getHeight(), Math.max(1, Math.round(desktopSize.getHeight() * (hint.getHeight() / 100f)))); + setWindowSize(width, height); + } + } + + @Override + public void setWindowSize(int width, int height) { + lastWindowSize = new Dimension(width, height); + displayWidth = width; + displayHeight = height; + windowBounds = new Rectangle(windowBounds == null ? 0 : windowBounds.getX(), windowBounds == null ? 0 : windowBounds.getY(), width, height); + } + + public Dimension getLastWindowSize() { + return lastWindowSize == null ? null : new Dimension(lastWindowSize); + } + @Override public void editString(com.codename1.ui.Component cmp, int maxSize, int constraint, String text, int initiatingKeycode) { if (cmp instanceof TextField) { diff --git a/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java b/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java new file mode 100644 index 0000000000..1ee3acda57 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/ui/WindowApiTest.java @@ -0,0 +1,83 @@ +package com.codename1.ui; + +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.ui.events.ActionListener; +import com.codename1.ui.events.WindowEvent; +import com.codename1.ui.geom.Dimension; +import com.codename1.ui.geom.Rectangle; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +class WindowApiTest extends UITestBase { + + @FormTest + void desktopInformationExposedThroughDisplay() { + Dimension desktopSize = new Dimension(1600, 900); + implementation.setDesktopSize(desktopSize); + + Dimension fromDisplay = Display.getInstance().getDesktopSize(); + + assertEquals(desktopSize.getWidth(), fromDisplay.getWidth()); + assertEquals(desktopSize.getHeight(), fromDisplay.getHeight()); + } + + @FormTest + void windowListenersReceiveDispatchedEvents() { + Display display = Display.getInstance(); + List events = new ArrayList(); + WindowEvent event = new WindowEvent(display, WindowEvent.Type.Resized, new Rectangle(10, 20, 300, 200)); + + ActionListener listener = evt -> events.add(evt); + display.addWindowListener(listener); + display.fireWindowEvent(event); + flushSerialCalls(); + + assertEquals(1, events.size()); + WindowEvent captured = events.get(0); + assertEquals(WindowEvent.Type.Resized, captured.getType()); + Rectangle bounds = captured.getBounds(); + assertNotNull(bounds); + assertEquals(10, bounds.getX()); + assertEquals(20, bounds.getY()); + assertEquals(300, bounds.getSize().getWidth()); + assertEquals(200, bounds.getSize().getHeight()); + display.removeWindowListener(listener); + } + + @FormTest + void firstFormWindowSizeHintUsesPercentages() { + implementation.setDesktopSize(new Dimension(2000, 1000)); + Form first = new Form(); + first.putClientProperty(Display.WINDOW_SIZE_HINT_PERCENT, new Dimension(50, 25)); + + first.show(); + flushSerialCalls(); + + Dimension initialHint = Display.getInstance().getInitialWindowSizeHintPercent(); + assertNotNull(initialHint); + assertEquals(50, initialHint.getWidth()); + assertEquals(25, initialHint.getHeight()); + + Dimension initialHintFromCN = CN.getInitialWindowSizeHintPercent(); + assertNotNull(initialHintFromCN); + assertEquals(50, initialHintFromCN.getWidth()); + assertEquals(25, initialHintFromCN.getHeight()); + + Dimension lastWindowSize = implementation.getLastWindowSize(); + assertNotNull(lastWindowSize); + assertEquals(1000, lastWindowSize.getWidth()); + assertEquals(250, lastWindowSize.getHeight()); + + Form second = new Form(); + second.putClientProperty(Display.WINDOW_SIZE_HINT_PERCENT, new Dimension(10, 10)); + second.show(); + flushSerialCalls(); + + assertEquals(1000, implementation.getLastWindowSize().getWidth()); + assertEquals(250, implementation.getLastWindowSize().getHeight()); + } +}