From 0fb4c811f6e2a51d3d195bb5a5b1d0b8c36e51de Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Sun, 26 Oct 2025 18:50:24 +0100 Subject: [PATCH] Zooming API --- CHANGELOG.md | 7 + .../java/com/formdev/flatlaf/FlatLaf.java | 31 +- .../com/formdev/flatlaf/util/UIScale.java | 291 +++++++++++++-- .../com/formdev/flatlaf/util/TestUIScale.java | 343 ++++++++++++++++++ .../com/formdev/flatlaf/demo/ControlBar.java | 10 +- .../com/formdev/flatlaf/demo/DemoFrame.java | 125 ++++++- .../com/formdev/flatlaf/demo/DemoFrame.jfd | 33 +- 7 files changed, 800 insertions(+), 40 deletions(-) create mode 100644 flatlaf-core/src/test/java/com/formdev/flatlaf/util/TestUIScale.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 4304a79a..274674e5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ FlatLaf Change Log ## 3.7-SNAPSHOT +#### New features and improvements + +- Zooming API. (PR #1051) + +#### Fixed bugs + - TextField: Fixed wrong leading/trailing icon placement if border is set to `null`. (issue #1047) - Extras: UI defaults inspector: Exclude inspector window from being blocked by @@ -10,6 +16,7 @@ FlatLaf Change Log - JideButton, JideToggleButton, JideSplitButton and JideToggleSplitButton: Paint border in button style `TOOLBAR_STYLE` if in selected state. (issue #1045) + ## 3.6.2 #### New features and improvements diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java index dfa9764f..6f5c3169 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatLaf.java @@ -368,6 +368,22 @@ public abstract class FlatLaf String.format( "a, address { color: #%06x; }", linkColor.getRGB() & 0xffffff ) ); } }; + + // Initialize UIScale user scale factor immediately after FlatLaf was activated, + // which is necessary to ensure that UIScale.setZoomFactor(float) + // scales FlatLaf defaultDont correctly even if UIScale.scale() was not yet used. + // In other words: Without this, UIScale.setZoomFactor(float) would + // not work correctly if invoked between FlatLaf.setup() and crating UI. + PropertyChangeListener listener = new PropertyChangeListener() { + @Override + public void propertyChange( PropertyChangeEvent e ) { + if( "lookAndFeel".equals( e.getPropertyName() ) ) { + UIManager.removePropertyChangeListener( this ); + UIScale.getUserScaleFactor(); + } + } + }; + UIManager.addPropertyChangeListener( listener ); } @Override @@ -707,11 +723,22 @@ public abstract class FlatLaf uiFont = ((ActiveFont)defaultFont).derive( baseFont, fontSize -> { return Math.round( fontSize * UIScale.computeFontScaleFactor( baseFont ) ); } ); - } + } else if( defaultFont instanceof LazyValue ) + uiFont = ActiveFont.toUIResource( (Font) ((LazyValue)defaultFont).createValue( defaults ) ); // increase font size if system property "flatlaf.uiScale" is set uiFont = UIScale.applyCustomScaleFactor( uiFont ); + // apply zoom factor to font size + float zoomFactor = UIScale.getZoomFactor(); + if( zoomFactor != 1 ) { + // see also UIScale.setZoomFactor() + int unzoomedFontSize = uiFont.getSize(); + defaults.put( "defaultFont.unzoomedSize", unzoomedFontSize ); + int newFontSize = Math.max( Math.round( unzoomedFontSize * zoomFactor ), 1 ); + uiFont = new FontUIResource( uiFont.deriveFont( (float) newFontSize ) ); + } + // set default font defaults.put( "defaultFont", uiFont ); } @@ -1768,7 +1795,7 @@ public abstract class FlatLaf return toUIResource( baseFont ); } - private FontUIResource toUIResource( Font font ) { + private static FontUIResource toUIResource( Font font ) { // make sure that font is a UIResource for LaF switching return (font instanceof FontUIResource) ? (FontUIResource) font diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/UIScale.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/UIScale.java index 01c17ab6..cd3bafae 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/util/UIScale.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/util/UIScale.java @@ -27,7 +27,9 @@ import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.lang.reflect.Method; +import java.util.Arrays; import javax.swing.LookAndFeel; +import javax.swing.UIDefaults; import javax.swing.UIManager; import javax.swing.plaf.DimensionUIResource; import javax.swing.plaf.FontUIResource; @@ -61,16 +63,28 @@ import com.formdev.flatlaf.FlatSystemProperties; * or if the default font is changed. * The user scale factor is computed based on the used font. * The JRE does not scale anything. - * So we have to invoke {@link #scale(float)} where necessary. + * So we have to invoke {@link #scale(int)} where necessary. * There is only one user scale factor for all displays. * The user scale factor may change if the active LaF, "defaultFont" or "Label.font" has changed. * If system scaling mode is available the user scale factor is usually 1, * but may be larger on Linux or if the default font is changed. * + *

Zooming

+ * + * Zooming allows appliations to easily zoom their UI, if FlatLaf is active Laf. + * This is done by changing user scale factor and default font. + * There are methods to increase, decrease and reset zoom factor. + *

+ * Note: Only standard Swing components are zoomed. + * Custom components need to use {@link #scale(int)} to zoom their UI. + * * @author Karl Tauber */ public class UIScale { + /** @since 3.7 */ public static final String PROP_USER_SCALE_FACTOR = "userScaleFactor"; + /** @since 3.7 */ public static final String PROP_ZOOM_FACTOR = "zoomFactor"; + private static final boolean DEBUG = false; private static PropertyChangeSupport changeSupport; @@ -87,7 +101,7 @@ public class UIScale changeSupport.removePropertyChangeListener( listener ); } - //---- system scaling (Java 9) -------------------------------------------- + //---- system scaling (Java 9+) ------------------------------------------- private static Boolean jreHiDPI; @@ -135,10 +149,13 @@ public class UIScale return (isSystemScalingEnabled() && gc != null) ? gc.getDefaultTransform().getScaleX() : 1; } - //---- user scaling (Java 8) ---------------------------------------------- + //---- user scaling (Java 8 / zooming) ------------------------------------ + private static float unzoomedScaleFactor = 1; private static float scaleFactor = 1; private static boolean initialized; + private static boolean listenerInitialized; // use extra flag for unit tests + private static boolean ignoreFontChange; private static void initialize() { if( initialized ) @@ -148,33 +165,43 @@ public class UIScale if( !isUserScalingEnabled() ) return; + initializeListener(); + + updateScaleFactor( true ); + } + + private static void initializeListener() { + if( listenerInitialized ) + return; + listenerInitialized = true; + // listener to update scale factor if LaF changed, "defaultFont" or "Label.font" changed PropertyChangeListener listener = new PropertyChangeListener() { @Override public void propertyChange( PropertyChangeEvent e ) { switch( e.getPropertyName() ) { case "lookAndFeel": - // it is not necessary (and possible) to remove listener of old LaF defaults + // it is not possible (and necessary) to remove listener of old LaF defaults + // because it is not possible to access the UIDefault object of the old LaF if( e.getNewValue() instanceof LookAndFeel ) UIManager.getLookAndFeelDefaults().addPropertyChangeListener( this ); - updateScaleFactor(); + updateScaleFactor( true ); break; case "defaultFont": case "Label.font": - updateScaleFactor(); + if( !ignoreFontChange ) + updateScaleFactor( false ); break; } } }; - UIManager.addPropertyChangeListener( listener ); UIManager.getDefaults().addPropertyChangeListener( listener ); UIManager.getLookAndFeelDefaults().addPropertyChangeListener( listener ); - - updateScaleFactor(); + UIManager.addPropertyChangeListener( listener ); } - private static void updateScaleFactor() { + private static void updateScaleFactor( boolean lafChanged ) { if( !isUserScalingEnabled() ) return; @@ -185,17 +212,20 @@ public class UIScale return; } - // use font size to calculate scale factor (instead of DPI) - // because even if we are on a HiDPI display it is not sure - // that a larger font size is set by the current LaF - // (e.g. can avoid large icons with small text) + // get font that is used to calculate scale factor Font font = null; if( UIManager.getLookAndFeel() instanceof FlatLaf ) font = UIManager.getFont( "defaultFont" ); if( font == null ) font = UIManager.getFont( "Label.font" ); - setUserScaleFactor( computeFontScaleFactor( font ), true ); + float fontScaleFactor = computeFontScaleFactor( font ); + if( lafChanged && UIManager.getLookAndFeel() instanceof FlatLaf ) { + // FlatLaf has applied zoom factor in FlatLaf.initDefaultFont() to defaultFont, + // so we need to take it into account to get correct user scale factor + fontScaleFactor /= zoomFactor; + } + setUserScaleFactor( fontScaleFactor, true ); } /** @@ -204,7 +234,7 @@ public class UIScale * @since 2 */ public static float computeFontScaleFactor( Font font ) { - if( SystemInfo.isWindows ) { + if( SystemInfo.isWindows && !inUnitTests ) { // Special handling for Windows to be compatible with OS scaling, // which distinguish between "screen scaling" and "text scaling". // - Windows "screen scaling" scales everything (text, icon, gaps, etc.) @@ -335,7 +365,7 @@ public class UIScale } /** - * Returns the user scale factor. + * Returns the user scale factor (including zoom factor). */ public static float getUserScaleFactor() { initialize(); @@ -345,27 +375,49 @@ public class UIScale /** * Sets the user scale factor. */ - private static void setUserScaleFactor( float scaleFactor, boolean normalize ) { - if( normalize ) { - if( scaleFactor < 1f ) { - scaleFactor = FlatSystemProperties.getBoolean( FlatSystemProperties.UI_SCALE_ALLOW_SCALE_DOWN, false ) - ? Math.round( scaleFactor * 10f ) / 10f // round small scale factor to 1/10 - : 1f; - } else if( scaleFactor > 1f ) // round scale factor to 1/4 - scaleFactor = Math.round( scaleFactor * 4f ) / 4f; - } + private static void setUserScaleFactor( float unzoomedScaleFactor, boolean normalize ) { + if( normalize ) + unzoomedScaleFactor = normalizeScaleFactor( unzoomedScaleFactor ); // minimum scale factor - scaleFactor = Math.max( scaleFactor, 0.1f ); + unzoomedScaleFactor = Math.max( unzoomedScaleFactor, 0.1f ); + + if( unzoomedScaleFactor == UIScale.unzoomedScaleFactor ) + return; + + if( DEBUG ) + System.out.println( "Unzoomed scale factor " + UIScale.unzoomedScaleFactor + " --> " + unzoomedScaleFactor ); + + UIScale.unzoomedScaleFactor = unzoomedScaleFactor; + setScaleFactor( unzoomedScaleFactor * zoomFactor ); + } + + private static void setScaleFactor( float scaleFactor ) { + // round scale factor to 1/100 + scaleFactor = Math.round( scaleFactor * 100f ) / 100f; + + if( scaleFactor == UIScale.scaleFactor ) + return; float oldScaleFactor = UIScale.scaleFactor; UIScale.scaleFactor = scaleFactor; if( DEBUG ) - System.out.println( "HiDPI scale factor " + scaleFactor ); + System.out.println( "Scale factor " + oldScaleFactor + " --> " + scaleFactor + " (unzoomed " + UIScale.unzoomedScaleFactor + ")" ); if( changeSupport != null ) - changeSupport.firePropertyChange( "userScaleFactor", oldScaleFactor, scaleFactor ); + changeSupport.firePropertyChange( PROP_USER_SCALE_FACTOR, oldScaleFactor, scaleFactor ); + } + + private static float normalizeScaleFactor( float scaleFactor ) { + if( scaleFactor < 1f ) { + return FlatSystemProperties.getBoolean( FlatSystemProperties.UI_SCALE_ALLOW_SCALE_DOWN, false ) + ? Math.round( scaleFactor * 10f ) / 10f // round small scale factor to 1/10 + : 1f; + } else if( scaleFactor > 1f ) // round scale factor to 1/4 + return Math.round( scaleFactor * 4f ) / 4f; + else + return scaleFactor; } /** @@ -451,4 +503,185 @@ public class UIScale ? new InsetsUIResource( scale( insets.top ), scale( insets.left ), scale( insets.bottom ), scale( insets.right ) ) : new Insets ( scale( insets.top ), scale( insets.left ), scale( insets.bottom ), scale( insets.right ) )); } + + //---- zoom --------------------------------------------------------------- + + private static float zoomFactor = 1; + private static float[] supportedZoomFactors = { 1f, 1.1f, 1.25f, 1.5f, 1.75f, 2f }; + + /** + * Returns the current zoom factor. Default is {@code 1}. + * + * @since 3.7 + */ + public static float getZoomFactor() { + return zoomFactor; + } + + /** + * Sets the zoom factor. + * Also updates user scale factor and default font (if FlatLaf is active Laf). + *

+ * UI needs to be updated if zoom factor has changed. E.g.: + *

{@code
+	 * if( UIScale.setZoomFactor( newZoomFactor ) )
+	 *     FlatLaf.updateUI();
+	 * }
+ * + * @param zoomFactor new zoom factor + * @return {@code true} if zoom factor has changed + * @since 3.7 + */ + public static boolean setZoomFactor( float zoomFactor ) { + // minimum zoom factor + zoomFactor = Math.max( zoomFactor, 0.1f ); + + if( UIScale.zoomFactor == zoomFactor ) + return false; + + float oldZoomFactor = UIScale.zoomFactor; + UIScale.zoomFactor = zoomFactor; + + if( DEBUG ) + System.out.println( "Zoom factor " + oldZoomFactor + " --> " + zoomFactor ); + + setScaleFactor( UIScale.unzoomedScaleFactor * zoomFactor ); + + if( initialized && UIManager.getLookAndFeel() instanceof FlatLaf ) { + // see also FlatLaf.initDefaultFont() + UIDefaults defaults = UIManager.getLookAndFeelDefaults(); + Font font = defaults.getFont( "defaultFont" ); + int unzoomedSize = defaults.getInt( "defaultFont.unzoomedSize" ); + if( unzoomedSize == 0 ) { + unzoomedSize = font.getSize(); + defaults.put( "defaultFont.unzoomedSize", unzoomedSize ); + } + + // update "defaultFont" + ignoreFontChange = true; + try { + // get application default font before updating Laf default font + Font appFont = UIManager.getFont( "defaultFont" ); + + // update Laf default font + int newFontSize = Math.max( Math.round( unzoomedSize * zoomFactor ), 1 ); + defaults.put( "defaultFont", new FontUIResource( font.deriveFont( (float) newFontSize ) ) ); + + if( DEBUG ) + System.out.println( "Zoom Laf font " + font.getSize() + " --> " + newFontSize + " (unzoomed " + unzoomedSize + ")" ); + + // check whether application has changed default font + if( appFont != font ) { + // application has own default font --> also zoom it + int newAppFontSize = Math.max( Math.round( (appFont.getSize() / oldZoomFactor) * zoomFactor ), 1 ); + UIManager.put( "defaultFont", appFont.deriveFont( (float) newAppFontSize ) ); + + if( DEBUG ) + System.out.println( "Zoom app font " + appFont.getSize() + " --> " + newAppFontSize ); + } + } finally { + ignoreFontChange = false; + } + } + + if( changeSupport != null ) + changeSupport.firePropertyChange( PROP_ZOOM_FACTOR, oldZoomFactor, zoomFactor ); + + return true; + } + + /** + * Increases zoom factor using next greater factor in supported factors array. + *

+ * UI needs to be updated if zoom factor has changed. E.g.: + *

{@code
+	 * if( UIScale.zoomIn() )
+	 *     FlatLaf.updateUI();
+	 * }
+ * + * @return {@code true} if zoom factor has changed + * @see #getSupportedZoomFactors() + * @since 3.7 + */ + public static boolean zoomIn() { + int i = Arrays.binarySearch( supportedZoomFactors, zoomFactor ); + int next = (i >= 0) ? i + 1 : -i - 1; + if( next >= supportedZoomFactors.length ) + return false; + + return setZoomFactor( supportedZoomFactors[next] ); + } + + /** + * Decreases zoom factor using next smaller factor in supported factors array. + *

+ * UI needs to be updated if zoom factor has changed. E.g.: + *

{@code
+	 * if( UIScale.zoomOut() )
+	 *     FlatLaf.updateUI();
+	 * }
+ * + * @return {@code true} if zoom factor has changed + * @see #getSupportedZoomFactors() + * @since 3.7 + */ + public static boolean zoomOut() { + int i = Arrays.binarySearch( supportedZoomFactors, zoomFactor ); + int prev = (i >= 0) ? i - 1 : -i - 2; + if( prev < 0 ) + return false; + + return setZoomFactor( supportedZoomFactors[prev] ); + } + + /** + * Resets zoom factor to {@code 1}. + *

+ * UI needs to be updated if zoom factor has changed. E.g.: + *

{@code
+	 * if( UIScale.zoomReset() )
+	 *     FlatLaf.updateUI();
+	 * }
+ * + * @return {@code true} if zoom factor has changed + * @since 3.7 + */ + public static boolean zoomReset() { + return setZoomFactor( 1 ); + } + + /** + * Returns the supported zoom factors used for {@link #zoomIn()} and {@link #zoomOut()}. + *

+ * Default is {@code [ 1f, 1.1f, 1.25f, 1.5f, 1.75f, 2f ]}. + * + * @since 3.7 + */ + public static float[] getSupportedZoomFactors() { + return supportedZoomFactors.clone(); + } + + /** + * Sets the supported zoom factors used for {@link #zoomIn()} and {@link #zoomOut()}. + * + * @since 3.7 + */ + public static void setSupportedZoomFactors( float[] supportedZoomFactors ) { + UIScale.supportedZoomFactors = supportedZoomFactors.clone(); + Arrays.sort( UIScale.supportedZoomFactors ); + + if( Arrays.binarySearch( UIScale.supportedZoomFactors, 1f ) < 0 ) + throw new IllegalArgumentException( "supportedZoomFactors array must contain value 1f" ); + } + + //---- unit testing ------------------------------------------------------- + + static boolean inUnitTests; + + static void tests_uninitialize() { + initialized = false; + unzoomedScaleFactor = 1; + scaleFactor = 1; + zoomFactor = 1; + } } diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/util/TestUIScale.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/util/TestUIScale.java new file mode 100644 index 00000000..a9246062 --- /dev/null +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/util/TestUIScale.java @@ -0,0 +1,343 @@ +/* + * Copyright 2025 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import java.awt.Font; +import java.util.Collections; +import java.util.Map; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; +import javax.swing.plaf.metal.MetalLookAndFeel; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import com.formdev.flatlaf.FlatDarkLaf; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.FlatLightLaf; +import com.formdev.flatlaf.FlatSystemProperties; + +/** + * @author Karl Tauber + */ +public class TestUIScale +{ + private static Map FONT_EXTRA_DEFAULTS_1x = Collections.singletonMap( + "defaultFont", "{instance}java.awt.Font,Dialog,0,12" ); + private static Map FONT_EXTRA_DEFAULTS_1_5x = Collections.singletonMap( + "defaultFont", "{instance}java.awt.Font,Dialog,0,18" ); + + @BeforeAll + static void setup() { + UIScale.inUnitTests = true; + + // disable platform specific fonts + System.setProperty( "flatlaf.uiScale.fontSizeDivider", "12" ); + FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1x ); + } + + @AfterAll + static void cleanup() throws UnsupportedLookAndFeelException { + System.clearProperty( "flatlaf.uiScale.fontSizeDivider" ); + FlatLaf.setGlobalExtraDefaults( null ); + + UIScale.inUnitTests = false; + } + + @AfterEach + void afterEach() throws UnsupportedLookAndFeelException { + UIManager.setLookAndFeel( new MetalLookAndFeel() ); + UIManager.put( "defaultFont", null ); + UIManager.put( "Label.font", null ); + FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1x ); + + UIScale.tests_uninitialize(); + } + + @Test + void testCustomScaleFactor() { + System.setProperty( FlatSystemProperties.UI_SCALE, "1.25x" ); + assertScaleFactor( 1.25f ); + + System.setProperty( FlatSystemProperties.UI_SCALE, "2x" ); + UIScale.tests_uninitialize(); + assertScaleFactor( 2f ); + + System.clearProperty( FlatSystemProperties.UI_SCALE ); + } + + @Test + void testLabelFontScaling() { + assertInstanceOf( MetalLookAndFeel.class, UIManager.getLookAndFeel() ); + + testLabelFont( 8, 1f ); + testLabelFont( 9, 1f ); + testLabelFont( 10, 1f ); + testLabelFont( 11, 1f ); + testLabelFont( 12, 1f ); + testLabelFont( 13, 1f ); + testLabelFont( 14, 1.25f ); + testLabelFont( 15, 1.25f ); + testLabelFont( 16, 1.25f ); + testLabelFont( 17, 1.5f ); + testLabelFont( 18, 1.5f ); + testLabelFont( 19, 1.5f ); + testLabelFont( 20, 1.75f ); + testLabelFont( 21, 1.75f ); + testLabelFont( 22, 1.75f ); + testLabelFont( 23, 2f ); + testLabelFont( 24, 2f ); + testLabelFont( 25, 2f ); + testLabelFont( 26, 2.25f ); + } + + private void testLabelFont( int fontSize, float expectedScaleFactor ) { + UIManager.put( "Label.font", new Font( Font.DIALOG, Font.PLAIN, fontSize ) ); + assertScaleFactor( expectedScaleFactor ); + } + + @Test + void testDefaultFontScaling() { + FlatLightLaf.setup(); + + testDefaultFont( 8, 1f ); + testDefaultFont( 9, 1f ); + testDefaultFont( 10, 1f ); + testDefaultFont( 11, 1f ); + testDefaultFont( 12, 1f ); + testDefaultFont( 13, 1f ); + testDefaultFont( 14, 1.25f ); + testDefaultFont( 15, 1.25f ); + testDefaultFont( 16, 1.25f ); + testDefaultFont( 17, 1.5f ); + testDefaultFont( 18, 1.5f ); + testDefaultFont( 19, 1.5f ); + testDefaultFont( 20, 1.75f ); + testDefaultFont( 21, 1.75f ); + testDefaultFont( 22, 1.75f ); + testDefaultFont( 23, 2f ); + testDefaultFont( 24, 2f ); + testDefaultFont( 25, 2f ); + testDefaultFont( 26, 2.25f ); + } + + private void testDefaultFont( int fontSize, float expectedScaleFactor ) { + UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, fontSize ) ); + assertScaleFactor( expectedScaleFactor ); + } + + @Test + void testInitialScaleFactorAndFontSizes() { + FlatLightLaf.setup(); + assertScaleFactorAndFontSizes( 1f, 12, -1 ); + + FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x ); + FlatDarkLaf.setup(); + assertScaleFactorAndFontSizes( 1.5f, 18, -1 ); + } + + @Test + void zoom_Metal() { + UIScale.setZoomFactor( 1.1f ); + assertScaleFactor( 1.1f ); + + UIScale.setZoomFactor( 1.3f ); + assertScaleFactor( 1.3f ); + + UIScale.setZoomFactor( 2.3f ); + assertScaleFactor( 2.3f ); + } + + @Test + void zoom_1x() { + FlatLightLaf.setup(); + testZoom( 0.7f, 0.7f, 8, -1 ); + testZoom( 0.75f, 0.75f, 9, -1 ); + testZoom( 0.8f, 0.8f, 10, -1 ); + testZoom( 0.9f, 0.9f, 11, -1 ); + testZoom( 1f, 1f, 12, -1 ); + testZoom( 1.1f, 1.1f, 13, -1 ); + testZoom( 1.2f, 1.2f, 14, -1 ); + testZoom( 1.25f, 1.25f, 15, -1 ); + testZoom( 1.3f, 1.3f, 16, -1 ); + testZoom( 1.4f, 1.4f, 17, -1 ); + testZoom( 1.5f, 1.5f, 18, -1 ); + testZoom( 1.6f, 1.6f, 19, -1 ); + testZoom( 1.7f, 1.7f, 20, -1 ); + testZoom( 1.75f, 1.75f, 21, -1 ); + testZoom( 1.8f, 1.8f, 22, -1 ); + testZoom( 1.9f, 1.9f, 23, -1 ); + testZoom( 2f, 2f, 24, -1 ); + testZoom( 2.25f, 2.25f, 27, -1 ); + testZoom( 2.5f, 2.5f, 30, -1 ); + testZoom( 2.75f, 2.75f, 33, -1 ); + testZoom( 3f, 3f, 36, -1 ); + testZoom( 4f, 4f, 48, -1 ); + } + + @Test + void zoom_1_5x() { + FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x ); + FlatLightLaf.setup(); + + testZoom( 0.7f, 1.05f, 13, -1 ); + testZoom( 0.75f, 1.13f, 14, -1 ); + testZoom( 0.8f, 1.2f, 14, -1 ); + testZoom( 0.9f, 1.35f, 16, -1 ); + testZoom( 1f, 1.5f, 18, -1 ); + testZoom( 1.1f, 1.65f, 20, -1 ); + testZoom( 1.2f, 1.8f, 22, -1 ); + testZoom( 1.25f, 1.88f, 23, -1 ); + testZoom( 1.3f, 1.95f, 23, -1 ); + testZoom( 1.4f, 2.1f, 25, -1 ); + testZoom( 1.5f, 2.25f, 27, -1 ); + testZoom( 1.6f, 2.4f, 29, -1 ); + testZoom( 1.7f, 2.55f, 31, -1 ); + testZoom( 1.75f, 2.63f, 32, -1 ); + testZoom( 1.8f, 2.7f, 32, -1 ); + testZoom( 1.9f, 2.85f, 34, -1 ); + testZoom( 2f, 3f, 36, -1 ); + testZoom( 2.25f, 3.38f, 41, -1 ); + testZoom( 2.5f, 3.75f, 45, -1 ); + testZoom( 2.75f, 4.13f, 50, -1 ); + testZoom( 3f, 4.5f, 54, -1 ); + testZoom( 4f, 6f, 72, -1 ); + } + + @Test + void zoomAppFont_1x() { + FlatLightLaf.setup(); + UIManager.put( "defaultFont", new Font( Font.DIALOG, Font.PLAIN, 14 ) ); + + testZoom( 1f, 1.25f, 12, 14 ); + testZoom( 1.1f, 1.38f, 13, 15 ); + testZoom( 1.25f, 1.56f, 15, 17 ); + testZoom( 1.5f, 1.88f, 18, 20 ); + testZoom( 1.75f, 2.19f, 21, 23 ); + testZoom( 2f, 2.5f, 24, 26 ); + testZoom( 1f, 1.25f, 12, 13 ); + testZoom( 2f, 2.5f, 24, 26 ); + } + + @Test + void zoomWithLafChange() { + FlatLightLaf.setup(); + assertScaleFactorAndFontSizes( 1f, 12, -1 ); + testZoom( 1.1f, 1.1f, 13, -1 ); + + FlatDarkLaf.setup(); + assertScaleFactorAndFontSizes( 1.1f, 13, -1 ); + testZoom( 1.2f, 1.2f, 14, -1 ); + + FlatLightLaf.setup(); + assertScaleFactorAndFontSizes( 1.2f, 14, -1 ); + testZoom( 1.3f, 1.3f, 16, -1 ); + + FlatLaf.setGlobalExtraDefaults( FONT_EXTRA_DEFAULTS_1_5x ); + FlatDarkLaf.setup(); + assertScaleFactorAndFontSizes( 1.95f, 23, -1 ); + testZoom( 1.4f, 2.1f, 25, -1 ); + + FlatLightLaf.setup(); + assertScaleFactorAndFontSizes( 2.1f, 25, -1 ); + testZoom( 1.5f, 2.25f, 27, -1 ); + } + + @Test + void zoomWithDefaultFontChange() { + FlatLightLaf.setup(); + assertScaleFactorAndFontSizes( 1f, 12, -1 ); + + float zoom1 = 1.4f; + testZoom( zoom1, zoom1, 17, -1 ); + testDefaultFont( 8, z( zoom1, 1f ) ); + testDefaultFont( 9, z( zoom1, 1f ) ); + testDefaultFont( 10, z( zoom1, 1f ) ); + testDefaultFont( 11, z( zoom1, 1f ) ); + testDefaultFont( 12, z( zoom1, 1f ) ); + testDefaultFont( 13, z( zoom1, 1f ) ); + testDefaultFont( 14, z( zoom1, 1.25f ) ); + testDefaultFont( 15, z( zoom1, 1.25f ) ); + testDefaultFont( 16, z( zoom1, 1.25f ) ); + testDefaultFont( 17, z( zoom1, 1.5f ) ); + testDefaultFont( 18, z( zoom1, 1.5f ) ); + testDefaultFont( 19, z( zoom1, 1.5f ) ); + testDefaultFont( 20, z( zoom1, 1.75f ) ); + testDefaultFont( 21, z( zoom1, 1.75f ) ); + testDefaultFont( 22, z( zoom1, 1.75f ) ); + testDefaultFont( 23, z( zoom1, 2f ) ); + testDefaultFont( 24, z( zoom1, 2f ) ); + testDefaultFont( 25, z( zoom1, 2f ) ); + testDefaultFont( 26, z( zoom1, 2.25f ) ); + + float zoom2 = 1.8f; + testZoom( zoom2, 4.05f, 22, 33 ); + testDefaultFont( 8, z( zoom2, 1f ) ); + testDefaultFont( 9, z( zoom2, 1f ) ); + testDefaultFont( 10, z( zoom2, 1f ) ); + testDefaultFont( 11, z( zoom2, 1f ) ); + testDefaultFont( 12, z( zoom2, 1f ) ); + testDefaultFont( 13, z( zoom2, 1f ) ); + testDefaultFont( 14, z( zoom2, 1.25f ) ); + testDefaultFont( 15, z( zoom2, 1.25f ) ); + testDefaultFont( 16, z( zoom2, 1.25f ) ); + testDefaultFont( 17, z( zoom2, 1.5f ) ); + testDefaultFont( 18, z( zoom2, 1.5f ) ); + testDefaultFont( 19, z( zoom2, 1.5f ) ); + testDefaultFont( 20, z( zoom2, 1.75f ) ); + testDefaultFont( 21, z( zoom2, 1.75f ) ); + testDefaultFont( 22, z( zoom2, 1.75f ) ); + testDefaultFont( 23, z( zoom2, 2f ) ); + testDefaultFont( 24, z( zoom2, 2f ) ); + testDefaultFont( 25, z( zoom2, 2f ) ); + testDefaultFont( 26, z( zoom2, 2.25f ) ); + } + + private static float z( float zoom, float scale ) { + // round scale factor to 1/100 + return Math.round( (zoom * scale) * 100f ) / 100f; + } + + private static void testZoom( float zoomFactor, float expectedScaleFactor, + int expectedLafFontSize, int expectedAppFontSize ) + { + UIScale.setZoomFactor( zoomFactor ); + assertScaleFactorAndFontSizes( expectedScaleFactor, expectedLafFontSize, expectedAppFontSize ); + } + + private static void assertScaleFactorAndFontSizes( float expectedScaleFactor, + int expectedLafFontSize, int expectedAppFontSize ) + { + assertScaleFactor( expectedScaleFactor ); + + Font lafFont = UIManager.getLookAndFeelDefaults().getFont( "defaultFont" ); + Font appFont = UIManager.getFont( "defaultFont" ); + assertEquals( expectedLafFontSize, lafFont.getSize() ); + if( expectedAppFontSize > 0 ) { + assertNotEquals( lafFont, appFont ); + assertEquals( expectedAppFontSize, appFont.getSize() ); + } else + assertEquals( lafFont, appFont ); + } + + private static void assertScaleFactor( float expectedScaleFactor ) { + assertEquals( expectedScaleFactor, UIScale.getUserScaleFactor() ); + } +} diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/ControlBar.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/ControlBar.java index 095b101c..55354e04 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/ControlBar.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/ControlBar.java @@ -111,6 +111,10 @@ class ControlBar UIScale.addPropertyChangeListener( e -> { // update info label because user scale factor may change updateInfoLabel(); + + // update "Font" menu (e.g. if zoom factor changed) + if( UIScale.PROP_ZOOM_FACTOR.equals( e.getPropertyName() ) ) + frame.updateFontMenuItems(); } ); } @@ -192,13 +196,15 @@ class ControlBar String javaVendor = System.getProperty( "java.vendor" ); if( "Oracle Corporation".equals( javaVendor ) ) javaVendor = null; + float zoomFactor = UIScale.getZoomFactor(); double systemScaleFactor = UIScale.getSystemScaleFactor( getGraphicsConfiguration() ); float userScaleFactor = UIScale.getUserScaleFactor(); Font font = UIManager.getFont( "Label.font" ); String newInfo = "(Java " + System.getProperty( "java.version" ) + (javaVendor != null ? ("; " + javaVendor) : "") - + (systemScaleFactor != 1 ? ("; system scale factor " + systemScaleFactor) : "") - + (userScaleFactor != 1 ? ("; user scale factor " + userScaleFactor) : "") + + (zoomFactor != 1 ? ("; zoom " + zoomFactor) : "") + + (systemScaleFactor != 1 ? ("; system scale " + systemScaleFactor) : "") + + (userScaleFactor != 1 ? ("; user scale " + userScaleFactor) : "") + (systemScaleFactor == 1 && userScaleFactor == 1 ? "; no scaling" : "") + "; " + font.getFamily() + " " + font.getSize() + (font.isBold() ? " BOLD" : "") diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java index 54b0e33e..4ad71d83 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.java @@ -24,6 +24,7 @@ import java.net.URISyntaxException; import java.time.Year; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.prefs.Preferences; import javax.swing.*; import javax.swing.text.DefaultEditorKit; @@ -45,11 +46,13 @@ import com.formdev.flatlaf.extras.components.FlatButton.ButtonType; import com.formdev.flatlaf.icons.FlatAbstractIcon; import com.formdev.flatlaf.themes.FlatMacDarkLaf; import com.formdev.flatlaf.themes.FlatMacLightLaf; +import com.formdev.flatlaf.ui.FlatUIUtils; import com.formdev.flatlaf.extras.FlatSVGUtils; import com.formdev.flatlaf.util.ColorFunctions; import com.formdev.flatlaf.util.FontUtils; import com.formdev.flatlaf.util.LoggingFacade; import com.formdev.flatlaf.util.SystemInfo; +import com.formdev.flatlaf.util.UIScale; import net.miginfocom.layout.ConstraintParser; import net.miginfocom.layout.LC; import net.miginfocom.layout.UnitValue; @@ -71,6 +74,7 @@ class DemoFrame Arrays.sort( availableFontFamilyNames ); initComponents(); + initZommMenuItems(); updateFontMenuItems(); initAccentColors(); initFullWindowContent(); @@ -286,6 +290,92 @@ class DemoFrame showHints(); } + private void initZommMenuItems() { + float currentZoomFactor = UIScale.getZoomFactor(); + UIScale.setSupportedZoomFactors( new float[] { 0.7f, 0.8f, 0.9f, 1f, 1.1f, 1.2f, 1.3f, 1.4f, 1.5f, 1.75f, 2f } ); + + ButtonGroup group = new ButtonGroup(); + HashMap items = new HashMap<>(); + + // add supported zoom factors to "Zoom" menu + zoomMenu.addSeparator(); + for( float zoomFactor : UIScale.getSupportedZoomFactors() ) { + JCheckBoxMenuItem item = new JCheckBoxMenuItem( (int)(zoomFactor * 100) + "%" ); + item.setSelected( zoomFactor == currentZoomFactor ); + item.addActionListener( this::zoomFactorChanged ); + zoomMenu.add( item ); + + group.add( item ); + items.put( zoomFactor, item ); + } + + // update menu item selection if zoom factor changed + UIScale.addPropertyChangeListener( e -> { + if( UIScale.PROP_ZOOM_FACTOR.equals( e.getPropertyName() ) ) { + float newZoomFactor = UIScale.getZoomFactor(); + JCheckBoxMenuItem item = items.get( newZoomFactor ); + if( item != null ) + item.setSelected( true ); + + zoomWindowBounds( this, (float) e.getOldValue(), (float) e.getNewValue() ); + } + } ); + } + + private static void zoomWindowBounds( Window window, float oldZoomFactor, float newZoomFactor ) { + if( window instanceof Frame && ((Frame)window).getExtendedState() != Frame.NORMAL ) + return; + + Rectangle oldBounds = window.getBounds(); + + // zoom window bounds + float factor = (1f / oldZoomFactor) * newZoomFactor; + int newWidth = (int) (oldBounds.width * factor); + int newHeight = (int) (oldBounds.height * factor); + int newX = oldBounds.x - ((newWidth - oldBounds.width) / 2); + int newY = oldBounds.y - ((newHeight - oldBounds.height) / 2); + + // get maximum window bounds (screen bounds minus screen insets) + GraphicsConfiguration gc = window.getGraphicsConfiguration(); + Rectangle screenBounds = gc.getBounds(); + Insets screenInsets = FlatUIUtils.getScreenInsets( gc ); + Rectangle maxBounds = FlatUIUtils.subtractInsets( screenBounds, screenInsets ); + + // limit new window width/height + newWidth = Math.min( newWidth, maxBounds.width ); + newHeight = Math.min( newHeight, maxBounds.height ); + + // move window into screen bounds + newX = Math.max( Math.min( newX, maxBounds.width - newWidth ), maxBounds.x ); + newY = Math.max( Math.min( newY, maxBounds.height - newHeight ), maxBounds.y ); + + // set new window bounds + window.setBounds( newX, newY, newWidth, newHeight ); + } + + private void zoomFactorChanged( ActionEvent e ) { + String zoomFactor = e.getActionCommand(); + float zoom = Integer.parseInt( zoomFactor.substring( 0, zoomFactor.length() - 1 ) ) / 100f; + + if( UIScale.setZoomFactor( zoom ) ) + FlatLaf.updateUI(); + } + + private void zoomReset() { + if( UIScale.zoomReset() ) + FlatLaf.updateUI(); + } + + private void zoomIn() { + if( UIScale.zoomIn() ) + FlatLaf.updateUI(); + } + + private void zoomOut() { + if( UIScale.zoomOut() ) + FlatLaf.updateUI(); + } + private void fontFamilyChanged( ActionEvent e ) { String fontFamily = e.getActionCommand(); @@ -533,6 +623,10 @@ class DemoFrame JRadioButtonMenuItem radioButtonMenuItem1 = new JRadioButtonMenuItem(); JRadioButtonMenuItem radioButtonMenuItem2 = new JRadioButtonMenuItem(); JRadioButtonMenuItem radioButtonMenuItem3 = new JRadioButtonMenuItem(); + zoomMenu = new JMenu(); + JMenuItem resetZoomMenuItem = new JMenuItem(); + JMenuItem incrZoomMenuItem = new JMenuItem(); + JMenuItem decrZoomMenuItem = new JMenuItem(); fontMenu = new JMenu(); JMenuItem restoreFontMenuItem = new JMenuItem(); JMenuItem incrFontMenuItem = new JMenuItem(); @@ -778,25 +872,49 @@ class DemoFrame } menuBar.add(viewMenu); + //======== zoomMenu ======== + { + zoomMenu.setText("Zoom"); + + //---- resetZoomMenuItem ---- + resetZoomMenuItem.setText("Reset Zoom"); + resetZoomMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + resetZoomMenuItem.addActionListener(e -> zoomReset()); + zoomMenu.add(resetZoomMenuItem); + + //---- incrZoomMenuItem ---- + incrZoomMenuItem.setText("Zoom In"); + incrZoomMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + incrZoomMenuItem.addActionListener(e -> zoomIn()); + zoomMenu.add(incrZoomMenuItem); + + //---- decrZoomMenuItem ---- + decrZoomMenuItem.setText("Zoom Out"); + decrZoomMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + decrZoomMenuItem.addActionListener(e -> zoomOut()); + zoomMenu.add(decrZoomMenuItem); + } + menuBar.add(zoomMenu); + //======== fontMenu ======== { fontMenu.setText("Font"); //---- restoreFontMenuItem ---- restoreFontMenuItem.setText("Restore Font"); - restoreFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + restoreFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_0, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.ALT_DOWN_MASK)); restoreFontMenuItem.addActionListener(e -> restoreFont()); fontMenu.add(restoreFontMenuItem); //---- incrFontMenuItem ---- incrFontMenuItem.setText("Increase Font Size"); - incrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + incrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.ALT_DOWN_MASK)); incrFontMenuItem.addActionListener(e -> incrFont()); fontMenu.add(incrFontMenuItem); //---- decrFontMenuItem ---- decrFontMenuItem.setText("Decrease Font Size"); - decrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); + decrFontMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()|KeyEvent.ALT_DOWN_MASK)); decrFontMenuItem.addActionListener(e -> decrFont()); fontMenu.add(decrFontMenuItem); } @@ -1045,6 +1163,7 @@ class DemoFrame private JMenuItem exitMenuItem; private JMenu scrollingPopupMenu; private JMenuItem htmlMenuItem; + private JMenu zoomMenu; private JMenu fontMenu; private JMenu optionsMenu; private JCheckBoxMenuItem windowDecorationsCheckBoxMenuItem; diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd index 08248c1d..190f8d9f 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DemoFrame.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "8.2.1.0.348" Java: "21.0.1" encoding: "UTF-8" +JFDML JFormDesigner: "8.3" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -362,6 +362,31 @@ new FormModel { addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "menuItemActionPerformed", true ) ) } ) } ) + add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { + name: "zoomMenu" + "text": "Zoom" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "resetZoomMenuItem" + "text": "Reset Zoom" + "accelerator": static javax.swing.KeyStroke getKeyStroke( 48, 4226, false ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "zoomReset", false ) ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "incrZoomMenuItem" + "text": "Zoom In" + "accelerator": static javax.swing.KeyStroke getKeyStroke( 521, 4226, false ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "zoomIn", false ) ) + } ) + add( new FormComponent( "javax.swing.JMenuItem" ) { + name: "decrZoomMenuItem" + "text": "Zoom Out" + "accelerator": static javax.swing.KeyStroke getKeyStroke( 45, 4226, false ) + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "zoomOut", false ) ) + } ) + } ) add( new FormContainer( "javax.swing.JMenu", new FormLayoutManager( class javax.swing.JMenu ) ) { name: "fontMenu" "text": "Font" @@ -371,19 +396,19 @@ new FormModel { add( new FormComponent( "javax.swing.JMenuItem" ) { name: "restoreFontMenuItem" "text": "Restore Font" - "accelerator": static javax.swing.KeyStroke getKeyStroke( 48, 4226, false ) + "accelerator": static javax.swing.KeyStroke getKeyStroke( 48, 4746, false ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "restoreFont", false ) ) } ) add( new FormComponent( "javax.swing.JMenuItem" ) { name: "incrFontMenuItem" "text": "Increase Font Size" - "accelerator": static javax.swing.KeyStroke getKeyStroke( 521, 4226, false ) + "accelerator": static javax.swing.KeyStroke getKeyStroke( 521, 4746, false ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "incrFont", false ) ) } ) add( new FormComponent( "javax.swing.JMenuItem" ) { name: "decrFontMenuItem" "text": "Decrease Font Size" - "accelerator": static javax.swing.KeyStroke getKeyStroke( 45, 4226, false ) + "accelerator": static javax.swing.KeyStroke getKeyStroke( 45, 4746, false ) addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "decrFont", false ) ) } ) } )